Background: #fff
Foreground: #000
PrimaryPale: #8cf
PrimaryLight: #18f
PrimaryMid: #04b
PrimaryDark: #014
SecondaryPale: #ffc
SecondaryLight: #fe8
SecondaryMid: #db4
SecondaryDark: #841
TertiaryPale: #eee
TertiaryLight: #ccc
TertiaryMid: #999
TertiaryDark: #666
Error: #f88
<!--{{{-->
<div class='toolbar' macro='toolbar [[ToolbarCommands::EditToolbar]]'></div>
<div class='title' macro='view title'></div>
<div class='editor' macro='edit title'></div>
<div macro='annotations'></div>
<div class='editor' macro='edit text'></div>
<div class='editor' macro='edit tags'></div><div class='editorFooter'><span macro='message views.editor.tagPrompt'></span><span macro='tagChooser excludeLists'></span></div>
<!--}}}-->
When getting started, you may want to:
* Set your username for signing your edits: <<option txtUserName>>
* Change the page [[title|SiteTitle]] (now "<<tiddler SiteTitle>>") and [[subtitle|SiteSubtitle]] (now "<<tiddler SiteSubtitle>>"); they also set the browser tab title
* Create a tiddler where your content "starts"
** Use the button on the sidebar or [[link|My first tiddler]] it here, follow the link, edit, and click "done"
** It will be shown in the Timeline (usually on the right), but you may want to link it in the MainMenu (usually on the left)
** and/or make it open when the ~TiddlyWiki is opened by editing the list of [[DefaultTiddlers]] (separate links with spaces or linebreaks)
* Save your ~TiddlyWiki
** Although "download saving" works in any browser, it may be not that convenient, so you'll probably want to use [[a dedicated saver|https://classic.tiddlywiki.com/#%5B%5BSetting up saving%5D%5D]]
<<importTiddlers>>
<!--{{{-->
<link rel='alternate' type='application/rss+xml' title='RSS' href='index.xml' />
<!--}}}-->
These [[InterfaceOptions]] for customising [[TiddlyWiki]] are saved in your browser

Your username for signing your edits. Write it as a [[WikiWord]] (eg [[JoeBloggs]])

<<option txtUserName>>
<<option chkSaveBackups>> [[SaveBackups]]
<<option chkAutoSave>> [[AutoSave]]
<<option chkRegExpSearch>> [[RegExpSearch]]
<<option chkCaseSensitiveSearch>> [[CaseSensitiveSearch]]
<<option chkAnimate>> [[EnableAnimations]]

----
Also see [[AdvancedOptions]]
<!--{{{-->
<div class='header' role='banner'>
  <div class='headerShadow'>
    <span class='siteTitle' refresh='content' tiddler='SiteTitle'></span>&nbsp;
    <span class='siteSubtitle' refresh='content' tiddler='SiteSubtitle'></span>
  </div>
  <div class='headerForeground'>
    <span class='siteTitle' refresh='content' tiddler='SiteTitle'></span>&nbsp;
    <span class='siteSubtitle' refresh='content' tiddler='SiteSubtitle'></span>
  </div>
</div>
<div id='mainMenu' role='navigation' refresh='content' tiddler='MainMenu'></div>
<div id='sidebar'>
  <div id='sidebarOptions' role='navigation' refresh='content' tiddler='SideBarOptions'></div>
  <div id='sidebarTabs' role='complementary' refresh='content' force='true' tiddler='SideBarTabs'></div>
</div>
<div id='displayArea' role='main'>
<div id='messageArea'></div>
<div id='tiddlerDisplay'></div>
</div>
<!--}}}-->
/*{{{*/
body {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];}

a {color:[[ColorPalette::PrimaryMid]];}
a:hover {background-color:[[ColorPalette::PrimaryMid]]; color:[[ColorPalette::Background]];}
a img {border:0;}

h1, h2, h3, h4, h5, h6 { color: [[ColorPalette::SecondaryDark]]; }
h1 {border-bottom:2px solid [[ColorPalette::TertiaryLight]];}
h2,h3 {border-bottom:1px solid [[ColorPalette::TertiaryLight]];}

.txtOptionInput {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];}

.button {color:[[ColorPalette::PrimaryDark]]; border:1px solid [[ColorPalette::Background]];}
.button:hover {color:[[ColorPalette::PrimaryDark]]; background:[[ColorPalette::SecondaryLight]]; border-color:[[ColorPalette::SecondaryMid]];}
.button:active {color:[[ColorPalette::Background]]; background:[[ColorPalette::SecondaryMid]]; border:1px solid [[ColorPalette::SecondaryDark]];}

.header {
	background: -moz-linear-gradient(to bottom, [[ColorPalette::PrimaryLight]], [[ColorPalette::PrimaryMid]]);
	background: linear-gradient(to bottom, [[ColorPalette::PrimaryLight]], [[ColorPalette::PrimaryMid]]);
}
.header a:hover {background:transparent;}
.headerShadow {color:[[ColorPalette::Foreground]];}
.headerShadow a {font-weight:normal; color:[[ColorPalette::Foreground]];}
.headerForeground {color:[[ColorPalette::Background]];}
.headerForeground a {font-weight:normal; color:[[ColorPalette::PrimaryPale]];}

.tabSelected {
	color:[[ColorPalette::Foreground]];
	background:[[ColorPalette::Background]];
	border-left:1px solid [[ColorPalette::TertiaryLight]];
	border-top:1px solid [[ColorPalette::TertiaryLight]];
	border-right:1px solid [[ColorPalette::TertiaryLight]];
}
.tabUnselected {color:[[ColorPalette::Background]]; background:[[ColorPalette::TertiaryMid]];}
.tabContents {border:1px solid [[ColorPalette::TertiaryLight]];}
.tabContents .button {border:0;}

#sidebar {}
#sidebarOptions input {border:1px solid [[ColorPalette::PrimaryMid]];}
#sidebarOptions .sliderPanel {background:[[ColorPalette::PrimaryPale]];}
#sidebarOptions .sliderPanel a {border:none;color:[[ColorPalette::PrimaryMid]];}
#sidebarOptions .sliderPanel a:hover {color:[[ColorPalette::Background]]; background:[[ColorPalette::PrimaryMid]];}
#sidebarOptions .sliderPanel a:active {color:[[ColorPalette::PrimaryMid]]; background:[[ColorPalette::Background]];}

.wizard { background:[[ColorPalette::PrimaryPale]]; }
.wizard__title    { color:[[ColorPalette::PrimaryDark]]; border:none; }
.wizard__subtitle { color:[[ColorPalette::Foreground]]; border:none; }
.wizardStep { background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]]; }
.wizardStep.wizardStepDone {background:[[ColorPalette::TertiaryLight]];}
.wizardFooter .status {background:[[ColorPalette::PrimaryDark]]; color:[[ColorPalette::Background]];}
.wizardFooter .status a { color: [[ColorPalette::PrimaryPale]]; }
.wizard .button {
	color:[[ColorPalette::Foreground]]; background:[[ColorPalette::SecondaryLight]]; border: 1px solid;
	border-color:[[ColorPalette::SecondaryDark]];
}
.wizard .button:hover {color:[[ColorPalette::Foreground]]; background:[[ColorPalette::Background]];}
.wizard .button:active {
	color:[[ColorPalette::Background]]; background:[[ColorPalette::Foreground]]; border: 1px solid;
	border-color:[[ColorPalette::PrimaryDark]] [[ColorPalette::PrimaryPale]] [[ColorPalette::PrimaryPale]] [[ColorPalette::PrimaryDark]];
}

.wizard .notChanged {background:transparent;}
.wizard .changedLocally {background:#80ff80;}
.wizard .changedServer {background:#8080ff;}
.wizard .changedBoth {background:#ff8080;}
.wizard .notFound {background:#ffff80;}
.wizard .putToServer {background:#ff80ff;}
.wizard .gotFromServer {background:#80ffff;}

#messageArea { background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]]; box-shadow: 1px 2px 5px [[ColorPalette::TertiaryMid]]; }
.messageToolbar__button { color:[[ColorPalette::PrimaryMid]]; background:[[ColorPalette::SecondaryPale]]; border:none; }
.messageToolbar__button_withIcon { background:inherit; }
.messageToolbar__button_withIcon:active { background:inherit; border:none; }
.tw-icon line { stroke: [[ColorPalette::TertiaryDark]]; }
.messageToolbar__button:hover .tw-icon line { stroke: [[ColorPalette::Foreground]]; }

.popup {
	background: [[ColorPalette::Background]];
	color: [[ColorPalette::TertiaryDark]];
	box-shadow: 1px 2px 5px [[ColorPalette::TertiaryMid]];
}
.popup li a, .popup li a:visited, .popup li a:hover, .popup li a:active {
	color:[[ColorPalette::Foreground]]; border: none;
}
.popup li a:hover { background:[[ColorPalette::SecondaryLight]]; }
.popup li a:active { background:[[ColorPalette::SecondaryPale]]; }
.popup li.disabled { color:[[ColorPalette::TertiaryMid]]; }
.popupHighlight {color:[[ColorPalette::Foreground]];}
.popup hr {color:[[ColorPalette::PrimaryDark]]; background:[[ColorPalette::PrimaryDark]]; border-bottom:1px;}
.listBreak div {border-bottom:1px solid [[ColorPalette::TertiaryDark]];}

.popupTiddler {background:[[ColorPalette::TertiaryPale]]; border:2px solid [[ColorPalette::TertiaryMid]];}

.tiddler .defaultCommand {font-weight:bold;}

.shadow .title {color:[[ColorPalette::TertiaryDark]];}

.title {color:[[ColorPalette::SecondaryDark]];}
.subtitle {color:[[ColorPalette::TertiaryDark]];}

.toolbar {color:[[ColorPalette::PrimaryMid]];}
.toolbar a {color:[[ColorPalette::TertiaryLight]];}
.selected .toolbar a {color:[[ColorPalette::TertiaryMid]];}
.selected .toolbar a:hover {color:[[ColorPalette::Foreground]];}

.tagging, .tagged { background: [[ColorPalette::Background]]; border: 2px solid [[ColorPalette::TertiaryPale]]; }
.selected .tagging, .selected .tagged { border: 2px solid [[ColorPalette::TertiaryLight]]; }
.tagging .listTitle, .tagged .listTitle {color:[[ColorPalette::PrimaryDark]];}
.tagging .button, .tagged .button { border:none; }

.footer {color:[[ColorPalette::TertiaryLight]];}
.selected .footer {color:[[ColorPalette::TertiaryMid]];}

.error, .errorButton {color:[[ColorPalette::Foreground]]; background:[[ColorPalette::Error]];}
.warning {color:[[ColorPalette::Foreground]]; background:[[ColorPalette::SecondaryPale]];}
.lowlight {background:[[ColorPalette::TertiaryLight]];}

.zoomer {background:none; color:[[ColorPalette::TertiaryMid]]; border:3px solid [[ColorPalette::TertiaryMid]];}

.imageLink, #displayArea .imageLink {background:transparent;}

.annotation { background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]]; }

.viewer .listTitle {list-style-type:none; margin-left:-2em;}
.viewer .button {border:1px solid [[ColorPalette::SecondaryMid]];}
.viewer blockquote {border-left:3px solid [[ColorPalette::TertiaryDark]];}

.twtable { background: [[ColorPalette::Background]]; }
.viewer th, .viewer thead td, .twtable th, .twtable thead td { background: [[ColorPalette::SecondaryMid]]; color: [[ColorPalette::Background]]; }
.viewer td, .viewer tr, .twtable td, .twtable tr { border: 1px solid [[ColorPalette::TertiaryLight]]; }
.twtable caption { color: [[ColorPalette::TertiaryMid]]; }

.viewer pre {background:[[ColorPalette::SecondaryPale]];}
.viewer code {color:[[ColorPalette::SecondaryDark]];}
.viewer hr {border:0; border-top:dashed 1px [[ColorPalette::TertiaryDark]]; color:[[ColorPalette::TertiaryDark]];}

.highlight, .marked {background:[[ColorPalette::SecondaryLight]];}

.editor input {border:1px solid [[ColorPalette::PrimaryMid]]; background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];}
.editor textarea {border:1px solid [[ColorPalette::PrimaryMid]]; width:100%; background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];}
.editorFooter {color:[[ColorPalette::TertiaryMid]];}
.readOnly {background:[[ColorPalette::TertiaryPale]];}

#backstageArea {background:[[ColorPalette::Foreground]]; color:[[ColorPalette::TertiaryMid]];}
#backstageArea a {background:[[ColorPalette::Foreground]]; color:[[ColorPalette::Background]]; border:none;}
#backstageArea a:hover {background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]]; }
#backstageArea a.backstageSelTab {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];}
#backstageButton a {background:none; color:[[ColorPalette::Background]]; border:none;}
#backstageButton a:hover {background:[[ColorPalette::Foreground]]; color:[[ColorPalette::Background]]; border:none;}
#backstagePanel {background:[[ColorPalette::Background]]; border-color: [[ColorPalette::Background]] [[ColorPalette::TertiaryDark]] [[ColorPalette::TertiaryDark]] [[ColorPalette::TertiaryDark]];}
.backstagePanelFooter .button {border:none; color:[[ColorPalette::Background]];}
.backstagePanelFooter .button:hover {color:[[ColorPalette::Foreground]];}
#backstageCloak {background:[[ColorPalette::Foreground]]; opacity:0.6; filter:alpha(opacity=60);}
/*}}}*/
/*{{{*/
body { font-size:.75em; font-family:arial,helvetica,sans-serif; margin:0; padding:0; }

* html .tiddler {height:1%;}

h1,h2,h3,h4,h5,h6 {font-weight:bold; text-decoration:none;}
h1,h2,h3 {padding-bottom:1px; margin-top:1.2em;margin-bottom:0.3em;}
h4,h5,h6 {margin-top:1em;}
h1 {font-size:1.35em;}
h2 {font-size:1.25em;}
h3 {font-size:1.1em;}
h4 {font-size:1em;}
h5 {font-size:.9em;}

hr {height:1px;}

dt {font-weight:bold;}

ol {list-style-type:decimal;}
ol ol {list-style-type:lower-alpha;}
ol ol ol {list-style-type:lower-roman;}
ol ol ol ol {list-style-type:decimal;}
ol ol ol ol ol {list-style-type:lower-alpha;}
ol ol ol ol ol ol {list-style-type:lower-roman;}
ol ol ol ol ol ol ol {list-style-type:decimal;}

.txtOptionInput {width:11em; border-width: 1px; }

#contentWrapper .chkOptionInput {border:0;}

.indent {margin-left:3em;}
.outdent {margin-left:3em; text-indent:-3em;}
code.escaped {white-space:nowrap;}


a {text-decoration:none;}

.externalLink {text-decoration:underline;}

.tiddlyLinkExisting {font-weight:bold;}
.tiddlyLinkNonExisting {font-style:italic;}

/* the 'a' is required for IE, otherwise it renders the whole tiddler in bold */
a.tiddlyLinkNonExisting.shadow {font-weight:bold;}

#mainMenu .tiddlyLinkExisting,
#mainMenu .tiddlyLinkNonExisting,
#sidebarTabs .tiddlyLinkNonExisting {font-weight:normal; font-style:normal;}
#sidebarTabs .tiddlyLinkExisting {font-weight:bold; font-style:normal;}


.header {position:relative;}
.headerShadow {position:relative; padding:3em 0 1em 1em; left:-1px; top:-1px;}
.headerForeground {position:absolute; padding:3em 0 1em 1em; left:0; top:0;}

.siteTitle {font-size:3em;}
.siteSubtitle {font-size:1.2em;}

#mainMenu {position:absolute; left:0; width:10em; text-align:right; line-height:1.6em; padding:1.5em 0.5em 0.5em 0.5em; font-size:1.1em;}

#sidebar {position:absolute; right:3px; width:16em; font-size:.9em;}
#sidebarOptions {padding-top:0.3em;}
#sidebarOptions a {margin:0 0.2em; padding:0.2em 0.3em; display:block;}
#sidebarOptions input {margin:0.4em 0.5em;}
#sidebarOptions .sliderPanel {margin-left:1em; padding:0.5em; font-size:.85em;}
#sidebarOptions .sliderPanel a {font-weight:bold; display:inline; padding:0;}
#sidebarOptions .sliderPanel input {margin:0 0 0.3em 0;}
#sidebarTabs .tabContents {width:15em; overflow:hidden;}
#sidebarTabs li:not(:last-child) { margin-bottom: 0.3em; }
#sidebarTabs ul:not(:last-child) { margin-bottom: 0.5em; }

.wizard { padding:0.1em 2em 0; }
.wizard__title    { font-size:2em; }
.wizard__subtitle { font-size:1.2em; }
.wizard__title, .wizard__subtitle { font-weight:bold; background:none; padding:0; margin:0.4em 0 0.2em; }
.wizardStep { padding:1em; }
.wizardFooter { padding: 0.8em 0; }
.wizardFooter .status { display: inline-block; line-height: 1.5; padding: 0.3em 1em; }
.wizardFooter .button { margin:0.5em 0 0; font-size:1.2em; padding:0.2em 0.5em; }

#messageArea { position:fixed; top:2em; right:0; margin:0.5em; padding:0.7em 1em; z-index:2000; }
.messageToolbar { text-align:right; padding:0.2em 0; }
.messageToolbar__button { text-decoration:underline; }
.messageToolbar__button_withIcon { display: inline-block; }
.tw-icon { height: 1em; width: 1em; } /* width for IE */
.tw-icon line { stroke-width: 1; stroke-linecap: round; }
.messageArea__text:not(:last-child) { margin-bottom: 0.3em; }
.messageArea__text a { text-decoration:underline; }

.popup {position:absolute; z-index:300; font-size:.9em; padding:0.3em 0; list-style:none; margin:0;}
.popup .popupMessage, .popup li.disabled, .popup li a { padding: 0.3em 0.7em; }
.popup li a {display:block; font-weight:normal; cursor:pointer;}
.popup hr {display:block; height:1px; width:auto; padding:0; margin:0.2em 0;}
.listBreak {font-size:1px; line-height:1px;}
.listBreak div {margin:2px 0;}

.tiddlerPopupButton {padding:0.2em;}
.popupTiddler {position: absolute; z-index:300; padding:1em; margin:0;}

.tabset {padding:1em 0 0 0.5em;}
.tab {display: inline-block; white-space: nowrap; position: relative; bottom: -0.7px; margin: 0 0.25em 0 0; padding:0.2em;}
.tabContents {padding:0.5em;}
.tabContents ul, .tabContents ol {margin:0; padding:0;}
.txtMainTab .tabContents li {list-style:none;}
.tabContents li.listLink { margin-left:.75em;}

#contentWrapper {display:block;}
#splashScreen {display:none;}

#displayArea {margin:1em 17em 0 14em;}

.toolbar {text-align:right; font-size:.9em;}

.tiddler { padding: 1em; }

.title { font-size: 1.6em; font-weight: bold; }
.subtitle { font-size: 1.1em; }

.missing .viewer, .missing .title { font-style: italic; }
.missing .subtitle { display: none; }

.tiddler .button {padding:0.2em 0.4em;}

.tagging {margin:0.5em 0.5em 0.5em 0; float:left; display:none;}
.isTag .tagging {display:block;}
.tagged {margin:0.5em; float:right;}
.tagging, .tagged {font-size:0.9em; padding:0.25em;}
.tagging ul, .tagged ul {list-style:none; margin:0.25em; padding:0;}
.tagged li, .tagging li { margin: 0.3em 0; }
.tagClear {clear:both;}

.footer {font-size:.9em;}
.footer li {display:inline;}

.annotation { padding: 0.5em 0.8em; margin: 0.5em 1px; }

.viewer {line-height:1.4em; padding-top:0.5em;}
.viewer .button {margin:0 0.25em; padding:0 0.25em;}
.viewer blockquote {line-height:1.5em; padding-left:0.8em;margin-left:2.5em;}
.viewer ul, .viewer ol {margin-left:0.5em; padding-left:1.5em;}

.viewer table, table.twtable { border-collapse: collapse; margin: 0.8em 0; }
.viewer th, .viewer td, .viewer tr, .viewer caption, .twtable th, .twtable td, .twtable tr, .twtable caption { padding: 0.2em 0.4em; }
.twtable caption { font-size: 0.9em; }
table.listView { margin: 0.8em 1.0em; }
table.listView th, table.listView td, table.listView tr { text-align: left; }
.listView > thead { position: sticky; top: 0; }

* html .viewer pre {width:99%; padding:0 0 1em 0;}
.viewer pre {padding:0.5em; overflow:auto;}
pre, code { font-family: monospace, monospace; font-size: 1em; }
.viewer pre, .viewer code { line-height: 1.4em; }

.editor {font-size:1.1em; line-height:1.4em;}
.editor input, .editor textarea { display: block; width: 100%; box-sizing: border-box; font: inherit; padding: 0.1em 0.4em; }
.editorFooter {padding:0.25em 0; font-size:.9em;}
.editorFooter .button {padding-top:0; padding-bottom:0;}

.fieldsetFix {border:0; padding:0; margin:1px 0;}

.zoomer {font-size:1.1em; position:absolute; overflow:hidden;}
.zoomer div {padding:1em;}

* html #backstage {width:99%;}
* html #backstageArea {width:99%;}
#backstageArea {display:none; position:relative; overflow: hidden; z-index:150; padding:0.3em 0.5em;}
#backstageToolbar {position:relative;}
#backstageArea a {font-weight:bold; margin-left:0.5em; padding:0.3em 0.5em;}
#backstageButton {display:none; position:absolute; z-index:175; top:0; right:0;}
#backstageButton a {padding: 0.3em 0.5em; display: inline-block;}
#backstage {position:relative; width:100%; z-index:50;}
#backstagePanel { display:none; z-index:100; position:absolute; width:90%; margin:0 5%; }
.backstagePanelFooter {padding-top:0.2em; float:right;}
.backstagePanelFooter a {padding:0.2em 0.4em;}
#backstageCloak {display:none; z-index:20; position:absolute; width:100%; height:100px;}

.whenBackstage {display:none;}
.backstageVisible .whenBackstage {display:block;}
/*}}}*/
/***
StyleSheet for use when a translation requires any css style changes.
This StyleSheet can be used directly by languages such as Chinese, Japanese and Korean which need larger font sizes.
***/
/*{{{*/
body {font-size:0.8em;}
#sidebarOptions {font-size:1.05em;}
#sidebarOptions a {font-style:normal;}
#sidebarOptions .sliderPanel {font-size:0.95em;}
.subtitle {font-size:0.8em;}
.viewer table.listView {font-size:0.95em;}
/*}}}*/
/*{{{*/
@media print {
  #mainMenu, #sidebar, #messageArea, .toolbar, #backstageButton, #backstageArea { display: none !important; }
  #displayArea { margin: 1em 1em 0em; }
}
/*}}}*/
<!--{{{-->
<div class='toolbar' role='navigation' macro='toolbar [[ToolbarCommands::ViewToolbar]]'></div>
<div class='title' macro='view title'></div>
<div class='subtitle'><span macro='view modifier link'></span>, <span macro='view modified date'></span> (<span macro='message views.wikified.createdPrompt'></span> <span macro='view created date'></span>)</div>
<div class='tagging' macro='tagging'></div>
<div class='tagged' macro='tags'></div>
<div class='viewer' macro='view text wikified'></div>
<div class='tagClear'></div>
<!--}}}-->
<<exportByTag "systemConfig" file:"./plugins/" format:js>>
/***
|''Name:''|ForEachTiddlerPlugin|
|''Version:''|1.0.8 (2007-04-12)|
|''Source:''|http://tiddlywiki.abego-software.de/#ForEachTiddlerPlugin|
|''Author:''|UdoBorkowski (ub [at] abego-software [dot] de)|
|''Licence:''|[[BSD open source license (abego Software)|http://www.abego-software.de/legal/apl-v10.html]]|
|''Copyright:''|&copy; 2005-2007 [[abego Software|http://www.abego-software.de]]|
|''TiddlyWiki:''|1.2.38+, 2.0|
|''Browser:''|Firefox 1.0.4+; Firefox 1.5; InternetExplorer 6.0|
!Description

Create customizable lists, tables etc. for your selections of tiddlers. Specify the tiddlers to include and their order through a powerful language.

''Syntax:''
|>|{{{<<}}}''forEachTiddler'' [''in'' //tiddlyWikiPath//] [''where'' //whereCondition//] [''sortBy'' //sortExpression// [''ascending'' //or// ''descending'']] [''script'' //scriptText//] [//action// [//actionParameters//]]{{{>>}}}|
|//tiddlyWikiPath//|The filepath to the TiddlyWiki the macro should work on. When missing the current TiddlyWiki is used.|
|//whereCondition//|(quoted) JavaScript boolean expression. May refer to the build-in variables {{{tiddler}}} and  {{{context}}}.|
|//sortExpression//|(quoted) JavaScript expression returning "comparable" objects (using '{{{<}}}','{{{>}}}','{{{==}}}'. May refer to the build-in variables {{{tiddler}}} and  {{{context}}}.|
|//scriptText//|(quoted) JavaScript text. Typically defines JavaScript functions that are called by the various JavaScript expressions (whereClause, sortClause, action arguments,...)|
|//action//|The action that should be performed on every selected tiddler, in the given order. By default the actions [[addToList|AddToListAction]] and [[write|WriteAction]] are supported. When no action is specified [[addToList|AddToListAction]]  is used.|
|//actionParameters//|(action specific) parameters the action may refer while processing the tiddlers (see action descriptions for details). <<tiddler [[JavaScript in actionParameters]]>>|
|>|~~Syntax formatting: Keywords in ''bold'', optional parts in [...]. 'or' means that exactly one of the two alternatives must exist.~~|

See details see [[ForEachTiddlerMacro]] and [[ForEachTiddlerExamples]].

!Revision history
* v1.0.8 (2007-04-12)
** Adapted to latest TiddlyWiki 2.2 Beta importTiddlyWiki API (introduced with changeset 2004). TiddlyWiki 2.2 Beta builds prior to changeset 2004 are no longer supported (but TiddlyWiki 2.1 and earlier, of cause)
* v1.0.7 (2007-03-28)
** Also support "pre" formatted TiddlyWikis (introduced with TW 2.2) (when using "in" clause to work on external tiddlers)
* v1.0.6 (2006-09-16)
** Context provides "viewerTiddler", i.e. the tiddler used to view the macro. Most times this is equal to the "inTiddler", but when using the "tiddler" macro both may be different.
** Support "begin", "end" and "none" expressions in "write" action
* v1.0.5 (2006-02-05)
** Pass tiddler containing the macro with wikify, context object also holds reference to tiddler containing the macro ("inTiddler"). Thanks to SimonBaird.
** Support Firefox 1.5.0.1
** Internal
*** Make "JSLint" conform
*** "Only install once"
* v1.0.4 (2006-01-06)
** Support TiddlyWiki 2.0
* v1.0.3 (2005-12-22)
** Features:
*** Write output to a file supports multi-byte environments (Thanks to Bram Chen)
*** Provide API to access the forEachTiddler functionality directly through JavaScript (see getTiddlers and performMacro)
** Enhancements:
*** Improved error messages on InternetExplorer.
* v1.0.2 (2005-12-10)
** Features:
*** context object also holds reference to store (TiddlyWiki)
** Fixed Bugs:
*** ForEachTiddler 1.0.1 has broken support on win32 Opera 8.51 (Thanks to BrunoSabin for reporting)
* v1.0.1 (2005-12-08)
** Features:
*** Access tiddlers stored in separated TiddlyWikis through the "in" option. I.e. you are no longer limited to only work on the "current TiddlyWiki".
*** Write output to an external file using the "toFile" option of the "write" action. With this option you may write your customized tiddler exports.
*** Use the "script" section to define "helper" JavaScript functions etc. to be used in the various JavaScript expressions (whereClause, sortClause, action arguments,...).
*** Access and store context information for the current forEachTiddler invocation (through the build-in "context" object) .
*** Improved script evaluation (for where/sort clause and write scripts).
* v1.0.0 (2005-11-20)
** initial version

!Code
***/
//{{{


//============================================================================
//============================================================================
//		   ForEachTiddlerPlugin
//============================================================================
//============================================================================

// Only install once
if (!version.extensions.ForEachTiddlerPlugin) {

if (!window.abego) window.abego = {};

version.extensions.ForEachTiddlerPlugin = {
	major: 1, minor: 0, revision: 8,
	date: new Date(2007,3,12),
	source: "http://tiddlywiki.abego-software.de/#ForEachTiddlerPlugin",
	licence: "[[BSD open source license (abego Software)|http://www.abego-software.de/legal/apl-v10.html]]",
	copyright: "Copyright (c) abego Software GmbH, 2005-2007 (www.abego-software.de)"
};

// For backward compatibility with TW 1.2.x
//
if (!TiddlyWiki.prototype.forEachTiddler) {
	TiddlyWiki.prototype.forEachTiddler = function(callback) {
		for(var t in this.tiddlers) {
			callback.call(this,t,this.tiddlers[t]);
		}
	};
}

//============================================================================
// forEachTiddler Macro
//============================================================================

version.extensions.forEachTiddler = {
	major: 1, minor: 0, revision: 8, date: new Date(2007,3,12), provider: "http://tiddlywiki.abego-software.de"};

// ---------------------------------------------------------------------------
// Configurations and constants
// ---------------------------------------------------------------------------

config.macros.forEachTiddler = {
	 // Standard Properties
	 label: "forEachTiddler",
	 prompt: "Perform actions on a (sorted) selection of tiddlers",

	 // actions
	 actions: {
		 addToList: {},
		 write: {}
	 }
};

// ---------------------------------------------------------------------------
//  The forEachTiddler Macro Handler
// ---------------------------------------------------------------------------

config.macros.forEachTiddler.getContainingTiddler = function(e) {
	while(e && !hasClass(e,"tiddler"))
		e = e.parentNode;
	var title = e ? e.getAttribute("tiddler") : null;
	return title ? store.getTiddler(title) : null;
};

config.macros.forEachTiddler.handler = function(place,macroName,params,wikifier,paramString,tiddler) {
	// config.macros.forEachTiddler.traceMacroCall(place,macroName,params,wikifier,paramString,tiddler);

	if (!tiddler) tiddler = config.macros.forEachTiddler.getContainingTiddler(place);
	// --- Parsing ------------------------------------------

	var i = 0; // index running over the params
	// Parse the "in" clause
	var tiddlyWikiPath = undefined;
	if ((i < params.length) && params[i] == "in") {
		i++;
		if (i >= params.length) {
			this.handleError(place, "TiddlyWiki path expected behind 'in'.");
			return;
		}
		tiddlyWikiPath = this.paramEncode((i < params.length) ? params[i] : "");
		i++;
	}

	// Parse the where clause
	var whereClause ="true";
	if ((i < params.length) && params[i] == "where") {
		i++;
		whereClause = this.paramEncode((i < params.length) ? params[i] : "");
		i++;
	}

	// Parse the sort stuff
	var sortClause = null;
	var sortAscending = true;
	if ((i < params.length) && params[i] == "sortBy") {
		i++;
		if (i >= params.length) {
			this.handleError(place, "sortClause missing behind 'sortBy'.");
			return;
		}
		sortClause = this.paramEncode(params[i]);
		i++;

		if ((i < params.length) && (params[i] == "ascending" || params[i] == "descending")) {
			 sortAscending = params[i] == "ascending";
			 i++;
		}
	}

	// Parse the script
	var scriptText = null;
	if ((i < params.length) && params[i] == "script") {
		i++;
		scriptText = this.paramEncode((i < params.length) ? params[i] : "");
		i++;
	}

	// Parse the action.
	// When we are already at the end use the default action
	var actionName = "addToList";
	if (i < params.length) {
	   if (!config.macros.forEachTiddler.actions[params[i]]) {
			this.handleError(place, "Unknown action '"+params[i]+"'.");
			return;
		} else {
			actionName = params[i];
			i++;
		}
	}

	// Get the action parameter
	// (the parsing is done inside the individual action implementation.)
	var actionParameter = params.slice(i);


	// --- Processing ------------------------------------------
	try {
		this.performMacro({
				place: place,
				inTiddler: tiddler,
				whereClause: whereClause,
				sortClause: sortClause,
				sortAscending: sortAscending,
				actionName: actionName,
				actionParameter: actionParameter,
				scriptText: scriptText,
				tiddlyWikiPath: tiddlyWikiPath});

	} catch (e) {
		this.handleError(place, e);
	}
};

// Returns an object with properties "tiddlers" and "context".
// tiddlers holds the (sorted) tiddlers selected by the parameter,
// context the context of the execution of the macro.
//
// The action is not yet performed.
//
// @parameter see performMacro
//
config.macros.forEachTiddler.getTiddlersAndContext = function(parameter) {

	var context = config.macros.forEachTiddler.createContext(parameter.place, parameter.whereClause, parameter.sortClause, parameter.sortAscending, parameter.actionName, parameter.actionParameter, parameter.scriptText, parameter.tiddlyWikiPath, parameter.inTiddler);

	var tiddlyWiki = parameter.tiddlyWikiPath ? this.loadTiddlyWiki(parameter.tiddlyWikiPath) : store;
	context["tiddlyWiki"] = tiddlyWiki;

	// Get the tiddlers, as defined by the whereClause
	var tiddlers = this.findTiddlers(parameter.whereClause, context, tiddlyWiki);
	context["tiddlers"] = tiddlers;

	// Sort the tiddlers, when sorting is required.
	if (parameter.sortClause) {
		this.sortTiddlers(tiddlers, parameter.sortClause, parameter.sortAscending, context);
	}

	return {tiddlers: tiddlers, context: context};
};

// Returns the (sorted) tiddlers selected by the parameter.
//
// The action is not yet performed.
//
// @parameter see performMacro
//
config.macros.forEachTiddler.getTiddlers = function(parameter) {
	return this.getTiddlersAndContext(parameter).tiddlers;
};

// Performs the macros with the given parameter.
//
// @param parameter holds the parameter of the macro as separate properties.
//				  The following properties are supported:
//
//						place
//						whereClause
//						sortClause
//						sortAscending
//						actionName
//						actionParameter
//						scriptText
//						tiddlyWikiPath
//
//					All properties are optional.
//					For most actions the place property must be defined.
//
config.macros.forEachTiddler.performMacro = function(parameter) {
	var tiddlersAndContext = this.getTiddlersAndContext(parameter);

	// Perform the action
	var actionName = parameter.actionName ? parameter.actionName : "addToList";
	var action = config.macros.forEachTiddler.actions[actionName];
	if (!action) {
		this.handleError(parameter.place, "Unknown action '"+actionName+"'.");
		return;
	}

	var actionHandler = action.handler;
	actionHandler(parameter.place, tiddlersAndContext.tiddlers, parameter.actionParameter, tiddlersAndContext.context);
};

// ---------------------------------------------------------------------------
//  The actions
// ---------------------------------------------------------------------------

// Internal.
//
// --- The addToList Action -----------------------------------------------
//
config.macros.forEachTiddler.actions.addToList.handler = function(place, tiddlers, parameter, context) {
	// Parse the parameter
	var p = 0;

	// Check for extra parameters
	if (parameter.length > p) {
		config.macros.forEachTiddler.createExtraParameterErrorElement(place, "addToList", parameter, p);
		return;
	}

	// Perform the action.
	var list = document.createElement("ul");
	place.appendChild(list);
	for (var i = 0; i < tiddlers.length; i++) {
		var tiddler = tiddlers[i];
		var listItem = document.createElement("li");
		list.appendChild(listItem);
		createTiddlyLink(listItem, tiddler.title, true);
	}
};

abego.parseNamedParameter = function(name, parameter, i) {
	var beginExpression = null;
	if ((i < parameter.length) && parameter[i] == name) {
		i++;
		if (i >= parameter.length) {
			throw "Missing text behind '%0'".format([name]);
		}

		return config.macros.forEachTiddler.paramEncode(parameter[i]);
	}
	return null;
}

// Internal.
//
// --- The write Action ---------------------------------------------------
//
config.macros.forEachTiddler.actions.write.handler = function(place, tiddlers, parameter, context) {
	// Parse the parameter
	var p = 0;
	if (p >= parameter.length) {
		this.handleError(place, "Missing expression behind 'write'.");
		return;
	}

	var textExpression = config.macros.forEachTiddler.paramEncode(parameter[p]);
	p++;

	// Parse the "begin" option
	var beginExpression = abego.parseNamedParameter("begin", parameter, p);
	if (beginExpression !== null)
		p += 2;
	var endExpression = abego.parseNamedParameter("end", parameter, p);
	if (endExpression !== null)
		p += 2;
	var noneExpression = abego.parseNamedParameter("none", parameter, p);
	if (noneExpression !== null)
		p += 2;

	// Parse the "toFile" option
	var filename = null;
	var lineSeparator = undefined;
	if ((p < parameter.length) && parameter[p] == "toFile") {
		p++;
		if (p >= parameter.length) {
			this.handleError(place, "Filename expected behind 'toFile' of 'write' action.");
			return;
		}

		filename = config.macros.forEachTiddler.getLocalPath(config.macros.forEachTiddler.paramEncode(parameter[p]));
		p++;
		if ((p < parameter.length) && parameter[p] == "withLineSeparator") {
			p++;
			if (p >= parameter.length) {
				this.handleError(place, "Line separator text expected behind 'withLineSeparator' of 'write' action.");
				return;
			}
			lineSeparator = config.macros.forEachTiddler.paramEncode(parameter[p]);
			p++;
		}
	}

	// Check for extra parameters
	if (parameter.length > p) {
		config.macros.forEachTiddler.createExtraParameterErrorElement(place, "write", parameter, p);
		return;
	}

	// Perform the action.
	var func = config.macros.forEachTiddler.getEvalTiddlerFunction(textExpression, context);
	var count = tiddlers.length;
	var text = "";
	if (count > 0 && beginExpression)
		text += config.macros.forEachTiddler.getEvalTiddlerFunction(beginExpression, context)(undefined, context, count, undefined);

	for (var i = 0; i < count; i++) {
		var tiddler = tiddlers[i];
		text += func(tiddler, context, count, i);
	}

	if (count > 0 && endExpression)
		text += config.macros.forEachTiddler.getEvalTiddlerFunction(endExpression, context)(undefined, context, count, undefined);

	if (count == 0 && noneExpression)
		text += config.macros.forEachTiddler.getEvalTiddlerFunction(noneExpression, context)(undefined, context, count, undefined);


	if (filename) {
		if (lineSeparator !== undefined) {
			lineSeparator = lineSeparator.replace(/\\n/mg, "\n").replace(/\\r/mg, "\r");
			text = text.replace(/\n/mg,lineSeparator);
		}
		saveFile(filename, convertUnicodeToUTF8(text));
	} else {
		var wrapper = createTiddlyElement(place, "span");
		wikify(text, wrapper, null/* highlightRegExp */, context.inTiddler);
	}
};


// ---------------------------------------------------------------------------
//  Helpers
// ---------------------------------------------------------------------------

// Internal.
//
config.macros.forEachTiddler.createContext = function(placeParam, whereClauseParam, sortClauseParam, sortAscendingParam, actionNameParam, actionParameterParam, scriptText, tiddlyWikiPathParam, inTiddlerParam) {
	return {
		place : placeParam,
		whereClause : whereClauseParam,
		sortClause : sortClauseParam,
		sortAscending : sortAscendingParam,
		script : scriptText,
		actionName : actionNameParam,
		actionParameter : actionParameterParam,
		tiddlyWikiPath : tiddlyWikiPathParam,
		inTiddler : inTiddlerParam, // the tiddler containing the <<forEachTiddler ...>> macro call.
		viewerTiddler : config.macros.forEachTiddler.getContainingTiddler(placeParam) // the tiddler showing the forEachTiddler result
	};
};

// Internal.
//
// Returns a TiddlyWiki with the tiddlers loaded from the TiddlyWiki of
// the given path.
//
config.macros.forEachTiddler.loadTiddlyWiki = function(path, idPrefix) {
	if (!idPrefix) {
		idPrefix = "store";
	}
	var lenPrefix = idPrefix.length;

	// Read the content of the given file
	var content = loadFile(this.getLocalPath(path));
	if(content === null) {
		throw "TiddlyWiki '"+path+"' not found.";
	}

	var tiddlyWiki = new TiddlyWiki();

	// Starting with TW 2.2 there is a helper function to import the tiddlers
	if (tiddlyWiki.importTiddlyWiki) {
		if (!tiddlyWiki.importTiddlyWiki(content))
			throw "File '"+path+"' is not a TiddlyWiki.";
		tiddlyWiki.dirty = false;
		return tiddlyWiki;
	}

	// The legacy code, for TW < 2.2

	// Locate the storeArea div's
	var posOpeningDiv = content.indexOf(startSaveArea);
	var posClosingDiv = content.lastIndexOf(endSaveArea);
	if((posOpeningDiv == -1) || (posClosingDiv == -1)) {
		throw "File '"+path+"' is not a TiddlyWiki.";
	}
	var storageText = content.substr(posOpeningDiv + startSaveArea.length, posClosingDiv);

	// Create a "div" element that contains the storage text
	var myStorageDiv = document.createElement("div");
	myStorageDiv.innerHTML = storageText;
	myStorageDiv.normalize();

	// Create all tiddlers in a new TiddlyWiki
	// (following code is modified copy of TiddlyWiki.prototype.loadFromDiv)
	var store = myStorageDiv.childNodes;
	for(var t = 0; t < store.length; t++) {
		var e = store[t];
		var title = null;
		if(e.getAttribute)
			title = e.getAttribute("tiddler");
		if(!title && e.id && e.id.substr(0,lenPrefix) == idPrefix)
			title = e.id.substr(lenPrefix);
		if(title && title !== "") {
			var tiddler = tiddlyWiki.createTiddler(title);
			tiddler.loadFromDiv(e,title);
		}
	}
	tiddlyWiki.dirty = false;

	return tiddlyWiki;
};



// Internal.
//
// Returns a function that has a function body returning the given javaScriptExpression.
// The function has the parameters:
//
//	 (tiddler, context, count, index)
//
config.macros.forEachTiddler.getEvalTiddlerFunction = function (javaScriptExpression, context) {
	var script = context["script"];
	var functionText = "var theFunction = function(tiddler, context, count, index) { return "+javaScriptExpression+"}";
	var fullText = (script ? script+";" : "")+functionText+";theFunction;";
	return eval(fullText);
};

// Internal.
//
config.macros.forEachTiddler.findTiddlers = function(whereClause, context, tiddlyWiki) {
	var result = [];
	var func = config.macros.forEachTiddler.getEvalTiddlerFunction(whereClause, context);
	tiddlyWiki.forEachTiddler(function(title,tiddler) {
		if (func(tiddler, context, undefined, undefined)) {
			result.push(tiddler);
		}
	});
	return result;
};

// Internal.
//
config.macros.forEachTiddler.createExtraParameterErrorElement = function(place, actionName, parameter, firstUnusedIndex) {
	var message = "Extra parameter behind '"+actionName+"':";
	for (var i = firstUnusedIndex; i < parameter.length; i++) {
		message += " "+parameter[i];
	}
	this.handleError(place, message);
};

// Internal.
//
config.macros.forEachTiddler.sortAscending = function(tiddlerA, tiddlerB) {
	var result =
		(tiddlerA.forEachTiddlerSortValue == tiddlerB.forEachTiddlerSortValue)
			? 0
			: (tiddlerA.forEachTiddlerSortValue < tiddlerB.forEachTiddlerSortValue)
			   ? -1
			   : +1;
	return result;
};

// Internal.
//
config.macros.forEachTiddler.sortDescending = function(tiddlerA, tiddlerB) {
	var result =
		(tiddlerA.forEachTiddlerSortValue == tiddlerB.forEachTiddlerSortValue)
			? 0
			: (tiddlerA.forEachTiddlerSortValue < tiddlerB.forEachTiddlerSortValue)
			   ? +1
			   : -1;
	return result;
};

// Internal.
//
config.macros.forEachTiddler.sortTiddlers = function(tiddlers, sortClause, ascending, context) {
	// To avoid evaluating the sortClause whenever two items are compared
	// we pre-calculate the sortValue for every item in the array and store it in a
	// temporary property ("forEachTiddlerSortValue") of the tiddlers.
	var func = config.macros.forEachTiddler.getEvalTiddlerFunction(sortClause, context);
	var count = tiddlers.length;
	var i;
	for (i = 0; i < count; i++) {
		var tiddler = tiddlers[i];
		tiddler.forEachTiddlerSortValue = func(tiddler,context, undefined, undefined);
	}

	// Do the sorting
	tiddlers.sort(ascending ? this.sortAscending : this.sortDescending);

	// Delete the temporary property that holds the sortValue.
	for (i = 0; i < tiddlers.length; i++) {
		delete tiddlers[i].forEachTiddlerSortValue;
	}
};


// Internal.
//
config.macros.forEachTiddler.trace = function(message) {
	displayMessage(message);
};

// Internal.
//
config.macros.forEachTiddler.traceMacroCall = function(place,macroName,params) {
	var message ="<<"+macroName;
	for (var i = 0; i < params.length; i++) {
		message += " "+params[i];
	}
	message += ">>";
	displayMessage(message);
};


// Internal.
//
// Creates an element that holds an error message
//
config.macros.forEachTiddler.createErrorElement = function(place, exception) {
	var message = (exception.description) ? exception.description : exception.toString();
	return createTiddlyElement(place,"span",null,"forEachTiddlerError","<<forEachTiddler ...>>: "+message);
};

// Internal.
//
// @param place [may be null]
//
config.macros.forEachTiddler.handleError = function(place, exception) {
	if (place) {
		this.createErrorElement(place, exception);
	} else {
		throw exception;
	}
};

// Internal.
//
// Encodes the given string.
//
// Replaces
//	 "$))" to ">>"
//	 "$)" to ">"
//
config.macros.forEachTiddler.paramEncode = function(s) {
	var reGTGT = new RegExp("\\$\\)\\)","mg");
	var reGT = new RegExp("\\$\\)","mg");
	return s.replace(reGTGT, ">>").replace(reGT, ">");
};

// Internal.
//
// Returns the given original path (that is a file path, starting with "file:")
// as a path to a local file, in the systems native file format.
//
// Location information in the originalPath (i.e. the "#" and stuff following)
// is stripped.
//
config.macros.forEachTiddler.getLocalPath = function(originalPath) {
	// Remove any location part of the URL
	var hashPos = originalPath.indexOf("#");
	if(hashPos != -1)
		originalPath = originalPath.substr(0,hashPos);
	// Convert to a native file format assuming
	// "file:///x:/path/path/path..." - pc local file --> "x:\path\path\path..."
	// "file://///server/share/path/path/path..." - FireFox pc network file --> "\\server\share\path\path\path..."
	// "file:///path/path/path..." - mac/unix local file --> "/path/path/path..."
	// "file://server/share/path/path/path..." - pc network file --> "\\server\share\path\path\path..."
	var localPath;
	if(originalPath.charAt(9) == ":") // pc local file
		localPath = unescape(originalPath.substr(8)).replace(new RegExp("/","g"),"\\");
	else if(originalPath.indexOf("file://///") === 0) // FireFox pc network file
		localPath = "\\\\" + unescape(originalPath.substr(10)).replace(new RegExp("/","g"),"\\");
	else if(originalPath.indexOf("file:///") === 0) // mac/unix local file
		localPath = unescape(originalPath.substr(7));
	else if(originalPath.indexOf("file:/") === 0) // mac/unix local file
		localPath = unescape(originalPath.substr(5));
	else // pc network file
		localPath = "\\\\" + unescape(originalPath.substr(7)).replace(new RegExp("/","g"),"\\");
	return localPath;
};

// ---------------------------------------------------------------------------
// Stylesheet Extensions (may be overridden by local StyleSheet)
// ---------------------------------------------------------------------------
//
setStylesheet(
	".forEachTiddlerError{color: #ffffff;background-color: #880000;}",
	"forEachTiddler");

//============================================================================
// End of forEachTiddler Macro
//============================================================================


//============================================================================
// String.startsWith Function
//============================================================================
//
// Returns true if the string starts with the given prefix, false otherwise.
//
version.extensions["String.startsWith"] = {major: 1, minor: 0, revision: 0, date: new Date(2005,11,20), provider: "http://tiddlywiki.abego-software.de"};
//
String.prototype.startsWith = function(prefix) {
	var n =  prefix.length;
	return (this.length >= n) && (this.slice(0, n) == prefix);
};



//============================================================================
// String.endsWith Function
//============================================================================
//
// Returns true if the string ends with the given suffix, false otherwise.
//
version.extensions["String.endsWith"] = {major: 1, minor: 0, revision: 0, date: new Date(2005,11,20), provider: "http://tiddlywiki.abego-software.de"};
//
String.prototype.endsWith = function(suffix) {
	var n = suffix.length;
	return (this.length >= n) && (this.right(n) == suffix);
};


//============================================================================
// String.contains Function
//============================================================================
//
// Returns true when the string contains the given substring, false otherwise.
//
version.extensions["String.contains"] = {major: 1, minor: 0, revision: 0, date: new Date(2005,11,20), provider: "http://tiddlywiki.abego-software.de"};
//
String.prototype.contains = function(substring) {
	return this.indexOf(substring) >= 0;
};

//============================================================================
// Array.indexOf Function
//============================================================================
//
// Returns the index of the first occurance of the given item in the array or
// -1 when no such item exists.
//
// @param item [may be null]
//
version.extensions["Array.indexOf"] = {major: 1, minor: 0, revision: 0, date: new Date(2005,11,20), provider: "http://tiddlywiki.abego-software.de"};
//
Array.prototype.indexOf = function(item) {
	for (var i = 0; i < this.length; i++) {
		if (this[i] == item) {
			return i;
		}
	}
	return -1;
};

//============================================================================
// Array.contains Function
//============================================================================
//
// Returns true when the array contains the given item, otherwise false.
//
// @param item [may be null]
//
version.extensions["Array.contains"] = {major: 1, minor: 0, revision: 0, date: new Date(2005,11,20), provider: "http://tiddlywiki.abego-software.de"};
//
Array.prototype.contains = function(item) {
	return (this.indexOf(item) >= 0);
};

//============================================================================
// Array.containsAny Function
//============================================================================
//
// Returns true when the array contains at least one of the elements
// of the item. Otherwise (or when items contains no elements) false is returned.
//
version.extensions["Array.containsAny"] = {major: 1, minor: 0, revision: 0, date: new Date(2005,11,20), provider: "http://tiddlywiki.abego-software.de"};
//
Array.prototype.containsAny = function(items) {
	for(var i = 0; i < items.length; i++) {
		if (this.contains(items[i])) {
			return true;
		}
	}
	return false;
};


//============================================================================
// Array.containsAll Function
//============================================================================
//
// Returns true when the array contains all the items, otherwise false.
//
// When items is null false is returned (even if the array contains a null).
//
// @param items [may be null]
//
version.extensions["Array.containsAll"] = {major: 1, minor: 0, revision: 0, date: new Date(2005,11,20), provider: "http://tiddlywiki.abego-software.de"};
//
Array.prototype.containsAll = function(items) {
	for(var i = 0; i < items.length; i++) {
		if (!this.contains(items[i])) {
			return false;
		}
	}
	return true;
};


} // of "install only once"

// Used Globals (for JSLint) ==============
// ... DOM
/*global 	document */
// ... TiddlyWiki Core
/*global 	convertUnicodeToUTF8, createTiddlyElement, createTiddlyLink,
			displayMessage, endSaveArea, hasClass, loadFile, saveFile,
			startSaveArea, store, wikify */
//}}}


/***
!Licence and Copyright
Copyright (c) abego Software ~GmbH, 2005 ([[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.
***/

"""//{{{"""
[
<<forEachTiddler 
where 
'tiddler.tags.contains("systemConfig")'
sortBy
'tiddler.title'
script '
	function getDescriptionFromTable(text, title) {
                var regexPatternString = store.getTiddlerText("JSONRegexPattern"); 
                var descriptionRegex = new RegExp(regexPatternString.substring(1, regexPatternString.length - 1)); 
		const match = text.match(descriptionRegex);
		return match ? match[1].trim() : title;
	}'
write 
'"{"
+"\n"
+"\"url\": \"https://github.com/wangyenshu/TiddlyWikiClassicPluginsArchives/blob/main/MartinsPlugins/"
+tiddler.title
+".js"
+"\","
+"\n"
+"\"description\": \""
+getDescriptionFromTable(tiddler.text, tiddler.title)
+"\""
+"\n"
+"},"
+"\n"'
>>]
"""//}}}"""
/\|(?:''Description:''|'Description:'|Description|'Description'|"Description"|''description:''|'description:'|description|'description'|"description")\s*\|\s*(.*?)\|/
/***
|Description |Allows to store any number of tiddlers as external files and more|
|Source      |https://github.com/YakovL/TiddlyWiki_TiddlerInFilePlugin/blob/master/TiddlerInFilePlugin.js|
|Author      |Yakov Litvin, Modified by Yanshu Wang|
|Version     |1.1.4|
|License     |[[MIT|https://github.com/YakovL/TiddlyWiki_YL_ExtensionsCollection/blob/master/Common%20License%20(MIT)]]|
!!!Requirements
Since the plugin loads and saves files, it needs that the browser and/or saver supports these operations. Here are some options:
* Loading files:
** Works when TW is opened (and external files are attempted to be loaded) via http (or https) protocol (but not, by default, via file:// url);
** Firefox allows loading local files when the {{{security.fileuri.strict_origin_policy}}} setting is set to {{{false}}} (type {{{about:config}}} in address bar to open the configuration UI). If Firefox is your primary browser, i.e. you use it not just for TW and some trusted sites, it is recommended to [[create|https://groups.google.com/g/tiddlywikiclassic/c/T1q0q9uF2aI/m/f2SVb8CYAAAJ]] a separate profile for using ~TWs and change the setting there to ensure better security.
** Some savers may provide full file loading functionality. Those yet to be tested: [[TiddlyDesktop|https://github.com/Jermolene/TiddlyDesktop]], [[tiddly-node-saver|https://github.com/fallwest/tiddly-node-saver]], [[nwTWcSaver|https://github.com/nwOkido/nwTWcSaver]], ..;
* Saving files:
** suitable savers include: [[Timimi|https://github.com/ibnishak/Timimi]]; the savers yet to be tested: [[TiddlyDesktop|https://github.com/Jermolene/TiddlyDesktop]], ..;

!!!Usage
Once the plugin is installed (copy, tag with {{{systemConfig}}}, reload) storing tiddlers in files is done via 2 steps:
# list (describe) those in [[ExternalTiddlersList]] by writing {{{<<external>>}}} macros there
# if the file exists and the tiddler doesn't, reload TW (external tiddler will be loaded on startup);<br>if the tiddler exists and the file doesn't, just save your TW
Note: external tiddler is always the main "source of truth": if {{{keepInternal:true}}} is used, it is saved in TW as well, but loading external content will overwrite what is saved in TW.

Here's how the macro is used:
{{{
<<external [[MyTestTiddler]]>>
}}}
will store the tiddler's text in {{{MyTestTiddler.txt}}} in the same folder as TW. There's a number of other options:
{{{
<<external [[tiddler name]]
  [file:<relative path with or without filename and extension>]
  [format:{externalized | text}]
  [keepInternal:true]
  [plugin:true]
>>
}}}
Examples of the {{{file}}} param usage:
* {{{<<external [[MyTestTiddler]] file:"other name">>}}} makes file name different from tiddler name
* {{{<<external [[MyPlugin]] file:"MyPlugin.js" plugin:true>>}}} sets a custom file extension (see also the {{{plugin}}} option below)
* {{{<<external [[MyLog]] file:"../logs/">>}}} will store the file in another folder (note that omitted filename after {{{/}}} means "use tiddler name as filename and default extension")
* the plugin doesn't take care of forbidden characters yet ({{{*}}}, {{{?}}}, {{{"}}} etc), so be careful about those

Supported formats are
* {{{text}}} (default) – only tiddler text is stored in the file with {{{.txt}}} extension; and
* {{{externalized}}} – whole tiddler is stored in the same format as in TW store, file has {{{.tid.html}}} extension and is displayed is monospace tiddler text when opened in browser
Formats can be added by extending {{{config.macros.external.fileFormats}}}.

The {{{keepInternal}}} option makes TW save the tiddler in both external file and TW itself. This, for instance, affects tiddlers stored in {{{text}}} format: without it, all fields like creator, modified etc are destroyed on reload (since are not saved), but with it they are preserved.

The {{{plugin}}} option makes TW evaluate the tiddler like it does with plugins. Note that it doesn't handle plugin dependencies yet. @@color:red;Warning@@: TIFP doesn't currently take care of installing only once, so use {{{plugin}}} with {{{keepInternal}}} ''only'' if you understand the outcome (for instance, some plugins may create infinite loops; also remember that internal version will be always installed first).
***/
//{{{
config.macros.external = {
	fileFormats: {
		text: {
			extension: 'txt',
			externalize: function(tiddler) { return tiddler.text },
			// changes tiddler as a side-effect not to remove existing fields
			internalize: function(tiddler, source) {
				tiddler.text = source
			}
		},
		externalized: {
			extension: 'tid.html',
			externalize: function(tiddler) {
				return store.getSaver().externalizeTiddler(store, tiddler)
			},
			// like for 'text', extends tiddler, doesn't create from scratch
			internalize: function(tiddler, source) {
				const div = createTiddlyElement(document.body, 'div')
				div.setAttribute('style','display:none;')
				div.innerHTML = source
				store.getLoader().internalizeTiddler(store, tiddler,
					tiddler.title, div.firstChild)
				div.remove()
			}
		},
                js: {
			extension: 'js',
			externalize: function(tiddler) { return tiddler.text },
			// changes tiddler as a side-effect not to remove existing fields
			internalize: function(tiddler, source) {
				tiddler.text = source
			}
		}/*,
		tid: { extension: 'tid' },
		json: { extension: 'tid.json' }
		externalizedWithFormatter? sane to implement?
		*/
	},
	// here and below "meta" means "info about registered external tiddler,
	// be it loaded or not"
	getExtension: function(meta) {
		const format = this.fileFormats[meta.fileFormat]
		if(!format) return //# ok??
		return format.extension
	},
	externalizeTiddler: function(meta) {
		const format = this.fileFormats[meta.fileFormat]
		if(!format) return //# ok??
		return format.externalize(meta.tiddler)
	},
	internalizeTiddler: function(meta, source) {
		const format = this.fileFormats[meta.fileFormat]
		if(!format) return //# ok??
		
		const tiddler = store.fetchTiddler(meta.tiddlerName) ||
			new Tiddler(meta.tiddlerName)
		format.internalize(tiddler, source) //# pass meta to tiddler?
		// a patch for Windows: don't get linebreaks duplicated
		tiddler.text = tiddler.text.replace(/\r\n/g, '\n')
		tiddler.doNotSave = function() { return !meta.keepInTW }
		meta.tiddler = tiddler
		
		return tiddler
	},

	listName: "ExternalTiddlersList",
	// read files list, load
	init: function() {
		const listTiddler = store.fetchTiddler(this.listName)
		if(!listTiddler || !listTiddler.text) return
		wikify(listTiddler.text, createTiddlyElement(null, 'div'))

		for(let meta of this.tiddlersMeta) this.loadExternal(meta)
	},
	handler: function(place, macroName, params, wikifier, paramString, tiddler) {
		// parse params, register
		const defaultParam = 'tiddler'
		const pParams = paramString.parseParams(defaultParam, null, true)
		const meta = {}
		meta.tiddlerName = getParam(pParams, defaultParam)
		if(!meta.tiddlerName) return

		// although called .fileName, it actually can contain relative part of a path
		// fallback to meta.tiddlerName is set when calculating the full path
		meta.fileName = getParam(pParams, 'file', '')
		//# check if contains "bad" characters (like " or * ..for local paths only)
		meta.fileFormat = getParam(pParams, 'format', 'text')
		meta.isPlugin = getFlag(pParams, 'plugin') //# allow just "plugin" instead of "plugin:true"?
		const keepInternal = getParam(pParams, 'keepInternal')
		meta.keepInTW = !!keepInternal && keepInternal !== 'false' //# ~
		this.registerExternal(meta)

		// visual feedback
		const macroText = wikifier.source.substring(wikifier.matchStart, wikifier.nextMatch)
		createTiddlyText(place, 'external ')
		createTiddlyLink(place, meta.tiddlerName, true)
		createTiddlyText(place, ' (')
		createTiddlyElement(place, 'code', '', '', macroText.slice(2 + macroName.length + 1, -2))
		createTiddlyText(place, ')')
	},
	// describes tiddlers registered as external, not necessarily loaded
	tiddlersMeta: [],
	registerExternal: function(meta) {
		//# check if already registered, don't register twice
		this.tiddlersMeta.push(meta)
	},
	getMetaFor: function(tiddlerOrTitle) {
		const isTitle = typeof tiddlerOrTitle == "string"
		for(meta of this.tiddlersMeta)
			if(isTitle && meta.tiddlerName == tiddlerOrTitle ||
			  !isTitle && meta.tiddler == tiddlerOrTitle)
				return meta
	},
	loadExternal: function(meta) {
		// sync loading fails on startup because TF injects new mozillaLoadFile too late
//		const tiddlerText = loadFile(getLocalPath(getFullPath(meta.fileName)));
//		onExternalTiddlerLoad(tiddlerText !== null, meta, tiddlerText);
		// so we use async instead:

		const callback = this.onExternalTiddlerLoad
		const path = getFullPath(meta.fileName, meta.tiddlerName, this.getExtension(meta))
		// httpReq("GET", path, callback, meta) uses default dataType,
		// which causes js to get evaluated on load. To avoid this, we customize the ajax call:
		jQuery.ajax({
			type: "GET",
			url: path,
			dataType: "text",
			processData: false,
			cache: false,
			complete: function(xhr, textStatus) {
				if((!xhr.status && location.protocol === "file:") || (xhr.status >= 200 && xhr.status < 300) || xhr.status === 304)
					callback(true, meta, xhr.responseText, path, xhr)
				else
					callback(false, meta, null, path, xhr)
			}
		})
		//# rename onExternalTiddlerLoad into internalizeAndRegister?
	},
	onExternalTiddlerLoad: function(success, meta, responseText) {
		if(!success) return //# notify somehow? may fail because file is not created yet or ...
		const tiddler = config.macros.external.internalizeTiddler(meta, responseText)
		store.addTiddler(tiddler)
		//# what if tiddler already exists?
		if(meta.isPlugin) {
			// make it look normally
			tiddler.tags.pushUnique('systemConfig')
			const author = store.getTiddlerText(tiddler.title + "::Author")
			if(author) {
				tiddler.creator = tiddler.creator || author
				tiddler.modifier = tiddler.modifier || tiddler.creator
			}

			eval(tiddler.text)
			// for plugins introducing macros, formatters etc (may be adjusted in the future)
			refreshAll()
		}
		//meta.lastLoaded = responseText
	},
	saveExternal: function(meta, callback) {
		const fullPath = getFullPath(meta.fileName, meta.tiddlerName, this.getExtension(meta))
		// we don't try to save remote files (yet)
		if(!isLocalAbsolutePath(fullPath)) {
			//# if(callback) callback(.., '[saving remote is not supported]')
			return
		}
		const localPath = getLocalPath(fullPath)

		const contentToSave = this.externalizeTiddler(meta)
		// save only if have something to save
		//if(contentToSave != meta.lastLoaded) {
			saveFile(localPath, contentToSave)
			//# get result of saving, return it
			//# or move externalizing into a separate helper?
		//	meta.lastLoaded = contentToSave
			//# this assumes saving didn't fail, which may be wrong
		//}

		//# if(callback) callback(.., '[...]')
	},
	saveAll: function() {
		let overallSuccess = true
		for(let meta of this.tiddlersMeta) {
			// a tiddler may got created after registration
			if(!meta.tiddler) {
				let tiddlerInStore = store.fetchTiddler(meta.tiddlerName)
				if(tiddlerInStore) {
					meta.tiddler = tiddlerInStore
				} else
					// tiddler doesn't exist and we do nothing
					continue
					//# based on config, we can show a warning instead
			}
			//# if(meta.tiddler.title != meta.tiddlerName)
			//  means tiddler got renamed → change meta.tiddlerName &
			//  update this.listName . If store contains another tiddler
			//  with that name, still keep the registered one?
			overallSuccess = this.saveExternal(meta) && overallSuccess
			//# if saving failed, do something! (.oO dirty, notifying)
		}
		return overallSuccess
	}
}

config.macros.exportByTag = {
    handler: function(place, macroName, params, wikifier, paramString, tiddler) {
        const pParams = paramString.parseParams("tag", null, true)
        const tag = getParam(pParams, "tag")
        if (!tag) {
            createTiddlyElement(place, "span", null, "error", "<<exportByTag>> macro requires a 'tag' parameter.")
            return
        }

        const fileFormat = getParam(pParams, 'format', 'text')
        const filePathPrefix = getParam(pParams, 'file', '')

        const tiddlersToExport = store.getTaggedTiddlers(tag);
        
        createTiddlyElement(place, "span", null, null, "Exporting tiddlers with tag '")
        createTiddlyElement(place, "b", null, null, tag)
        createTiddlyElement(place, "span", null, null, "':")
        const ul = createTiddlyElement(place, "ul")

        let exportCount = 0;
        for (let i = 0; i < tiddlersToExport.length; i++) {
            const currentTiddler = tiddlersToExport[i];
            const meta = {
                tiddlerName: currentTiddler.title,
                tiddler: currentTiddler,
                fileFormat: fileFormat,
                fileName: filePathPrefix, // Will be combined with tiddler name and extension
                keepInTW: true // Always keep in TW for this export function
            };

            const li = createTiddlyElement(ul, "li")
            createTiddlyLink(li, currentTiddler.title, true)
            
            try {
                config.macros.external.saveExternal(meta);
                createTiddlyText(li, " - Exported successfully.")
                exportCount++;
            } catch (e) {
                createTiddlyText(li, " - Export failed: " + e.message)
                displayMessage("Error exporting tiddler " + currentTiddler.title + ": " + e.message);
            }
        }
        createTiddlyElement(place, "p", null, null, "Total exported: " + exportCount + " of " + tiddlersToExport.length);
    }
};

//# see implementations in STP (share to the core?)
function isAbsolutePath(path) {
	// covers http:, https:, file:, other schemas, windows paths (D:\...)
	if(/^\w+\:/.exec(path)) return true
	// unix absolute paths, starting with /
	if(/^\//.exec(path)) return true
	return false
}
function isLocalAbsolutePath(path) {
	//# rename? we're going to check whether an absolute path is local, not path is absolute local
	return /^\w\:/.exec(path) || /^\//.exec(path) || /^file\:/.exec(path)
}
function getFullPath(subPath, nameFallback, extension) {
	const fileNamePosition = subPath.lastIndexOf('/') + 1
	const fileName = subPath.substr(fileNamePosition)
	if(fileName && fileName.indexOf('.') == -1)
		subPath += '.' + extension
	if(!fileName)
		subPath += nameFallback + '.' + extension

	if(isAbsolutePath(subPath)) return subPath

	const url = window.location.toString()
	const base = url.substr(0, url.lastIndexOf('/') + 1)
	return base + subPath
}

//# ideally, don't save main store if it were not changed
if(!config.macros.external.orig_saveChanges) {
	config.macros.external.orig_saveChanges = saveChanges
	saveChanges = function(onlyIfDirty, tiddlers) {
		config.macros.external.saveAll()
		//# should we do smth about setDirty (if saving of a tiddler failed)?

		return config.macros.external.orig_saveChanges.apply(this, arguments)
	}
}

// hijack method of store (since not present in TiddlyWiki.prototype)
if(!config.macros.external.orig_deleteTiddler) {
	config.macros.external.orig_deleteTiddler = store.deleteTiddler
	store.deleteTiddler = function(title) {
		const registeredMeta = config.macros.external.getMetaFor(title)
		if(registeredMeta) registeredMeta.tiddler = null

		return config.macros.external.orig_deleteTiddler.apply(this, arguments)
	}
}
//}}}