<!--{{{-->
<link rel='alternate' type='application/rss+xml' title='RSS' href='index.xml' />
<!--}}}-->
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
/*{{{*/
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]]; background:transparent;}
h1 {border-bottom:2px solid [[ColorPalette::TertiaryLight]];}
h2,h3 {border-bottom:1px solid [[ColorPalette::TertiaryLight]];}

.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:[[ColorPalette::PrimaryMid]];}
.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::PrimaryDark]];
	background:[[ColorPalette::TertiaryPale]];
	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 {color:[[ColorPalette::PrimaryDark]]; background:[[ColorPalette::TertiaryPale]]; 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]]; border:1px solid [[ColorPalette::PrimaryMid]];}
.wizard h1 {color:[[ColorPalette::PrimaryDark]]; border:none;}
.wizard h2 {color:[[ColorPalette::Foreground]]; border:none;}
.wizardStep {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];
	border:1px solid [[ColorPalette::PrimaryMid]];}
.wizardStep.wizardStepDone {background:[[ColorPalette::TertiaryLight]];}
.wizardFooter {background:[[ColorPalette::PrimaryPale]];}
.wizardFooter .status {background:[[ColorPalette::PrimaryDark]]; color:[[ColorPalette::Background]];}
.wizard .button {color:[[ColorPalette::Foreground]]; background:[[ColorPalette::SecondaryLight]]; border: 1px solid;
	border-color:[[ColorPalette::SecondaryPale]] [[ColorPalette::SecondaryDark]] [[ColorPalette::SecondaryDark]] [[ColorPalette::SecondaryPale]];}
.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 {border:1px solid [[ColorPalette::SecondaryMid]]; background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]];}
#messageArea .button {color:[[ColorPalette::PrimaryMid]]; background:[[ColorPalette::SecondaryPale]]; border:none;}

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

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

.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 {border:1px solid [[ColorPalette::TertiaryPale]]; background-color:[[ColorPalette::TertiaryPale]];}
.selected .tagging, .selected .tagged {background-color:[[ColorPalette::TertiaryLight]]; border:1px solid [[ColorPalette::TertiaryMid]];}
.tagging .listTitle, .tagged .listTitle {color:[[ColorPalette::PrimaryDark]];}
.tagging .button, .tagged .button {border:none;}

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

.sparkline {background:[[ColorPalette::PrimaryPale]]; border:0;}
.sparktick {background:[[ColorPalette::PrimaryDark]];}

.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]]; border:2px solid [[ColorPalette::SecondaryMid]];}

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

.viewer table, table.twtable {border:2px solid [[ColorPalette::TertiaryDark]];}
.viewer th, .viewer thead td, .twtable th, .twtable thead td {background:[[ColorPalette::SecondaryMid]]; border:1px solid [[ColorPalette::TertiaryDark]]; color:[[ColorPalette::Background]];}
.viewer td, .viewer tr, .twtable td, .twtable tr {border:1px solid [[ColorPalette::TertiaryDark]];}

.viewer pre {border:1px solid [[ColorPalette::SecondaryLight]]; 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]];}
.editor textarea {border:1px solid [[ColorPalette::PrimaryMid]]; width:100%;}
.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)';}
/*}}}*/
/*{{{*/
* html .tiddler {height:1%;}

body {font-size:.75em; font-family:arial,helvetica; margin:0; padding:0;}

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;}

a {text-decoration:none;}

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;}

#contentWrapper .chkOptionInput {border:0;}

.externalLink {text-decoration:underline;}

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

.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;}
.header a:hover {background:transparent;}
.headerShadow {position:relative; padding:4.5em 0 1em 1em; left:-1px; top:-1px;}
.headerForeground {position:absolute; padding:4.5em 0 1em 1em; left:0px; top:0px;}

.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;}

.wizard {padding:0.1em 1em 0 2em;}
.wizard h1 {font-size:2em; font-weight:bold; background:none; padding:0; margin:0.4em 0 0.2em;}
.wizard h2 {font-size:1.2em; font-weight:bold; background:none; padding:0; margin:0.4em 0 0.2em;}
.wizardStep {padding:1em 1em 1em 1em;}
.wizard .button {margin:0.5em 0 0; font-size:1.2em;}
.wizardFooter {padding:0.8em 0.4em 0.8em 0;}
.wizardFooter .status {padding:0 0.4em; margin-left:1em;}
.wizard .button {padding:0.1em 0.2em;}

#messageArea {position:fixed; top:2em; right:0; margin:0.5em; padding:0.5em; z-index:2000; _position:absolute;}
.messageToolbar {display:block; text-align:right; padding:0.2em;}
#messageArea a {text-decoration:underline;}

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

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

.tabset {padding:1em 0 0 0.5em;}
.tab {margin:0 0 0 0.25em; padding:2px;}
.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 18em 0 14em;}

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

.tiddler {padding:1em 1em 0;}

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

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

.missing .subtitle {display:none;}
.subtitle {font-size:1.1em;}

.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;}
.tagClear {clear:both;}

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

.annotation {padding:0.5em; margin:0.5em;}

* html .viewer pre {width:99%; padding:0 0 1em 0;}
.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 1.0em;}
.viewer th, .viewer td, .viewer tr,.viewer caption,.twtable th, .twtable td, .twtable tr,.twtable caption {padding:3px;}
table.listView {font-size:0.85em; margin:0.8em 1.0em;}
table.listView th, table.listView td, table.listView tr {padding:0px 3px 0px 3px;}

.viewer pre {padding:0.5em; margin-left:0.5em; font-size:1.2em; line-height:1.4em; overflow:auto;}
.viewer code {font-size:1.2em; line-height:1.4em;}

.editor {font-size:1.1em;}
.editor input, .editor textarea {display:block; width:100%; font:inherit;}
.editorFooter {padding:0.25em 0; font-size:.9em;}
.editorFooter .button {padding-top:0px; padding-bottom:0px;}

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

.sparkline {line-height:1em;}
.sparktick {outline: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.1em 0.4em; margin:0.1em;}
#backstage {position:relative; width:100%; z-index:50;}
#backstagePanel {display:none; z-index:100; position:absolute; width:90%; margin-left:3em; padding:1em;}
.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;}
noscript {display:none;} /* Fixes a feature in Firefox 1.5.0.2 where print preview displays the noscript content */
}
/*}}}*/
<!--{{{-->
<div class='header' macro='gradient vert [[ColorPalette::PrimaryLight]] [[ColorPalette::PrimaryMid]]'>
<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' refresh='content' tiddler='MainMenu'></div>
<div id='sidebar'>
<div id='sidebarOptions' refresh='content' tiddler='SideBarOptions'></div>
<div id='sidebarTabs' refresh='content' force='true' tiddler='SideBarTabs'></div>
</div>
<div id='displayArea'>
<div id='messageArea'></div>
<div id='tiddlerDisplay'></div>
</div>
<!--}}}-->
<!--{{{-->
<div class='toolbar' 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>
<!--}}}-->
<!--{{{-->
<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>
<!--}}}-->
To get started with this blank [[TiddlyWiki]], you'll need to modify the following tiddlers:
* [[SiteTitle]] & [[SiteSubtitle]]: The title and subtitle of the site, as shown above (after saving, they will also appear in the browser title bar)
* [[MainMenu]]: The menu (usually on the left)
* [[DefaultTiddlers]]: Contains the names of the tiddlers that you want to appear when the TiddlyWiki is opened
You'll also need to enter your username for signing your edits: <<option txtUserName>>
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]]
<<importTiddlers>>
[[AngularJS Form Validation|https://scotch.io/tutorials/angularjs-form-validation]]
[[Making Skinny AngularJS Controllers|https://scotch.io/tutorials/making-skinny-angularjs-controllers]]


''BOOTSTRAP SWITCHES''
----
http://www.zamanak.ir/themes/zamanak/bootstrap-switch-3.0/
http://www.bootply.com/92189
http://www.bootstraptoggle.com/
http://www.bootstrap-switch.org/examples.html


''ANGULAR SWITCHES''
----
http://cgarvis.github.io/angular-toggle-switch/
http://codepen.io/iamtyce/pen/kxrhC 
https://github.com/xpepermint/angular-ui-switch  <<<
http://plnkr.co/edit/53Ma6FVlxaPYwoSc7CTc?p=preview
http://plnkr.co/edit/aBq78nWMf5dGVxbsBmP2?p=preview
http://www.codinginsight.com/angularjs-bootstrap-toggle-button/
http://www.codeproject.com/Tips/854988/Create-an-ON-OFF-switch-using-Angular-JS-and-FontA


Ony one row of a Group By open at a time
----
http://plnkr.co/edit/IffWa7?p=preview

https://material.angularjs.org/latest/#/

http://www.cssscript.com/
http://www.cssscript.com/create-a-realistic-switch-with-pure-css-css3/
http://www.cssscript.com/checkbox-based-toggle-buttons-with-pure-javascript/
http://www.cssscript.com/ios-7-style-switches-with-pure-css-css3/
http://www.cssscript.com/demo/pure-css-css3-smooth-toggle-switch/
http://www.cssscript.com/creating-accessible-switch-controls-with-pure-css-scss/
http://www.cssscript.com/animated-segmented-controls-with-pure-css-css3/

http://abpetkov.github.io/switchery/


http://www.angularcode.com/angularjs-datagrid-paging-sorting-filter-using-php-and-mysql/
http://demos.angularcode.com/grid/
http://a.disquscdn.com/uploads/mediaembed/images/1500/82/original.jpg

http://ng-tt! Likable.com/#/demo/5-1
me of this place is Saurograd? I know there was
https://github.com/esvit/ng-table/wiki/Configuring-your-table-with-ngTableParamsDefines table groups. You can group by field name by setting groupBy to a string:


https://scotch.io/tutorials/building-custom-angularjs-filters
https://waykeep going back to the earliest you manipulate data with jQu
{{{
  <!-- were smart they’d just wait it out while the mutants kill each other off.
PROCESS FORM WITH AJAX (OLD) -->
  <script>
    <!-- WE WILLRetrieve changed PROCESS OUR FORM HERE -->
  </script>
}}}
scotch.io/tutorials/getting-started-with-foundation-for-appsdominant on the campaign trail.at unless you have a videocard with a lot of VRAM (I’m going to guess 1024MB minimum, as the issue occurs on my 512MB card under Gnome Shell) then tables which you download (but strangely not the Sci-Fi table that comes with FP) are likely to be missing 

She has rolled out more liberal positions on immigration reform and college debt and stayed mum on inconvenient things she does not want to 
https://scotch.io/ork andtutorials/making-skinny-angularjs-controllers#disqus_thread
https://scotch.io/tutorials/how-to-correctly-use-bootstrapjs-and-angularjs-together
https://scotch.io/tutorials/sort-and-filter-a-table-using-angular
{{{
 loadProductList = function () {
    //Set default values.
    pageIndex = 0;
    pageSize = pageSizeSelectedDef;
}}}
https://scotch.io/tutorials/all-about-the-built-in-angularjs-filters
https://scotch.io/quick-tips/pretty-urls-in-angularjs-removing-the-hashtag
{{{
//Subtract two dates
DAY()	Synonym for DAYOFM
}}}
https://scotch.io/tutorials/submitting-ajax-forms-the-angularjs-way

* [[Uploading Files To MySQL Database|http://www.php-mysql-tutorial.com/wikis/mysql-tutorials/uploading-files-to-mysql-database.aspx]]
* [[How to insert a file in MySQL database|http://stackoverflow.com/questions/5959043/how-to-insert-a-file-in-mysql-database]]
* [[Uploading binary files to mySQL|http://www.dreamwerx.net/site/article01]]
* From MS [[To BLOB or Not To BLOB: Large Object Storage in a Database or a Filesystem|]] (As expected from the common wisdom, objects smaller than 256K are best stored in a database while objects larger than 1M are best stored in the filesystem.)
http://www.toptal.com/web/cookie-free-authentication-with-json-web-tokens-an-example-in-laravel-and-angularjs
http://beletsky.net/2013/11/simple-authentication-in-angular-dot-js-app.html
http://engineering.talis.com/articles/elegant-api-auth-angular-js/

http://www.slideshare.net/stormpath/secure-your-rest-api-the-right-way
http://blog.brainattica.com/restful-json-api-jwt-go/
http://security.stackexchange.com/questions/51294/json-web-tokens-jwt-as-user-identification-and-authentication-tokens
https://auth0.com/blog/2014/12/02/using-json-web-tokens-as-api-keys/
https://github.com/adrotec/breeze.server.php
https://github.com/DanWahlin/CustomerManager
https://github.com/adrotec/breeze.server.php  Doctrine/Breeze .. Sample https://github.com/adrotec/emp-directory 
http://stackoverflow.com/questions/15938866/alternative-to-breeze-js
http://www.getbreezenow.com/blog/handcuffed-microsoft
http://breeze.github.io/doc-samples/
http://breeze.github.io/doc-samples/edmunds.html
http://breeze.github.io/doc-main/community.html
http://stackoverflow.com/questions/tagged/breeze

https://breezejs.uservoice.com/forums/173093-1-breezejs-feature-suggestions/suggestions/3584333-examples-that-uses-other-backends
https://breezejs.uservoice.com/forums/173093-1-breezejs-feature-suggestions/suggestions/3311065-support-for-php-and-mysql
http://emp-directory.herokuapp.com/

http://www.doctrine-project.org/jira/browse/DBAL-95
[[Doctrine Manaul - Defining Models|http://doctrine.readthedocs.org/en/latest/en/manual/defining-models.html]]
[[Doctrine Manual - DQL:Doctrine Query Language|http://doctrine.readthedocs.org/en/latest/en/manual/dql-doctrine-query-language.html]]
[[Debugging This Stuff|http://stackoverflow.com/questions/17892111/why-are-my-breeze-js-entities-not-creating-ko-observables]]
[[Metadata By Hand|http://breeze.github.io/doc-js/metadata-by-hand.html]]

http://breeze.github.io/doc-samples/php-employee-directory.html 
http://emp-directory.herokuapp.com/
http://emp-directory.herokuapp.com/client/



NGADMIN CONTROL PANEL
https://github.com/marmelab/ng-admin  Demo: http://ng-admin.marmelab.com/#/posts/list

Persistant objects

Lawnchair
http://brian.io/lawnchair/
https://gist.github.com/g0ody/5386237

JS-Data
http://www.js-data.io/
https://github.com/js-data/js-data-angular

RestMod
https://github.com/platanus/angular-restmod

Persistant Storage Options

# Breezejs - Looks more focused on server. Is there an SQLite interface?
# YDN-DB - Seems like an option.
# JayData - Is this still active? Concerned about commercial aspect of it.
# Persistencejs - This looks promising. Is the project still active?
# ngStorage - is this just a localStorage interface? Does it solve the 5M limit?
# [[Angular-cache|http://jmdobry.github.io/angular-cache/|]] - Can I have data to pre-load with this? How long can I persist data?
# [[localForage|https://github.com/mozilla/localForage]] - don't know much about this. Does it solve the 5M limit?
# Pouchdb - concerned about query language. does not solve 5M restriction
# Couchdb Lite - concerned about query language.
# WebSQL - I don't to use this since it seems like it is on the way out.. plus 5M limit.
# Indexeddb - There is a shim that builds compat-layer for most major browsers. 5M limit. If I could use this on top of Sqlite that would prob be a winner for me since more standards based.
#  Store in json file - Just use plain old objects and then use Phonegap file api to load and store serialized data. Seems like a pain to have to serialize all the data every time we want to save... but an option so long as I can use Angular filters.



[[Enhancing Web Apps with AmplifyJS|http://code.tutsplus.com/tutorials/enhancing-web-apps-with-amplifyjs--net-22389]]
/***
| ''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 angular '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
<<<
https://www.backand.com/
https://parse.com/products
https://www.firebase.com/
.restrict
|Letter|Type|Example|h
|A|Attribute|{{{<div my-item></div>}}}|
|E|Elemen|{{{<my-item></my-item>}}}|
|C|Class|{{{class="my-item"}}}|
|M|coMment|{{{<!-- directive: my-item -->}}}|

.replace
|Flag|Result|h
|true|remove custom element only show content of template|
|false|show custom element and embed html into element|

.template
Inline template such as: {{{.template: '<a href="#"></a>'}}}

.templateUrl
URL of html to use for the templeat {{{.templateUrl: 'templates/myitem.html'}}}


custom attribute - {{{<my-directive custom-attribute="{{ var }}}"></my-directive>}}}
isolated scope - the {{{.scope}}} inside the directive.


.scope 
|Element|Description|h
|@|One way text binding - copy from the html page the parameter with the same name as the variable we are assigning to|
|@//name//|copy from the html page the parameter with then name of //name// and assign that to the variable|
|=|2 way binding - bind to object|
|=//name//|bind to object with different name|
|&|Passing in a function|

''One Way Text Binding''
----
Empty Scope blocks $scope. variables from entering into the directive. It creates an empty subscope list.
{{{
.scope { }
}}}


In the html page there is {{{person-name}}} and in the html template there is{{{ {{ personName }} }}}. 

.scope of the directive contains the variable name that will be passed to the directive html template. In the .scope it shows up as {{{personName}}} in the directive html template it shows up as{{{ {{person-name}} }}}.  The {{{'@'}}} means we expect to pull this value from the parameter from the html page templates assignment of {{{variable}}} to {{{person-name}}}.
{{{
.scope { personName: '@' }
}}}

''html page template'' (passing {{{variable}}} as {{{person-name}}} which ends up in java-script {{{personName}}}
{{{
<my-directive person-name="{{ variable }}"></my-directive>
}}}

''directive html template'' takes the scopes {{{personName}}} and uses it in this template as {{{personName}}}
{{{
<h1>{{ personName }}>h1>
}}}


In the html page there is {{{my-name}}} and in the html template there is{{{ {{ personName }} }}}. 

.scope of the directive contains the variable name that will be passed to the directive html template. In the .scope it shows up as {{{personName}}} in the directive html template it shows up as{{{ {{my-name}} }}}.  The {{{'@myName'}}} means we expect to pull this value from the parameter from the html page templates assignment of {{{variable}}} into {{{{my-name}}} on that page and pass thru {{{@myName}}} and into {{{person-name}}} which will them be used on the directive template page.
{{{
.scope { personName: '@myName' }
}}}

''html page template'' (passing {{{variable}}} as {{{my-name}}} which ends up in java-script {{{personName}}} via {{{@myName}}}
{{{
<my-directive my-name="{{ variable }}"></my-directive>
}}}

''directive html template'' takes the scopes {{{personName}}} and uses it in this template as {{{personName}}}
{{{
<h1>{{ personName }}>h1>
}}}


''2 way bind to Object''
----
In the html template we don't pass the text of from the object like 
{{{
<my-directive my-attribute="{{ object.var }}"></my-directive>
}}}
Instead we pass the actual object like
{{{
<my-directive my-attribute="object"></my-directive>
}}}

Then in the directive html template we used one way text binding with the '@' so we would just access a variable like
{{{
<h1>{{ var }}</h1>
}}}

Now we are using 2 way object binding and we would access attributes of the object like 
{{{
<h1>{{ myAttribute.var }}</h1>
}}}


''Function ''
----
Will use an object map inside of the directive html template
Inside of html template
{{{
<my-directive formatted-function="formattedFunction(datum)" my-attribute="object"></my-directive>
}}}

Inside of scope. Note: Must also pass in data for function to work with
{{{
.Scope {
    myAttribute: "@",
    formattedFunction: "&"
}}}

Inside of directive html template. Note in html page template we say there is an object called {{datum}} we now create a map to map the name we used in the html template for the object/var to the object/var that we passed in via the scope.
{{{
<h1>{{ formattedFunction ({ datum: myAttribute )} }}</h1>
}}}

The key is to manipulate as much as possible in a service, other function or otherwise outside of the directive. Since you will be using 2 way data binding on objects with '=' the more you manipulate in the directive the more likely you are to make a mistake and make changes to the object or to create a loop where the directive modifies the HTML which triggers a watch which modifies html which updates the directive which once again modifies the html and triggers a watch. ''Remember'': directives are to manipulate  html, not change javascript values. 



''Repeating a custom directive''
----
This will work but each occurrence of {{{myDirective}}} will be wrapped in a {{{<div>}}}
{{{
<div class="list-group" ng-repeat="thing in things">
    <my-directive my-attribute="thing" formatted-function="formattedFunction(datum)"></my-directive>
</div>
}}}

To eliminate that repeated {{{<div>}}} put the {{{ng-repeat}}} inside of {{{myDirective}}}
{{{
<div class="list-group">
    <my-directive my-attribute="thing" formatted-function="formattedFunction(datum)" ng-repeat="thing in things"></my-directive>
</div>
}}}
[[Learning AngularJS by Example – The Customer Manager Application|http://weblogs.asp.net/dwahlin/learning-angularjs-by-example-the-customer-manager-application]]
[[Video Tutorial: AngularJS Fundamentals in 60-ish Minutes|http://weblogs.asp.net/dwahlin/video-tutorial-angularjs-fundamentals-in-60-ish-minutes]] [[AngularJS Fundamentals In 60-ish Minutes|https://www.youtube.com/watch?v=i9MHigUZKEM]]
!Get both FROM and TO emails for user 42
{{{
SELECT  CONCAT(v.firstname, ' ', v.lastname) AS 'from', CONCAT(u.firstname, ' ', u.lastname) AS 'to', e.subject, e.sent, e.content
FROM examples e
JOIN users u ON e.to = u.id
JOIN users v ON e.from = v.id
WHERE e.from = 42
UNION
SELECT  CONCAT(v.firstname, ' ', v.lastname) AS 'from', CONCAT(u.firstname, ' ', u.lastname) AS 'to', e.subject, e.sent, e.content
FROM examples e
JOIN users u ON e.to = u.id
JOIN users v ON e.from = v.id
WHERE e.to = 42
ORDER BY sent
}}}


!TO USE A UNION in active record
{{{
yada yada
this->db->get(); 
$query1 = $this->db->last_query();

yada yada
this->db->get(); 
2query1 = $this->db->last_query();

$query = $this->db->query($query1." UNION ".$query2);
$results = $query->result();
}}}


[[Database Security Best Practices|https://www.owasp.org/index.php/OWASP_Proactive_Controls]]
[[SqlFiddle|http://sqlfiddle.com/#!2/19b76/9]]

!!!Escape
{{{
   $email= $this->input->post('email');
   $query = 'SELECT * FROM subscribers_tbl WHERE user_name='.$this->db->escape($email);
   $this->db->query($query);
}}}
 
!!!Bind
{{{
    // $this->db->escape_like_str() 
    $sql = "SELECT * FROM subscribers_tbl WHERE status = ? AND email= ?"; 
    $this->db->query($sql, array('active', 'info@arjun.net.in'));
}}}

!!!Active Record
{{{
   $this->db->get_where('subscribers_tbl',array('status' => 'active','email' => 'info@arjun.net.in'));
}}}

!!!Another Example of Binding
{{{
$sql = "UPDATE table_name 
SET value_1 = ?, 
value_2 = ?,
value_3 = ?,
value_4 = ?,
modified=NOW()
WHERE user_id= ?";
if ($this->db->query($sql, array($value_1, $value_2, $value_3, $value_4, $user_id))) 
  return $this->db->last_query();
}}}

!!!CSS class style to make a cell a hyperlink by adding this to the <a href
{{{
style="display:block;"
}}}
http://stackoverflow.com/questions/14523186/codeigniter-active-record-vs-regular-queries

pivot tables as in rows to columns
FROM_UNIXTIME( UNIX_TIMESTAMP( m.sent ) ,  '%W %M %D %Y  %h:%i:%s' )  Will produce Sunday April 27th 2014  07:58:54
/***
| ''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"+"//}}}";
//}}}
[[Intro]
|Directive|Description|Example|h
|ng-app|"appName"||
|ng-controler|"controlerName"||
|ng-model|"variable"|Binds input to model. Two way data binding |
|ng-bind|"variable/expression"|Same as{{{ {{ }} }}}. One way data binding to html element|
|ng-if|"t/f expression"|Removes/Adds to DOM based on True/False|
|ng-show|"t/f expression"|Show if expression is true|
|ng-hide|"t/f expression"|Hide if expression is true|
|ng-class|"{ JSON obejct expressions}"|add class(es)based on T/F |
||>|"{ 'class1': x === 5, 'class2': x >5 }"|
|ng-repeat|"dog in dogs"|for each loop|
|ng-click|"namedFunction()"|run when button is clicked|
|ng-changed||When state changed|
|ng-cloaked||Hide Element till angular performs interpulation|

https://docs.angularjs.org/api to find more directives
Pushups
* Beginner standard: 1 set of 10
* Intermediate standard: 2 sets of 25
* Progression standard: 3 sets of 50

|Step|Type|Pages|Work up to|h
|One|Wall Pushups|46-47|3 Sets of 50|
|Two|Incline Pushups|48-49|3 Sets of 40|
|Three|Kneeling Pushups|50-51|3 Sets of 30|
|Four|Half Pushups|52-53|3 Sets of 25|
|Five|Full Pushups|54-55|2 Sets of 20|
|Six|Close Pushups|56-57|2 Sets of 20|
|Seven|Uneven Pushups|58-59|2 Sets of 20|
|Eight|1/2 One Arm Pushups|60-61|2 Sets of 20|
|Nine|Lever Pushups|62-63|2 Sets of 20|
|Ten|One Arm Pushups|64-65|1 Set of 100|


https://github.com/php-fig/fig-standards/tree/master/accepted
http://codepen.io/davegandy/pen/dlCuq

"Responsive Sidebar Menu" (the one that worked)
http://bootsnipp.com/snippets/featured/responsive-sidebar-menu

http://codepen.io/frippa/pen/vEVvOG  top of email bar?


Lets try:  http://embed.plnkr.co/bZauyf/index.html
{{{
  <nav class="navbar navbar-default" role="navigation">
      <!-- Brand and toggle get grouped for better mobile display -->
      <div class="navbar-header">

        <button type="button" class="navbar-toggle" ng-init="navCollapsed = true" ng-click="navCollapsed = !navCollapsed">
          <span class="sr-only">Toggle navigation</span>
          <span class="icon-bar"></span>
          <span class="icon-bar"></span>
          <span class="icon-bar"></span>
        </button>
        <a class="navbar-brand" href="#">Brand</a>
      </div>
    
      <!-- Collect the nav links, forms, and other content for toggling -->
      <div class="collapse navbar-collapse" ng-class="!navCollapsed && 'in'" ng-click="navCollapsed = true">
      
        <ul class="nav navbar-nav">
          <li class="active"><a href="#">Link</a></li>
          <li><a href="#">Link</a></li>
          
          <li class="dropdown">
            <a href="#" class="dropdown-toggle" ng-controller="DropdownCtrl">Dropdown <b class="caret"></b></a>
            <ul class="dropdown-menu">
              <li><a href="#">Action</a></li>
              <li><a href="#">Another action</a></li>
              <li><a href="#">Something else here</a></li>
              <li><a href="#">Separated link</a></li>
              <li><a href="#">One more separated link</a></li>
            </ul>
          </li>
          
        </ul>
        <form class="navbar-form navbar-left" role="search">
          <div class="form-group">
            <input type="text" class="form-control" placeholder="Search">
          </div>
          <button type="submit" class="btn btn-default">Submit</button>
        </form>
        <ul class="nav navbar-nav navbar-right">
          <li><a href="#">Link</a></li>
          <li class="dropdown">
            <a href="#" class="dropdown-toggle" data-toggle="dropdown">Dropdown <b class="caret"></b></a>
            <ul class="dropdown-menu">
              <li><a href="#">Action</a></li>
              <li><a href="#">Another action</a></li>
              <li><a href="#">Something else here</a></li>
              <li><a href="#">Separated link</a></li>
            </ul>
          </li>
        </ul>
      </div><!-- /.navbar-collapse -->
    </nav>
}}}

http://codepen.io/davpayne/pen/JohfI
{{{
<nav class="navbar navbar-default navbar-fixed-top" role="navigation">
  <div class="container-fluid">
    <!-- Brand and toggle get grouped for better mobile display -->
    <div class="navbar-header">
      <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#navbar-collapse">
        <span class="sr-only">Toggle navigation</span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
      </button>
      <a class="navbar-brand" href="#top">David Payne</a>
    </div>

    <!-- Collect the nav links, forms, and other content for toggling -->
    <div class="collapse navbar-collapse" id="navbar-collapse">
      <ul class="nav navbar-nav">
        <li><a href="#portfolio">Portfolio</a></li>
        <li><a href="#quotes">Favorite Quotes</a></li>
        <li><a href="#contact">Contact</a></li>
          </ul>
        </li>
      </ul>
    </div><!-- /.navbar-collapse -->
  </div><!-- /.container-fluid -->
</nav>
}}}


http://codepen.io/ItsTheCoon/pen/NGKvMP
{{{
<header class="navbar navbar-bright navbar-fixed-top" role="banner" data-spy="affix" data-offset-top="80">
      <div class="container">
        <div class="navbar-header">
          <button class="navbar-toggle" type="button" data-toggle="collapse" data-target=".navbar-collapse">
            <span class="sr-only">Toggle navigation</span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
          </button>
          <a class="navbar-brand" id="brand" href="https://itsthecoon.github.io/rhapsody.io/" title="Rhapsody.IO. Something for everyone.">Rhapsody.<span style="color:#f9f9f9;font-weight:200;">IO</span></a>
        </div>
        <nav class="collapse navbar-collapse" role="navigation" id="navbar">
            <ul class="nav navbar-nav">
                <li>
                  <a class="dropdown-toggle" data-toggle="dropdown">Framework <span class="caret"></span></a>
                  <ul class="dropdown-menu">
                    <li class="dropdown-header">General CSS</li>
                    <li><a href="/typography">Typography</a></li>
                    <li><a href="/buttons">Buttons</a></li>
                    <li><a href="/bootstrap-templates" title="All themes are responsive">Grid</a></li>
                    <li class="divider"></li>
                    <li class="dropdown-header">Components</li>
                    <li><a href="/navigation">Navigation</a></li>
                    <li><a href="/headers">Headers</a></li>    <li><a href="/forms">Forms</a></li>    <li><a href="/labels">Labels</a></li>    <li><a href="/navigation">Alerts</a></li>
                    <li><a href="/fancy-bgs">Fancy Backgrounds</a></li>
                    <li class="divider"></li>
                    <li class="dropdown-header">More</li>
                    <li><a href="/http://fortawesome.github.io/Font-Awesome/">Font Awesome</a></li>
                  </ul>
                </li>
            </ul>
            <ul class="nav navbar-nav navbar-right">
                <li>
                  <a class="dropdown-toggle" data-toggle="dropdown">Community <span class="caret"></span></a>
                  <ul class="dropdown-menu">
                    <li class="dropdown-header">Connect</li>
                    <li><a href="/forums">Forums (Coming Soon)</a></li>
                    <li class="divider"></li>
                    <li class="dropdown-header">Develop</li>                   <li><a href="https://github.com/ItsTheCoon/rhapsody.io">Open-Source Github</a></li>
                    
                    <li><a href="/devs">Developers</a></li>
                    <li><a href="/commission">Commission</a></li>
                </li>
            </ul>
                <li><a href="/about" id="btnAbout">About</a></li>
            </ul>
        </nav>
      </div>
    </header>
}}}

Angular List App http://codepen.io/xstherrera1987/pen/gIquA
{{{
<div class="navbar navbar-default" role="navigation">
<div class="container">
	<div class="navbar-header">
		<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
			<span class="sr-only">Toggle navigation</span>
			<span class="icon-bar"></span>
			<span class="icon-bar"></span>
			<span class="icon-bar"></span>
		</button>
		<a class="navbar-brand" href="#">Angular Data List</a>
	</div>
	<div class="collapse navbar-collapse">
		<ul class="nav navbar-nav">
			<li class="active"><a href="#/home">Home</a></li>
			<li><a href="#/list">List</a></li>
		</ul>
	</div>
</div>
}}}


http://codepen.io/mrausch/pen/KwYWYm tabbed page



{{{
down vote
accepted
You should replace bootstrap native js properties with ui-bootstrap directives (note the ng-click and collapse):

<nav class="navbar navbar-default" role="navigation">
    <!-- Brand and toggle get grouped for better mobile display -->
    <div class="navbar-header">
      <button type="button" class="navbar-toggle" ng-click="navbarCollapsed = !navbarCollapsed">
        <span class="sr-only">Toggle navigation</span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
      </button>
      <a class="navbar-brand" href="#">
        <!-- your branding here -->
      </a>
    </div>

    <!-- Collect the nav links, forms, and other content for toggling -->
    <div class="collapse navbar-collapse" collapse="navbarCollapsed">
      <!-- your normal collapsable content here -->
    </div>
</nav>
}}}
Set the initial value in your controller:
{{{
$scope.navbarCollapsed = true;
}}}
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|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>
There is 2 main representations and algorithms to represent hierarchical structures with databases :
* nested set also known as modified preorder tree traversal algorithm
* adjacency list model

It's well explained here:
* http://www.sitepoint.com/article/hierarchical-data-database
* [[Managing Hierarchical Data in MySQL|http://mikehillyer.com/articles/managing-hierarchical-data-in-mysql/]]
* http://www.evolt.org/article/Four_ways_to_work_with_hierarchical_data/17/4047/index.html

Here are some more links that I've collected:
* http://en.wikipedia.org/wiki/Tree_%28data_structure%29
* http://en.wikipedia.org/wiki/Category:Trees_%28structure%29

adjacency list model
* http://www.sqlteam.com/item.asp?ItemID=8866

nested set
* http://www.sqlsummit.com/AdjacencyList.htm
* http://www.edutech.ch/contribution/nstrees/index.php
* http://www.phpriot.com/d/articles/php/application-design/nested-trees-1/
* http://www.dbmsmag.com/9604d06.html
* http://en.wikipedia.org/wiki/Tree_traversal
* http://www.cosc.canterbury.ac.nz/mukundan/dsal/BTree.html (applet java montrant le fonctionnement )

Graphes
* http://www.artfulsoftware.com/mysqlbook/sampler/mysqled1ch20.html

''Classes :''

Nested Sets DB Tree Adodb
* http://www.phpclasses.org/browse/package/2547.html

Visitation Model ~ADOdb
* http://www.phpclasses.org/browse/package/2919.html

PEAR::~DB_NestedSet
* http://pear.php.net/package/DB_NestedSet
* utilisation : https://www.entwickler.com/itr/kolumnen/psecom,id,26,nodeid,207.html

PEAR::Tree
* http://pear.php.net/package/Tree/download/0.3.0/
* http://www.phpkitchen.com/index.php?/archives/337-PEARTree-Tutorial.html

nstrees
* http://www.edutech.ch/contribution/nstrees/index.php

----
Very good read on using closures 
* [[The simplest(?) way to do tree-based queries in SQL|http://dirtsimple.org/2010/11/simplest-way-to-do-tree-based-queries.html]]
* [[Keeping it simple: Rendering Trees with Closure Tables|http://karwin.blogspot.com/2010/03/rendering-trees-with-closure-tables.html]]
*[[Optimize Hierarchy Queries with a Transitive Closure Table|http://kylecordes.com/2008/transitive-closure]]

Entities in HTML documents allow characters to be entered that can't easily be typed on an ordinary keyboard. They take the form of an ampersand (&), an identifying string, and a terminating semi-colon (;). There's a complete reference [[here|http://www.htmlhelp.com/reference/html40/entities/]]; some of the more common and useful ones are shown below.

|>|>|>|>|>|>| !HTML Entities |
| &amp;nbsp; | &nbsp; | no-break space | &nbsp;&nbsp; | &amp;apos; | &apos; | single quote, apostrophe |
| &amp;ndash; | &ndash; | en dash |~| &amp;quot; | " | quotation mark |
| &amp;mdash; | &mdash; | em dash |~| &amp;prime; | &prime; | prime; minutes; feet |
| &amp;hellip; | &hellip; |	horizontal ellipsis |~| &amp;Prime; | &Prime; | double prime; seconds; inches |
| &amp;copy; | &copy; | Copyright symbol |~| &amp;lsquo; | &lsquo; | left single quote |
| &amp;reg; | &reg; | Registered symbol |~| &amp;rsquo; | &rsquo; | right  single quote |
| &amp;trade; | &trade; | Trademark symbol |~| &amp;ldquo; | &ldquo; | left double quote |
| &amp;dagger; | &dagger; | dagger |~| &amp;rdquo; | &rdquo; | right double quote |
| &amp;Dagger; | &Dagger; | double dagger |~| &amp;laquo; | &laquo; | left angle quote |
| &amp;para; | &para; | paragraph sign |~| &amp;raquo; | &raquo; | right angle quote |
| &amp;sect; | &sect; | section sign |~| &amp;times; | &times; | multiplication symbol |
| &amp;uarr; | &uarr; | up arrow |~| &amp;darr; | &darr; | down arrow |
| &amp;larr; | &larr; | left arrow |~| &amp;rarr; | &rarr; | right arrow |
| &amp;lArr; | &lArr; | double left arrow |~| &amp;rArr; | &rArr; | double right arrow |
| &amp;harr; | &harr; | left right arrow |~| &amp;hArr; | &hArr; | double left right arrow |

The table below shows how accented characters can be built up by subsituting a base character into the various accent entities in place of the underscore ('_'):

|>|>|>|>|>|>|>|>|>|>|>|>|>|>|>|>|>| !Accented Characters |
| grave accent | &amp;_grave; | &Agrave; | &agrave; | &Egrave; | &egrave; | &Igrave; | &igrave; | &Ograve; | &ograve; | &Ugrave; | &ugrave; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; |
| acute accent | &amp;_acute; | &Aacute; | &aacute; | &Eacute; | &eacute; | &Iacute; | &iacute; | &Oacute; | &oacute; | &Uacute; | &uacute; | &nbsp; | &nbsp; | &Yacute; | &yacute; | &nbsp; | &nbsp; |
| circumflex accent | &amp;_circ; | &Acirc; | &acirc; | &Ecirc; | &ecirc; | &Icirc; | &icirc; | &Ocirc; | &ocirc; | &Ucirc; | &ucirc; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; |
| umlaut mark | &amp;_uml; | &Auml; | &auml; |  &Euml; | &euml; | &Iuml; | &iuml; | &Ouml; | &ouml; | &Uuml; | &uuml; | &nbsp; | &nbsp; | &Yuml; | &yuml; | &nbsp; | &nbsp; |
| tilde | &amp;_tilde; | &Atilde; | &atilde; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &Otilde; | &otilde; | &nbsp; | &nbsp; | &Ntilde; | &ntilde; | &nbsp; | &nbsp; | &nbsp; | &nbsp; |
| ring | &amp;_ring; | &Aring; | &aring; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; |
| slash | &amp;_slash; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &Oslash; | &oslash; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; |
| cedilla | &amp;_cedil; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &Ccedil; | &ccedil; |

!Notes
For a full list of available HTML references see:
http://www.w3schools.com/tags/ref_entities.asp
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
<<<
http://stackoverflow.com/questions/16828287/what-things-can-be-injected-into-others-in-angular-js
//{{{
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;
}
//}}}
<tabs mine>
<tab application.js>
# Sets the Module Name
# Loads js submodules (filters, services, directives and controlers)
# Loads other files (ngRoute, ngTable, uiBootstrap)
# Setup Route Provider
# Setup actual routes {{block{
{{{
$routeProvider.when('/administrator', controller: 'administrator', templateUrl: 'partials/administrator.html'});
}}}
}}}
# performs actual routing ???
</tab>

<tab controllers.js>
# javascript programming logic
# each page will have its own controller in this file
# each page controller can have other functions that are used on the page embeded in them. 

</tab>

<tab directives.js>
# definition of custom directives 
</tab>

<tab filters.js>
# Load custom filters
</tab>

<tab services.js>

# Based on factories ({{{alerts, users}}})
# Contain subfunctions
# See http://tylermcginnis.com/angularjs-factory-vs-service-vs-provider/
</tab>

<tab partials>
# HTML files with text to display for the actual page
# Can have ng directives in it
# Is found via a routeProvder in {{{js/application.js}}}
</tab>

<tab css>
</tab>
</tabs>

<tabs table>
<tab persistent data>
When you first get started with Angular, you’ll naturally find yourself flooding your controllers and scopes with unnecessary logic. It’s important to realize early on that your controller should be very thin; meaning, most of the business logic and persistent data in your application should be taken care of or stored in a service. I see a few questions a day on Stack Overflow regarding someone trying to have persistent data in his or her controller. That’s just not the purpose of a controller. For memory purposes, controllers are instantiated only when they are needed and discarded when they are not. Because of this, every time you switch a route or reload a page, Angular cleans up the current controller. Services however provide a means for keeping data around for the lifetime of an application while they also can be used across different controllers in a consistent manner.
 
Angular provides us with three ways to create and register our own service.
</tab>

<tab promises>
</tab>

<tab dependency injection>
</tab>
<tab jquery>
jQuery is an amazing library. It has standardized cross-platform development and is almost a requirement in modern web development. While jQuery has many great features, its philosophy does not align with ~AngularJS.
~AngularJS is a framework for building applications; jQuery is a library for simplifying "HTML document traversal and manipulation, event handling, animation, and Ajax". This is the fundamental difference between the two. ~AngularJS is about architecture of applications, not augmenting HTML pages.

In order to really understand how to build an ~AngularJS application, stop using jQuery. jQuery keeps the developer thinking of existing HTML standards, but as the docs say "~AngularJS lets you extend HTML vocabulary for your application."

DOM manipulation should only be done in directives, but this doesn't mean they have to be jQuery wrappers. Always consider what features~AngularJS already provides before reaching for jQuery. Directives work really well when they build upon each other to create powerful tools.

The day may come when a very nice jQuery library is necessary, but including it from the beginning is an all too common mistake.
</tab>
<tab templates>
In ~AngularJS, a template is just plain-old-HTML. The HTML vocabulary is extended, to contain instructions on how the model should be projected into the view.

The HTML templates are parsed by the browser into the DOM. The DOM then becomes the input to the ~AngularJS compiler. ~AngularJS traverses the DOM template for rendering instructions, which are called directives. Collectively, the directives are responsible for setting up the data-binding for your application view.

It is important to realize that at no point does ~AngularJS manipulate the template as strings. The input to ~AngularJS is browser DOM and not an HTML string. The data-bindings are DOM transformations, not string concatenations or ~innerHTML changes. Using the DOM as the input, rather than strings, is the biggest differentiation ~AngularJS has from its sibling frameworks. Using the DOM is what allows you to extend the directive vocabulary and build your own directives, or even abstract them into reusable components!

One of the greatest advantages to this approach is that it creates a tight workflow between designers and developers. Designers can mark up their HTML as they normally would, and then developers take the baton and hook in functionality, via bindings with very little effort.
</tab>

<tab mvc>
~AngularJS incorporates the basic principles behind the original MVC software design pattern into how it builds client-side web applications.

The MVC or ~Model-View-Controller pattern means a lot of different things to different people. ~AngularJS does not implement MVC in the traditional sense, but rather something closer to MVVM (~Model-View-ViewModel).

''The Model''
The model is simply the data in the application. The model is just plain old ~JavaScript objects. There is no need to inherit from framework classes, wrap it in proxy objects, or use special getter/setter methods to access it. The fact that we are dealing with vanilla ~JavaScript is a really nice feature, which cuts down on the application boilerplate.

''The ~ViewModel''
A viewmodel is an object that provides specific data and methods to maintain specific views.

The viewmodel is the $scope object that lives within the ~AngularJS application. $scope is just a simple ~JavaScript object with a small API designed to detect and broadcast changes to its state.

''The Controller''
The controller is responsible for setting initial state and augmenting $scope with methods to control behavior. It is worth noting that the controller does not store state and does not interact with remote services.

''The View''
The view is the HTML that exists after ~AngularJS has parsed and compiled the HTML to include rendered markup and bindings.

This division creates a solid foundation to architect your application. The $scope has a reference to the data, the controller defines behavior, and the view handles the layout and handing off interaction to the controller to respond accordingly.
</tab>

</tabs>


<tabs thing>
<tab Modules>
Applications in ~AngularJS are structured in modules. A module can depend on other modules and a module can contain controllers, services, directives, filters, etc ..which we have already reviewed.

Some programmers use modules as classes, some others as just code libraries, but in any of the cases Modules are used to organize and structure your code allowing you to re-use it.
</tab>
<tab Controllers>
</tab>
<tab Scopes>
</tab>
<tab Directives>
</tab>
<tab Data Bindings>
</tab>
<tab Promises>
http://www.codeproject.com/Articles/770325/AngularJS-Promises-The-Definitive-Guide
</tab>
<tab Accessibility>
https://docs.angularjs.org/guide/accessibility
</tab>
</tabs>

TadApp 

<tabs ongoing>
<tab Filter1>
ng-Table save parameters as you enter/leave page

https://scotch.io/tutorials/sort-and-filter-a-table-using-angular

http://askproblem.com/question/custom-filter-with-angularjs-ngtable/

http://web320hh.blogspot.com/2015/02/changing-height-of-ng-table.html

http://php-c.org/faustino/2015/04/05/angularjs-how-to-reload-ngtable-with-successful-post/

http://stackoverflow.com/questions/22459281/custom-filter-with-angularjs-ngtable

https://github.com/esvit/ng-table/issues/383
</tab>
<tab Filter2>
https://scotch.io/tutorials/sort-and-filter-a-table-using-angular

http://codeforgeek.com/2014/09/5-must-have-packages-atom-editor/
http://codeforgeek.com/2014/08/ajax-live-search-angular-node/
http://codeforgeek.com/2014/12/highlight-search-result-angular-filter/

http://angularcode.com/angularjs-datagrid-paging-sorting-filter-using-php-and-mysql/
http://demos.angularcode.com/grid/
http://a.disquscdn.com/uploads/mediaembed/images/1500/82/original.jpg

https://github.com/esvit/ng-table/wiki/Configuring-your-table-with-ngTableParams
http://ng-table.com/#/demo/3-1
http://ng-table.com/#/demo/4-1
http://ng-table.com/#/demo/0-3

https://github.com/esvit/ng-table/issues/570
https://github.com/esvit/ng-table/issues/563
</tab>
<tab Clickable TR>
https://github.com/esvit/ng-table/issues/571
</tab>
<tab UI>
https://angular-ui.github.io/bootstrap/#/getting_started
https://github.com/slushjs/slush-angular
https://docs.google.com/document/d/1XXMvReO8-Awi1EZXAXS4PzDzdNvV6pGcuaF4Q9821Es/pub
https://github.com/InnovaLangues/AngularUIBootsrapBundle/blob/master/composer.json
</tab>
<tab Grid/Page>
https://github.com/begriffs/angular-paginate-anything
https://www.ng-book.com/p/Caching/
http://ui-grid.info/
</tab>
<tab Watch>
http://angular-tips.com/blog/2013/08/watch-how-the-apply-runs-a-digest/
</tab>
</tabs>

{{{
<select class="span2 navbar-btn">
    <option>Things</option>
</select> in 
 <select class="span2 navbar-btn">
   <option>City, NY</option>
</select>
<button type="button" class="btn btn-default navbar-btn">Go!<button>
}}}

http://www.appgyver.com/

http://code.tutsplus.com/tutorials/building-a-web-app-from-scratch-in-angularjs--net-32944
http://www.revillwebdesign.com/demos/nettuts/angularjs/
[[http://www.dwmkerr.com/]]
[[A great read for JavaScript newcomers|http://www.dwmkerr.com/a-great-read-for-javascript-newcomers/]] is [[Understanding JavaScript Object Creation Patterns|http://www.codeproject.com/Articles/687093/Understanding-JavaScript-Object-Creation-Patterns]]
[[Introducing Practical AngularJS|http://www.dwmkerr.com/introducing-practical-angularjs/]]
[[Practical AngularJS Part 2|http://www.dwmkerr.com/practical-angularjs-part-2/]]
[[Space Invaders on the CodeProject|http://www.dwmkerr.com/space-invaders-on-the-codeproject/]]
[[ConsoleControl and Happy Coders|http://www.dwmkerr.com/consolecontrol-and-happy-coders/]]
[[Introducing Experiments|http://www.dwmkerr.com/introducing-experiments/]]
[[Space Invaders in JavaScript|http://www.dwmkerr.com/space-invaders-in-javascript/]]

[[Practical AngularJS Part 1 – Introducing AngularJS|http://www.dwmkerr.com/practical-angularjs-part1/]]
[[Practical AngularJS Part 2 – Components of an AngularJS Application|http://www.dwmkerr.com/practical-angularjs-part2/]]
[[Angular JS Cheet Sheet|https://web.archive.org/web/20131212092533/http://www.dwmkerr.com/practical-angularjs/angularjs-cheat-sheet/]]
config.tiddlyspotSiteId = 'broken';

your control panel username is //broken//
{{{
SiteUrl = 'http://wiki.elder-geek.net'+config.tiddlyspotSiteId;

&quot;| site management:|&lt;&lt;upload http://wiki.elder-geek.net/&quot; + config.tiddlyspotSiteId + &quot;/store.php index.html . .  &quot; + config.tiddlyspotSiteId + &quot;&gt;&gt;//(requires tiddlyspot password)//&lt;br&gt;[[control panel|http://wiki.elder-geek.net/&quot; + config.tiddlyspotSiteId + &quot;/controlpanel]], [[download (go offline)|http://wiki.elder-geek.net/&quot; + config.tiddlyspotSiteId + &quot;/download]]|&quot;,

&quot;@@font-weight:bold;font-size:1.3em;color:#444; //What now?// &amp;nbsp;&amp;nbsp;@@ 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/&quot; + config.tiddlyspotSiteId + &quot;/controlpanel]] (your control panel username is //&quot; + config.tiddlyspotSiteId + &quot;//).&quot;,


'TspotSidebar':[
 &quot;&lt;&lt;upload http://wiki.elder-geek.net/&quot; + config.tiddlyspotSiteId + &quot;/store.php index.html . .  &quot; + config.tiddlyspotSiteId + &quot;&gt;&gt;&lt;html&gt;&lt;a href='http://wiki.elder-geek.net/&quot; + config.tiddlyspotSiteId + &quot;/download' class='button'&gt;download&lt;/a&gt;&lt;/html&gt;&quot;
].join(&quot;\n&quot;)
}}}

<div title="~TspotSetupPlugin"
<div title="~UploadLog"
<div title="~WelcomeToTiddlyspot"

keto           RUNNING  192.168.11.144               -     NO         
wiki           RUNNING  192.168.11.143               -     NO         

ping keto 143    143
ping wiki 143    ---
[[110+ Best Free Angular JS Tutorials PDF & eBooks To Learn|http://www.fromdev.com/2015/06/angular-js-tutorials-pdf.html]]


[[Great Tutorials|http://www.ng-newsletter.com/?s=+from+beginner+to+expert]]
[[2048|Building the 2048 game in AngularJS]]

[[The Top 10 Mistakes AngularJS Developers Make|https://www.airpair.com/angularjs/posts/top-10-mistakes-angularjs-developers-make]]
http://tylermcginnis.com/angularjs-factory-vs-service-vs-provider/

''Setup Testing Etc''
[[Demo|http://rboaventura.com/f1feeder/]] [[Part1|http://www.toptal.com/angular-js/a-step-by-step-guide-to-your-first-angularjs-app]] [[Part2|http://www.toptal.com/angular-js/your-first-angularjs-app-part-2-scaffolding-building-and-testing]]

[[Processing Forms|https://www.lullabot.com/articles/processing-forms-in-angularjs]]

[[Build a gmail clone|https://www.thinkful.com/learn/angularjs-tutorial-build-a-gmail-clone/]]

https://www.airpair.com/angularjs/posts/top-10-mistakes-angularjs-developers-make
https://chrome.google.com/webstore/detail/angularjs-batarang/ighdmehidhipcmcojjgiloacoafjmpfk/related?hl=en
http://niquola.github.io/angular-in-nutshell/#/ looks good!

[[AngularJS / Bootstrap navbar|http://codepen.io/m-e-conroy/pen/bcEsA]]

[[Angular - Bootstrap - Jade - Stylus - CoffeeScript boilerplate webapp with Yeoman|http://oskarhane.com/angular-bootstrap-jade-stylus-coffeescript-boilerplate-webapp-with-yeoman/]]
[[Cookies vs Tokens. Getting auth right with Angular.JS|https://auth0.com/blog/2014/01/07/angularjs-authentication-with-cookies-vs-token/]]

''To Much To Process''
http://www.freakzion.com/index.php/The-Blog-November-2014/jsapps-101-angularjs-in-a-nutshell.html
http://blog.informatech.cr/2014/04/15/jsapps-101-angular-js/
https://www.thinkful.com/learn/angularjs-tutorial-build-a-gmail-clone/
https://www.airpair.com/angularjs/building-angularjs-app-tutorial
http://www.simplygoodcode.com/2013/12/how-to-make-an-email-web-app-using-angular/
http://www.dwmkerr.com/introducing-practical-angularjs/
http://www.dwmkerr.com/practical-angularjs-part1/
http://www.dwmkerr.com/the-only-angularjs-modal-service-youll-ever-need/
http://onehungrymind.com/angularjs-dynamic-templates/

[[Another Seed|http://www.amasik.com/angularjs-sample-application/]]

[[5minute website|https://www.airpair.com/angularjs/building-angularjs-app-tutorial]]


[[Fancy Navbar|http://bootsnipp.com/snippets/featured/fancy-navbar-login-sign-in-form]]
[[Responsive Sidebar|http://bootsnipp.com/snippets/featured/responsive-sidebar-menu]]

[[AngularJS Form Validation with ngMessages|https://scotch.io/tutorials/angularjs-form-validation-with-ngmessages]]


''~AngularJS Best Practices: I’ve Been Doing It Wrong!''
[[Part 1|http://artandlogic.com/2013/05/ive-been-doing-it-wrong-part-1-of-3/]] [[Part 2|http://artandlogic.com/2013/05/angularjs-best-practices-ive-been-doing-it-wrong-part-2-of-3/]] [[Part 3|http://artandlogic.com/2013/05/angularjs-best-practices-ive-been-doing-it-wrong-part-3-of-3/]] [[AngularJS: 6 Common Pitfalls Using Scopes|http://thenittygritty.co/angularjs-pitfalls-using-scopes]] [[angular-app|https://github.com/angular-app]] for node.js

[[AngularJS Style Guide|https://github.com/angular-ui/AngularJS-StyleGuide]]


''~JohnPapa on Structuring an Angular App''
http://www.johnpapa.net/structuring-an-angular-project/
http://www.johnpapa.net/angular-growth-structure/
http://www.johnpapa.net/angular-app-structuring-guidelines/


''Routing based on State''
[[angular-ui/ui-router|https://github.com/angular-ui/ui-router/wiki#resolve]]
[[Angular Routing Using Ui-Router|https://scotch.io/tutorials/angular-routing-using-ui-router]]
[[3 Simple Tips for Using Ui-Router|https://scotch.io/tutorials/3-simple-tips-for-using-ui-router]]



[[Easy AngularJS Forms with angular-formly|https://scotch.io/tutorials/easy-angularjs-forms-with-angular-formly]]



http://www.bootstrapzero.com/bootstrap-templates
http://blog.fontawesome.io/2014/05/19/stacking-text-and-icons/
http://codepen.io/davegandy/pen/dlCuq
https://fortawesome.github.io/Font-Awesome/examples/

http://www.bootstrapzero.com/bootstrap-template/facebook

http://www.bootstrapzero.com/bootstrap-template/devoops <<< has mail client


https://mgechev.github.io/angularjs-style-guide/
http://toddmotto.com/
http://www.johnpapa.net/
http://angularscript.com/
[[ng-file-upload|https://github.com/danialfarid/ng-file-upload]]
[[AngularJS File Uploads with HTML5 FileAPI|https://mrfrosti.com/2014/03/01/angularjs-file-uploads-with-html5-fileapi/]]
[[Angular HTML5 file upload|https://flowjs.github.io/ng-flow/]]

[[AngularJS Step-by-Step: Services|https://www.pluralsight.com/blog/tutorials/angularjs-step-by-step-services]]
[[Services in AngularJS simplified with examples|https://www.airpair.com/javascript/posts/services-in-angularjs-simplified-with-examples]]
/***
|''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);
}
//}}}
[[Techniques for authentication in AngularJS applications|https://medium.com/opinionated-angularjs/techniques-for-authentication-in-angularjs-applications-7bbf0346acec]] - A collection of ideas for authentication & access control
[[Listening on Route Changes to Implement a Login Mechanism|http://fdietz.github.io/recipes-with-angular-js/urls-routing-and-partials/listening-on-route-changes-to-implement-a-login-mechanism.html]]
[[Authentication made simple in Single Page AngularJS Applications|http://brewhouse.io/blog/2014/12/09/authentication-made-simple-in-single-page-angularjs-applications.html]]
[[The Login Page: Angular JS and Spring Security Part I|http://spring.io/blog/2015/01/12/spring-and-angular-js-a-secure-single-page-application]] and [[The Login Page: Angular JS and Spring Security Part II|https://spring.io/blog/2015/01/12/the-login-page-angular-js-and-spring-security-part-ii]]


Stack Overflow
http://stackoverflow.com/questions/11541695/redirecting-to-a-certain-route-based-on-condition
http://stackoverflow.com/questions/13478568/angularjs-redirect-to-login-page-and-persistence-of-session-id
http://stackoverflow.com/questions/27212182/angularjs-ui-router-how-to-redirect-to-login-page
[[Intro]]
[[Links]]

[[TiddlyLinks]]
[[Formatting]]
Here is the basic layout
{{{
$scope.tableParams = new ngTableParams(parameters, settings)
}}}

Since both @@parameters@@ and @@settings@@ are arrays {} you get
{{{
$scope.tableParams = new ngTableParams({parameters}, {settings})
}}}

With all the possible options
{{{
$scope.tableParams = new ngTableParams(
  { // Inital set and forget values
    page: 1
    count: 10
    filter: {},
    sorting: { name: 'asc' }  we
  } , {

  }
)
}}}

{{{
$scope.tableParams = new ngTableParams({
        page: 1,
        count: 25,
		filter: $scope.filter , 
        sorting: {
            sent: 'desc'
        }
    }, {
        total: 0,
		counts: {},
        getData: function($defer, params) {
            $http.post('api/email/table', {
                token: $scope.user.getToken(),
                params: JSON.stringify(params.$params)
            }).success(function(data) {
                params.total(data.total);
                $defer.resolve(data.messages);
                $scope.tableLoaded = true;
            });
        }
    });
}}}


''For Filter''
----
in controller we have a filter wich is an object containing a list of one filed we can filter on @@status@@ and the filter is set to null
{{{
$scope.filters = {
  status: ''
};
}}}

in view the select field is bound to @@filters.status@@ in the controller
{{{
<select name="status" ng-model="filters.status" ...>
}}}
in @@ngTableParams@@ which is also in the contoller
{{{
$scope.tableParams = new ngTableParams(
  {
    ...
    // Attach filters to ng-table.
    filter: $scope.filters
  },
  {
    ...
  }
);
}}}

On the back end { subject: 'asc' content: 'desc' }
$sorting = get_object_vars($params->sorting);  // yeilds an array [subject]='asc' [content]='desc'
$direction = reset($sorting); // yields 'asc' it is the first item in the array that rest is pulling
$key = key($sorting); // yields 'subject', pull the name of the key for the first item in the array 
if we were pulling anther item $direction1 = next($sorting) to get the next array item. Need to detrmine how many array items there are

We could also pull items the other way
$subjectdir = $sorting['subject'];
http://www.dwmkerr.com/the-only-angularjs-modal-service-youll-ever-need/
https://github.com/dwmkerr/angular-modal-service
http://dwmkerr.github.io/angular-modal-service/
http://angular-js.in/bootstrap-nav-tree/

http://angular-ui-tree.github.io/angular-ui-tree/#/basic-example


https://github.com/mateusmcg/angular-table-restful
| function/variable | data |h
|permissions.administrator|list of CRUD access rights combined from all the roles the current user is a member of related to administrator resources.|
|permissions.users|list of CRUD access rights combined from all the roles the current user is a member of related to user resources.|
|permissions.roles|list of CRUD access rights combined from all the roles the current user is a member of related to role resources.|
|active(' ')|is '/home' or '/administrator' the active page? will give a true / false answer|
|user.loggedIn()|is the current user logged in?|
|user.getEmail()|Get the email address of the current user|
/***
|''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"
	});
*/
//}}}
Each resource type (administrator, user, role, resource) seems to have a {{{permission.<resource>.contains()}}} function except for resource.
* {{{permission.administrator.contains()}}}
* {{{permission.user.contains()}}}
* {{{permission.role.contains()}}}

Resource uses {{{resource.permission.indexOf()}}}

{{{controller.js}}} contains a series of promises that reads in the permissions
{{{
    $scope.init = function() {
        if (!user.loggedIn()) {
            $scope.loaded = true;
            return;
        }

        var promises = [];

        promises.push(user.permissions('administrator')
        .then(function(permissions) {
            $scope.permissions.administrator = permissions;
        }));
    
        promises.push(user.permissions('user')
        .then(function(permissions) {
            $scope.permissions.users = permissions;
        }));
    
        promises.push(user.permissions('role')
        .then(function(permissions) {
            $scope.permissions.roles = permissions;
        }));
        
        $q.all(promises)
        .then(function() {
            $scope.loaded = true;
        }, function() {
            $scope.loaded = true;
        });
    };
}}}
In the database are 4 resource types. There is ''resource'' which is a meta container holding the other stuff. The other 3 are ''administrator'', ''user'' and ''role''. Each of these resources are read in via a {{{user.permissions('resource-type')}}} and it is then assigned to a {{{$scope}}} variable.  You can then use constructs like 
{{{
ng-if="permissions.administrator.contains('read')"
}}}

If a new resource is created, then a new promise must be added.
http://phpjs.org/functions/base64_decode/ - Will base64 decode to get {{{winmail.desc}}} and {{{winmail.dat}}}
https://addons.mozilla.org/en-us/thunderbird/addon/lookout/ 
Display RTF   http://quilljs.com/  or http://dhtmlx.com/docs/products/dhtmlxEditor/ or 

# Workflow is allow upload of file and base64 decode to get {{{winmail.desc}}} and {{{winmail.dat}}}
# On new page show region with winmail dat in it and region with list of files to download
# To get that list create an array of files and process the winmail.dat file to generate fie objects.
# Display a list of files to download / view

If needed can use php on the backend to do this. IE. Upload from browser, put thru base 64 decoder, put aside the .desc file and decode the .dat file with php TNEF take those files and bundle them up in an array and send them back to the browser. 
http://weblog.west-wind.com/posts/2014/Oct/24/AngularJs-and-Promises-with-the-http-Service

http://solutionoptimist.com/2013/12/27/javascript-promise-chains-2/

http://codepen.io/willh/pen/fCtuw
/***
|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]&&params[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,'&#x22;')+'">> /%\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+="~~&nbsp; searched in "+opts.join(" + ")+"~~\n";
	body+=(co.chkCaseSensitiveSearch||co.chkRegExpSearch?"^^&nbsp; 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,"&quot;");
		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:
<<<
*+++*[&lt;&lt;search "wood"&gt;&gt;]>...
<<search "wood">>
===

*+++*[&lt;&lt;search "wood" "/%%/"&gt;&gt;]>...
<<search "wood" "/%%/">>
===

*+++*[&lt;&lt;search "wood" "See also: "&gt;&gt;]>...
<<search "wood" "See also: ">>
===

*+++*[&lt;&lt;search "wood" "See also:\n*" "\n*"&gt;&gt;]>...
<<search "wood" "See also:\n*" "\n*">>
===

*+++*[&lt;&lt;search "wood" report=list "See also:"&gt;&gt;]>...
<<search "wood" report=list "See Also:" >>
===

*+++*[&lt;&lt;search "wood" report&gt;&gt;]>...
<<search "wood" report>>
===

*+++*[&lt;&lt;search "wood" report=&gt;&gt;]>...
<<search "wood" report=>>
===

*+++*[&lt;&lt;search "wood" report=all&gt;&gt;]>...
<<search "wood" report=all>>
===

*+++*[&lt;&lt;search "wood" report=summary+buttons+again+list&gt;&gt;]>...
<<search "wood" report=summary+buttons+again+list>>
===

*+++*[&lt;&lt;search "wood" report=summary+again&gt;&gt;]>...
<<search "wood" report=summary+again>>
===

*+++*[&lt;&lt;search "wood" report=summary&gt;&gt;]>...
<<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}}}'''
~~&nbsp; 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>@@
$scope
$timeout
$location
$log
/***
|''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, "&quot;");
                }

                // 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, "&quot;");
            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:  ![alt text](url "optional title")
        // 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, "&quot;");
        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, "&quot;");
        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, "&amp;");

        // Do the angle bracket song and dance:
        text = text.replace(/</g, "&lt;");
        text = text.replace(/>/g, "&gt;");

        // 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, "&amp;");

        // Encode naked <'s
        text = text.replace(/<(?![a-z\/?\$!])/gi, "&lt;");

        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="&#x6D;&#97;&#105;&#108;&#x74;&#111;:&#102;&#111;&#111;&#64;&#101;
//	   x&#x61;&#109;&#x70;&#108;&#x65;&#x2E;&#99;&#111;&#109;">&#102;&#111;&#111;
//	   &#64;&#101;x&#x61;&#109;&#x70;&#108;&#x65;&#x2E;&#99;&#111;&#109;</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;
    });
}
<<calendar thismonth>>
<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>>
need to include {{{angular-route.js}}}
Uses the directive {{{ng-route}}}
Add 
{{{
var MyApp = angular.model('myApp', ['ngRoute']);

myApp.config(function ($routeProvider) {
    $routeProvider

    .when('/', { 
        templateUrl: 'pages/main.html',
        controller: 'mainController'
    })
    .when('/second', { 
        templateUrl: 'pages/second.html',
        controller: 'mainController'
    })
};
}}}

In the html we need a {{{ng-view}}} for the {{{templateUrl}}} to attach to
{{{
<div ng-app="myApp">
    <div ng-view></div>
</div>
}}}

Angular
user/system info
{{{
SELECT u.email, s.system_name, c.component_name, u2c.component_start, u2c.component_end
FROM users u
INNER JOIN users2components u2c ON u2c.user_id = u.id
INNER JOIN components c on u2c.component_id = c.id
INNER JOIN systems s on s.id = c.system_id
}}}

Mail: Inbox
{{{
SELECT uf.email AS `From`, ut.email AS `To`, DATE_FORMAT(mfu.sent_at, '%m/%d/%Y %I:%i %p' )AS `Sent`, 
       (CASE WHEN mtu.message_status_id = 4 THEN "Read" ELSE "Unread" END) AS `Read`, (CASE WHEN m.attachments = 0 THEN "No" ELSE "Yes" END) AS Attachments, 
       m.subject AS Subject, "InBox" AS Folder,
       m.body AS Message
FROM messages m
INNER JOIN messages_from_user mfu ON mfu.message_id = m.id
INNER JOIN users uf ON uf.id = mfu.user_id
INNER JOIN messages_to_user mtu ON mtu.message_id = m.id
INNER JOIN users ut ON ut.id = mtu.user_id
WHERE mfu.message_status_id = 2
}}}

Mail: Sent
{{{
SELECT uf.email AS `From`, ut.email AS `To`, DATE_FORMAT(mfu.sent_at, '%m/%d/%Y %I:%i %p' )AS `Sent`, 
       (CASE WHEN mtu.message_status_id = 1 THEN "Draft" ELSE "Sent" END) AS `Read`, (CASE WHEN m.attachments = 0 THEN "No" ELSE "Yes" END) AS Attachments, 
       m.subject AS Subject, "Sent" AS Folder,
       m.body AS Message
FROM messages m
INNER JOIN messages_from_user mfu ON mfu.message_id = m.id
INNER JOIN users uf ON uf.id = mfu.user_id
INNER JOIN messages_to_user mtu ON mtu.message_id = m.id
INNER JOIN users ut ON ut.id = mtu.user_id
WHERE mfu.message_status_id = 2
}}}
/***
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
// %/
Coaching System Mass Emial

Copy Champion to new system

~CS4 will have 2 coaches

----

http://www.affiliatesforall.org/
http://www.elitius.com/services.html


----
Home | Control Panel | Email | Documents | Attachments | Log Out

Control Panel
Counters: Total Emails | New Emails | Documents | Attachments
Shows Name Profile Information
Offer to change email address
Offer to change password


----

Home (Media List)
   download Links 

Read Messages

Send Messages

Attachments (Archives of Documents You Sent)

Profiles
  Name
  Email
  Expiration Date

Log Out

----

Home (~InBox)   Filter Messages

Send Message

Attachments

Documents 
     Upload Document  |  New Category  | Edit Category  | Delete Category (w/confirmation)
     (Edit) | Download | Title | Category |
     (Upload a Document - Category) | Title |  Descrption | Sort Order | File to Upload | Upload |

Students  (Download this list)
  (edit) | emial | name | signed up | enabled (date or NO) | Expires
 (create a new student)  First | Last | ~E-Mail | Phone | Password | 

Mass Email (Current/Former/All)

Log Out

----

''Media Players''
HTML 5 video player 
[[Plyr|http://www.queness.com/post/17427/plyrfully-customisable-html5-media-player]]  https://github.com/Selz/plyr/issues/44
http://jplayer.org/
*[[index.html|TadAppIndex]]
*[[js/application.js|TadAppApplicaton]]
*[[js/controllers.js|TadAppControlers]]

http://embed.plnkr.co/b5V0OU/preview
Loads the following ~JavaScript into the application scope
* acs.filters
* acs.services
* acs.directives
* acs.controllers
* ngRoute
* ui.bootstrap
* ngTable

Loads the routes to all the pages on the site

Sets up helper functions for routing such as param
|sortable|k
|Controller|Route|Notes|h
|[[root|TadAppControlersRoot]]|Loads via [[index.html|TadAppIndex]] no route||
|[[navigation|TadAppControlersNavigation]]|Loads via [[index.html|TadAppIndex]] no route||
|[[login|TadAppControlersLogin]]|/login||
|[[register|TadAppControlersRegister]]|/register||
|[[home|TadAppControlersHome]]|/home||
|[[administrator|TadAppControlersAdministrator]]|/administrator|Loads billboard even if user is not logged in. However Navigation is not visible|
|[[users|TadAppControlersUsers]]|/administrator/users||
|[[user|TadAppControlersUser]]|/administrator/user/:id||
|[[roles|TadAppControlersRoles]]|/administrator/roles||
|[[role|TadAppControlersRole]]|/administrator/role/:role||
|[[emails|TadAppControlersEmails]]|/emails||
|[[email|TadAppControlersEmail]]|/emails/:id||
This controller bootstraps the application. There is no route to this controller, it can only be loaded by [[index.html|TadAppIndex]]. The main bootstrap container for the page contains the root controller which then runs the init function upon loading.
{{{
<div class="container" ng-controller="root" ng-init="init()">
}}}

''Variables Injected''
{{{
$scope', '$location', '$q', 'user'
}}}

On First Page Load
{{{
$scope.loaded = false;       \\ Page is marked as not yet loaded
$scope.user = user;          \\ If there is an active user, it is injected into the page
$scope.permissions = {};     \\ Permission are cleared and will be re-read in
$scope.waiting = false;      \\ Waiting flag is cleared
}}}

|sortable|k
|Variables|Notes|h
|{{{$scope.loaded = false;}}}|Page is marked as not yet loaded|
|{{{$scope.user = user;}}}|If there is an active user, it is injected into the page|
|{{{$scope.permissions = {};}}}|Resource Permission are cleared and will be re-read in|
|{{{$scope.waiting = false;}}}|Waiting flag is cleared|

|sortable|k
|Functions|Notes|h
|{{{init()}}}|Reads in CRUD permissions for the resources of administrator, user, role, and email.|
|{{{active()}}}|Returns the active/current page loaded|
|{{{logout()}}}|Clears user array and reloads page|

''Note:'' A resource can be a group of pages, a single page, a portion of a page, or even a form or page element. It is any resource you want granular control of access over. 

All resource CRUD permissions are read in upon page load of the [[index.html|TadAppIndex]] by loading this root controller.
|sortable|k
|Function|File|Description|h
|param|application.js||
|$httpProvider.defaults.transformRequest|application.js||
|Array.prototype.contains|application.js||
|controller|controllers.js||


# {{{<DOCTYPE HTML!>}}} ~HTML5 applicaton
# {{{<html lang="en" ng-app="acs">}}} applicaton ''acs''
# {{{<head>}}} CSS Libraries
## jquery-1.10.0
## twwiter-boostrat-3.1.1
## font-awesome-4.3.0
## ng-table-.0.3.3
## bootstrap-select-1.6.3
## ladda-bootstrap-0.9.4
## our custom application stylesheet
# {{{<body>}}} Views and controllers
## {{{<div class="container" ng-controller="root" ng-init="init()">}}} Container is setup and [[root controller|TadAppControllersRoot]] is loaded
## {{{<div class="navigation" ng-controller="navigation" ng-include="navigation()"></div>}}} [[navigation controller|TadAppControllersNavigation]] is loaded 
## {{{<div class="view" ng-view></div>}}} Application view is loaded with default [[home controller|TadAppControllersHome]]
# Pre {{{</body>}}} ~JavaScript Libraries
## ajax-loadash-3.1.0
## jquery-2.0.3
## jqueryui-1.10.0
## twitter-bootstrap-3.1.1
## store-1.3.14
## moment.js-0.4.6
## odometer-04..6
## angularjs-1.2.16
## angular-route-1.2.16
## angular-ui-0.4.0
## angulear-ui-bootstrap-0.10.0
## ng-table-0.3.3
## bootstrap-select-1.6.3
## ladda-bootstrap-spin-0.9.4
## ladda-bootstrap-0.9.4
# Pre {{{</body>}}} Application Specific ~JavaScript in
## [[locales/en-US.js|TadAppLocales]] i18n Translations
## [[js/application.js|TadAppApplication]] Starts application and has router logic
## [[s/services.js|TadAppServices]] Services for displaying alerts and 
## [[js/controllers.js|TadAppControllers]] Controller logic for all the routes provided by [[js/application.js|TadAppApplication]] to load the correct html partials as well as the not routable controller logic for the navigation page
## [[js/filters.js|TadAppFilters]] currently there are no filters to load
## [[js/directives.js|TadAppDirectives]] such as i18n translations, odometer or to display the "loading"  alert. 

----
The application scope is for ''acs'' and then a master controller named [[root|TadAppControllersRoot]] is setup which is invisible and is always running and knows what CRUD permissions each user type has as well as which page on the site is currently loaded and the logout function.

The first child controller is for navigation. It calls the function {{{navigation()}}} from the [[navigation controller|TadAppControllersNavigation]] which is capable of loading the correct html partial for whatever menu we want at the top of the page. This is loaded without a route. It is not possible to pass a URL from the web browser and bring the navigation html up directly. This means the [[navigation controller|TadAppControllersNavigation]] has the logic to tell what page we are on ({{{$scope.active{'/page-route')}}}) if the user is logged in ({{{user.loggedIn()}}}) or even which user ({{{user.getEmail()}}} and maybe {{{user.getFirstname()}}} ) or what CRUD permissions they have ({{{permissions.email.contains('read')}}}) to determine which html partial is loaded for the navigation menu. As a security issue, it is not possible for a user to circumvent security for which menu is displayed and what options are displayed on that menu.  The navigation html partial itself can also use these functions to further control what menu options are displayed. 

The second child controller is for displaying the app view for the html partial of the active page which will be reached via a route. The default route is the [[home controller|TadAppControllersHome]] via the {{{$routeProvider.otherwise()}}} route mechanism provided in the [[js/application.js|TadAppApplication]]. 


Since the [[root|TadAppControllersRoot]] is invisible and contains both the [[navigation controller|TadAppControllersNavigation]] and {{{apps ng-view}}}. The look of the page is anything that can be devised with an outer {{{<div></div>}}} containing two inner {{{<div></div>}}}s

For example. Most of the site has a nav bar across the top with the view being the entire body of the page below it. However the administrator page has navigation that is an upside down "L" being a nav menu at the top, and a sidebar menu running down the left hand side, with the area directly to the right of the sidebar menu and below the top navbar menu being the {{{ng-app}}} view area. With CSS it would be possible to place the {{{ng-app}}} view above the nav menu, or to have the sidebar run down the right hand side of the display instead of the left hand side.

It would also be possible to place a header or footer on the page that never changes. 

----

See http://www.formget.com/codeigniter-encrypt/ for using ci's encrypt function to encrypt the user id.
add number of attachments so messages table
{{{
CREATE TABLE booger (id INT, attachments INT  );

INSERT INTO booger
SELECT messages.id,  (SELECT COUNT(message_id) FROM attachments WHERE message_id = messages.id) AS attachments 
FROM messages ;

UPDATE messages 
JOIN booger ON messages.id = booger.id
SET messages.attachments = booger.attachments;

DROP TABLE booger;
}}}
make folder names for documents
{{{
mkdir $(printf "%02x " {0..255})
}}}
 | Home | Login
 | Home | Messages | Attachments | Documents | Media | Settings | Student Info | ~PayPal | Logout

|Menu Item|Admin|Teacher|Student|Former|Visitor|h
|''Home'' &dtrif;| x | x | x | x | x |
|&bull;&nbsp;Sign Up| x | x | x | x | x |
|&bull;&nbsp;About FAQ| x | x | x | x | x |
|&bull;&nbsp;Table| x | x | x | x | x |
|&bull;&nbsp;About Us| x | x | x | x | x |
|&bull;&nbsp;Login| x | x | x | x | x |
|&bull;&nbsp;Logout| x | x | x | x ||
|&bull;&nbsp;Password Reset||||| x |
|''Attachments''| x | x | x | x ||
|''Messages'' &dtrif;| x | x | x | xx ||
|&bull;&nbsp;Read| x | x | x | xx ||
|&bull;&nbsp;Create| x | x | x |||
|&bull;&nbsp;Reply| x | x | x |||
|&bull;&nbsp;Forward| x | x | x |||
|''Documents''| x | x | x | xx ||
|&bull;&nbsp;Create| x | x ||||
|&bull;&nbsp;Update| x | x ||||
|&bull;&nbsp;Delete| x | x ||||
|''Media''| x | x | x | xx ||
|&bull;&nbsp;Create| x | x ||||
|&bull;&nbsp;Update| x | x ||||
|&bull;&nbsp;Delete| x | x ||||
|''Settings'' &dtrif;| x | x | x | x ||
|&bull;&nbsp;Update Email| x | x | x | x ||
|&bull;&nbsp;Update Password| x | x | x | x ||
|||||||
|''Student Info'' v| xx | x ||||
|&bull;&nbsp;Create| xx | x ||||
|&bull;&nbsp;Update| xx | x ||||
|&bull;&nbsp;Delete| xx | x ||||
|''Paypal Info'' &dtrif;| x |||||

xx Former - Can only view this content up to the date their account expired
xx Admin - Can Update teachers and CRUD secondary teacher with DELETES moving the email to the primary instructor


Remember Fred A Course on
* Free Antivirus
* Free Malware
* Browser Plugins
** uBlock
** Flash Block
** Ghostery 
* Stop Windows 10 Snitching
* PGP
* TrueCrypt
* Email
* TOR
https://github.com/danialfarid/ng-file-upload
https://github.com/danialfarid/ng-file-upload/wiki/PHP-Example


https://blueimp.github.io/jQuery-File-Upload/angularjs.html
https://github.com/blueimp/jQuery-File-Upload/blob/master/angularjs.html
https://github.com/blueimp/jQuery-File-Upload/wiki/Demo-implementation


https://github.com/wender/angular-multiple-file-upload

{{{
when('/school/:schoolId', {            // $scope.school_id = $routParams.schoolId;
    templateUrl: 'partials/school',
    controller: 'schoolController',    // $scope.extra = $route.current.foodata;
    extraData: 'data thing'
});
}}}
can use :parm? the ? = optional
{{{
when('/school/:schoolId/:feature?', {   //  if ( !$routeParams.feature ) {
    templateUrl: 'partials/school',     //     // Do /school
    controller: 'schoolController'      //   } else if { // do /school/feature }
});
}}}

{{{
function FirstController($scope) {
    $scope.$on('someEvent', function (event, args) {
    });
    // this is another controller or even directive
}
 
function SecondController($scope) {
    $scope.$emit('someEvent', args);
}
}}}

http://stackoverflow.com/questions/9293423/can-one-controller-call-another
{{{
<script type="text/ng-template" id="add_order.html">
    <h2> Add Order </h2>
    {{message}}
</script>
}}}
http://angular-ui.github.io/ui-router/sample/#/about

{{{
controllers.controller('test1', ['$scope', '$location', '$http', 'user', 'systemsFactory', function($scope, $location, $http,  user, systemsFactory) {

    $scope.user = user;
	$scope.systems = systemsFactory.getSystems();

}]);


controllers.controller('test2', ['$scope', '$location', '$http', '$routeParams', 'user', 'systemsFactory', function($scope, $location, $http, $routeParams, user, systemsFactory) {

	$scope.user = user;
	$scope.system = $routeParams.system;
	$scope.messages = ['1','2','3','4','5','6'];
	$scope.systems = systemsFactory.getSystems();

}]);

controllers.controller('test3', ['$scope', '$location', '$http', '$routeParams', 'user', 'systemsFactory', function($scope, $location, $http, $routeParams, user, systemsFactory) {

	$scope.user = user;
	$scope.system = $routeParams.system;
	$scope.message = $routeParams.message;
	$scope.systems = systemsFactory.getSystems();

}]);

factory('systemsFactory', function() {
  return {
    getSystems : function() {
      return ["CS1", "CS2", "Land Patents","Riddle","Probate"];
    }
  }
});
}}}


http://fdietz.github.io/2015/04/13/day-1-how-to-build-your-own-team-chat-in-five-days.html
http://weblogs.asp.net/dwahlin/dynamically-loading-controllers-and-views-with-angularjs-and-requirejs

/
/static/:page
/school/loby
/school/info
/school/compose
/school/messages/:item
/school/media/:folder
/school/audio/:item
/school/document/:item
/school/video/:item



ROLE                        RESOURCE
cs1-cs1-student             cs1-cs1-email-to-teacher     cs1-cs1-email-to-students
cs1-cs1-teacher             cs1-cs1-documents

SIMPLER                     RESOURCE
cs1-cs1-student             cs1-cs1
cs1-cs1-teacher
cs1-cs1-trial
cs1-cs1-guest


active[{ component: "", expires: "" } ... ] 


system: cs1 --> component: cs1 --> subscription(cs1 cs1 start expires is_active)


https://docs.angularjs.org/api/ng/directive/ngInclude
http://www.scriptscoop.net/t/a0eb60597ef5/angularjs-custom-pushpin-with-angular-directive-content-on-a-bing-maps.html
http://stackoverflow.com/questions/25391279/angularjs-ng-include-failover
{{{
$includeContentError <-- is emited


function("ErrorCtrl", ------ function() {
    $scope.$on($includeContentError, function() { console.log('404 Error'); });
    $location.url('/404');
});
}}}

{{{
<div ng-include="getPartialUrl()"></div>
}}}
{{{
    this.partialId = 1;
// need to test and throw 404 

      this.getPartialUrl = function() {
        return 'partial' + this.partialId + '.html';
      }
}}}
{{{
Component (edit screen)
----
can update name IF name is not same as system 
    AND other component of system is not named the same
can change system attached to IF name is not same as system 
    AND other system does not have component with same name

return "Update Successful" or "Error"
}}}

''Components Add''
{{{
$sy = str_replace(' ', '', strtolower(trim($system)));
$co = str_replace(' ', '', strtolower(trim($component)));
$syco = concat('%"' . "$sy-$co" . '"%')
sql( "SELECT value FROM acl WHERE key = 'resource' AND value LIKE ?", array($syco) );
IF result = true then  return with error "Component Already Exists"
}}}

{{{
can create IF comonent name is not same as system name 
    AND system does not have component of the same name
}}}

ALL OF THIS MUST UPDATE ACLs


https://css-tricks.com/specifics-on-css-specificity/

alerts
alerts.clear()
alerts.get()
alerts.set(val)
alerts.success(msg)
alerts.fail(msg)gular >=1.


http://www.dwmkerr.com/the-only-angularjs-modal-service-youll-ever-need/
https://github.com/dwmkerr/angular-modal-service
http://dwmkerr.github.io/angular-modal-service/
https://github.com/kriskowal/q#chaining
https://www.airpair.com/angularjs



http://www.bennadel.com/blog/2757-creating-an-html-based-select-menu-in-angularjs-using-ngmodel-and-ngmodelcontroller.htm
https://vimeo.com/116439160
https://github.com/jseppi/angular-dropdowns



ng-disabled="!permissions.component.contains('create') || "
ng-disabled=" (mt0||mt1||mt2) ?  false : true"
mt0 || mt1 || mt2==true then it will disable the button
!mt0 || !mt1 || !mt2==false if everything is true and will not disable the button
mt0 && mt1 && mt2==true if everything is true and will disable the button
!mt0 && !mt1 && !mt2==false if everthing is true and not disable the button


http://nya.io/nya-bootstrap-select/#!/main/getting-started
http://stackoverflow.com/questions/15090185/angularjs-bootstrap-dropdown-cant-do-ng-click-in-ng-repeat
http://jsfiddle.net/zzw9nq63/


look at 
http://moonstorm.github.io/trNgGrid/
http://adaptv.github.io/adapt-strap/
Guest    -- 
Student  -- permissions apply to all systems that a user has access to
Teacher  -- 
Admin    -- 



New System means new role  student-system teacher-system

New Component means new resources system-component-email
                                  system-component-media


Component Types: acess-only-while-member
                 access-while-member / limited-access-when-not member


SINGLE SYSTEM SETUP
user -> role -> resource

MULTI SYSTEM SETUP
user -> role -> resoruce -> component
user -> role -> component -> resource



I Fred am a:  student of CS1-CS1
              student of CS1-A New Hope
              student of CS2
              teacher of Riddle

Get Permissions -> 
We need to be aware of userID, componentID ===

roles are not student teacher admin 
roles are student-cs1-cs1 teacher






Site Navigation

Main - pages - email - media (doc/video/audio) - media-search


/#/static
/#/yhtr
/#/school

CS1
     role: student-cs1
           teacher-cs1

resoruces: cs1-cs1
           cs1-jul2015
           cs1-oct2015


role_resoruce_permission::cs1-cs1::cs1-student  << email is based on this one
role_resoruce_permission::cs1-cs1::cs1-teacher  << email is based on this one
role_resoruce_permission::cs1-jul2015::cs1-student
role_resoruce_permission::cs1-jul2015::cs1-teacher
role_resoruce_permission::cs1-oct2015::cs1-stuend
role_resoruce_permission::cs1-oct2015::cs1-teacher


/#/schools/---/messages
/#/schools/---/message
/#/schools/---/compose
/#/schools/---/attachments
/#/schools/---/attachment
/#/schools/---/media
/#/schools/---/messages





You have # unread messages




mind maps


document
id category_id sortorder title description filename filecontent

documents_categories







recordings
id date title descripton content

recordings_in_categories
recording_id category_id


categories
id title teacher

teacher 1 3 5 10 11 12 13 15
id name


shows
id teacheid date description mp3

talkshoe_calls
id showdate priority name screenname email expeirnce researched doneseimnar
{{{
select r.date r.title r.description
from recordings r
inner join recordings_in_categories rnc on rnc.recording_id = r.id
inner join categories cat on cat.id = rnc.category_id
inner join teachers t on t.id = cat.teacher 
where t.id = 01
}}}
http://preview.editmysite.com/LN8F75

https://wordpress.org/themes/tags/responsive-layout/
https://wordpress.org/themes/profound/
https://wordpress.org/themes/zerogravity/
https://wordpress.org/themes/gardenia/
https://wordpress.org/themes/wen-associate/
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
# Attachments 
## Library
## Adding 
## Accessing
/***
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 = 'angular';

// 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?// &nbsp;&nbsp;@@ 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// &nbsp;&nbsp;@@ 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// &nbsp;&nbsp;@@ 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!// &nbsp;&nbsp;@@ 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 :)// &nbsp;&nbsp;@@ 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 |
| 07/03/2016 12:32:48 | FredW | [[/|http://wiki.elder-geek.net/angular/]] | [[store.php|http://wiki.elder-geek.net/angular/store.php]] | . | [[index.html | http://wiki.elder-geek.net/angular/index.html]] | . | ok |
| 07/03/2016 12:46:44 | FredW | [[/|http://wiki.elder-geek.net/angular/]] | [[store.php|http://wiki.elder-geek.net/angular/store.php]] | . | [[index.html | http://wiki.elder-geek.net/angular/index.html]] | . | ok |
| 07/03/2016 12:48:25 | FredW | [[/|http://wiki.elder-geek.net/angular/]] | [[store.php|http://wiki.elder-geek.net/angular/store.php]] | . | [[index.html | http://wiki.elder-geek.net/angular/index.html]] | . | ok |
| 07/03/2016 12:49:20 | FredW | [[/|http://wiki.elder-geek.net/angular/]] | [[store.php|http://wiki.elder-geek.net/angular/store.php]] | . | [[index.html | http://wiki.elder-geek.net/angular/index.html]] | . |
| 16/03/2016 22:55:28 | FredW | [[/|http://wiki.elder-geek.net/angular/]] | [[store.php|http://wiki.elder-geek.net/angular/store.php]] | . | [[index.html | http://wiki.elder-geek.net/angular/index.html]] | . | ok |
| 16/03/2016 22:56:33 | FredW | [[/|http://wiki.elder-geek.net/angular/]] | [[store.php|http://wiki.elder-geek.net/angular/store.php]] | . | [[index.html | http://wiki.elder-geek.net/angular/index.html]] | . |
| 03/04/2016 22:58:44 | FredW | [[/|http://wiki.elder-geek.net/angular/]] | [[store.php|http://wiki.elder-geek.net/angular/store.php]] | . | [[index.html | http://wiki.elder-geek.net/angular/index.html]] | . |
| 16/04/2016 10:50:58 | FredW | [[/|http://wiki.elder-geek.net/angular/]] | [[store.php|http://wiki.elder-geek.net/angular/store.php]] | . | [[index.html | http://wiki.elder-geek.net/angular/index.html]] | . | ok |
| 16/04/2016 11:14:32 | FredW | [[/|http://wiki.elder-geek.net/angular/]] | [[store.php|http://wiki.elder-geek.net/angular/store.php]] | . | [[index.html | http://wiki.elder-geek.net/angular/index.html]] | . | ok |
| 16/04/2016 11:16:18 | FredW | [[/|http://wiki.elder-geek.net/angular/]] | [[store.php|http://wiki.elder-geek.net/angular/store.php]] | . | [[index.html | http://wiki.elder-geek.net/angular/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?// &nbsp;&nbsp;@@ 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 //angular//).
<<tiddler TspotControls>>
See also GettingStarted.

@@font-weight:bold;font-size:1.3em;color:#444; //Working online// &nbsp;&nbsp;@@ 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// &nbsp;&nbsp;@@ 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!// &nbsp;&nbsp;@@ 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 :)// &nbsp;&nbsp;@@ We hope you like using your tiddlyspot.com site.  Please email [[feedback@tiddlyspot.com|mailto:feedback@tiddlyspot.com]] with any comments or suggestions.
''Good Overviews''
----
Generally speaking you're making a decision between fast read times (e.g. nested set) or fast write times (adjacency list). Usually you end up with a combination of the options below that best fit your needs. The following provides some in depth reading:
* [[One more Nested Intervals vs. Adjacency List comparison|http://vadimtropashko.wordpress.com/2008/08/09/one-more-nested-intervals-vs-adjacency-list-comparison/]]: ''the best comparison'' of Adjacency List, Materialized Path, Nested Set and Nested Interval I've found.
* [[Models for hierarchical data|http://www.slideshare.net/billkarwin/models-for-hierarchical-data]]: slides with good explanations of tradeoffs and example usage
* [[Representing hierarchies in MySQL|http://mikehillyer.com/articles/managing-hierarchical-data-in-mysql/]]: very good overview of Nested Set in particular
* [[Hierarchical data in RDBMSs|http://troels.arvin.dk/db/rdbms/links/#hierarchical]]: most comprehensive and well organized set of links I've seen, but not much in the way on explanation


''Options''
----
Ones I am aware of and general features:

# [[Adjacency List|http://en.wikipedia.org/wiki/Adjacency_list]]:
** Columns: ID, ~ParentID
** Easy to implement.
** Cheap node moves, inserts, and deletes.
** Expensive to find level (can store as a computed column), ancestry &amp; descendants (Bridge Table combined with level column can solve), path (Lineage Column can solve).
** Use [[Common Table Expressions|http://en.wikipedia.org/wiki/Common_table_expressions]] in those databases that support them to traverse.
# [[Nested Set|http://en.wikipedia.org/wiki/Nested_set_model]] (a.k.a Modified Preorder Tree Traversal)
** Popularized by Joe Celko in numerous articles and his book [[Trees and Hierarchies in SQL for Smarties|http://rads.stackoverflow.com/amzn/click/0123877334]]
** Columns: Left, Right
** Cheap level, ancestry, descendants
** Compared to Adjacency List, moves, inserts, deletes more expensive.
** Requires a specific sort order (e.g. created). So sorting all descendants in a different order requires additional work.
# [[Nested Intervals|http://communities.bmc.com/communities/docs/DOC-9902]]
** Combination of Nested Sets and Materialized Path where left/right columns are floating point decimals instead of integers and encode the path information. In the later development of this idea nested intervals gave rise to [[matrix encoding|http://vadimtropashko.files.wordpress.com/2011/07/ch5.pdf]].
# [[Bridge Table|http://intelligent-enterprise.informationweek.com/showArticle.jhtml;jsessionid=MRBJR2LLRV1ANQE1GHPSKH4ATMY32JVN?articleID=219400252]] (a.k.a. [[Closure Table|http://dirtsimple.org/2010/11/simplest-way-to-do-tree-based-queries.html]]: some good ideas about how to use triggers for maintaining this approach)
** Columns: ancestor, descendant
** Stands apart from table it describes.
** Can include some nodes in more than one hierarchy.
** Cheap ancestry and descendants (albeit not in what order)
** For complete knowledge of a hierarchy needs to be combined with another option.
# [[Flat Table|http://evolt.org/node/4047/]]
** A modification of the Adjacency List that adds a Level and Rank (e.g. ordering) column to each record.
** Expensive move and delete
** Cheap ancestry and descendants
** Good Use: threaded discussion - forums / blog comments
# [[Lineage Column|http://www.ferdychristant.com/blog//articles/DOMM-7QJPM7]] (a.k.a. [[Materialized Path|http://communities.bmc.com/communities/docs/DOC-9902]], Path Enumeration)
** Column: lineage (e.g. /parent/child/grandchild/etc...)
** Limit to how deep the hierarchy can be.
** Descendants cheap (e.g. {{{LEFT(lineage, #) = '/enumerated/path'}}})
** Ancestry tricky (database specific queries)
# [[Multiple lineage columns|http://stackoverflow.com/a/6802687/684229]]
** Columns: one for each lineage level, refers to all the parents up to the root, levels down from the items level are set to NULL
** Limit to how deep the hierarchy can be</li>
** Cheap ancestors, descendants, level</li>
** Cheap insert, delete, move of the leaves</li>
** Expensive insert, delete, move of the internal nodes</li>


''Database Specific Notes''
----
''~MySQL''
* [[Use session variables for Adjacency List|http://explainextended.com/2009/09/29/adjacency-list-vs-nested-sets-mysql/]]

''Oracle''
* Use [[CONNECT BY|http://www.ypl.com/oracle/sql/hierarchical_queries/html_deep/index.html]] to traverse Adjacency Lists

''~PostgreSQL''
* [[ltree datatype|http://www.postgresql.org/docs/current/static/ltree.html]] for Materialized Path

''SQL Server''
* [[General summary|http://msdn.microsoft.com/en-us/magazine/cc794278.spx]]
* 2008 offers [[HierarchyId|http://msdn.microsoft.com/en-us/library/bb677290.aspx]] data type appears to help with Lineage Column approach and expand the depth that can be represented.


''Other''
----
* [[Managing Hierarchical Data in SQL|http://www.pure-performance.com/2009/03/managing-hierarchical-data-in-sql/]]
* http://www.depesz.com/2008/04/11/my-take-on-trees-in-sql/
* http://stackoverflow.com/questions/4048151/what-are-the-options-for-storing-hierarchical-data-in-a-relational-database
* http://stackoverflow.com/questions/24036434/query-parents-and-children-in-self-referencing-table
* http://stackoverflow.com/questions/26886002/mysql-query-to-maintain-sort-order-in-ascending-order-for-self-referencing-table

''closure tables''
* http://dirtsimple.org/2010/11/simplest-way-to-do-tree-based-queries.html with closure-tables
** http://www.slideshare.net/billkarwin/sql-antipatterns-strike-back (excellent slide-show)
*** [[Rendering Trees with Closure Tables|http://karwin.blogspot.com/2010/03/rendering-trees-with-closure-tables.html]]
** [[Optimize Hierarchy Queries with a Transitive Closure Table|http://kylecordes.com/2008/transitive-closure]]
* [[Stored procedure driven hierarchial closure-table|https://github.com/developerworks/hierarchy-data-closure-table]]

''materialized path''
* [[Trees in SQL : an approach based on materialized paths and normalization for MySQL|http://www.cloudconnected.fr/2009/05/26/trees-in-sql-an-approach-based-on-materialized-paths-and-normalization-for-mysql/]]
* https://news.ycombinator.com/item?id=709713
* [[Using Materialized Path to create a paths table|http://joecelkothesqlapprentice.blogspot.com/2006/05/using-materialized-path-to-create.html]]
* [[Storing hierarchical data: Materialized Path|https://bojanz.wordpress.com/2014/04/25/storing-hierarchical-data-materialized-path/]]

<<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>&nbsp;-&nbsp;
<span macro='foundTiddler field includeURL'/></span>&nbsp;-&nbsp;
<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:''|&copy; 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 = "&hellip;";
};

//#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">&copy; 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&nbsp;of&nbsp;<span class=\"yourSearchCount\" macro=\"yourSearch count\"></span>\n"+
"\t\tfor&nbsp;<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>&nbsp;-&nbsp;\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>
		&nbsp;of&nbsp;<span class="yourSearchCount" macro="yourSearch count"></span>
		for&nbsp;<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;
}
/*}}}*/
Can be found at [[GitHub|https://github.com/rmcdaniel/angular-codeigniter-seed]]. To download
{{{
git clone https://github.com/rmcdaniel/angular-codeigniter-seed
}}}

To setup the database
{{{
cd angular-codeigniter-seed
php api/index.php cli install
php api/index.php cli add administrator foo@bar.com password123
}}}

''js Libraries''
----
|Library|Version|Purpose|h
|lodash.js|3.1.0|A JavaScript utility library delivering consistency, modularity, performance, & extras.|
|jquery.js|2.0.3|It makes things like HTML document traversal and manipulation, event handling, animation, and Ajax much simpler with an easy-to-use.|
|jqueryui.js|1.10.0|jQuery UI is a curated set of user interface interactions, effects, widgets, and themes built on top of the jQuery |
|twitter-bootstrap.js|3.1.1|Twitter Bootstrap|
|store.js|1.3.14|marcuswestin/store.js - Uses localStorage, globalStorage, and userData behavior under the hood - used in services|
|moment.js|2.9.0|date manupulation|
|odometer.js|0.4.6|animate numbers|
|angular.js|1.2.16||
|angular-ui.js|0.4.0||
|angular-ui-bootstrap.js|0.10.0||
|ng-table.js|0.3.3|Tables with paging |
|bootstrap-select.js|1.6.3|A custom <select> for Bootstrap using button dropdown as replacement - Create your <select> with the .selectpicker class.|
|ladda-bootstrap.js|0.9.4|Buttons with built-in loading indicators, effectively bridging the gap between action and feedback.|
|locales/en-US.js| -- |Angular Applicaton laguage|
|js/application.js| -- |Angular Application Main Module|
|js/services.js| -- |Angular Application Services|
|js/controllers.js| -- |Angular Application Services|
|js/filters.js| -- |Angular Application Filters|
|js/directives.js| -- |Angulare Application Directivs|

Notes: Drop ''jquery'' and ''jquery-ui'' in favor of ''[[ui-bootstrap|https://angular-ui.github.io/bootstrap/]]'' for Angular

Find [[Libraries|https://cdnjs.com/libraries]]


''CSS''
----
|CSS File|Version|Purpose|h
|jquery-ui-custom.css|1.10.0||
|bootstrap.css|3.1.1||
|font-awesome.css|4.3.0||
|ng-table.css|0.3.3||
|bootstrap-select.css|1.6.3||
|ladda-themeless.css|0.9.4|ladda-bootstrap|
|css/application.css| -- ||
Timestamps in ~MySQL generally used to track changes to records, and are often updated every time the record is changed. If you want to store a specific value you should use a datetime field.

If you meant that you want to decide between using a UNIX timestamp or a native ~MySQL datetime field, go with the native format. You can do calculations within ~MySQL that way  ("SELECT ~DATE_ADD(my_datetime, INTERVAL 1 DAY)") and it is simple to change the format of the value to a UNIX timestamp ("SELECT ~UNIX_TIMESTAMP(my_datetime)") when you query the record if you want to operate on it with PHP.

An important difference is that DATETIME represents a date (as found in a calendar) and a time (as can be observed on a wall clock), while TIMESTAMP represents a well defined point in time. This could be very important if your application handles time zones. How long ago was '2010-09-01 16:31:00'? It depends on what timezone you're in. For me it was just a few seconds ago, for you it may represent a time in the future. If I say 1283351460 seconds since '1970-01-01 00:00:00 UTC', you know exactly what point in time I talk about.  [Downside: valid range].

Also 1 important note, DATETIME and TIMESTAMP can use ~CURRENT_TIMESTAMP, NOW() respectfully as it's default value, but DATE for example can't, because it uses '0000-00-00' by default, so to solve that matter U should write Your own trigger to that table, to insert current date (without time) in the field/col with DATE mysql type. – zeusakm Oct 15 '12 at 19:13
Another way to solve the continuous filtering described at point 6, is to debounce user input. For instance, if a user types a search string, the filter only needs to be activated after the user stops typing. A good solution is to use this debounce service http://jsfiddle.net/Warspawn/6K7Kd/. Apply it in your view and controller as follows:

Controller
{{{
// Watch the queryInput and debounce the filtering by 350 ms.
$scope.$watch('queryInput', function(newValue, oldValue) {
    if (newValue === oldValue) { return; }
    $debounce(applyQuery, 350);
});
var applyQuery = function() { 
    $scope.filter.query = $scope.query;
};
}}}

View
{{{
<input ng-model="queryInput"/>
<li ng-repeat= item in items | filter:filter.query>{{ item.title }} </li>
}}}

http://tech.small-improvements.com/2013/09/10/angularjs-performance-with-large-lists/
Group By Feature
{{{
app/
    app.js
    Feed/
        _feed.html
        FeedController.js
        FeedEntryDirective.js
        FeedService.js
    Login/
        _login.html
        LoginController.js
        LoginService.js
    Shared/
        CapatalizeFilter.js
}}}

NOT by file type
{{{
templates/
    _login.html
    _feed.html
app/
    app.js
    controllers/
        LoginController.js
        FeedController.js
    directives/
        FeedEntryDirective.js
    services/
        LoginService.js
        FeedService.js
    filters/
        CapatalizeFilter.js
}}}
{{{<a href="#">}}} is an html fragment. Anything after the hash mark # is considered a link by the web browser

"{{{#one}}}" and "{{{#one/two}}}" or even "{{{#one\two/three}}}" is considered a link.

{{{
window.addEventListener('hashchange', function() {

    if (window.location.hash === '#/bookmark/1') {
        console.log('Page 1 is cool.');

    if (window.location.hash === '#/bookmark/2') {
        console.log('Page 2 is cooler.');

});
}}}
http://jonsamwell.com/batching-http-requests-in-angular/
[[Elegant token-based API access with AngularJS|http://engineering.talis.com/articles/elegant-api-auth-angular-js/]]
[[Cookies vs Tokens. Getting auth right with Angular.JS|https://auth0.com/blog/2014/01/07/angularjs-authentication-with-cookies-vs-token/]]
[[Token-Based Authentication With AngularJS & NodeJS|http://code.tutsplus.com/tutorials/token-based-authentication-with-angularjs-nodejs--cms-22543]]
[[A great way to solve this problem is to create an authInterceptor factory responsible for adding the header to all $http requests|http://stackoverflow.com/questions/25009634/where-should-i-inject-bearer-tokens-into-http-in-angularjs]]
[[Token-Based Authentication for AngularJS and Laravel Apps|https://scotch.io/tutorials/token-based-authentication-for-angularjs-and-laravel-apps]]
[[Authentication to a RESTful web service in an AngularJS web app|http://blog.brunoscopelliti.com/authentication-to-a-restful-web-service-in-an-angularjs-web-app/]]

[[Building a RESTful Service using CodeIgniter|http://outergalactic.org/blog/building-a-restful-service-using-codeigniter/]]



http://stackoverflow.com/questions/28918519/does-securing-a-rest-application-with-a-jwt-and-basic-authentication-make-sense
http://stackoverflow.com/questions/1545099/how-to-prevent-form-replay-man-in-the-middle-attack-in-php-csrf-xsrf
https://auth0.com/blog/2014/01/27/ten-things-you-should-know-about-tokens-and-cookies/
http://security.stackexchange.com/questions/86857/can-user-send-fake-request-if-it-has-jwt-token
https://www.toptal.com/web/cookie-free-authentication-with-json-web-tokens-an-example-in-laravel-and-angularjs
http://angular-js.in/angular2-jwt/


''VIDEO''
https://egghead.io/series/angularjs-authentication-with-jwt
https://github.com/eggheadio/egghead-angularjs-aunthentication-with-jwt
https://www.youtube.com/watch?v=_JAn-hH8i_s&feature=youtu.be

https://thinkster.io/topics/angular
https://thinkster.io/angularjs-jwt-auth

[[JWT Authentication with AngularJS|https://www.youtube.com/watch?v=mecILj3p4VA]]
[[Build Secure User Interfaces using JWTs|https://www.youtube.com/watch?v=Lv9jun9Eqqw]]
[[Ditching Cookies for JSON Web Tokens|https://www.youtube.com/watch?v=X7t2pdJYHNI]]



{{{
$_SERVER['HTTP_X_REQUESTED_WITH']
}}}

{{{
$headers = array();
foreach ($_SERVER as $key => $value) {
    if (strpos($key, 'HTTP_') === 0) {
        $headers[str_replace(' ', '', ucwords(str_replace('_', ' ', strtolower(substr($key, 5)))))] = $value;
    }
}
}}}
Now just use {{{$headers['XRequestedWith']}}} to retrieve the desired header.


Since PHP 5.4.0 you can use [[getallheaders|http://php.net/manual/en/function.getallheaders.php]] function which returns all requested headers as an associative array:
{{{
var_dump(getallheaders());

// array(8) {
//   ["Accept"]=>
//   string(63) "text/html[...]"
//   ["Accept-Charset"]=>
//   string(31) "ISSO-8859-1[...]"
//   ["Accept-Encoding"]=>
//   string(17) "gzip,deflate,sdch"
//   ["Accept-Language"]=>
//   string(14) "en-US,en;q=0.8"
//   ["Cache-Control"]=>
//   string(9) "max-age=0"
//   ["Connection"]=>
//   string(10) "keep-alive"
//   ["Host"]=>
//   string(9) "localhost"
//   ["User-Agent"]=>
//   string(108) "Mozilla/5.0 (Windows NT 6.1; WOW64) [...]"
// }
}}}


{{{
if (!function_exists('getallheaders')) {
   foreach ($_SERVER as $name => $value) {
      /* RFC2616 (HTTP/1.1) defines header fields as case-insensitive entities. */
      if (strtolower(substr($name, 0, 5)) == 'http_') {
         $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value;
      }
   }
   $this->request_headers=$headers;
} else {
   $this->request_headers = getallheaders();
}
}}}
Notice the subtle differences with previous suggestions. The function here also works on php-fpm (+nginx).
http://stackoverflow.com/questions/11580004/angular-js-link-behaviour-disable-deep-linking-for-specific-urls

http://www.bennadel.com/blog/2422-capturing-document-click-events-with-angularjs.htm

http://www.dwmkerr.com/the-only-angularjs-modal-service-youll-ever-need/
{{{
<div ng-controller="MyCtrl">
    <table class="table table-hover">
        <thead>
            <tr>
                <th>Name</th>
                <th>Status</th>
            </tr>
        </thead>
        <tbody>
            <tr ng-repeat="ingredient in ingredients" ng-click="setSelected();" style="cursor: hand;cursor: pointer;">
                <td style="width: 8em;">{{ ingredient.name }}</td>
                <td>{{ ingredient.status }}</td>
            </tr>
        </tbody>
    </table>
</div>
}}}

{{{
var myApp = angular.module('myApp',[]);

function MyCtrl($scope) {
    
    $scope.ingredients = [
        {'name': 'potato','id': 1, 'status': 'off'},
        {'name': 'trnn','id': 2, 'status': 'off'},
        {'name': 'cat','id': 3, 'status': 'off'},
        {'name': 'toaster','id': 4, 'status': 'off'},
        {'name': 'tomato','id': 5, 'status': 'off'}
    ];
    
    $scope.setSelected = function() {
        $scope.selected = this.ingredient;
        console.log($scope.selected);
    };
    
}
}}}

Ref: [[jsfiddle|http://jsfiddle.net/wnvSD/1/]]


My nicer example:
{{{
<!DOCTYPE html>
<html lang="en" ng-app="myApp">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
        <title>AngularJS Scratchpad</title>
        <link href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet">
        <link href="//cdnjs.cloudflare.com/ajax/libs/font-awesome/4.3.0/css/font-awesome.min.css" rel="stylesheet">
        <link href="//cdnjs.cloudflare.com/ajax/libs/ng-table/0.3.3/ng-table.min.css" rel="stylesheet">
        <link href="//cdnjs.cloudflare.com/ajax/libs/bootstrap-select/1.6.3/css/bootstrap-select.min.css" rel="stylesheet">
        <link href="//cdnjs.cloudflare.com/ajax/libs/ladda-bootstrap/0.9.4/ladda-themeless.min.css" rel="stylesheet">
    </head>
    <body>
        <div class="container" ng-controller="MyCtrl">
            <table class="table table-hover">
                <thead>
                    <tr>
                        <th>Name</th>
                        <th>Status</th>
                    </tr>
                </thead>
                <tbody>
                    <tr ng-repeat="ingredient in ingredients" ng-click="setSelected();" style="cursor: hand;cursor: pointer;">
                        <td>{{ ingredient.name }}</td>
                        <td>{{ ingredient.status }}</td>
                    </tr>
                </tbody>
            </table>            
        </div>
        <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
        <script src="//cdnjs.cloudflare.com/ajax/libs/jqueryui/1.10.0/jquery-ui.min.js"></script>
        <script src="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.1.1/js/bootstrap.min.js"></script>
        <script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.16/angular.min.js"></script>
        <script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.16/angular-route.min.js"></script>
        <script src="//cdnjs.cloudflare.com/ajax/libs/angular-ui/0.4.0/angular-ui.min.js"></script>
        <script src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.10.0/ui-bootstrap-tpls.min.js"></script>
        <script src="//cdnjs.cloudflare.com/ajax/libs/bootstrap-select/1.6.3/js/bootstrap-select.min.js"></script>
        <script>
            var myApp = angular.module('myApp',[]);

                function MyCtrl($scope) {

                    $scope.ingredients = [
                        {'name': 'potato','id': 1, 'status': 'off'},
                        {'name': 'trnn','id': 2, 'status': 'off'},
                        {'name': 'cat','id': 3, 'status': 'off'},
                        {'name': 'toaster','id': 4, 'status': 'off'},
                        {'name': 'tomato','id': 5, 'status': 'off'}
                    ];

                    $scope.setSelected = function() {
                        $scope.selected = this.ingredient;
                        console.log($scope.selected);
                    };

                }
        </script>
    </body>
</html>
}}}

This returns the full object for each table row. You can access the id as {{{this.ingredient.id}}} or {{{$schope.setSelected.id}}}


We can change {{{ng-click="setSelected();"}}} to {{{ng-click="setSelected(ingredients.id);"}}} AND {{{$scope.setSelected = function(id}}} At that point {{{id}}} will hold the current item id.
{{{
    $scope.tableParams = new ngTableParams({
        page: 1,
        count: 10,
		filter: $scope.filter,
		sorting: {
			component_name: 'asc'
		}
    }, {
        total: 0,
		getData: function($defer, params) {
			$q.all([
				$http.post('api/component/table', {
					token: $scope.user.getToken(),
					params: JSON.stringify(params.$params)
				}),
				$http.post('api/systemx/items', {
					token: $scope.user.getToken()
				})
			]).then(function(results) {
				params.total(results[0].data.total);
				$defer.resolve(results[0].data.components);
				$scope.systems = results[1].data.systems;
				// console.log($scope.systems);
				$scope.tableLoaded = true;
			});
		}
	});
}}}
[[ng-table width and non-overflowing text|http://stackoverflow.com/questions/26744672/ng-table-width-and-non-overflowing-text]]
HTML:
{{{
<td data-title="'Name'" width="310"><p>{{user.name}}</p></td>
}}}
CSS:
{{{
td p {
  text-overflow: ellipsis !important;
  overflow: hidden; 
  white-space: nowrap;
  width:300px;
}
}}}
Or you can just add a display:block to your td elements, though that might cause other issues.
----

[[ng-table does not render data got from ngResource|https://github.com/esvit/ng-table/issues/106]] making data render on page load. 
{{{
controllers.controller('recentActivityCtrl',
    function($scope, $filter, RecentActivities, ngTableParams) {
        $scope.tableParams = new ngTableParams({
            page: 1,            // show first page
            count: 10,           // count per page
            sorting: {
                name : 'desc' // initial sorting
            }
        }, {
            getData: function($defer, params) {
                RecentActivities.query(function(activities) {

                        var orderedRecentActivity = params.sorting() ?
                                                $filter('orderBy')(activities, params.orderBy()):
                                                name;
                        params.total(orderedRecentActivity.length);
                        $defer.resolve(orderedRecentActivity.slice((params.page() - 1) * params.count(), params.page() * params.count()));
                });
            }
        })
    }
);
}}}
----
[[Part I: ng-grid and a Simple REST API|http://blog.backand.com/ng-grid-and-a-simple-rest-api/]]

Notice on the page
* {{{loadCompleted}}} for showing a spinner
* {{{isError}}} for showing an error message
{{{
<div ng-app="bizPlatformModule" ng-controller="UsersController">
    <img src="~/Content/images/ajax_loader/ajax-loader.gif" ng-hide="loadCompleted"/>
    <p ng-show="isError==true">{{errorMessage}}</p>
 
    <div class="row" id="table-wrapper" ng-hide='isError==true || loadCompleted==false'>
        <table ng-table="tableParams" show-filter="true" class="table">
            <tr ng-repeat="user in users">
                <td data-title="'@CRMStrings.Name' "sortable="'Name'">
                    <p class="table-cell"> {{user.Name}}</p>
                </td>
                <td data-title="'@CRMStrings.Email'" sortable="'Email'" filter="{' Email': 'text' }">
                    <p class="table-cell"> {{user.Email}}</p>
                </td>
                <td data-title="'@CRMStrings.Telephone'" sortable="'Telephone'" filter="{ 'Telephone': 'text' }">
                    <p class="table-cell">{{user.Telephone}}</p>
                </td>
                <td>
                    <button type="button" class="btn btn-green">@CRMStrings.Details</button>
                </td>
            </tr>
        </table>
    </div>
</div>
}}}

{{{
bizPlatfromApp.controller('UsersController', ['RequestURL','$scope', '$http', '$filter', 'ngTableParams', function (RequestURL,$scope, $http, $filter, ngTableParams) {
    //url patter for api request

    $scope.loadCompleted = false;
    
    //email adress of concrete venue owner
    var mail = document.getElementById("User_Identity_Name").value;
    //concatenated request url
    var request = RequestURL.url.concat(mail);
    //var Ousers = [];
    var _getData = function(data) {
            $scope.isError = false;
           
            $scope.tableParams = new ngTableParams(
            {
                page: 1,            // show first page
                count: 10,          // count per page
                sorting: {
                    name: 'asc'     // initial sorting
                }
            }, {
                total: data.length,
 
                getData: function ($defer, params) {
                    
                    var filteredData = params.filter() ?$filter('filter')(data, {Name:"", Email:params.filter().Email})  : data;
                    
                    var orderedData = params.sorting() ? $filter('orderBy')(filteredData, params.orderBy()) :data;
 
                    console.log("Name: " + params.filter().Name + " Email:" + params.filter().Email + "Telephone:" + params.filter().Telephone);
                    
                    // use build-in angular filter
                    
                    params.total(orderedData.length);
                   var  page = orderedData.slice((params.page() - 1) * params.count(), params.page() * params.count());
                    console.log("Users: " + data.length + "Filtered: " + filteredData.length + "Ordered: "+orderedData.length+"count:"+params.count()+"total: "+ params.total());
                    $scope.users = page;
                    console.log(data.length);
                    $defer.resolve(page);
                }
            });
    }
        //make a request 
    $http.get(request)
        .success(function (data) {
            //Ousers = data;
            _getData(data);
            $scope.loadCompleted = true;
        })
            .error(function(status) {
                $scope.errorMessage = "Some trouble happened.We try to fix it shortly.";
                $scope.isError = true;
            });
 

}])
}}}
The database is made up of 2 tables {{{users}}} and {{{acl}}}
!!Users
Here is a sample users database. It will normally start with just 1 record for the administrator
| id | email | password |h
|1|foo@bar.com|sha256:1024:qtAd6JAZJ15lsOIxJwTO5/~Lf31df3RF0:wMJNTN2NhgrvhIe50KfSvAarc4PhqD+L|
|2|fred.warren@gmail.com|sha256:1024:1MT/5EWK9o3KLKC03Fl+~DYkn9lM/wEmL:0lfTIQEcQNJjBO5DfK1BQ2pyl45qdsGb|
|3|cody.warren@gmail.com|sha256:1024:~EuB3CxeXWmcsJNPsgW/5xOW3cCJroeWO:+~K7KtggG0IgRhwAzT24B5HklzD+~EA8+t|
|4|diane.warren@gmail.com|sha256:1024:~GiiqCirCgpgYvmfh8WBpWqN4rAAqAtHH:~VemLgQBTQU3oEIHmqM5/tIFmt920vew9|

!!ACL
The access control list database holds several differnt kinds of information. ''resources'', ''roles'', ''resoruce_role_permissions'' and ''user_roles''. Each value is stored in table as a JSON array.
| key | value|h
|resources|["administrator","user","role","resource"]|
|resource_role_permissions::administrator::administrator|["read"]|
|resource_role_permissions::administrator::teacher|["read"]|
|resource_role_permissions::resource::administrator|["create","read","update","delete"]|
|resource_role_permissions::resource::teacher|["create"]|
|resource_role_permissions::role::administrator|["create","read","update","delete"]|
|resource_role_permissions::role::teacher|["read","update"]|
|resource_role_permissions::user::administrator|["create","read","update","delete"]|
|resource_role_permissions::user::teacher|["read","update"]|
|roles|["administrator","teacher","student"]|
|user_roles::1|["administrator"]|
|user_roles::2|["teacher"]|
|user_roles::3|["student"]|
|user_roles::4|["student","teacher"]|

| Type | Function |h
|resource|Can be a class of pages, an individual page or even a section of a page. The ''administrator'' resource is a page, both ''user'' and ''role'' are sections on the administrator page|
|role|Is a type of user, like administrator, publisher, editor, and author, or teacher, assistant and student.|
|user_roles|Ties the user by id so user id 2 will have a {{{user_roles::2}}} record, with the value being an array of roles this user is a member of|
|resource_role_permissions|Is a matrix of {{{resource_role_permissions::<resource>::<role>}}} with a values being the CRUD entries they have permission for. For each ''role'' there will be a key with an existing ''resource'' and a value of what CRUD operations can be performed|

The code allows new users the register thus creating ''users'' and ''user_roles''. On the administrator page you can create new ''roles'', and add these ''roles'' to individual ''users''. You can also determine what each ''role'' can do CRUDwise with each ''resource''. What you can't do is create new ''resources''.

The best time to create new resources is when first configuring the system. The resources can be added to {{{api/application/controlers/cli}}}. However you can modify the resoruce list, and then in app assign roles and then CRUD for those roles in each resource.

Resource is a "meta" roll. In that the resources the system uses by default are ''administrator'', ''user'', ''role'' which are used by the system all over the place. the other resource ''resource'' is a meta item that holds the other resources.

{{{
<div class="row">
  <div class="col-lg-6">
    <div class="input-group">
      <div class="input-group-btn">
        <button type="button" class="btn btn-secondary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
          Action
        </button>
        <div class="dropdown-menu">
          <a class="dropdown-item" href="#">Action</a>
          <a class="dropdown-item" href="#">Another action</a>
          <a class="dropdown-item" href="#">Something else here</a>
          <div role="separator" class="dropdown-divider"></div>
          <a class="dropdown-item" href="#">Separated link</a>
        </div>
      </div>
      <input type="text" class="form-control" aria-label="Text input with dropdown button">
      <span class="input-group-btn">
        <button class="btn btn-secondary" type="button">Go!</button>
      </span>
    </div>
  </div>
</div>

}}}
[[Demo Here|http://bootsnipp.com/snippets/featured/responsive-navigation-menu]]  Bootstrap JS and CSS version 3.2.0
html
{{{
<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css" rel="stylesheet">

<div class="nav-side-menu">
    <div class="brand">Brand Logo</div>
    <i class="fa fa-bars fa-2x toggle-btn" data-toggle="collapse" data-target="#menu-content"></i>
  
        <div class="menu-list">
  
            <ul id="menu-content" class="menu-content collapse out">
                <li>
                  <a href="#">
                  <i class="fa fa-dashboard fa-lg"></i> Dashboard
                  </a>
                </li>

                <li  data-toggle="collapse" data-target="#products" class="collapsed active">
                  <a href="#"><i class="fa fa-gift fa-lg"></i> UI Elements <span class="arrow"></span></a>
                </li>
                <ul class="sub-menu collapse" id="products">
                    <li class="active"><a href="#">CSS3 Animation</a></li>
                    <li><a href="#">General</a></li>
                    <li><a href="#">Buttons</a></li>
                    <li><a href="#">Tabs & Accordions</a></li>
                    <li><a href="#">Typography</a></li>
                    <li><a href="#">FontAwesome</a></li>
                    <li><a href="#">Slider</a></li>
                    <li><a href="#">Panels</a></li>
                    <li><a href="#">Widgets</a></li>
                    <li><a href="#">Bootstrap Model</a></li>
                </ul>


                <li data-toggle="collapse" data-target="#service" class="collapsed">
                  <a href="#"><i class="fa fa-globe fa-lg"></i> Services <span class="arrow"></span></a>
                </li>  
                <ul class="sub-menu collapse" id="service">
                  <li>New Service 1</li>
                  <li>New Service 2</li>
                  <li>New Service 3</li>
                </ul>


                <li data-toggle="collapse" data-target="#new" class="collapsed">
                  <a href="#"><i class="fa fa-car fa-lg"></i> New <span class="arrow"></span></a>
                </li>
                <ul class="sub-menu collapse" id="new">
                  <li>New New 1</li>
                  <li>New New 2</li>
                  <li>New New 3</li>
                </ul>


                 <li>
                  <a href="#">
                  <i class="fa fa-user fa-lg"></i> Profile
                  </a>
                  </li>

                 <li>
                  <a href="#">
                  <i class="fa fa-users fa-lg"></i> Users
                  </a>
                </li>
            </ul>
     </div>
</div>
}}}
css
{{{
.nav-side-menu {
  overflow: auto;
  font-family: verdana;
  font-size: 12px;
  font-weight: 200;
  background-color: #2e353d;
  position: fixed;
  top: 0px;
  width: 300px;
  height: 100%;
  color: #e1ffff;
}
.nav-side-menu .brand {
  background-color: #23282e;
  line-height: 50px;
  display: block;
  text-align: center;
  font-size: 14px;
}
.nav-side-menu .toggle-btn {
  display: none;
}
.nav-side-menu ul,
.nav-side-menu li {
  list-style: none;
  padding: 0px;
  margin: 0px;
  line-height: 35px;
  cursor: pointer;
  /*    
    .collapsed{
       .arrow:before{
                 font-family: FontAwesome;
                 content: "\f053";
                 display: inline-block;
                 padding-left:10px;
                 padding-right: 10px;
                 vertical-align: middle;
                 float:right;
            }
     }
*/
}
.nav-side-menu ul :not(collapsed) .arrow:before,
.nav-side-menu li :not(collapsed) .arrow:before {
  font-family: FontAwesome;
  content: "\f078";
  display: inline-block;
  padding-left: 10px;
  padding-right: 10px;
  vertical-align: middle;
  float: right;
}
.nav-side-menu ul .active,
.nav-side-menu li .active {
  border-left: 3px solid #d19b3d;
  background-color: #4f5b69;
}
.nav-side-menu ul .sub-menu li.active,
.nav-side-menu li .sub-menu li.active {
  color: #d19b3d;
}
.nav-side-menu ul .sub-menu li.active a,
.nav-side-menu li .sub-menu li.active a {
  color: #d19b3d;
}
.nav-side-menu ul .sub-menu li,
.nav-side-menu li .sub-menu li {
  background-color: #181c20;
  border: none;
  line-height: 28px;
  border-bottom: 1px solid #23282e;
  margin-left: 0px;
}
.nav-side-menu ul .sub-menu li:hover,
.nav-side-menu li .sub-menu li:hover {
  background-color: #020203;
}
.nav-side-menu ul .sub-menu li:before,
.nav-side-menu li .sub-menu li:before {
  font-family: FontAwesome;
  content: "\f105";
  display: inline-block;
  padding-left: 10px;
  padding-right: 10px;
  vertical-align: middle;
}
.nav-side-menu li {
  padding-left: 0px;
  border-left: 3px solid #2e353d;
  border-bottom: 1px solid #23282e;
}
.nav-side-menu li a {
  text-decoration: none;
  color: #e1ffff;
}
.nav-side-menu li a i {
  padding-left: 10px;
  width: 20px;
  padding-right: 20px;
}
.nav-side-menu li:hover {
  border-left: 3px solid #d19b3d;
  background-color: #4f5b69;
  -webkit-transition: all 1s ease;
  -moz-transition: all 1s ease;
  -o-transition: all 1s ease;
  -ms-transition: all 1s ease;
  transition: all 1s ease;
}
@media (max-width: 767px) {
  .nav-side-menu {
    position: relative;
    width: 100%;
    margin-bottom: 10px;
  }
  .nav-side-menu .toggle-btn {
    display: block;
    cursor: pointer;
    position: absolute;
    right: 10px;
    top: 10px;
    z-index: 10 !important;
    padding: 3px;
    background-color: #ffffff;
    color: #000;
    width: 40px;
    text-align: center;
  }
  .brand {
    text-align: left !important;
    font-size: 22px;
    padding-left: 20px;
    line-height: 50px !important;
  }
}
@media (min-width: 767px) {
  .nav-side-menu .menu-list .menu-content {
    display: block;
  }
}
body {
  margin: 0px;
  padding: 0px;
}
}}}

[[Second Demo|http://bootsnipp.com/snippets/featured/responsive-sidebar-menu]]
html
{{{
<nav class="navbar navbar-inverse sidebar" role="navigation">
    <div class="container-fluid">
		<!-- Brand and toggle get grouped for better mobile display -->
		<div class="navbar-header">
			<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-sidebar-navbar-collapse-1">
				<span class="sr-only">Toggle navigation</span>
				<span class="icon-bar"></span>
				<span class="icon-bar"></span>
				<span class="icon-bar"></span>
			</button>
			<a class="navbar-brand" href="#">Brand</a>
		</div>
		<!-- Collect the nav links, forms, and other content for toggling -->
		<div class="collapse navbar-collapse" id="bs-sidebar-navbar-collapse-1">
			<ul class="nav navbar-nav">
				<li class="active"><a href="#">Home<span style="font-size:16px;" class="pull-right hidden-xs showopacity glyphicon glyphicon-home"></span></a></li>
				<li ><a href="#">Profile<span style="font-size:16px;" class="pull-right hidden-xs showopacity glyphicon glyphicon-user"></span></a></li>
				<li ><a href="#">Messages<span style="font-size:16px;" class="pull-right hidden-xs showopacity glyphicon glyphicon-envelope"></span></a></li>
				<li class="dropdown">
					<a href="#" class="dropdown-toggle" data-toggle="dropdown">Settings <span class="caret"></span><span style="font-size:16px;" class="pull-right hidden-xs showopacity glyphicon glyphicon-cog"></span></a>
					<ul class="dropdown-menu forAnimate" role="menu">
						<li><a href="#">Action</a></li>
						<li><a href="#">Another action</a></li>
						<li><a href="#">Something else here</a></li>
						<li class="divider"></li>
						<li><a href="#">Separated link</a></li>
						<li class="divider"></li>
						<li><a href="#">One more separated link</a></li>
					</ul>
				</li>
				<li><a href="#">Home<span style="font-size:16px;" class="pull-right hidden-xs showopacity glyphicon glyphicon-home"></span></a></li>
				<li ><a href="#">Profile<span style="font-size:16px;" class="pull-right hidden-xs showopacity glyphicon glyphicon-user"></span></a></li>
				<li ><a href="#">Messages<span style="font-size:16px;" class="pull-right hidden-xs showopacity glyphicon glyphicon-envelope"></span></a></li>
				<li class="dropdown">
					<a href="#" class="dropdown-toggle" data-toggle="dropdown">Settings <span class="caret"></span><span style="font-size:16px;" class="pull-right hidden-xs showopacity glyphicon glyphicon-cog"></span></a>
					<ul class="dropdown-menu forAnimate" role="menu">
						<li><a href="#">Action</a></li>
						<li><a href="#">Another action</a></li>
						<li><a href="#">Something else here</a></li>
						<li class="divider"></li>
						<li><a href="#">Separated link</a></li>
						<li class="divider"></li>
						<li><a href="#">One more separated link</a></li>
					</ul>
				</li>
			</ul>
		</div>
	</div>
</nav>
<div class="main">
<!-- Content Here -->
</div>
}}}
css
{{{
    body,html{
		height: 100%;
	}

	/* remove outer padding */
	.main .row{
		padding: 0px;
		margin: 0px;
	}

	/*Remove rounded coners*/

	nav.sidebar.navbar {
		border-radius: 0px;
	}

	nav.sidebar, .main{
		-webkit-transition: margin 200ms ease-out;
	    -moz-transition: margin 200ms ease-out;
	    -o-transition: margin 200ms ease-out;
	    transition: margin 200ms ease-out;
	}

	/* Add gap to nav and right windows.*/
	.main{
		padding: 10px 10px 0 10px;
	}

	/* .....NavBar: Icon only with coloring/layout.....*/

	/*small/medium side display*/
	@media (min-width: 768px) {

		/*Allow main to be next to Nav*/
		.main{
			position: absolute;
			width: calc(100% - 40px); /*keeps 100% minus nav size*/
			margin-left: 40px;
			float: right;
		}

		/*lets nav bar to be showed on mouseover*/
		nav.sidebar:hover + .main{
			margin-left: 200px;
		}

		/*Center Brand*/
		nav.sidebar.navbar.sidebar>.container .navbar-brand, .navbar>.container-fluid .navbar-brand {
			margin-left: 0px;
		}
		/*Center Brand*/
		nav.sidebar .navbar-brand, nav.sidebar .navbar-header{
			text-align: center;
			width: 100%;
			margin-left: 0px;
		}

		/*Center Icons*/
		nav.sidebar a{
			padding-right: 13px;
		}

		/*adds border top to first nav box */
		nav.sidebar .navbar-nav > li:first-child{
			border-top: 1px #e5e5e5 solid;
		}

		/*adds border to bottom nav boxes*/
		nav.sidebar .navbar-nav > li{
			border-bottom: 1px #e5e5e5 solid;
		}

		/* Colors/style dropdown box*/
		nav.sidebar .navbar-nav .open .dropdown-menu {
			position: static;
			float: none;
			width: auto;
			margin-top: 0;
			background-color: transparent;
			border: 0;
			-webkit-box-shadow: none;
			box-shadow: none;
		}

		/*allows nav box to use 100% width*/
		nav.sidebar .navbar-collapse, nav.sidebar .container-fluid{
			padding: 0 0px 0 0px;
		}

		/*colors dropdown box text */
		.navbar-inverse .navbar-nav .open .dropdown-menu>li>a {
			color: #777;
		}

		/*gives sidebar width/height*/
		nav.sidebar{
			width: 200px;
			height: 100%;
			margin-left: -160px;
			float: left;
			z-index: 8000;
			margin-bottom: 0px;
		}

		/*give sidebar 100% width;*/
		nav.sidebar li {
			width: 100%;
		}

		/* Move nav to full on mouse over*/
		nav.sidebar:hover{
			margin-left: 0px;
		}
		/*for hiden things when navbar hidden*/
		.forAnimate{
			opacity: 0;
		}
	}

	/* .....NavBar: Fully showing nav bar..... */

	@media (min-width: 1330px) {

		/*Allow main to be next to Nav*/
		.main{
			width: calc(100% - 200px); /*keeps 100% minus nav size*/
			margin-left: 200px;
		}

		/*Show all nav*/
		nav.sidebar{
			margin-left: 0px;
			float: left;
		}
		/*Show hidden items on nav*/
		nav.sidebar .forAnimate{
			opacity: 1;
		}
	}

	nav.sidebar .navbar-nav .open .dropdown-menu>li>a:hover, nav.sidebar .navbar-nav .open .dropdown-menu>li>a:focus {
		color: #CCC;
		background-color: transparent;
	}

	nav:hover .forAnimate{
		opacity: 1;
	}
	section{
		padding-left: 15px;
	}

}}}
js
{{{
    function htmlbodyHeightUpdate(){
		var height3 = $( window ).height()
		var height1 = $('.nav').height()+50
		height2 = $('.main').height()
		if(height2 > height3){
			$('html').height(Math.max(height1,height3,height2)+10);
			$('body').height(Math.max(height1,height3,height2)+10);
		}
		else
		{
			$('html').height(Math.max(height1,height3,height2));
			$('body').height(Math.max(height1,height3,height2));
		}
		
	}
	$(document).ready(function () {
		htmlbodyHeightUpdate()
		$( window ).resize(function() {
			htmlbodyHeightUpdate()
		});
		$( window ).scroll(function() {
			height2 = $('.main').height()
  			htmlbodyHeightUpdate()
		});
	});
}}}
[[Demo|http://www.codeply.com/go/bp/mL7j0aOINa]]
html
{{{
<body>
<div class="navbar navbar-inverse navbar-fixed-top">
    <div class="navbar-header">
      <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
      </button>
      <a class="navbar-brand" href="#">Brand</a>
    </div>
    <div class="collapse navbar-collapse">
      <ul class="nav navbar-nav">
        <li class="active"><a href="#">Home</a></li>
        <li><a href="#about">About</a></li>
        <li><a href="#contact">Contact</a></li>
      </ul>
    </div><!--/.nav-collapse -->
</div><!--/.navbar -->

<div class="row-offcanvas row-offcanvas-left">
  <div id="sidebar" class="sidebar-offcanvas">
      <div class="col-md-12">
        <h3>Sidebar (fixed)</h3>
        <ul class="nav nav-pills nav-stacked">
          <li class="active"><a href="#">Section</a></li>
          <li><a href="#">Section</a></li>
          <li><a href="#">Section</a></li>
          <li><a href="#">Section</a></li>
          <li><a href="#">Section</a></li>
          <li><a href="#">Section</a></li>
          <li><a href="#">Section</a></li>
          <li><a href="#">Section</a></li>
          <li><a href="#">Section</a></li>
          <li><a href="#">Section</a></li>
        </ul>
      </div>
  </div>
  <div id="main">
      <div class="col-md-12">
      	  <p class="visible-xs">
            <button type="button" class="btn btn-primary btn-xs" data-toggle="offcanvas"><i class="glyphicon glyphicon-chevron-left"></i></button>
          </p>
          <h2>Fixed + Fluid Bootstrap Template with Off-canvas Sidebar</h2>
          <div class="row">
              <div class="col-md-12"><div class="well"><p>Shrink the browser width to make the sidebar collapse off canvase.</p></div></div>
          </div>
          <div class="row">
              <div class="col-md-4"><div class="well"><p>4 cols</p></div></div>
              <div class="col-md-4"><div class="well"><p>4 cols</p></div></div>
              <div class="col-md-4"><div class="well"><p>4 cols</p></div></div>
          </div>
          <div class="row">
              <div class="col-lg-6 col-sm-6"><div class="well"><p>6 cols, 6 small cols</p></div></div>
              <div class="col-lg-6 col-sm-6"><div class="well"><p>6 cols, 6 small cols</p></div></div>
          </div>
          <div class="row">
              <div class="col-lg-4 col-sm-6"><div class="well">4 cols, 6 small cols</div></div>
              <div class="col-lg-4 col-sm-6"><div class="well">4 cols, 6 small cols</div></div>
              <div class="col-lg-4 col-sm-12"><div class="well">4 cols, 12 small cols</div></div>
          </div>
      </div>
  </div>
</div><!--/row-offcanvas -->
</body>
}}}
js
{{{
$(document).ready(function() {
  $('[data-toggle=offcanvas]').click(function() {
    $('.row-offcanvas').toggleClass('active');
  });
});
}}}

css
{{{
body,html,.row-offcanvas {
  height:100%;
}

body {
  padding-top: 50px;
}

#sidebar {
  width: inherit;
  min-width: 220px;
  max-width: 220px;
  background-color:#f5f5f5;
  float: left;
  height:100%;
  position:relative;
  overflow-y:auto;
  overflow-x:hidden;
}
#main {
  height:100%;
  overflow:auto;
}

/*
 * off Canvas sidebar
 * --------------------------------------------------
 */
@media screen and (max-width: 768px) {
  .row-offcanvas {
    position: relative;
    -webkit-transition: all 0.25s ease-out;
    -moz-transition: all 0.25s ease-out;
    transition: all 0.25s ease-out;
    width:calc(100% + 220px);
  }
    
  .row-offcanvas-left
  {
    left: -220px;
  }

  .row-offcanvas-left.active {
    left: 0;
  }

  .sidebar-offcanvas {
    position: absolute;
    top: 0;
  }
}
}}}