Background: #fff
Foreground: #000
PrimaryPale: #8cf
PrimaryLight: #18f
PrimaryMid: #04b
PrimaryDark: #014
SecondaryPale: #ffc
SecondaryLight: #fe8
SecondaryMid: #db4
SecondaryDark: #841
TertiaryPale: #eee
TertiaryLight: #ccc
TertiaryMid: #999
TertiaryDark: #666
Error: #f88
<!--{{{-->
<div class='toolbar' macro='toolbar [[ToolbarCommands::EditToolbar]]'></div>
<div class='title' macro='view title'></div>
<div class='editor' macro='edit title'></div>
<div macro='annotations'></div>
<div class='editor' macro='edit text'></div>
<div class='editor' macro='edit tags'></div><div class='editorFooter'><span macro='message views.editor.tagPrompt'></span><span macro='tagChooser excludeLists'></span></div>
<!--}}}-->
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>>
<<importTiddlers>>
<!--{{{-->
<link rel='alternate' type='application/rss+xml' title='RSS' href='index.xml' />
<!--}}}-->
These [[InterfaceOptions]] for customising [[TiddlyWiki]] are saved in your browser

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

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

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

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

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

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

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

.tabSelected {
	color:[[ColorPalette::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]]; }
.wizard__title    { color:[[ColorPalette::PrimaryDark]]; border:none; }
.wizard__subtitle { color:[[ColorPalette::Foreground]]; border:none; }
.wizardStep { background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]]; }
.wizardStep.wizardStepDone {background:[[ColorPalette::TertiaryLight]];}
.wizardFooter {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 { background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]]; box-shadow: 1px 2px 5px [[ColorPalette::TertiaryMid]]; }
.messageToolbar__button { color:[[ColorPalette::PrimaryMid]]; background:[[ColorPalette::SecondaryPale]]; border:none; }
.messageToolbar__button_withIcon { background:inherit; }
.messageToolbar__button_withIcon:active { background:inherit; border:none; }
.messageToolbar__icon { fill:[[ColorPalette::TertiaryDark]]; }
.messageToolbar__icon:hover { fill:[[ColorPalette::Foreground]]; }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

.editor input {border:1px solid [[ColorPalette::PrimaryMid]];}
.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);}
/*}}}*/
/*{{{*/
body { font-size:.75em; font-family:arial,helvetica,sans-serif; margin:0; padding:0; }

* html .tiddler {height:1%;}

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

hr {height:1px;}

dt {font-weight:bold;}

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

.txtOptionInput {width:11em;}

#contentWrapper .chkOptionInput {border:0;}

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


a {text-decoration:none;}

.externalLink {text-decoration:underline;}

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

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

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


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

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

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

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

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

#messageArea { position:fixed; top:2em; right:0; margin:0.5em; padding:0.7em 1em; z-index:2000; }
.messageToolbar { text-align:right; padding:0.2em 0; }
.messageToolbar__button { text-decoration:underline; }
.messageToolbar__icon { height: 1em; width: 1em; } /* width for IE */
.messageArea__text a { text-decoration:underline; }

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

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

.tabset {padding:1em 0 0 0.5em;}
.tab {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 17em 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;}
.tagged li, .tagging li { margin: 0.3em 0; }
.tagClear {clear:both;}

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

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

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

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

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

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

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

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

* html #backstage {width:99%;}
* html #backstageArea {width:99%;}
#backstageArea {display:none; position:relative; overflow: hidden; z-index:150; padding:0.3em 0.5em;}
#backstageToolbar {position:relative;}
#backstageArea a {font-weight:bold; margin-left:0.5em; padding:0.3em 0.5em;}
#backstageButton {display:none; position:absolute; z-index:175; top:0; right:0;}
#backstageButton a {padding:0.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; }
.backstagePanelFooter {padding-top:0.2em; float:right;}
.backstagePanelFooter a {padding:0.2em 0.4em;}
#backstageCloak {display:none; z-index:20; position:absolute; width:100%; height:100px;}

.whenBackstage {display:none;}
.backstageVisible .whenBackstage {display:block;}
/*}}}*/
/***
StyleSheet for use when a translation requires any css style changes.
This StyleSheet can be used directly by languages such as Chinese, Japanese and Korean which need larger font sizes.
***/
/*{{{*/
body {font-size:0.8em;}
#sidebarOptions {font-size:1.05em;}
#sidebarOptions a {font-style:normal;}
#sidebarOptions .sliderPanel {font-size:0.95em;}
.subtitle {font-size:0.8em;}
.viewer table.listView {font-size:0.95em;}
/*}}}*/
/*{{{*/
@media print {
  #mainMenu, #sidebar, #messageArea, .toolbar, #backstageButton, #backstageArea { display: none !important; }
  #displayArea { margin: 1em 1em 0em; }
}
/*}}}*/
<!--{{{-->
<div class='toolbar' role='navigation' macro='toolbar [[ToolbarCommands::ViewToolbar]]'></div>
<div class='title' macro='view title'></div>
<div class='subtitle'><span macro='view modifier link'></span>, <span macro='view modified date'></span> (<span macro='message views.wikified.createdPrompt'></span> <span macro='view created date'></span>)</div>
<div class='tagging' macro='tagging'></div>
<div class='tagged' macro='tags'></div>
<div class='viewer' macro='view text wikified'></div>
<div class='tagClear'></div>
<!--}}}-->
/***
|Description|checks and reports updates of installed extensions on startup, introduces a macro/backstage button to explore, install and update extensions|
|Version    |0.6.2|
|Author     |Yakov Litvin|
|Source     |https://github.com/YakovL/TiddlyWiki_ExtensionsExplorerPlugin/blob/master/ExtensionsExplorerPlugin.js|
|License    |[[MIT|https://github.com/YakovL/TiddlyWiki_YL_ExtensionsCollection/blob/master/Common%20License%20(MIT)]]|
!!!Installation & configuration
Installation of the plugin is as usual: import the tiddler or copy and tag it with {{{systemConfig}}}; reload TW.

{{FpG{not yet (will be useful once collections are introduced): In some cases, you may want to customize AvailableExtensions: {{PoGc{when/why? how?}}} }}}

!!!What EEP does, how to use it
Once you install this plugin, on startup, it will try to check if installed extensions have any updates available and report if it finds any. An update of a particular extension is looked up by the url in the Source slice (see this tiddler for example). EEP will recognize an "update" if it finds the content by that url, and that content has a Version slice and the version is higher than the installed one (like: 0.4.2 is higher than 0.3.9; 0.0.1 is also higher than none).

It also adds "explore extensions" in the backstage (and the {{{<<extensionsExplorer>>}}} macro with the same interface) that shows some extensions available for installation and the list of installed plugins with buttons to check for updates.

Note: With some TW savers/servers, loading an extension may fail if its author hasn't enabled CORS on the server pointed by Source.

!!!For extension authors: how to prepare extensions and repositories
To make EEP find updates for your extensions, you have to
# put it somewhere in the internet:
** the server should have CORS enabled (~GitHub is fine);
** the extension should be in either form: "plain text" (.js or .txt file extension) or a tiddler in a TW (.html extension);
# ensure that the extension has a Source slice with a url that points to itself (i.e. where to look for the latest version):
** for plain text, one can use a direct url, like: https://raw.githubusercontent.com/YakovL/TiddlyWiki_ShowUnsavedPlugin/master/ShowUnsavedPlugin.js;
** for ~GitHub, one can also use the url of the UI page (i.e. navigate to it via ~GitHub UI and copy the address): https://github.com/YakovL/TiddlyWiki_ShowUnsavedPlugin/blob/master/ShowUnsavedPlugin.js;
** for a tiddler inside a TW, use a permalink, like: https://TiddlyTools.com/Classic/#NestedSlidersPlugin (note that the Source slice in this plugin is in fact outdated: http://www.TiddlyTools.com/#NestedSlidersPlugin – you should avoid that as this will break the updating flow);
** for a tiddler inside a TW on ~GitHub, use ~GitHub Pages (this is in fact how ~TiddlyTools is served, they just use a custom domain; an example of an "ordinary" url: https://yakovl.github.io/TiddlyWiki_ExtraFilters/#ExtraFiltersPlugin);
** for your dev flow, it may be useful to put the plugin to ~GitHub as a .js file and load it into the demo TW via [[TiddlerInFilePlugin|https://github.com/YakovL/TiddlyWiki_TiddlerInFilePlugin]]. An example of such setup can be found [[here|https://github.com/YakovL/TiddlyWiki_FromPlaceToPlacePlugin]].

To make your extension explorable, you can do one of the following:
* simply announce [[in the community|https://groups.google.com/group/TiddlyWikiClassic]] or elsewhere
* [single extension, PR to .. collection]
* [multiple extensions, own collection, PR to ..]
{{PoGc{collections: not yet}}}
***/
//{{{
// Returns the slice value if it is present or defaultText otherwise
//
Tiddler.prototype.getSlice = Tiddler.prototype.getSlice || function(sliceName, defaultText) {
	let re = TiddlyWiki.prototype.slicesRE, m
	re.lastIndex = 0
	while(m = re.exec(this.text)) {
		if(m[2]) {
			if(m[2] == sliceName) return m[3]
		} else {
			if(m[5] == sliceName) return m[6]
		}
	}
	return defaultText
}

const centralSourcesListName = "AvailableExtensions"

config.macros.extensionsExplorer = {
	lingo: {
// TODO: review order, looks somewhat chaotic
// TODO: probably add listeners of custom events, displayMessage there
		installButtonLabel: "install",
		installButtonPrompt: "get and install this extension",
		getFailedToLoadMsg: name => "failed to load " + name,
		getSucceededToLoadMsg: name => `loaded ${name}, about to import and install...`,
		noSourceUrlAvailable: "no source url",
		getEvalSuccessMsg: name => `Successfully installed ${name} (reload is not necessary)`,
		getEvalFailMsg: (name, error) => `${name} failed with error: ${error}`,
		getImportSuccessMsg: (title, versionString, isUpdated) => isUpdated ?
			`Updated ${title}${versionString ? " to " + versionString : ""}` :
			`Imported ${title}${versionString ? " v" + versionString : ""}`,

		updateButtonCheckLabel: "check",
		updateButtonCheckPrompt: "check for updates",
		updateButtonUpdateLabel: "update",
		updateButtonUpdatePrompt: "install available update",
		getUpdateAvailableMsg: name => `update of ${name} is available!`,
		getUpdateAvailableAndVersionsMsg: (existingTiddler, newTiddler) => {
			const getVersionString = config.macros.extensionsExplorer.getVersionString
			return `update of ${existingTiddler.title} is available ` +
				"(current version: " + getVersionString(existingTiddler) +
				", available version: " + getVersionString(newTiddler) + ")"
		},
		updateNotAvailable: "update is not available",
		getUpdateConfirmMsg: (title, loadedVersion, presentVersion) => {
			const loadedVersionString = loadedVersion ? formatVersion(loadedVersion) : ""
			const presentVersionString = presentVersion ? formatVersion(presentVersion) : ""
			return `Would you like to update ${title}` +
				` (new version: ${loadedVersionString || "unknown"}, ` +
			 	`current version: ${presentVersionString || "unknown"})?`
		},

		centralSourcesListAnnotation: "The JSON here describes extensions so that ExtensionsExplorerPlugin can install them"
	},

	// helpers specific to tiddler format
	guessExtensionType: function(tiddler) {
		if(tiddler.tags.contains('systemConfig') ||
		   tiddler.getSlice('Type', '').toLowerCase() == 'plugin' ||
		   /Plugin$/.exec(tiddler.title)
		)
			return 'plugin'
	},
	// We use the server.host field a bit different than the core does (see importing):
	// we keep #TiddlerName part which won't hurt except for the plugin https://github.com/TiddlyWiki/tiddlywiki/blob/master/plugins/Sync.js (which we kinda substitute anyway),
	// we also don't set server.type and server.page.revision fields yet (unlike import); see also server.workspace, wikiformat fields.
	sourceUrlField: 'server.host',
	getSourceUrl: function(tiddler) {
		return tiddler.fields[this.sourceUrlField] || tiddler.getSlice('Source')
		//# try also the field set by import (figure the name by experiment)
	},
	setSourceUrl: function(tiddler, url) {
		//# simple implementation, not sure if setValue should be used instead
		tiddler.fields[this.sourceUrlField] = url
	},
	getDescription: tiddler => tiddler.getSlice('Description', ''),
	getVersionString: tiddler => tiddler.getSlice('Version', ''),
	getVersion: function(tiddler) {
		const versionString = this.getVersionString(tiddler)
		//# should use a helper from core instead
		const parts = /(\d+)\.(\d+)(?:\.(\d+))?/.exec(versionString)
		return parts ? {
			major: parseInt(parts[1]),
			minor: parseInt(parts[2]),
			revision: parseInt(parts[3] || '0')
		} : {}
	},

	// helpers to get stuff from external repos
	//# start from hardcoding 1 (.oO data sctructures needed
	//  for getAvailableExtensions and various user scenarios),
	//  then several (TW/JSON, local/remote)
	availableRepositories: [],
	getAvailableRepositories: function() {
		return this.availableRepositories
	},
	// fallback used when AvailableExtensions is empty
	defaultAvailableExtensions: [
		{
			url: 'https://github.com/YakovL/TiddlyWiki_ExtensionsExplorerPlugin/blob/master/ExtensionsCollection.txt',
			description: 'A central extensions collection for ExtensionsExplorerPlugin meant to both gather collections of existing extensions and help new authors make their work more explorable',
			type: 'collection'
		},
		{
			// js file @ github - worked /# simplify url to be inserted?
			name: 'ShowUnsavedPlugin',
			sourceType: 'txt',
			url: 'https://github.com/YakovL/TiddlyWiki_ShowUnsavedPlugin/blob/master/ShowUnsavedPlugin.js',
			description: 'highlights saving button (bold red by default) and the document title (adds a leading "*") when there are unsaved changes',
			type: 'plugin',
			text: ''
		},
		{
			url: 'https://github.com/YakovL/TiddlyWiki_DarkModePlugin/blob/master/DarkModePlugin.js',
			description: 'This plugin introduces "dark mode" (changes styles) and switching it by the {{{darkMode}}} macro and operating system settings'
		},
		{
			// in TW @ remote (CORS-enabled) – worked
			name: 'FieldsEditorPlugin',
			sourceType: 'tw',
			url: 'https://yakovl.github.io/VisualTW2/VisualTW2.html#FieldsEditorPlugin',
			description: 'adds controls (create/edit/rename/delete) to the "fields" toolbar dropdown',
			type: 'plugin'
		},
		{
			// txt file @ remote without CORS – worked with _
			url: 'http://yakovlitvin.pro/TW/pre-releases/Spreadsheets.html#HandsontablePlugin',
			description: 'a test plugin on a site without CORS'
		},
		{
			url: 'https://github.com/tobibeer/TiddlyWikiPlugins/blob/master/plugins/ListFiltrPlugin.js'
		}
	],
	guessNameByUrl: function(extension) {
		if(!extension.url) return undefined
		const urlParts = extension.url.split('#')

		// site.domain/path/tw.html#TiddlerName  or  site.domain/path/#TiddlerName
		if(urlParts.length > 1 && /(\.html|\/)$/.exec(urlParts[0])) return urlParts[1]

		// <url part>/TiddlerName.txt or <url part>/TiddlerName.js
		const textPathMatch = /\/(\w+)\.(js|txt)$/.exec(urlParts[0])
		return textPathMatch ? textPathMatch[1] : undefined
	},
	collectionTag: 'systemExtensionsCollection',
	parseCollection: function(text) {
		/* expected format:

		< additional info, like |Source|...| and other metadata >
		//{{{
		< extensions as JSON >
		//}}}

		*/
		const match = /(\/\/{{{)\s+((?:.|\n)+)\s+(\/\/}}})$/.exec(text)
		if(match) try {
			return JSON.parse(match[2])
		} catch (e) {
			console.log(`problems with parsing ${centralSourcesListName}:`, e)
			return null
		}
	},
	// checks .centralSourcesListName, .defaultAvailableExtensions, collections
	getAvailableExtensions: function() {
		const listText = store.getTiddlerText(centralSourcesListName)
		const availableExtensions = this.parseCollection(listText)
			|| this.defaultAvailableExtensions

		const otherCollections = store.filterTiddlers("[tag[" + this.collectionTag + "]]")
		for(const collectionTiddler of otherCollections) {
			const extensions = this.parseCollection(collectionTiddler.text)
			// for now, just merge
			if(extensions) for(const extension of extensions) {
				availableExtensions.push(extension)
			}
		}

		//# move name normalizing to the reading method
		//  once we move the list of available extensions from hardcode
		for(const extension of availableExtensions) {
			extension.name = extension.name || this.guessNameByUrl(extension)
		}
		return availableExtensions
	},
	// map by url of extension tiddlers
	// (those loaded from txt may be detected by !tiddler.creator)
	availableUpdatesCache: {},
	cacheAvailableUpdate: function(sourceUrl, tiddler) {
		this.availableUpdatesCache[sourceUrl] = { tiddler: tiddler }
	},
	// github urls like https://github.com/tobibeer/TiddlyWikiPlugins/blob/master/plugins/FiltrPlugin.js
	// are urls of user interface; to get raw code, we use the official githubusercontent.com service
	// also, we change the old urls https://raw.github.com/tobibeer/TiddlyWikiPlugins/master/plugins/FiltrPlugin.js
	getUrlOfRawIfGithub: function(url) {
		const ghUrlRE = /^https:\/\/github\.com\/(\w+?)\/(\w+?)\/blob\/(.+)$/
		const oldGhRawUrlRE = /^https:\/\/raw.github.com\/(\w+?)\/(\w+?)\/(.+)$/
//# test
		const match = ghUrlRE.exec(url) || oldGhRawUrlRE.exec(url)
		if(match) return 'https://raw.githubusercontent.com/' + match[1] + // username
			'/' + match[2] + // repository name
			'/' + match[3] // path
		return url
	},
	twsCache: {}, // map of strings
	/*
	@param sourceType: 'tw' | string | fasly (default = 'txt') -
	 of the tiddler source (a TW or a text file)
	@param url: string - either url of the text file or url#TiddlerName
	 for a TW (TiddlerName defines the title of the tiddler to load)
	@param title: string - is assigned to the loaded tiddler
	@param callback: tiddler | null => void
	 support second param of callback? (error/xhr)
	*/
	loadExternalTiddler: function(sourceType, url, title, callback, useCache) {
		sourceType = sourceType || this.guessSourceType(url)
		//# if sourceType is uknown, we can load file and guess afterwards
		if(sourceType == 'tw') {
			const tiddlerName = url.split('#')[1] || title
			const requestUrl = url.split('#')[0]
			const cache = this.twsCache
			const onTwLoad = function(success, params, responseText, url, xhr) {
				//# pass more info? outside: warn?
				if(!success) return callback(null)
				if(!useCache) cache[requestUrl] = responseText

				const externalTW = new TiddlyWiki()
				const result = externalTW.importTiddlyWiki(responseText)
				//# pass more info? outside: warn?
				if(!result) return callback(null)

				const tiddler = externalTW.fetchTiddler(tiddlerName)
				tiddler.title = title
				callback(tiddler)

				// above is a simple "from scratch" implementation
				//# should we reuse existing core code? (see import)
				//  currently, this only loads and passes tiddler,
				//  actual import is done in _
				const context = {
					adaptor: {},
					complete: function() {}
				}
//				FileAdaptor.loadTiddlyWikiSuccess(context, );
				//# import, see ...
				//# tiddler.title = title;
				//# callback(tiddler);
			}
			if(useCache && cache[requestUrl])
				onTwLoad(true, null, cache[requestUrl])
			else
				httpReq('GET', requestUrl, onTwLoad)
		} else {
			url = this.getUrlOfRawIfGithub(url)
			httpReq('GET', url, function(success, params, responseText, url, xhr) {
				//# pass more info? outside: warn?
				if(!success) return callback(null)

				const tiddler = new Tiddler(title)
				tiddler.text = responseText
				tiddler.generatedByTextOnly = true
				callback(tiddler)
			})
		}
	},

	getInstalledExtensions: function() {
		//# instead of returning tiddlers, create extension objects,
		//  those should have ~isInstalled, ~isEnabled, ~hasUpdates flags
		//  (and change refresh accordingly)
		return store.filterTiddlers(`[tag[systemConfig]] ` +
			`[tag[${this.collectionTag}]] [[${centralSourcesListName}]]`)
		//# implement others: themes, transclusions
	},
	// for each installed extension, check for update and reports (now: displays message)
	init: function() {
		//# set delegated handlers of install, update buttons
		const extensionTiddlers = this.getInstalledExtensions()
		if(!config.options.chkSkipExtensionsUpdatesCheckOnStartup)
			for(const eTiddler of extensionTiddlers) {
				const url = this.getSourceUrl(eTiddler)
				if(!url) continue
				this.checkForUpdate(url, eTiddler, result => {
		console.log(`checkForUpdate for ${url},`, eTiddler, 'result is:', result)
					if(result.tiddler && !result.noUpdateMessage) {
						displayMessage(this.lingo.getUpdateAvailableAndVersionsMsg(eTiddler, result.tiddler))
					}
					//# either report each one at once,
					//   (see onUpdateCheckResponse)
					//  create summary and report,
					//   (use availableUpdates)
					//  create summary and just show "+4" or alike (better something diminishing),
					//  or even update (some of) ext-s silently
					//# start with creating summary
				})
			}

		const taskName = "explorePlugins"
		config.backstageTasks.push(taskName)
		config.tasks[taskName] = {
			text: "explore extensions",
			tooltip: "see if there's any updates or install new ones",
			content: '<<extensionsExplorer>>',
		}
	},
	handler: function(place, macroName, params, wikifier, paramString) {
		const tableHeaderMarkup = "|name|description|version||h"
		// name is supposted to be a link to the repo; 3d row – for "install" button
		wikify(tableHeaderMarkup, place)
		const table = place.lastChild

		jQuery(table).attr({ refresh: 'macro', macroName: macroName })
			.addClass('extensionsExplorer').append('<tbody>')

		this.refresh(table)
	},
	// grabs list of available extensions and shows with buttons to install;
	// for each installed plugin, shows a button to check update or "no url" message,
	refresh: function(table) {
		const $tbody = jQuery(table).find('tbody')
			.empty()

		// safe method (no wikification, innerHTML etc)
		const appendRow = function(cells) {
			const row = document.createElement('tr')
			const nameCell = createTiddlyElement(row, 'td')
			if(cells.url)
				createExternalLink(nameCell, cells.url, cells.name)
			else
				createTiddlyLink(nameCell, cells.name, true)

			createTiddlyElement(row, 'td', null, null, cells.description)

			createTiddlyElement(row, 'td', null, null, cells.version)

			const actionsCell = createTiddlyElement(row, 'td')
			for(const e of cells.actionElements)
				actionsCell.appendChild(e)

			$tbody.append(row)
		}

		//# when implemented: load list of available extensions (now hardcoded)

		const installedExtensionsTiddlers = this.getInstalledExtensions()
			.sort((e1, e2) => {
				const up1 = this.availableUpdatesCache[this.getSourceUrl(e1)]
				const up2 = this.availableUpdatesCache[this.getSourceUrl(e2)]
				return	up1 && up2 ? 0 :
					up1 && !up2 ? -1 :
					up2 && !up1 ? +1 :
					!this.getSourceUrl(e1) ? +1 :
					!this.getSourceUrl(e2) ? -1 : 0
			})

		// show extensions available to install
		const availableExtensions = this.getAvailableExtensions()

		for(const extension of availableExtensions) {
			// skip installed
			if(installedExtensionsTiddlers.some(tid => tid.title === extension.name
				&& this.getSourceUrl(tid) === extension.url)) continue

			if(!extension.name && extension.sourceType == 'tw')
				extension.name = extension.url.split('#')[1]

			appendRow({
				name:		extension.name,
				url:		extension.url,
				description:	extension.description,
				version:	extension.version,
				actionElements: [
					createTiddlyButton(null,
						this.lingo.installButtonLabel,
						this.lingo.installButtonPrompt,
						() => this.grabAndInstall(extension) )
				]
			})
		}
		//# add link to open, update on the place of install – if installed

		// show installed ones.. # or only those having updates?
		$tbody.append(jQuery(`<tr><td colspan="4" style="text-align: center;">Installed</td></tr>`))
		for(const extensionTiddler of installedExtensionsTiddlers) {
			//# limit the width of the Description column/whole table
			const updateUrl = this.getSourceUrl(extensionTiddler)
				//# check also list of extensions to install
			const onUpdateCheckResponse = (result, isAlreadyReported) => {
				if(!result.tiddler) {
					displayMessage(this.lingo.updateNotAvailable)
					//# use result.error
					return
				}
				const versionOfLoaded = this.getVersion(result.tiddler)
				const versionOfPresent = this.getVersion(extensionTiddler)
				if(compareVersions(versionOfLoaded, versionOfPresent) >= 0) {
					displayMessage(this.lingo.updateNotAvailable)
					//# use result.error
					return
				}
debugger;
				if(!isAlreadyReported) displayMessage(this.lingo.getUpdateAvailableMsg(extensionTiddler.title), updateUrl)

				//# later: better than confirm? option for silent?
				if(confirm(this.lingo.getUpdateConfirmMsg(
					extensionTiddler.title,
					versionOfLoaded, versionOfPresent))
				) {
					this.updateExtension(result.tiddler, updateUrl)
//					displayMessage(this.lingo.getImportedUpdateMsg(
//						result.tiddler.title,
//						this.getVersionString(result.tiddler)
//					))
				}
			}

			const checkUpdateButton = createTiddlyButton(null,
				this.lingo.updateButtonCheckLabel,
				this.lingo.updateButtonCheckPrompt,
				() => this.checkForUpdate(updateUrl, extensionTiddler,
					onUpdateCheckResponse))

			const cachedUpdate = this.availableUpdatesCache[updateUrl]
			const installUpdateButton = createTiddlyButton(null,
				this.lingo.updateButtonUpdateLabel,
				this.lingo.updateButtonUpdatePrompt,
				() => onUpdateCheckResponse(cachedUpdate, true))

			appendRow({
				name: extensionTiddler.title,
				description: this.getDescription(extensionTiddler),
				version: this.getVersionString(extensionTiddler),
				actionElements: [
					!updateUrl ? document.createTextNode(this.lingo.noSourceUrlAvailable) :
					cachedUpdate ? installUpdateButton :
					checkUpdateButton
				]
			})
		}
	},
	grabAndInstall: function(extension) {
//# initial goal: add displayMessage on .install success
//# move ~user interaction~ (displayMessage) into callbacks? merge with checkForUpdate?
		if(!extension) return
		if(extension.text) {
//# is this ever called? do we cache extension.text on checking update? what about version?
			const extensionTiddler = new Tiddler(extension.name)
			extensionTiddler.text = extension.text
			extensionTiddler.generatedByTextOnly = true
			//# share 3 ↑ lines as ~internalize helper (with loadExternalTiddler)
			this.install(extensionTiddler, extension.type, extension.url)
			return
		}
		this.loadExternalTiddler(
			extension.sourceType,
			extension.url,
			extension.name,
			tiddler => {
				if(!tiddler) {
					displayMessage(this.lingo.getFailedToLoadMsg(extension.name))
					return
				}
				displayMessage(this.lingo.getSucceededToLoadMsg(tiddler.title))
				this.install(tiddler, extension.type ||
					this.guessExtensionType(tiddler), extension.url)
			}
		)
	},
	// evaluate if a plugin, import
	//# simple unsafe version, no dependency handling, registering as installed,
	//  _install-only-once check_, result reporting, refreshing/notifying, ..
	install: function(extensionTiddler, extensionType, sourceUrl) {
		if(!extensionTiddler) return
		//# displayMessage _

		const { text, title } = extensionTiddler
		switch(extensionType) {
			case 'plugin':
				// enable at once
				try {
					eval(text)
					displayMessage(this.lingo.getEvalSuccessMsg(title))
				} catch(e) {
					displayMessage(this.lingo.getEvalFailMsg(title, e))
					//# don't import? only on confirm?
					//# if(!confirm(title + " seem to produce errors, import anyway?")) return
				}
				// plugin-specific import preparation
				extensionTiddler.tags.pushUnique('systemConfig')
			break;

			case 'collection':
				extensionTiddler.tags.pushUnique(this.collectionTag)
			break;

			default:
			//# add _ tag for themes?
			//# displayMessage _
		}

		// actually import etc
		this.updateExtension(extensionTiddler, sourceUrl)
		//# what if exists already? (by the same name; other name)
	},
	updateExtension: function(extensionTiddler, sourceUrl) {
		// import
		var existingTiddler = store.fetchTiddler(extensionTiddler.title)
		if(extensionTiddler.generatedByTextOnly && existingTiddler) {
			existingTiddler.text = extensionTiddler.text
			existingTiddler.modified = new Date()
			//# update also modifier? changecount?
		} else {
			store.addTiddler(extensionTiddler)
		}
		if(sourceUrl && this.getSourceUrl(extensionTiddler) !== sourceUrl) {
			this.setSourceUrl(extensionTiddler, sourceUrl)
		}

		delete this.availableUpdatesCache[sourceUrl]
		store.setDirty(true)
		//# store url for updating if slice is not present?
		// make explorer and other stuff refresh
		store.notify(extensionTiddler.title, true)
		//# .oO reloading, hot reinstalling
		displayMessage(this.lingo.getImportSuccessMsg(extensionTiddler.title,
			this.getVersionString(extensionTiddler), !!existingTiddler))
	},
	guessSourceType: function(url) {
		if(/\.(txt|js)$/.exec(url.split('#')[0])) return 'txt'
		//# guess by url instead, fall back to 'txt'
		return 'tw'
	},
//# careful: extension keyword is overloaded (extension object/tiddler)
	/*
	  tries to load update for tiddler, if succeeds calls callback with
	   argument depending on whether it has newer version than the existing one
	  @param url: _
	  @param extensionTiddler: _
	  @param callback: is called [not always yet..] with argument
		{ tiddler: Tiddler | null, error?: string, noUpdateMessage?: string }
		if update is found and it has version newer than extensionTiddler,
		it is called with { tiddler: Tiddler }
	*/
	checkForUpdate: function(url, extensionTiddler, callback) {
		if(!url) return
		const title = extensionTiddler.title
		this.loadExternalTiddler(null, url, title, loadedTiddler => {
			if(!loadedTiddler) return callback({
				tiddler: null,
				error: "" //# specify
			})
			if(compareVersions(this.getVersion(loadedTiddler),
					   this.getVersion(extensionTiddler)
					  ) >= 0)
			//# also get and compare modified dates?
			{
				//# what about undefined?
				console.log('loaded is not newer')
				callback({
					tiddler: loadedTiddler,
					// TODO: move to lingo; may be change to noUpdate
					noUpdateMessage: "current version is up-to-date"
				})
			} else {
				this.cacheAvailableUpdate(url, loadedTiddler)
				callback({ tiddler: loadedTiddler })
			}
		})
	}
}

config.shadowTiddlers[centralSourcesListName] = '//{{{\n' +
	JSON.stringify(config.macros.extensionsExplorer.defaultAvailableExtensions, null, 2) +
	'\n//}}}'
config.annotations[centralSourcesListName] =
	config.macros.extensionsExplorer.lingo.centralSourcesListAnnotation
//}}}
/***
|''Name''|ForEachTiddlerPlugin|
|''Version''|1.3.3'|
|''Forked from''|[[abego.ForEachTiddlerPlugin|http://tiddlywiki.abego-software.de/#ForEachTiddlerPlugin]], by Udo Borkowski|
|''Author''|Yakov Litvin|
|''CoreVersion''|2.6.2|
|~|Although 2.6.2 is theoretically minimal TW version required for the correct operation, tests showed that the plugin works in 2.6.0, too.|
***/
//{{{
// defines whether to set "none" param to "same" if it is not used (i.e. = begin + end)
if(config.options.chkFetUseSameIfNone === undefined)
	config.options.chkFetUseSameIfNone = true;

(function(){

// Only install once
if (version.extensions.ForEachTiddlerPlugin) {
	alert("Warning: more than one copy of ForEachTiddlerPlugin is set to be launched");
	return;
} else
	version.extensions.ForEachTiddlerPlugin = {
		source: "[repository url here]",
		licence: "[licence url here]",
		copyright: "Copyright (c) Yakov Litvin, 2012-2015 [url of the meta page]"
	};

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

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

config.macros.forEachTiddler =
{
	actions: {
		addToList: {},
		write: {}
	}
};

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

config.macros.forEachTiddler.handler = function(place,macroName,params,wikifier,paramString,tiddler)
{
	// --- Pre-parsing for up-to-date params ----------------

	var preParsedParams = this.getUpToDateParams(paramString);
	// for backward compability, "params" are used as well

	// --- Parsing ------------------------------------------

	var parsedParams = this.parseParams(preParsedParams,params);
	if (parsedParams.errorText) {
		this.handleError(place, parsedParams.errorText);
		return;
	}//else
		parsedParams.place = place;
		parsedParams.inTiddler = tiddler ? tiddler : getContainingTiddler(place);

	// --- "Static" processing ------------------------------

	// Choose the action
	var actionName = parsedParams.actionName;
	var action = this.actions[actionName]; // no this is always a "known" action

	// Create the element
	var element = document.createElement(action.element);
	jQuery(element).attr({ refresh: "macro", macroName: macroName }).data(parsedParams);
	place.appendChild(element);

	// --- "Dynamic" processing -----------------------------

	this.refresh(element);
};

config.macros.forEachTiddler.refresh = function(element)
{
	var parsedParams = jQuery(element).data(),
	    action = this.actions[parsedParams.actionName];

	jQuery(element).empty();
	try {
		var tiddlersAndContext = this.getTiddlersAndContext(parsedParams);

		// Perform the action
		action.handler(element, tiddlersAndContext.tiddlers,
				parsedParams.actionParameter, tiddlersAndContext.context);
	} catch (e) {
		this.handleError(place, e);
	}
};

config.macros.forEachTiddler.oldFashionParams = ["in", "filter", "where", "sortBy",
	"script", "write", "begin", "end", "none", "toFile", "withLineSeparator"
//# add to docs: new actions are to be added here or used in name:param notation only
];

config.macros.forEachTiddler.getUpToDateParams = function(paramString)
// turns stuff like "... where 'tiddler.title.length < 20' ..."
//               to "... where:'tiddler.title.length < 20' ..." and then applies parseParams,
// which allows to use params in an arbitrary order and other goodies of parsed params
{
	var paramPairRegExp = new RegExp("("+this.oldFashionParams.join("|")+")\\s+"+
			"("+ // adapted from String.prototype.parseParams
			'(?:"(?:(?:\\\\")|[^"])+")|'+		// double-quoted param
			"(?:'(?:(?:\\\\')|[^'])+')|"+		// quoted param
			"(?:\\[\\[(?:\\s|\\S)*?\\]\\])|"+	// [[...]]-wrapped
			"(?:\\{\\{(?:\\s|\\S)*?\\}\\})|"+	// {{...}}-wrapped
			"(?:[^\"':\\s][^\\s:]*)|"+		// non-wrapped
			"(?:\"\")|(?:'')"+			// empty '' or ""
			")","g");
	paramString =
		paramString.replace(paramPairRegExp,function($0,$1,$2){ return $1+":"+$2; });

	return paramString.parseParams("filter",null,true,false,true);
	// the first unnamed param is now considered as the 'filter' param
};

// Returns an object with properties "tiddlers" and "context".
// tiddlers holds the (sorted) tiddlers selected by the parameter,
// context the context of the execution of the macro.
//
// The action is not yet performed.
//
// @param parameter  holds the parameter of the macro as separate properties.
//				  The following properties are supported:
//
//						place
//						filter
//						whereClause
//						sortClause
//						sortAscending
//						actionName
//						actionParameter
//						scriptText
//						tiddlyWikiPath
//
//					All properties are optional.
//					For most actions the place property must be defined.
//
config.macros.forEachTiddler.getTiddlersAndContext = function(parameter)
{
	var context = config.macros.forEachTiddler.createContext(parameter.place, parameter.filter, parameter.whereClause, parameter.sortClause, parameter.sortAscending, parameter.actionName, parameter.actionParameter, parameter.scriptText, parameter.tiddlyWikiPath, parameter.inTiddler);

	var tiddlyWiki = parameter.tiddlyWikiPath ? this.loadTiddlyWiki(parameter.tiddlyWikiPath) : store;
	context["tiddlyWiki"] = tiddlyWiki;
	
	// Get the tiddlers, as defined by the filter and the whereClause
	var tiddlers = this.findTiddlers(parameter.filter, parameter.whereClause, context, tiddlyWiki);
	context["tiddlers"] = tiddlers;

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

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

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

// Internal.
//
// --- The addToList Action -----------------------------------------------
//
config.macros.forEachTiddler.actions.addToList.element = "ul";
config.macros.forEachTiddler.actions.addToList.handler = function(place, tiddlers, parameter, context)
{
	for (var i = 0; i < tiddlers.length; i++)
	{
		var tiddler = tiddlers[i];
		var listItem = document.createElement("li");
		place.appendChild(listItem);
		createTiddlyLink(listItem, tiddler.title, true);
	}
};

// Internal.
//
// --- The write Action ---------------------------------------------------
//
config.macros.forEachTiddler.actions.write.element = "span";
config.macros.forEachTiddler.actions.write.handler = function(place, tiddlers, parameter, context)
{
	var params = parameter[0].nonParsedParams;
	if(!parameter[0]["write"])
		return this.handleError(place, "Missing expression behind 'write'.");
	var textExpression = config.macros.forEachTiddler.paramEncode(getParam(parameter,["write"]));

	var getParamExpression = function(name)
	{
		if(params.contains(name) && !parameter[0][name])
			throw "Missing text behind '%0'".format([name]);
		return config.macros.forEachTiddler.paramEncode(getParam(parameter,name));
	};
	var beginExpression = getParamExpression("begin");
	var   endExpression = getParamExpression("end");
	var  noneExpression = getParamExpression("none") 
		|| (config.options.chkFetUseSameIfNone ? "same" : "");

	var lineSeparator = undefined;
	if(params.contains("toFile") && !parameter[0]["toFile"])
		return this.handleError(place, "Filename expected behind 'toFile' of 'write' action.");
	var filename = getParam(parameter,"toFile");
	filename = config.macros.forEachTiddler.paramEncode(filename);
	if(filename) {
		filename = config.macros.forEachTiddler.getLocalPath(filename);

		if(params.contains("withLineSeparator")&& !parameter[0]["withLineSeparator"])
			return this.handleError(place, "Line separator text expected behind 'withLineSeparator' of 'write' action.")
		lineSeparator = getParamExpression("withLineSeparator");
	}

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

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

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

	if (count == 0 && noneExpression)
	{
		var beginAddition = beginExpression ? "("+beginExpression+")" : '""',
		      endAddition =   endExpression ? "("+  endExpression+")" : '""',
		     bothAddition = "("+beginAddition+"+"+endAddition+")";
		noneExpression = noneExpression
				.replace(/(?=\W|^)begin(?=\W|$)/,beginAddition)
				.replace(/(?=\W|^)end(?=\W|$)/,    endAddition)
				.replace(/(?=\W|^)same(?=\W|$)/,  bothAddition);
		text += config.macros.forEachTiddler.getEvalTiddlerFunction(noneExpression, context)(undefined, context, count, undefined);
	}

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


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

config.macros.forEachTiddler.parseParams = function(preParsedParams,params)
{
	if(params.contains("in") && !preParsedParams[0]["in"])
		return { errorText: "TiddlyWiki path expected behind 'in'." };
	var TWpath = getParam(preParsedParams,"in");

	if(params.contains("filter") && !preParsedParams[0]["filter"])
		return { errorText: "No filter specified." };

	if(params.contains("where") && !preParsedParams[0]["where"])
		return { errorText: "whereClause missing behind 'where'." };
	var where = getParam(preParsedParams,"where");

	var ascending = true;
	if(params.contains("sortBy") && !preParsedParams[0]["sortBy"])
		return { errorText: "sortClause missing behind 'sortBy'." };
	var sortClause = getParam(preParsedParams,"sortBy");
	if(preParsedParams[0]["sortBy"] && preParsedParams[0]["sortBy"].length > 1)
		ascending = !(preParsedParams[0]["sortBy"][1] == "descending");

	if(params.contains("script") && !preParsedParams[0]["script"])
		return { errorText: "scriptText is not specified." };
	var scriptText = !preParsedParams ? "" :
		(!preParsedParams[0]["script"] ? "" :
		 preParsedParams[0]["script"].join(";"));

	var actionName = "addToList";
	for(var knownActionName in this.actions)
		if(preParsedParams[0][knownActionName]) {
			actionName = knownActionName;
			break;
		}
	// no error handling if there's an unknown action
	// because now the order is not important and actionName can have another position
	preParsedParams[0].nonParsedParams = params; // for parsing inside actions

	return {
		tiddlyWikiPath:	this.paramEncode(TWpath),
		filter:		getParam(preParsedParams,"filter"),
		whereClause:	this.paramEncode(where) || true,
		sortClause:	this.paramEncode(sortClause),
		sortAscending:	ascending,
		scriptText:	this.paramEncode(scriptText),
		actionName:	actionName,
		actionParameter:preParsedParams // not much need to cut out other params
	}
};

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


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


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

	var lenPrefix = idPrefix.length;
	
	// Read the content of the given file
	var content = loadFile(this.getLocalPath(path));
	if(content === null)
		throw "TiddlyWiki '"+path+"' not found.";
	
	var tiddlyWiki = new TiddlyWiki();

	if (!tiddlyWiki.importTiddlyWiki(content))
		throw "File '"+path+"' is not a TiddlyWiki.";
	tiddlyWiki.dirty = false;

	return tiddlyWiki;
};


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


// Internal.
//
config.macros.forEachTiddler.findTiddlers = function(filter, whereClause, context, tiddlyWiki) {
	var result = [];
	var func = config.macros.forEachTiddler.getEvalTiddlerFunction(whereClause, context);
	if(filter) {
		var tids = tiddlyWiki.filterTiddlers(filter);
		for(var i = 0; i < tids.length; i++)
			if(func(tids[i], context, undefined, undefined))
				result.push(tids[i]);
	} else
		tiddlyWiki.forEachTiddler(function(title,tiddler) {
			if(func(tiddler, context, undefined, undefined))
				result.push(tiddler);
		});
	return result;
};


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

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

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

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

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

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


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

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


// Internal.
//
// Encodes the given string.
//
// Replaces 
//	 "$))" to ">>"
//	 "$)" to ">"
//
config.macros.forEachTiddler.paramEncode = function(s)
{
	if(!s) return s;
	var reGTGT = new RegExp("\\$\\)\\)","mg");
	var reGT = new RegExp("\\$\\)","mg");
	return s.replace(reGTGT, ">>").replace(reGT, ">");
};
//# document the .paramEncode transformation of the params; or get rid of it?

// Internal.
//
// Returns the given original path (that is a file path, starting with "file:")
// as a path to a local file, in the systems native file format.
//
// Handles relative links, too.
//
config.macros.forEachTiddler.getLocalPath = function(originalPath) {
// code adapted from SharedTiddlersPlugin to handle relative paths

	var originalAbsolutePath = originalPath;
	if(originalAbsolutePath.search(/^((http(s)?)|(file)):/) != 0) {
	// no protocol prefix..

		if (originalAbsolutePath.search(/^(.\:\\)|(\\\\)|(\/)/) != 0){// is relative?
		// as Unix filesystem root is "/", urls starting with it are not considered as relative

			var currentUrl  = document.location.toString();
			var currentPath = (currentUrl.lastIndexOf("/") > -1) ?
				currentUrl.substr(0, currentUrl.lastIndexOf("/") + 1) :
				currentUrl + "/";
			originalAbsolutePath = currentPath + originalAbsolutePath;
		} else
		// an "absolute" path to a local file. Prefix it with file://

			originalAbsolutePath = "file://" + originalAbsolutePath;

		// replace every \ by a /, to cover Windows style pathes
		originalAbsolutePath = originalAbsolutePath.replace(/\\/mg,"/");
	}
	return getLocalPath(originalAbsolutePath);
};


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

// ---------------------------------------------------------------------------
// fet alias for the the forEachTiddler Macro
// ---------------------------------------------------------------------------

config.macros.fet = config.macros.forEachTiddler;

//============================================================================
// utilities for String and Tiddler objects useful in fet macros
//============================================================================

// Returns true if the string starts with the given prefix, false otherwise.
//
String.prototype.startsWith = function(prefix) {
	var n =  prefix.length;
	return (this.length >= n) && (this.slice(0, n) == prefix);
};

// Returns true if the string ends with the given suffix, false otherwise.
//
String.prototype.endsWith = function(suffix) {
	var n = suffix.length;
	return (this.length >= n) && (this.right(n) == suffix);
};

// Returns true when the string contains the given substring, false otherwise.
//
String.prototype.contains = function(substring) {
	return this.indexOf(substring) >= 0;
};
})();

// Returns the slice value if it is present or defaultText otherwise
//
Tiddler.prototype.getSlice = function(sliceName,defaultText)
{
	var re = TiddlyWiki.prototype.slicesRE;
	re.lastIndex = 0;
	var m = re.exec(this.text);
	while(m) {
		if(m[2]) {
			if(m[2] == sliceName)
				return m[3];
		} else {
			if(m[5] == sliceName)
				return m[6];
		}
		m = re.exec(this.text);
	}
	return defaultText;
};

// Returns the section value if it is present or defaultText otherwise
//
Tiddler.prototype.getSection = function(sectionName,defaultText)
{
	var beginSectionRegExp = new RegExp("(^!{1,6}[ \t]*" + sectionName.escapeRegExp() + "[ \t]*\n)","mg"),
	    sectionTerminatorRegExp = /^!/mg;

	var match = beginSectionRegExp.exec(this.text), sectionText;
	if(match) {
		sectionText = this.text.substr(match.index+match[1].length);
		match = sectionTerminatorRegExp.exec(sectionText);
		if(match)
			sectionText = sectionText.substr(0,match.index-1); // don't include final \n
		return sectionText;
	}
	return defaultText;
};

var transText = function(tiddlerOrGetTiddlerTextArg, moreArguments)
{
	var title = (tiddlerOrGetTiddlerTextArg instanceof Tiddler) ? tiddlerOrGetTiddlerTextArg.title : tiddlerOrGetTiddlerTextArg;
	return "<<tiddler [[" + title + "]] "+ (moreArguments||"") +">>"
};
//}}}
ForEachTiddlerPlugin
<<include "node: ForEachTiddlerPluginRepo"
filters:"[[ForEachTiddlerPlugin]] [[ForEachTiddlerPlugin installer and updater]]" import:4 noRefresh>>
<<search>><<closeAll>><<permaview>><<saveChanges>><<slider chkSliderOptionsPanel OptionsPanel "options »" "Change TiddlyWiki advanced options">>
/***
|''Version''|0.3.0|
|Short status|<<insertEditable container:"@stateComment" size:max cell>>|
|''Author''|Yakov Litvin|
|''Description''|This plugin fixes [[issue 146|https://github.com/TiddlyWiki/tiddlywiki/issues/146]]; changes in {{{TiddlyWiki.prototype.saveTiddler}}}: remove the {{{newTitle = title}}} bit (fixes renaming in {{{setData}}} in IEP/SMP), fix setting custom fields when {{{title instanceof Tiddler}}} and refactored the code. Also makes {{{store.setDirty}}} work if {{{!readOnly}}}.|
|''Overwriting notice''|This plugin overwrites the {{{Story.prototype.saveTiddler}}} and {{{TiddlyWiki.prototype.saveTiddler}}} methods. So any plugin that hijack any of these should use the {{{Requires}}} slice mentioning this plugin (if it is installed); plugins that overwrite these functions will likely cause conflicts/won't work.|
***/
//{{{
Story.prototype.saveTiddler = function(title,minorUpdate)
{
	var tiddlerElem = this.getTiddler(title);
	if(!tiddlerElem)
		return null;

	var fields = {};
	this.gatherSaveFields(tiddlerElem,fields);
	var newTitle = fields.title || title;
	if(!store.tiddlerExists(newTitle)) {
		newTitle = newTitle.trim();
		var creator = config.options.txtUserName;
	}
	if(store.tiddlerExists(newTitle) && newTitle != title) {
		if(!confirm(config.messages.overwriteWarning.format([newTitle.toString()])))
			return null;
// removed:	title = newTitle; // which causes the bug #146
// the usages of "title" below marked with "+>" change the behaviour
// *learn when and why this was introduced
	}
/*+>*/	if(newTitle != title)
		this.closeTiddler(newTitle,false);
	tiddlerElem.id = this.tiddlerId(newTitle);
	tiddlerElem.setAttribute("tiddler",newTitle);
	tiddlerElem.setAttribute("template",DEFAULT_VIEW_TEMPLATE);
	tiddlerElem.setAttribute("dirty","false");
	if(config.options.chkForceMinorUpdate)
		minorUpdate = !minorUpdate;
	if(!store.tiddlerExists(newTitle))
		minorUpdate = false;
	var newDate = new Date();
/*+>*/	if(store.tiddlerExists(title)) {
/*+>*/		var t = store.fetchTiddler(title);
		var extendedFields = t.fields;
		creator = t.creator;
	} else
		extendedFields = merge({},config.defaultCustomFields);
	
	for(var n in fields)
		if(!TiddlyWiki.isStandardField(n))
			extendedFields[n] = fields[n];

/*+>*/	var tiddler = store.saveTiddler(title,newTitle,fields.text,minorUpdate ? undefined : config.options.txtUserName,minorUpdate ? undefined : newDate,fields.tags,extendedFields,null,null,creator);
	autoSaveChanges(null,[tiddler]);
	return newTitle;
};
//}}}

// // Refactor {{{TiddlyWiki.prototype.saveTiddler}}}, remove the {{{newTitle = title}}} bit (fixes renaming in {{{setData}}} in IEP/SMP), fix setting custom fields when {{{title instanceof Tiddler}}}.
//{{{
TiddlyWiki.prototype.saveTiddler = function(title,newTitle,newBody,modifier,modified,tags,fields,clearChangeCount,created,creator)
{
	// resolve the tiddler and remove it from the store
	var tiddler = this.resolveTiddler(title);
	if(tiddler) {
		title = tiddler.title;
		this.deleteTiddler(title);
	} else
		tiddler = new Tiddler();

	// update tiddler data	
	if(tiddler) {
		created = created || tiddler.created; // Preserve created date
		creator = creator || tiddler.creator;
	} else
		created = created || modified;
	fields = merge(merge(tiddler ? tiddler.fields : {},fields), config.defaultCustomFields,true);
	tiddler.set(newTitle,newBody,modifier,modified,tags,created,fields,creator);
	if(clearChangeCount)
		tiddler.clearChangeCount();
	else
		tiddler.incChangeCount();

	// bring the tiddler back to the store
	this.addTiddler(tiddler);
	this.setDirty(true);

	if(title != newTitle)
		this.notify(title,true);
	this.notify(newTitle,true);

	return tiddler;
};
//}}}
// // Изменение для работы через http – {{{store.setDirty}}} hijacked to work if {{{!readOnly}}} {{DDnc{зачем это здесь? в отдельный плагин?}}}
//{{{
store.setDirtyEverywhere = store.setDiry;
store.setDiry = function(dirty) {
	if(readOnly)
		return false;
	this.setDirtyEverywhere.apply(this,arguments);
};
//}}}
/***
|Version|0.1|
|Short status|<<insertEditable container:"@stateComment" size:max cell>>|
***/
//{{{
// adapted from the 2.7.1 core:

// Sets the value of the given field of the tiddler to the value.
// Setting an ExtendedField's value to null or undefined removes the field.
// Setting a namespace to undefined removes all fields of that namespace.
// The fieldName is case-insensitive.
// All values will be converted to a string value.
TiddlyWiki.prototype.setValue = function(tiddler,fieldName,value,noNotify)
{
	TiddlyWiki.checkFieldName(fieldName);
	var t = this.resolveTiddler(tiddler);
	if(!t)
		return;
	fieldName = fieldName.toLowerCase();
	var isRemove = (value === undefined) || (value === null);
	var accessor = TiddlyWiki.standardFieldAccess[fieldName];
	if(accessor) {
		if(isRemove)
			// don't remove StandardFields
			return;
		var h = TiddlyWiki.standardFieldAccess[fieldName];
		if(!h.set(t,value))
			return;
	} else {
		var oldValue = t.fields[fieldName];
		if(isRemove) {
			if(oldValue !== undefined)
				// deletes a single field
				delete t.fields[fieldName];
			else {
				// no concrete value is defined for the fieldName
				// so we guess this is a namespace path.
				// delete all fields in a namespace
				var re = new RegExp("^"+fieldName+"\\.");
				var dirty = false;
				var n;
				for(n in t.fields)
					if(n.match(re)) {
						delete t.fields[n];
						dirty = true;
					}
				if(!dirty)
					return;
			}
		} else {
			// the "normal" set case. value is defined (not null/undefined)
			// For convenience provide a nicer conversion Date->String
			value = value instanceof Date ? value.convertToYYYYMMDDHHMMSSMMM() : String(value);
			if(oldValue == value)
				return;
			t.fields[fieldName] = value;
		}
	}
	// When we are here the tiddler/store really was changed.
	if(!noNotify)
		this.notify(t.title,true);
	if(!fieldName.match(/^temp\./))
		this.setDirty(true);
};
//}}}
/***
|Description  ||
|Documentation|[[SetManagerPluginInfo]]|
|Source       |http://yakovlitvin.pro/TW/pre-releases/ForEachTiddler%20+%20SetManagerPlugin.html#SetManagerPlugin|
|Author       |Yakov Litvin|
|Requires     |ForEachTiddlerPlugin SetFieldPlugin SharedTiddlersPlugin|
|~|ForEachTiddlerPlugin is not required for the operation of the plugin, but if FETP is present, it should be evaluated before this plugin (same for SharedTiddlersPlugin); also, there's no other simple way to use this plugin aside with FETP; SetFieldPlugin is required for quality (removes extra refreshing which quickens some actions and removes some representation bugs|
|Version      |0.11.6|
|License      |[[MIT|https://github.com/YakovL/TiddlyWiki_YL_ExtensionsCollection/blob/master/Common%20License%20(MIT)]]|
***/

//{{{
// sort counters methods ("S" stands for "sort")
Tiddler.prototype.getSCounterPlace = function(storageName)
{
	storageName = storageName || config.macros.itemMenu.defaultField
	var withFieldNameRegExp = /(.*?)@@(.*)/,
	    withFieldNameMatch  = withFieldNameRegExp.exec(storageName);
	return {
		storageName: withFieldNameMatch ? withFieldNameMatch[1] : null,
		fieldName: withFieldNameMatch ? withFieldNameMatch[2] : storageName
	}
};
Tiddler.prototype.getRawSData = function(fieldName)
{
	fieldName = fieldName || config.macros.itemMenu.defaultField;

	if(!this.getIncludeURL || !this.getIncludeURL())
		return store.getValue(this,fieldName);
};
Tiddler.prototype.getSCounter = function(fieldName)
{
	var storageData  = this.getSCounterPlace(fieldName),
	    storageName  = storageData.storageName,
	    storageField = storageData.fieldName,
	    indexText;

	if(!this.getIncludeURL || !this.getIncludeURL())
		return parseInt(this.getRawSData(storageField));

	// for included tiddlers use a separate tiddler as a stored index
	if(storageName) {
		var storageTiddler = store.fetchTiddler(storageName);
		indexText = storageTiddler ?
				storageTiddler.getRawSData(storageField) : "";
	} else
		indexText = store.getTiddlerText(storageField);

	// find the line in the index which describes the tiddler, if present
	var indexLineRegExp = config.macros.itemMenu.getIndexLineRegExp(this.title),
	    indexMatch      = indexLineRegExp.exec(indexText);

	return indexMatch ? parseInt(indexMatch[1]) : undefined;
};
Tiddler.prototype.setRawSData = function(fieldName, value)
{
	fieldName = fieldName || config.macros.itemMenu.defaultField;

	// reduced version of store.setValue(this, fieldName, value) for SData
	TiddlyWiki.checkFieldName(fieldName);
	fieldName = fieldName.toLowerCase();
	if(TiddlyWiki.standardFieldAccess[fieldName]) return;

	if(this.fields[fieldName] == value) return;
	this.fields[fieldName] = ""+value; // as a String, only string values are stored

	store.setDirty(true);
};
Tiddler.prototype.updateSIndex = function(indexText,value)
{
	// find the line in the index which describes the tiddler, if present
	var indexLineRegExp = config.macros.itemMenu.getIndexLineRegExp(this.title),
	    indexMatch      = indexLineRegExp.exec(indexText);

	var newIndexLine = config.macros.itemMenu.createIndexLine(this.title, value);
	if(indexMatch)
		indexText = indexText.replace(indexLineRegExp, newIndexLine);
	else
		indexText += (newIndexLine+"\n");
	return indexText;
};
Tiddler.prototype.setSCounter = function(fieldName, value)
{
	var storageData  = this.getSCounterPlace(fieldName),
	    storageName  = storageData.storageName,
	    storageField = storageData.fieldName,
	    indexText;

	if(!this.getIncludeURL || !this.getIncludeURL()) {
		this.setRawSData(storageField, value);
		return;
	};

	// for included tiddlers use a separate tiddler as a stored index
	// for orderField@@tiddlerName syntax, use the storageName tiddler
	//  for storage, otherwise use the fieldName tiddler
	var indexTid = store.fetchTiddler(storageName || fieldName) ||
		store.createTiddler(storageName || fieldName);

	if(storageName) {
		indexText = indexTid.getRawSData(storageField) || "";
		indexText = this.updateSIndex(indexText, value);
		indexTid.setRawSData(storageField, indexText);
	} else
		indexTid.text = this.updateSIndex(indexTid.text, value);
};
Tiddler.prototype.deleteSCounter = function(fieldName)
{
	fieldName = fieldName.toLowerCase();
	// use of StandardFields is unlikely, but don't remove them anyway
	if(TiddlyWiki.standardFieldAccess[fieldName]) return;

	if(this.getSCounter(fieldName) !== undefined) delete this.fields[fieldName];
};
// --------------------------------------------------------------------------------

TiddlyWiki.prototype.saveModifiedTiddler = function(title, newTitle, newBody, tags, fields, clearChangeCount, created, creator) {
	var tidBeingChanged = (title instanceof Tiddler) ? title : this.fetchTiddler(title);
	title = tidBeingChanged.title;
	var conflictingTiddler = this.fetchTiddler(newTitle) || this.fetchTiddler(title);
	if(conflictingTiddler && conflictingTiddler != tidBeingChanged)
		if(!confirm("A tiddler named \""+title+"\" already exists. Do you want to overwrite it?"))
			return;

	return this.saveTiddler(title, newTitle, newBody,
		config.options.txtUserName,
		new Date(),
	  tags, fields, clearChangeCount, created, creator)
};

var preventOtherHandling = window.preventOtherHandling = function(e)
{
	// prevent propagation
	if (e.stopPropagation) e.stopPropagation();
	e.cancelBubble = true;

	// prevent browser action from firing
	if(e.preventDefault) e.preventDefault();
	e.returnValue = false;
	// see https://learn.javascript.ru/default-browser-action
}

// helps to avoid popup closing on event
var wrapNoClose = window.wrapNoClose = function(func)
{
	return function(ev)
	{
		if(func) func.apply(this,arguments);

		var e = ev || window.event; // support old IE
		if(!e)
			return false;

		preventOtherHandling(e);
	};
};

// helps to make the height of textareas appropriate (a working prototype)
//#  defaultHeight should be calced as a height of one line;
//#  maxHeight - not more than 1/2 or 3/4 of the screen
//#  also, better to get rid of shrinking scrollbar..
var adjustHeightToContent = function()
{
	var defaultHeight = 30;
	var maxHeight = 400;
	jQuery(this).height(defaultHeight);
	jQuery(this).height(Math.min(this.scrollHeight, maxHeight));
};

// --------------------------------------------------------------------------------
// constants for using with jQuery .which() in keyup handlers
var $enter =	13,
    $up =	38,
    $down =	40,
    $pgUp =     33,
    $pgDn =     34

// --------------------------------------------------------------------------------
// detecting touch-screens (see http://stackoverflow.com/a/4819886/3995261)
window.isOpenedOnTouchScreen = function()
{
	return !!('ontouchstart' in this);
}
//}}}
//{{{
config.macros.itemMenu =
{
	getIndexLineRegExp: function(tiddlerName) {
		return new RegExp(tiddlerName.escapeRegExp() + ": ([0-9]+)");
	},
	createIndexLine: function(tiddlerName, value) {
		return tiddlerName+": "+value;
	},
	defaultField: "orderCounter",
	itemMenuClass: "listMenuButton",
	sortByCounter: function(tiddlerArray, fieldName)
	{
		var defaultValue = -1; // undefinedUp (1000 for undefinedDown)
		return tiddlerArray.sort(function(t1,t2){
			var c1 = t1.getSCounter(fieldName), c2 = t2.getSCounter(fieldName);
			c1 = (!c1 && c1 != 0) ? defaultValue : c1;
			c2 = (!c2 && c2 != 0) ? defaultValue : c2;
			return  c1 - c2;
		});
	},

	currentlyDragged: [],
	setCurrentlyDragged: function(tidName,sortField,dropAction,itemMenuElement,
					onKeyDown,onKeyUp)
	{
		this.currentlyDragged.push({
			name:		 tidName,
			field:		 sortField,
			dropAction:	 dropAction,
			itemMenuElement: itemMenuElement,
			onKeyDown:	 onKeyDown,
			onKeyUp:	 onKeyUp
		});

		if(itemMenuElement) {
			jQuery(itemMenuElement).bind("keyup",onKeyUp);
			jQuery(itemMenuElement).bind("keydown",onKeyDown);
		}
	},
	clearCurrentlyDragged: function()
	{
		if(!this.currentlyDragged[0]) return;
		var i, context, upHandler, downHandler;
		for(i = 0; i < this.currentlyDragged.length; i++)
		{
			context = this.currentlyDragged[i];
			upHandler = context.onKeyUp;
			downHandler = context.onKeyDown;

			if(!context.itemMenuElement) continue;

			if(downHandler)
			    jQuery(context.itemMenuElement).unbind("keydown",downHandler);
			if(upHandler)
			    jQuery(context.itemMenuElement).unbind("keyup",upHandler);
		}
		this.currentlyDragged = [];

		jQuery(".buttonSortState").parent().parent().removeClass("selected");
		//# check if /\ grandparent is <tr> and remove only in that case
		jQuery(".buttonSortState").removeClass("buttonSortState");
	},
	getCurrentlyDragged: function() {
	//# for now, works only with the first selection (if multiple)
		return this.currentlyDragged[0] ? this.currentlyDragged[0].name : null;
	},
	getCurrentSourceListContext: function() {
	//# for now, works only with the first selection (if multiple)
		return this.currentlyDragged[0];
	},
	markSelected: function(itemMenu)
	{
		if(!itemMenu) return;
		jQuery(itemMenu).addClass("buttonSortState").focus();
		if(itemMenu.parentElement.tagName.toLowerCase() == "td")
			jQuery(itemMenu).parent().parent().addClass("selected");
	},
	ensureFocusOnCurrentlyDragged: function()
	{
		var currentlyDragged = this.getCurrentSourceListContext();
console.log("currentlyDragged:");console.log(currentlyDragged);
		if(!currentlyDragged) return;

		var itemMenu = currentlyDragged.itemMenuElement;
		// because of refreshing, itemMenu can be no longer attached to the ~root
		// element (document? html?). If that's the case, find the new one
		var newItemMenu, itemMenus = jQuery("."+this.itemMenuClass+
			'[filter=\''+itemMenu.getAttribute("filter")+'\']')
			.each(function(i,el){
				if(el.tiddler == itemMenu.tiddler)
					newItemMenu = el;
			});
		if(newItemMenu != itemMenu) {
			// remember actual element, reattach onkeyup, onkeydown handlers
			currentlyDragged.itemMenuElement = newItemMenu;
			jQuery(newItemMenu).bind("keyup",currentlyDragged.onKeyUp);
			jQuery(newItemMenu).bind("keydown",currentlyDragged.onKeyDown);
		}
		this.markSelected(newItemMenu);
	},

	actionStepsWithArguments: {},
	actionStepsWithoutArguments: {},
	applyActionStep: function(tiddler, actionStep, rootElement)
	{
		var actionRegExp = /(.\w+)\.\.(.*)/,
		    match = actionRegExp.exec(actionStep),
		    actionStepName;

		if(match) { // action with an argument
			for(actionStepName in this.actionStepsWithArguments)
				if(actionStepName == match[1])
					return this.actionStepsWithArguments[actionStepName](tiddler,match[2],rootElement);
			//# may be throw an error/warning?
			return;
		} else      // actionStep without arguments
			for(actionStepName in this.actionStepsWithoutArguments)
				if(actionStepName == actionStep)
					return this.actionStepsWithoutArguments[actionStepName](tiddler,rootElement);

		//# may be throw an error/warning?
		return;
	},
	parseAndApplyAction: function(tiddler, actionLine, rootElement, noNotify)
	{
		if(!tiddler || !actionLine)
			return;
		var actionStepsArray = actionLine.split(",,");

		for(var i = 0; i < actionStepsArray.length; i++)
			this.applyActionStep(tiddler,jQuery.trim(actionStepsArray[i]), rootElement);

		if(!noNotify)
			store.notify(tiddler.title,true);
	},
	parseAndSeparateActions: function(actionsLine)
	{
		var actionsArray = actionsLine.split(";;");
		if(actionsArray.length == 1 && !actionsArray[0].contains("::"))
			return jQuery.trim(actionsArray[0]);

		var actionMap = {},
		    name_and_action_RegExp = /(.+?)::(.*)/, match;
		for(var i = 0; i < actionsArray.length; i++)
		{
			match = name_and_action_RegExp.exec(actionsArray[i]);
			if(!match || !match[1] || !match[2])
				//# may be throw an error/warning?
				continue;
			actionMap[jQuery.trim(match[1])] = jQuery.trim(match[2]);
			actionMap["default"] = actionMap["default"] || jQuery.trim(match[2]);
		}
		return actionMap;
	},

	// moving helpers
	checkCounters: function(filter,field)
	{
		var tids = store.filterTiddlers(filter);
		tids = this.sortByCounter(tids, field);
	
		for(var i = 0; i < tids.length; i++)
			tids[i].setSCounter(field,i);

		return tids;
	},
	moveToArbitraryPlace: function(filter,tiddler,field,index,doCycle)
	{
		var tids     = this.checkCounters(filter,field), i,
		    tidIndex = tids.indexOf(tiddler),
		    tidsNum  = tids.length;

		// parse "top"/"bottom" values
		index = (index == "top") ? 0 : (index == "bottom" ? tidsNum-1 : index);
		if(doCycle) {
			index = index % tidsNum;
			index = (index < 0) ? (index + tidsNum) : index;
		}
		// do nothing in cases.. ("tidIndex < 0" = "tiddler is not in the list")
		if(tidIndex == index || tidIndex < 0 || index < 0 || index >= tids.length)
			return;

		// move items
		tiddler.setSCounter(field,index);
		if(tidIndex > index)
			for(i = index; i < tidIndex; i++)
				tids[i].setSCounter(field,i+1);
		else
			for(i = index; i > tidIndex; i--)
				tids[i].setSCounter(field,i-1);

		// refresh the list (order)
		store.notify(field,true);
// ~200 ms for notifying / in which browser?
	},
	moveToTop: function(filter,tiddler,field)
		{ this.moveToArbitraryPlace(filter,tiddler,field,"top"); },
	moveToBottom: function(filter,tiddler,field)
		{ this.moveToArbitraryPlace(filter,tiddler,field,"bottom"); },

	// syntax: <<itemMenu tiddlerName filter field:fieldName addAction:actionSyntax
	//		dropAction:actionSyntax switchActions:actionsSyntax>>
	handler: function(place,macroName,params,wikifier,paramString,tiddler)
	{
		// parse params: tiddlerName, context filter; field, noConflicts
		var pParams = paramString.parseParams("pP",null,true,false,true),
		    tName   = pParams[0]["pP"][0], // name of the tid governed by itemMenu
		    tid     = store.fetchTiddler(tName),
		    filter  = pParams[0]["pP"][1], // tids among which the sorting is done
		    field   = getParam(pParams,"field",this.defaultField), // field which holds the counter value
		    addAction     = getParam(pParams,"addAction",""),     // 
		    dropAction    = getParam(pParams,"dropAction",""),    // 
		    switchActions = getParam(pParams,"switchActions",""); // 

		var cmi = config.macros.itemMenu; // shortcut

		var serapartedSwitchActions = this.parseAndSeparateActions(switchActions);

		var checkCounters = function()
			{ return cmi.checkCounters(filter,field); };
		
		// selecting/rearranging helpers
		var cancelSelection = function()
		{
			cmi.clearCurrentlyDragged();
			Popup.remove(); // close the popup, including on esc
		};
		var getSelected = function()
		{
			// get currently "dragged" itemMenu element
			var draggedContext = cmi.getCurrentSourceListContext();
			return draggedContext ? draggedContext.itemMenuElement : null;
		};
		// jQuery of itemMenu elements with the same filter
		var getJMenus = function()
		{
			return jQuery("."+cmi.itemMenuClass+"[filter='"+filter+"']");
		};
		var reselectByIndex = function(jMenus,index) // jMenus is "jQuery(menus)"
		{
			var menuToSelect = jMenus[index];
			if(!menuToSelect) return;

			cancelSelection();

			// select (focus is needed for keyboard events to fire)
			jQuery(menuToSelect).focus().click();
console.log("click generated in reselectByIndex");

			//# may be optimize the two previous steps (do reselection directly)
		};
		var selectNext = function()
		{
			var itemMenu = getSelected();
			if(!itemMenu) return;

			// other item menus, found by the same filter
			var menus = getJMenus(),
			    currentIndex = menus.index(itemMenu);
			reselectByIndex(menus,currentIndex+1);
		};
		var selectPrev = function()
		{
			var itemMenu = getSelected();
			if(!itemMenu) return;

			// other item menus, found by the same filter
			var menus = getJMenus(),
			    currentIndex = menus.index(itemMenu);
			reselectByIndex(menus,currentIndex-1);
		};
		var selectNeighbour = function()
		{
			var itemMenu = getSelected();
			if(!itemMenu) return;

			// other item menus, found by the same filter
			var menus = getJMenus(),
			    currentIndex = menus.index(itemMenu);
			if(currentIndex > 0)
				reselectByIndex(menus,currentIndex-1);
			else
				reselectByIndex(menus,currentIndex+1);

			// returns true when successfully selected another item
			return getSelected() != itemMenu;
		};
		var selectFirst = function()
		{
			// other item menus, found by the same filter
			var menus = getJMenus();
			reselectByIndex(menus,0);
		};
		var selectLast = function()
		{
			// other item menus, found by the same filter
			var menus = getJMenus();
			reselectByIndex(menus,menus.length-1);
		};
		var moveDown = function(doReselect)
		{
			var tids = checkCounters(), i = tids.indexOf(tid);
			if(i < 0) return; // not among tids
			if(i >= tids.length-1) // out of boundaries
				return doReselect ? "" : cancelSelection();

			// make the switch with the neighbour
			tids[i].setSCounter(field,i+1);
			tids[i+1].setSCounter(field,i);

			// refresh the list (order)
			store.notify(field,true);
			//# try to avoid extra refreshing on long press down

			if(doReselect) reselectByIndex(getJMenus(),i+1);
		};
		var moveUp = function(doReselect)
		{
			var tids = checkCounters(), i = tids.indexOf(tid);
			if(i < 0) return; // not among tids
			if(i <= 0) // out of boundaries
				return doReselect ? "" : cancelSelection();

			// make the switch with the neighbour
			tids[i].setSCounter(field,i-1);
			tids[i-1].setSCounter(field,i);

			// refresh the list (order)
			store.notify(field,true);

			if(doReselect) reselectByIndex(getJMenus(),i-1);
		};
		var moveToTop = function(doReselect)
		{
			cmi.moveToTop(filter,tid,field);
			if(doReselect) reselectByIndex(getJMenus(),0);
		};
		var moveToBottom = function(doReselect)
		{
			cmi.moveToBottom(filter,tid,field);
			var menus = getJMenus();
			if(doReselect) reselectByIndex(menus,menus.length-1);
		};
		var moveToArbitraryPlace = function(movingTidName, above)
		{ // filter, targetTidName is got via enclosure
			var tids = checkCounters(),
			    movingTid = store.fetchTiddler(movingTidName),
			    targetTid = store.fetchTiddler(tName),
			    movingInd = tids.indexOf(movingTid),
			    targetInd = tids.indexOf(targetTid),
			    index = targetInd + ((movingInd > targetInd) ? (above ? 0 : 1) : (above ? -1 : 0));
			cmi.moveToArbitraryPlace(filter,movingTid,field, index);
		};
		var onClickItemMenu = wrapNoClose(function(e)
		{
			if(!cmi.getCurrentlyDragged())		// ~drag
			{
				clearMessage(); // useful on smartphones

				// open the popup menu:
				var manageButton = this;
				var popup = Popup.create(this);
				jQuery(popup).addClass("itemMenu");
				// then, add buttons there:

				// the "cancel selection" button
				createTiddlyButton(popup,"","cancel selection",
					cancelSelection,"cancelSelectionButton button");

				// switch action(s) button
				var createActionButtonInPopup = function(place,actionName)
				{
					var li = createTiddlyElement(place,"li"),
					    action = serapartedSwitchActions[actionName];

					createTiddlyButton(li,actionName,"",function(){
						cmi.parseAndApplyAction(tid,action,this);
						cancelSelection();
					},"listedDoActionButton button");
				};
				var bringActions = wrapNoClose(function() {
					if(serapartedSwitchActions["default"])
					{
						// named actions
						var dPopup = Popup.create(popup), li, actName;
						for(actName in serapartedSwitchActions) {
							if(actName == "default")
								continue;
							createActionButtonInPopup(dPopup, actName);
						}
						Popup.show("bottom","left");
					} else { // single unnamed action is defined
						cmi.parseAndApplyAction(tid,switchActions, this);
						cancelSelection();
					}

					cmi.clearCurrentlyDragged();
					// no class removing since refreshing is applied
				});
				if(serapartedSwitchActions && tid)
					createTiddlyButton(popup,"","drop this item",
						bringActions,"doActionButton button");

				// tag toggler
				var macroText = "<<tagToggler [["+tName+"]] \"\">>";
				wikify(macroText, popup);
				var tagButton = popup.lastChild,
				    startTagToggling = function() {
					jQuery(tagButton).click();
console.log("click generated in startTagToggling");
				    };

				// the "info" button
				var showTiddlerInfo = wrapNoClose(function(){
					var infoPopup = Popup.create(popup);
					createTiddlyText(infoPopup,"references:");
					config.commands.references.handlePopup(infoPopup, tid.title);
					Popup.show("bottom","left");
					return false;
				});
				createTiddlyButton(popup,"","tiddler info", showTiddlerInfo,"tiddlerInfoButton button");

				// the "rename" button
				var startRenaming = wrapNoClose(function(){
					var renamePopup = Popup.create(popup),
					    li = createTiddlyElement(renamePopup,"li"),
					    initTitle = tid.title,
					    nameField = createTiddlyElement(li,"textarea",null,"nameInput"),
					    changeName = function(doSave,goOnSelected)
					    {
					       var newTitle = jQuery.trim(nameField.value);
					       store.saveModifiedTiddler(initTitle,newTitle);
					       story.refreshTiddler(tid.title,null,true);
					       if(doSave) autoSaveChanges();
					       if(!goOnSelected)
							cancelSelection();
					       else
							cmi.ensureFocusOnCurrentlyDragged();
					    };

					nameField.value = tid.title;
					createTiddlyButton(li,"rename","rename \""+tid.title
						+"\"",changeName,"button renameButton");
					nameField.onclick = wrapNoClose(); // prevent popup closing
					// press enter to apply or esc to exit
					// (shift and ctrl are also taken into account)
					nameField.onkeydown = function(ev)
					{
						var e = ev || window.event;
						if(e.which == $enter) {
							changeName(e.ctrlKey,e.shiftKey);
							Popup.remove(1); // renamePopup only

							window.preventOtherHandling(e);
						}
						if(e.key === 'Escape') {
						    if(e.shiftKey) {
							cmi.ensureFocusOnCurrentlyDragged();
							Popup.remove(1);
							window.preventOtherHandling(e);
						    } else
							cancelSelection();
						}
					};

					Popup.show("bottom","left");
					nameField.focus(); // put the cursor inside the name edit area
				});
				createTiddlyButton(popup,"","rename this tiddler", startRenaming,"renameTiddlerButton button");

				// the "edit text" button
				var startEditing = wrapNoClose(function()
				{
					var textEditPopup = Popup.create(popup),
					    li = createTiddlyElement(textEditPopup,"li"),
					    textField = createTiddlyElement(li,"textarea",null,"ttextInput"),
					    changeText = function(e)
					    // e is either click or keydown (enter) event
					    {
						var goOnSelected = e.shiftKey;
						tid.set(null,textField.value);
						store.saveModifiedTiddler(tid);
						story.refreshTiddler(tid.title,null,true);
						autoSaveChanges();
						if(goOnSelected)
							cmi.ensureFocusOnCurrentlyDragged();
						else
							cancelSelection();
					    };


					textField.value = tid.text;
					// change the width of the popup and hence of {{{textField}}}
					textEditPopup.style.width = "100%";
					createTiddlyButton(li,"save","save the text of \""+
								tid.title+"\"",changeText,
								"button saveTextButton");
					textField.onclick = wrapNoClose(); // prevent popup closing
					// press enter to apply or esc to exit
					// (shift and ctrl are also taken into account)
					textField.onkeydown = function(ev)
					{
						var e = ev || window.event;
						if(e.ctrlKey && e.which == $enter) {
							changeText(e);
							Popup.remove(1); //textEditPopup only

							window.preventOtherHandling(e);
						}
						if(e.key === 'Escape') {
						    if(e.shiftKey) {
							cmi.ensureFocusOnCurrentlyDragged();
							Popup.remove(1);
							window.preventOtherHandling(e);
						    } else {
							Popup.remove();
							cancelSelection();
						    }
						}
					};
					textField.onkeypress = function(e)
					{
						adjustHeightToContent.apply(this);
						// avoid firing of story.onTiddlerKeyPress
						if(e.which == $enter) {
							if(e.stopPropagation)
								e.stopPropagation();
							e.cancelBubble = true;
						}
						// don't return false to insert linebreaks
						// on enter (when typing)
					}

					Popup.show("bottom", "left");
					adjustHeightToContent.apply(textField);
					textField.focus(); // put the cursor inside the text edit area
				});
				createTiddlyButton(popup, "", "edit the tiddler text",
					startEditing, "editTiddlerTextButton button");

				// the "open" button
				var i = getTiddlyLinkInfo(tName,"openTiddlerButton button"),
				    openTiddler = function(e) {
					onClickTiddlerLink(e);
					cancelSelection();
				    },
				    openButton = createTiddlyButton(popup,"","open "+tName,
					openTiddler, i.classes, null, null, {
						refresh: "link", tiddlyLink: tName
					}), // see createTiddlyLink source
				    clickOpenTiddler = function() {
					jQuery(openButton).click();
				    };

				// "add" button
				var cma = config.macros.addTiddler;
				if(cma && addAction) {
					var btn = createTiddlyButton(popup, "",
						"add tiddler here", cma.onClick,
						"addTiddlerButton button"),
					    currentIndex = getJMenus().index(this);
					btn.params = { title:"", text:"", commonTags:[],
						addAction:	addAction,
						orderCounter:	field,
						orderFilter:	filter,
						orderMode:	currentIndex + 1
					}
					var addTiddler = function() {
 						jQuery(btn).click()
					};
				}

				// the "delete" button
				var confirmDeleteMsg = config.commands.deleteTiddler.warning,
				    deleteTiddler = function(e, goOnSelected)
				    {
					if(e) // on click
						goOnSelected = goOnSelected || e.shiftKey;
					if(confirm(confirmDeleteMsg.format([tName]))) {
						if(goOnSelected)
							goOnSelected = selectNeighbour();
						  // if no neighbour, nothing to reselect
						store.removeTiddler(tName);
						autoSaveChanges();
					}
					if(goOnSelected)
						cmi.ensureFocusOnCurrentlyDragged();
					else
						cancelSelection();
				    };
				createTiddlyButton(popup, "", "delete this tiddler",
					deleteTiddler, "deleteTiddlerButton button");

				// support keyboard
				//  keypress ignores "delete", arrows etc, hence keydown
				var onKeyDownItemMenu = function(e) {
					switch(e.key) {
						case 'Escape': 
						// onkeydown here, because onkeyup ~can't be
						// prevented from ~propagation in subpopups
							if(!e.shiftKey) cancelSelection();
							// for the info and tag popups
							else Popup.remove(1);
							break;

						case 'ArrowDown':
							if(e.ctrlKey) moveDown(true);
							else selectNext();
							break;
						case 'ArrowUp':
							if(e.ctrlKey) moveUp(true);
							else selectPrev();
							break;
						case 'End':
							if(e.ctrlKey) moveToBottom(true);
							else selectLast();
							break;
						case 'Home':
							if(e.ctrlKey) moveToTop(true);
							else selectFirst();
							break;

						case 'Enter': clickOpenTiddler(); break;
					}

					// allow browser tab switching, prevent other default actions
					if(e.key !== 'Tab' || !e.ctrlKey) return false;
				};
				var onKeyUpItemMenu = function(e) {
					switch(e.originalEvent.code) {
						case 'KeyI': showTiddlerInfo();	break;
						case 'KeyR': startRenaming();	break;
						case 'KeyE': startEditing();	break;
						case 'KeyT': startTagToggling();break;
						case 'KeyD':
							if(serapartedSwitchActions && tid)
								bringActions();
						break;
						// tries to reselect if e.shiftKey
						case 'KeyX':
							deleteTiddler(null, e.shiftKey);
						break;
					}
					switch(e.key) {
						case '+':
						case '=':
							if(cma && addAction) addTiddler(e);
						break;
						// tries to reselect if e.shiftKey
						case 'Delete':
							deleteTiddler(null, e.shiftKey);
						break;
//# add keyboard support for actions D[A?]
					}
					return false;
				};

				cmi.setCurrentlyDragged(tName, field, dropAction, this, onKeyDownItemMenu, onKeyUpItemMenu);
				cmi.markSelected(this);

/*# create array of { handler: .. ,  label: .. , activationKeys: .. }
    and try to make button creation, keyboard support "for each ..", extendable set of .. */

				Popup.show("top","right");
jQuery(popup).addClass("hide");
			} else {				// ~drop
				// if taken from another list, the item must be changed by the drop+add actions
				var dragListContext = cmi.getCurrentSourceListContext(),
				    dropActionNow = dragListContext.dropAction,
/* not 100% accurate */		    sameList = (dropActionNow == dropAction) && (dragListContext.field == field),
					dropFromOtherList = !sameList && (dropActionNow || addAction),
					dropAndAddAction = (dropActionNow && addAction) ?
						(dropActionNow+",,"+addAction)
						: (dropActionNow || addAction);

				if(dropFromOtherList)
				{
					var draggedTid = store.fetchTiddler(cmi.getCurrentlyDragged());
					cmi.parseAndApplyAction(draggedTid,dropAndAddAction);
					draggedTid.deleteSCounter(dragListContext.field);
				}

				// move the item
				Popup.remove(); // close the popup
				if(cmi.getCurrentlyDragged() == tName) {
					jQuery(this).removeClass("buttonSortState");
					moveDown(); // on drop-on-self
				} else
					moveToArbitraryPlace(cmi.getCurrentlyDragged(),true);
					// refreshing removes the "buttonSortState" class
				cmi.clearCurrentlyDragged();
			}
		});

		// create the 2 sets of classes for the button and for the table row
		// (if itemMenu is inside a td element) based on the tiddler's tags
		var itemMenuClasses = this.itemMenuClass,
		    rowClasses = "",
		    badSymbolsRE = /[\.,;:`!@#\$%\^&\*\(\)\+=\[\]\{\}\|\\/'"~ ]/g;
		for(var i = 0 ; tid && i < tid.tags.length ; i++) {
			// process each tag: substitute spaces and symbols .,;:!'"()[]{}=+\|/*&^%$#@`~ with "_"
			itemMenuClasses += " marker_"+ tid.tags[i].replace(badSymbolsRE,"_");
			rowClasses += "line_"+ tid.tags[i].replace(badSymbolsRE,"_")+" ";
		}

		// create the button(s) // text is set via CSS
		var btn = createTiddlyButton(place,"","",onClickItemMenu,itemMenuClasses);
		btn.setAttribute("filter",filter);
		btn.tiddler = tid;

		// in table, add the line_className classes based on the tiddler's tags
		if(place.tagName.toLowerCase() == "td")
			jQuery(place).parent().addClass(rowClasses);
	}
}

// define the actions
  // add tag
config.macros.itemMenu.actionStepsWithArguments["+tag"] = function(tiddler,tag)
{
	if(tiddler.isTagged(tag)) return;

	tiddler.tags.push(tag);
	tiddler.modifier = config.options.txtUserName;
	tiddler.modified = new Date();
	store.setDirty(true);
};
  // remove tag
config.macros.itemMenu.actionStepsWithArguments["-tag"] = function(tiddler,tag)
{
	if(!tiddler.isTagged(tag)) return;

	tiddler.tags.splice(tiddler.tags.indexOf(tag),1);
	tiddler.modifier = config.options.txtUserName;
	tiddler.modified = new Date();
	store.setDirty(true);
};
  // toggle tag
config.macros.itemMenu.actionStepsWithArguments["!tag"] = function(tiddler,tag)
{
	if(tiddler.isTagged(tag))
		tiddler.tags.splice(tiddler.tags.indexOf(tag),1);
	else
		tiddler.tags.push(tag);
	tiddler.modifier = config.options.txtUserName;
	tiddler.modified = new Date();
	store.setDirty(true);
};
  // set field
config.macros.itemMenu.actionStepsWithArguments["setField"] = function(tiddler,argument)
{
	var argRE = /(\w+)\.\.(.*)/, argMatch = argRE.exec(argument);
	if(!argMatch) return;
	var field = argMatch[1], value = argMatch[2];
	value = value || null; // to remove the field if empty

	store.setValue(tiddler,field,value,true);
};
  // delete field
config.macros.itemMenu.actionStepsWithArguments["deleteField"] = function(tiddler,argument)
{
	store.setValue(tiddler,argument,null,true);
};
//config.macros.itemMenu.actionStepsWithArguments["<name>"] = function(tiddler,arg) {
//};
  // save changes
config.macros.itemMenu.actionStepsWithoutArguments["save"] = function() {
	saveChanges();
};
config.macros.itemMenu.actionStepsWithoutArguments["delete"] = function(tiddler) {
	var confirmDeleteMsg = config.commands.deleteTiddler.warning;
	if(confirm(confirmDeleteMsg.format([tiddler.name])))
	{
		store.removeTiddler(tiddler.title);
		autoSaveChanges();
	}
};
//config.macros.itemMenu.actionStepsWithoutArguments["<name>"] = function(tiddler) {
//};

// implement the styling
var iMenuClass = config.macros.itemMenu.itemMenuClass;
config.shadowTiddlers["StyleSheetManualSorter"] = "/*{{{*/\n"+
	".editTTextItem  { width: 100% }\n"+
	".ttextInput, .nameInput, .newTitleInput, .newTagInput {\n"+
	"	padding: 0.2em 0.4em; \n"+
	"	border: none;\n"+
	"	margin: 0.4em 0.4em 0;\n"+
	"	box-shadow: 1px 1px 3px #999 inset;\n"+
	"}\n"+
	".ttextInput, .nameInput {\n"+
	"	box-sizing: border-box;\n"+
	"	width: calc(100% - 0.8em);\n"+
	"}\n\n"+

	"."+iMenuClass+"               { color: inherit; font-weight: bold; }\n"+
	"."+iMenuClass+":hover         { background-color: inherit; color: green; }\n"+ // use ColorPalette?
	"."+iMenuClass+":before        { content: \"⊙\"; }\n"+
	".cancelSelectionButton:before { content: \"⊗\"; }\n"+
	".doActionButton:before        { content: \"D\"; }\n"+
	".tiddlerInfoButton:before     { content: \"I\"; }\n"+
	".renameTiddlerButton:before   { content: \"R\"; }\n"+
	".editTiddlerTextButton:before { content: \"E\"; }\n"+
	".openTiddlerButton:before     { content: \"O\"; font-weight: normal; }\n"+
	".addTiddlerButton:before      { content: \"+\"; }\n"+
	".deleteTiddlerButton:before   { content: \"X\"; }\n"+
	".itemMenu .tagTogglerButton:before { content: \"T\"; }\n"+
	".itemMenu .button                  { padding-left: 0.25em; padding-right: 0.25em; }\n"+
	".buttonSortState:hover,\n"+
	".buttonSortState { background-color: inherit; color: #00B000; }\n"+
	// for "lists" with buttons as "markers" (using tables)
	".tableList,\n"+
	".tableList > tbody,\n"+
	".tableList > tbody > tr,\n"+
	".tableList > tbody > tr > td,\n"+
	".tableList > tbody > tr > td > .button { border: none !important; }\n"+
	".tableList td            { vertical-align: top; padding: 0px 3px; }\n"+
	".tableList ul,\n"+
	".tableList ol,\n"+
	"li .tableList,\n"+
	".tableList .tableList    { margin: 0; }\n"+
	".tableList > tbody > .selected { background-color: rgba(0,0,255,0.15); }\n\n"+
	".darkMode .tableList > tbody > .selected { background-color: rgba(0,0,255,0.4); }\n\n"+

	// full width of table with minimal width of the first column
	".tableList { width: calc(100% - 2em); }\n"+ // 2em of margins on both sides
	".tableList > tbody > tr > td:first-child { width: 1px; }\n"+
	/* is some cases
	 *	.tableList td { width: 1px; }
	 *	.tableList td:last-child { width: 100%; }
	 * would be more suitable */
	"/*}}}*/";
store.addNotification("StyleSheetManualSorter", refreshStyles);
//}}}
/***
!!!Tagging helpers and tagToggler macro
***/
//{{{
config.filters.existingTags = function(results,match)
{
	var allTags = store.getTags();
	for(var i = 0; i < allTags.length; i++)
		allTags[i] = allTags[i][0];

	if(match[3] == "-") {
		for(i = 0; i < results.length; i++)
			if(!allTags.contains(results[i].title))
				results.splice(i--,1);
		return results;
	}

	for(var i = 0; i < allTags.length; i++)
		allTags[i] = store.fetchTiddler(allTags[i]) || new Tiddler(allTags[i]);

	// default action: add all the tags
	for(i = 0; i < allTags.length; i++)
		results.pushUnique(allTags[i]);
	return results;
};

config.filters.tagging = function(results,match)
{
	var prefix = match[3].substr(0,1), title = match[3].substr(1), tid = store.fetchTiddler(title),
	    tagging = [], notTagging = [];

	if(!tid)
		return [];
//# this behaviour may be changed after some testing

	for(var i = 0; i < results.length; i++)
		if(tid.tags.contains(results[i].title))
			tagging.push(results[i]);
		else
			notTagging.push(results[i]);

	switch(prefix) {
		case ">":
			return tagging.concat(notTagging)
		case "<":
			return notTagging.concat(tagging)
		case "+":
			return tagging
		case "-":
			return notTagging
	}
	displayMessage("Warning: the \"tagging\" filter must be used with one of the prefixes +, -, > or <, which is not the case.");
// use cookie to decide whether to suppress the message?
	return results;
};

config.macros.tagToggler = {};
config.macros.tagToggler.handler = function(place,macroName,params,wikifier,paramString,tiddler)
{
	// parse params
	var pParams = paramString.parseParams("pParams",null,true,false,true),
	    tid     = store.fetchTiddler(pParams[0]["pParams"][0]) || tiddler; // tid. which tags will be toggled
	if(!tid) return;

	var label = pParams[0]["pParams"][1];
	if(label == "." || label == undefined)
		label = "toggle tags";

	var tooltip = getParam(pParams, "tooltip", "toggle tags of " + tid.title),
	    doAutoSave = params.contains('doAutoSave') || false,
	    tagsSet = getParam(pParams, "tags", "[existingTags[+]] [tagging[>"+tid.title+"]]");

	// for compability with SetManagerPlugin
	var cmi = config.macros.itemMenu,
	    clearSelected = function()
	    {
		if(cmi) cmi.clearCurrentlyDragged();
	    },
	    returnItemMenuSelection = function()
	    {
		if(cmi) cmi.ensureFocusOnCurrentlyDragged();
	    };

	var whereToScroll;
	var toggleTag = function(tid,tag,refreshTagListIfNotClosing)
	{
		if(tid.isTagged(tag))
			tid.tags.splice(tid.tags.indexOf(tag), 1);
		else
			tid.tags.push(tag);

		if(refreshTagListIfNotClosing) return refreshTagListIfNotClosing();
		
		store.saveModifiedTiddler(tid);
		story.refreshTiddler(tid.title, null, true);
		if(doAutoSave)
			autoSaveChanges();
		
		if(whereToScroll)
			window.scrollTo(0,ensureVisible(whereToScroll));
		clearSelected();
	};

	// define the onclick handlers
	var onTagClick = wrapNoClose(function(e)
	{
		var tag = this.getAttribute("tag"),
		    shiftWasHold = (e || window.event).shiftKey;
		toggleTag(tid,tag,shiftWasHold ? this.refreshTagList : null);
		return false;
	});
	var onClick = wrapNoClose(function()
	{
		// form the list of the tags to choose from, all tags to start somewhere
		var availableTags = [],
		    allTags = store.getTags(),
		    noTagsMsg = "No tags found by the provided criterion";
		for(var i = 0; i < allTags.length; i++)
			availableTags.push(allTags[i][0]);

		// build the list of tags and labels
		var tagsToWorkWith = store.filterTiddlers(tagsSet);
		for(i = 0; i < tagsToWorkWith.length; i++)
			tagsToWorkWith[i] = tagsToWorkWith[i].title;
		var menuTags = [], menuLabels = [], menuItems = [], t;
		for(i = 0; i < tagsToWorkWith.length; i++) {
			t = tagsToWorkWith[i];
			menuItems.push({
				tag: t,
				label: t // '[x] ' or '[ ] ' addition is now defined via css
			})
		}

		// create the popup menu
		var popup = Popup.create(this), li, tagButton;

		// arbitrary tag toggler
		li = createTiddlyElement(popup, "li");
		var newTagField = createTiddlyElement(li, "input", null, "newTagInput", { type: "text" });
		newTagField.onclick = wrapNoClose(); // prevent the popup from closing on click here

		var selectedTagIndex = 0, // 0 means "not selected"
		    selectNextTag = function()
		    {
			var nextListItem = popup.childNodes[selectedTagIndex+1];
			if(!nextListItem) return // out of boundaries, won't move

			jQuery(popup).children().children().removeClass("selectedTag");
			selectedTagIndex++;
			nextListItem.childNodes[0].classList.add("selectedTag");
		    },
		    selectTag15ahead = function()
		    {
			var numberOfItems = jQuery(popup).children().length; // tags+1
			selectedTagIndex  = Math.min(selectedTagIndex + 15, numberOfItems - 1);
			var newListItem   = popup.childNodes[selectedTagIndex];
			jQuery(popup).children().children().removeClass("selectedTag");
			newListItem.childNodes[0].classList.add("selectedTag");
		    },
		    selectPrevTag = function()
		    {
			var prevListItem = popup.childNodes[selectedTagIndex - 1];
			if(selectedTagIndex == 0) return;

			jQuery(popup).children().children().removeClass("selectedTag");
			selectedTagIndex--;
			if(selectedTagIndex == 0) return; // don't color the main field
				//# or do so but color it in the beginning as well
			prevListItem.childNodes[0].classList.add("selectedTag");
		    },
		    selectTag15back = function()
		    {
			var numberOfItems = jQuery(popup).children().length; // tags+1
			selectedTagIndex  = Math.max(selectedTagIndex - 15, 0);
			var newListItem   = popup.childNodes[selectedTagIndex];
			jQuery(popup).children().children().removeClass("selectedTag");
			if(selectedTagIndex > 0)
				newListItem.childNodes[0].classList.add("selectedTag");
		    },
		    selectTag = function(tag)
		    {
			var button = jQuery(popup).children().children("*[tag='" + tag + "']");
			if(!button.length) return;

			button.addClass("selectedTag");
			// get index, set selectedTagIndex
			selectedTagIndex = jQuery(popup).children().children().index(button)
				- 1;
		    },
		    getSelectedTagButton = function() {
			if(selectedTagIndex == 0) // fosuced on new tag field
				return null;
			return popup.childNodes[selectedTagIndex].childNodes[0];
		    },
		    toggleSelected = function(goOnToggling)
		    {
			var button = getSelectedTagButton();
			if(!button) return;
			tag = button.getAttribute("tag");
			fieldValue = newTagField.value;
			toggleTag(tid,tag,goOnToggling ? refreshTagList : null);

			newTagField.value = fieldValue;
			newTagField.select();
			selectTag(tag);
			return tag;
		    },
		    toggleNew = function(goOnToggling) {

			var tag = jQuery.trim(newTagField.value), isNewTag = true, i;

			// add the tag to the list if it's totally new:
			for(i = 0; i < menuItems.length; i++)
				if(menuItems[i].tag == tag)
					isNewTag = false;
			if(isNewTag)
				menuItems.push({ tag: tag, label: tag });

			toggleTag(tid, tag, goOnToggling ? refreshTagList : null);
		    };

		// push the button to apply/click elsewhere to cancel..
		createTiddlyButton(li, "toggle", "toggle the entered tag in the tiddler",
						toggleNew, "button tagAdderButton");
		// ..or use keyboard (see below)

		// tags from the set
		var refreshTagList = function()
		{
//console.log("caller is " + arguments.callee.caller.toString());
			// clear the list (but don't remove the first item)
			while(popup.childNodes[1])
				popup.removeChild(popup.childNodes[1]);
			newTagField.focus();
			selectedTagIndex = 0;

			// refill the list
			if(menuItems.length == 0) {
				createTiddlyText(createTiddlyElement(popup, "li"), noTagsMsg);
				return;
			}

			var fieldValueLowered = newTagField.value.toLocaleLowerCase(),
			    sortedMenuItems = menuItems.concat([]).sort(function(a, b){
				if(!fieldValueLowered) return 0;
				// store where the value from the field starts in the tag
				a.index = a.tag.toLocaleLowerCase().search(fieldValueLowered);
				b.index = b.tag.toLocaleLowerCase().search(fieldValueLowered);
//				if(tid.tags.contains(a.tag) && a.index != -1) a.index = -2;
//				if(tid.tags.contains(b.tag) && b.index != -1) b.index = -2;
				return a.index > b.index;
			    }),
			    item;

			for(i = 0; i < sortedMenuItems.length; i++)
			{
				item = fieldValueLowered ? sortedMenuItems[i] : menuItems[i];
				if(fieldValueLowered && item.index == -1) continue;

				li = createTiddlyElement(popup, "li");
				tagButton = createTiddlyButton(li, item.label,
					"toggle '" + item.tag+"'", onTagClick, "button tag"+
					(tid.tags.contains(item.tag)? "" : "Not") + "Present");
				tagButton.setAttribute("tag", item.tag);
				tagButton.refreshTagList = refreshTagList;
			}
		};
		refreshTagList();

		// show the popup menu
		Popup.show("bottom","left");

		// support keyboard navigation
		jQuery(newTagField).bind('input',function(e)
		{
			refreshTagList();
		});
		jQuery(newTagField).bind('keydown', function(e)
		{
			goOnToggling = e.shiftKey;
			if(e.which == $enter) {
				toggleSelected(goOnToggling) || toggleNew(goOnToggling);
				if(!goOnToggling) Popup.remove();
//document.getElementById("displayArea").focus()
				return;
			}
			if(e.key === 'Escape') {
				if(whereToScroll)
					window.scrollTo(0, ensureVisible(whereToScroll));
				if(e.shiftKey) {
					returnItemMenuSelection();
					Popup.remove(1);
//# make sure doesn't get propagated?
				} else {
					clearSelected();
					Popup.remove();
				}
//document.getElementById("displayArea").focus()
			}
		});
		jQuery(newTagField).bind('keydown', function(e)
		{
			if(e.which == $down && !e.ctrlKey)
				selectNextTag();
			if(e.which == $up && !e.ctrlKey)
				selectPrevTag();
			if(e.which == $pgDn || (e.which == $down && e.ctrlKey))
				selectTag15ahead();
			if(e.which == $pgUp || (e.which == $up && e.ctrlKey))
				selectTag15back();
			if(selectedTagIndex)
				window.scrollTo(0, ensureVisible(getSelectedTagButton()));
			else
				window.scrollTo(0, ensureVisible(newTagField));
		});
	});

	// create the button
	whereToScroll = createTiddlyButton(place, label, tooltip, onClick, "button tagTogglerButton");
}

// set styling
config.shadowTiddlers["TagAdderStyleSheet"] = "/*{{{*/\n"+
	".tagPresent:before	{ content: \"[x] \"; }\n"+
	".tagNotPresent:before	{ content: \"[ ] \"; }\n"+
	".newTagInput		{ float: left; margin-right: 0.5em; }\n"+
	".tagAdderButton	{ text-align: center; }\n"+
	".selectedTag		{ color: blue !important; }\n"+
	"/*}}}*/";
store.addNotification("TagAdderStyleSheet", refreshStyles);
//}}}
/***
!!!Hijack forEachTiddler macro to enable the new params
***/
//{{{
// helper filter for hiding hidden tiddlers
config.filters.hideFromFet = function(results,match)
{
	var contextName = match[3], noContext = contextName == "-";
	for(var i = 0; i < results.length; i++)
		if(results[i].fields["hideInFet".toLowerCase()] &&
		   (results[i].fields["hideInFet".toLowerCase()] && noContext ||
		    results[i].fields["hideInFet".toLowerCase()]
							.split(" ").contains(contextName)))
			results.splice(i--,1);
	return results;
};

// hijack config.macros.forEachTiddler.parseParams so that it handles
//  "set", "sortable"/"sortableBy", "addAction", "dropAction", "switchAction" params
// the "params" array is not changed as its usages in parseParams don't need it,
//  same story for preParsedParams[i] (i > 0)
if(config.macros.forEachTiddler && !config.macros.forEachTiddler.hijacked_sortable)
{
//# check if the proper version of FET (1.3.0 or above) is used
	config.macros.forEachTiddler.hijacked_sortable = true;
	config.macros.forEachTiddler.oldFashionParams =
		config.macros.forEachTiddler.oldFashionParams.concat([
		"sortableBy", "addAction", "dropAction", "switchActions", "writeToList"]);

	config.extensions.ManualSortMacroPlugin = {
		orig_fet_parseParams: config.macros.forEachTiddler.parseParams
	};
	var origParse = config.extensions.ManualSortMacroPlugin.orig_fet_parseParams;
	config.macros.forEachTiddler.parseParams = function(preParsedParams,params)
	{
		// parse the "set" param
		var setDescription = getParam(preParsedParams,"set",""), filter, sortField,
		    setAddAction, setDropAction, cmd = config.macros.defineSet;
		if(setDescription) {
			if(!setDescription.contains("[")) {
				filter = "[set["+ setDescription +"]]"; // named set
				sortField = cmd.getSortFieldForNamedSet(setDescription);
				setAddAction = cmd.getAddToNamedSetAction(setDescription);
				setDropAction= cmd.getDropFromNamedSetAction(setDescription);
			} else {
				filter = "set: "+setDescription; // inline set
				var setDefinition = cmd.parseSetDefinition(setDescription);
				// don't overwrite setDefinition as it is passed to adder
				sortField = setDefinition.sortField;
				setAddAction = cmd.getAddToSetAction(setDefinition);
				setDropAction = cmd.getDropFromSetAction(setDefinition);
			}
			sortField = sortField || config.macros.itemMenu.defaultField;
		}

		// remember the filter (calc from both "set" and "filter" params)
		var filterParam = getParam(preParsedParams,"filter","") +
				 (setDescription ? " [hideFromFet[-]]" : "");
		if(filter && filterParam) {
			if(filter.indexOf("set: ") == 0)
				filter = filter + " modify: " + filterParam;
			else
				filter = filter + " " + filterParam;
		} else if(filterParam)
			filter = filterParam;
		if(!filter)
			return origParse.apply(this,arguments);

		// the "in", "where" params stay untouched; change the filter param
		preParsedParams[0]["filter"] = [filter];

		// hijack the "script" param (define the "editable" and "adder" helpers)
		//  for now, deprecated "insert" helper is supported (same as "editable")
		var usedScript = getParam(preParsedParams,"script",""),
		    insertDefinition = "var editable = "+
		        "function(container,params,defaultText,preprocessScript) {"+
			"container = container || 't';"+
			"params = params || '';"+
			"if(defaultText)"+
			"	params = 'showIfNoValue:\\''+defaultText+'\\' '+params;"+
			"if(preprocessScript)"+
			"	params = 'preprocess:\\''+preprocessScript+'\\''+params;"+
			"return '<<insertEditable tiddler:['+'['+tiddler.title+']] container:\"'+container+'\" '+(params||'')+'>>';"+
		    "}, insert = editable;";
		    adderDefinitionBegin = 'var adder = '+
			'function(args,label,orderMode,title) {'+
			'return "<<addTiddler"'+
			'	+(" label:\'"+(label || "+")+"\'")'+
			'	'+(setDescription ? ('+" set:\''+setDescription+'\'"') : ''),
		    adderDefinitionEnd =
			'	+(" title:\'"+(title || "")+"\'")'+ // empty by default
			'	+" "+(args||"")'+
			'	+">>"'+
		    '};',
		    adderDefinition = adderDefinitionBegin + adderDefinitionEnd,
			// unless sortableBy is defined, orderMode is ignored (see below)
		    fullScript = insertDefinition + adderDefinition + usedScript;

		preParsedParams[0]["script"] = [fullScript];

		// process and apply sortable/sortableBy params/sortField from set definition
		var sortableParamIndex = params.indexOf("sortable"),
		    justSortable       = sortableParamIndex != -1,
		    sortableBy         = getParam(preParsedParams,"sortableBy");

		if(params.contains("sortableBy") && !sortableBy // empty = default
		   || justSortable)
			sortableBy = config.macros.itemMenu.defaultField;

		 // support the deprecated {{{sortableBy '"orderCountName"'}}} syntax
		var fieldWithQuotsMatch = /^"(.+)"$/.exec(sortableBy);
		sortableBy = fieldWithQuotsMatch ? fieldWithQuotsMatch[1] : sortableBy;
		 // sortField can be defined directly or from the set (see above)
		sortField = sortableBy || sortField;

//# rethink from here: either move this stuff below actions parsing etc (more meaninglful)
//	or add "&& !setDescription" (this is to enable actions and other stuff for sets,
//	even if sortField is not defined)
//+ from here
		if(!justSortable && !sortField)
			return origParse.apply(this,arguments);

		// support the "extended scope for sorting"
//# is it extended or narrowed?
		var fieldAndFilterMatch = /^(\w+) (\[.+)$/.exec(sortField),
		    sortFilter = fieldAndFilterMatch ? fieldAndFilterMatch[2] : filter;
		sortField = fieldAndFilterMatch ? fieldAndFilterMatch[1] : sortField;

		// set the "sortBy" param
		var undefinedUp = true;
		var sortScript = "(function(){ var c = tiddler.getSCounter(\"" + sortField +
				"\"); return (c != 0 && !c)?" + (undefinedUp ? "-1" : "1000") + ": c; })()";
				// lists of 999+ tiddlers long are not supposed to be used with manual sorting

		preParsedParams[0]["sortBy"] = [sortScript];
		// the sortable/sortableBy part is left in preParsedParams[0] as is

		// extend the "adder" helper in the "script" param using specified sortField
		adderDefinition = adderDefinitionBegin +
			'	+(orderMode ? (" order:\''+sortField+','
					+filter.replace(/"/g,'\\"')
//# do smth about "'"s in filter (macro param parsing)
					+',"+orderMode+"\'") : "")'+
			adderDefinitionEnd;
		fullScript = insertDefinition + adderDefinition + usedScript;

		preParsedParams[0]["script"] = [fullScript];
//= up to here

		// for actions other than "write" (and "writeToList" ~action), do no more
		for(var knownActionName in config.macros.forEachTiddler.actions)
			if(knownActionName != "write" && params.contains(knownActionName))
				return origParse.apply(this,arguments);
		// in original FETP, that's "addToList" action only

		// parse the [SMP] actions-defining params
		var addAction     = getParam(preParsedParams,"addAction",setAddAction),
		    dropAction    = getParam(preParsedParams,"dropAction",setDropAction),
		    switchActions = preParsedParams ? (
				preParsedParams[0]["switchActions"] ?
					preParsedParams[0]["switchActions"].join(";;")
				: ""
			) : "";
		    // allow multiple switchActions params (but each must have a name..)

		var commonText = "description is expected behind";
		if(!addAction && !(addAction == "") && params.contains("addAction"))
			return { errorText: "An action "+commonText+" 'addAction'." };
		if(!dropAction && !(dropAction == "") && params.contains("dropAction"))
			return { errorText: "An action "+commonText+" 'dropAction'." };
		if(!switchActions && !(switchActions=="")&& params.contains("switchActions"))
			return { errorText: "An action(s) "+commonText+" 'switchActions'."};

		// parse [FET] action
		var action = "writeToList"; // default pseudo-action

		//  when action is not specified it is considered as writeToList with..
		var defaultText = '"["+"["+tiddler.title+"]]"';
//# unknown actions are considered as the default one.. which is bad for other extensions

		//  when "writeToList" is used, in fact it preparses the argument for "write"
		writeToListText = getParam(preParsedParams,"writeToList",defaultText);
		var writeText = '"| "+itemMenu()+" |"+(' +writeToListText+ ')+"|\\n"';

		//  when "write" is used, its argument is used after only "minimal"preparsing
		if(preParsedParams[0]["write"])
			action = "write";
		writeText = getParam(preParsedParams,"write",writeText);

		//  substitute all the "itemMenu()" expressions in the argument of "write"
		//   with their intended "meaning" (hence use non-greedy regexp)
		var itemMenuRegExp = /(.*?)itemMenu\(\)/g;
		var insertItemMenu = function($0,$1) {
			var escapedFilter = sortFilter.contains('"') ?
				('\\\''+sortFilter.replace(/"/g,'\\\"')+'\\\'') :
				("\\\""+sortFilter.replace(/'/g,"\\\'")+"\\\"") ;
// this is a semi-fix: tags with both ' and " will cause troubles with manual sorting..
// escape the actions as well

			return $1 + "\"<<itemMenu [[\"+tiddler.title+\"]] "+ escapedFilter +
				(sortField ? (" field:\\\""+ sortField +"\\\"") : "")+
				(addAction ? " addAction:\\\""+addAction+"\\\"" : "")+
				(dropAction ? " dropAction:\\\""+dropAction+"\\\"" : "")+
				(switchActions ? " switchActions:\\\""+switchActions+"\\\"" : "")+">>\"";
		}
		writeText = writeText.replace(itemMenuRegExp,insertItemMenu);

		//  change preParsedParams accordingly (use writeText, "write" action)
		preParsedParams[0]["write"] = [writeText];

		// change the begin argument (leave end, none, toFile parts unchanged)
		if(action == "writeToList")
			preParsedParams[0]["begin"] = [(preParsedParams[0]["begin"] ?
				preParsedParams[0]["begin"][0]:'""') + '+"|tableList|k\\n"'];

		// call the parser with the new arguments
		return origParse.apply(this,arguments);
	}
}
//}}}
/***
!!!addTiddler macro
***/
//{{{
config.macros.addTiddler = {
	handler: function(place, macroName, params, wikifier, paramString) {
		if(readOnly)
			return;

		// param parsing (partially taken from the newTiddler macro)
		params = paramString.parseParams("title", null, true, false, false);
		var title  = getParam(params, "title",  config.macros.newTiddler.title),
		    label  = getParam(params, "label",  config.macros.newTiddler.label),
		    prompt = getParam(params, "prompt", config.macros.newTiddler.prompt),
		    text   = getParam(params, "text", ""),
		    set    = getParam(params, "set", ""),
		    commonTags = [], t,

		    orderParts      = getParam(params,"order",""),
		    orderPartsMatch = /^(\w*),(.+),([\w\d\-]+(?:,\w+)?)$/ .exec(orderParts),
		    orderCounter    = orderPartsMatch ? orderPartsMatch[1] : undefined,
		    orderFilter     = orderPartsMatch ? orderPartsMatch[2] : undefined,
		    orderMode       = orderPartsMatch ? orderPartsMatch[3] : undefined,
		    orderParamDefault;
		if(orderMode) {
			orderPartsMatch = /^(\w+),(\w+)$/.exec(orderMode);
			orderMode = orderPartsMatch ? orderPartsMatch[1] : orderMode;
			orderParamDefault = orderPartsMatch ? orderPartsMatch[2] : undefined;
		}
		var cmd = config.macros.defineSet;
		// get addAction for the set and orderCounter
		if(set && cmd) {
			// set may be either set name or set definition
			if(!set.contains("[")) {
				orderCounter = orderCounter ||
					cmd.getSortFieldForNamedSet(set);
				var action = cmd.getAddToNamedSetAction(set);
			} else {
				var setDefinition = cmd.parseSetDefinition(set);
				orderCounter = orderCounter || setDefinition.sortField;
				var action = cmd.getAddToSetAction(setDefinition)
			}
		}

		for(t = 1; t < params.length; t++)
			if((params[t].name == "anon" && t != 1) || (params[t].name == "tag"))
				commonTags.push(params[t].value);
		if((orderCounter =="default" || orderCounter =="") && config.macros.itemMenu)
			orderCounter = config.macros.itemMenu.defaultField;

		// create button, attach params to it
		var btn = createTiddlyButton(place, label, prompt, this.onClick);
		btn.params =
		{
			title:		title,
			commonTags:	commonTags,
			addAction:	(set && cmd) ? action : "",
			text:		text,
			orderCounter:	orderCounter,
			orderFilter:	orderFilter,
			orderMode:	orderMode,
			orderParamDefault: orderParamDefault
		};
	},
	onClick: window.wrapNoClose(function()
	{
		// extract params
		var title = this.params.title,
		    text  = this.params.text,
		    tags  = [].concat(this.params.commonTags), // should be a new array
			//# do the same "copying" for fields, if are set here
		    addAction		= this.params.addAction,
		    orderCounter	= this.params.orderCounter,
		    orderFilter		= this.params.orderFilter,
		    orderMode		= this.params.orderMode,
		    orderParamDefault	= this.params.orderParamDefault,

		// create DOM
		    popup = Popup.create(this),
		    wrapper = createTiddlyElement(popup, "li"),
		    nameField = createTiddlyElement(wrapper, "input", null, "newTitleInput",{type:"text"});

		nameField.onclick = window.wrapNoClose();
		nameField.value = title;

		var cmi = config.macros.itemMenu,
		    addTidToSet = function(tiddler)
		    {
			if(!addAction || !cmi) return;
			cmi.parseAndApplyAction(tiddler, addAction, null, true);
		    };

		var createTheTiddler = function(goOnSelected)
		{
			var theTiddler = new Tiddler(jQuery.trim(nameField.value)),
			    modifier   = config.options.txtUserName;
			theTiddler.assign(null, text, modifier, null, tags);

			if(store.fetchTiddler(theTiddler.title) && !confirm("A tiddler named \""+theTiddler.title+"\" already exists. Do you want to overwrite it?"))
				return;
			addTidToSet(theTiddler);

			store.saveTiddler(theTiddler);

			if(orderCounter && Tiddler.prototype.setSCounter && cmi)
			{
				if(orderMode == "top")
				    cmi.moveToTop(orderFilter, theTiddler, orderCounter);
				if(orderMode == "bottom")
				    cmi.moveToBottom(orderFilter,theTiddler,orderCounter);
				var orderModeIndex = parseInt(orderMode);
				if(!isNaN(orderModeIndex))
				    cmi.moveToArbitraryPlace(orderFilter, theTiddler, orderCounter, orderModeIndex, true);
//  use:  orderParamDefault
			}

			// for compability with SetManagerPlugin (usage in itemMenus)
			if(cmi)
			{
				if(goOnSelected)
					cmi.ensureFocusOnCurrentlyDragged();
				else
					cmi.clearCurrentlyDragged();
			}

			// bad fix for SMP: if autoSaveChanges(); is called directly,
			// reselection doesn't work; but it's enough to set timeout
			// of 1 ms and the bug disappears
			setTimeout(autoSaveChanges, 1);
		};

		// process enter/esc key presses
		// compatible with SetManagerPlugin (for usage in itemMenus)
		nameField.onkeydown = function(ev)
		{
			var e = ev || window.event;
			if(e.which == $enter)
			{
				createTheTiddler(e.shiftKey);

				if(e.shiftKey)
					Popup.remove(1);
				else
					Popup.remove();

				window.preventOtherHandling(e);
			}
			if(e.key === 'Escape') {
				if(e.shiftKey) {
					if(cmi) cmi.ensureFocusOnCurrentlyDragged();
					Popup.remove(1);
				} else {
					if(cmi) cmi.clearCurrentlyDragged();
					Popup.remove();
				}
			}
		};

if(orderMode == "checkboxes") {
// add possibilities to put the tiddler on top/bottom of a certain list (certain orderCounter):
//    create 2 checkboxes (t: [] b: []), add .. behaviour
			createTiddlyText(popup,"t:");
			var checkBoxTop = createTiddlyElement(popup,"input","test"/*null*/,null,null,{
				type:'checkbox',
				value:false
// calc the value the way it should be calced (chkAddToTop, ..)
			});
			checkBoxTop.onclick = window.wrapNoClose(function(){
				checkBoxTop.setAttribute(!checkBoxTop.value);
// checkboxes should deactivate each other, ..
			});
// add the onclick handler (change .., no close)
createTiddlyText(popup,"b:");
config.macros.option.handler(popup, "option", null, wikifier, "chkAddToBottom");
checkBox = popup.lastChild;
checkBox.onclick = window.wrapNoClose(checkBox.onclick);
// this works, but the checkbox being checked/unchecked is not displayed unless the popup is reopened
// - try config.macros.option.genericCreate(place, type, opt, className, desc)
// process the orderCounter taken from the check box
}

		// "add" button
		createTiddlyButton(wrapper, "add", "add the tiddler", createTheTiddler);
		// cancel - on click elsewhere

		// show the popup menu, focus inside the text field
		Popup.show();
		nameField.focus();
		nameField.select()
	})
}
//}}}
/***
!!!insertEditable macro
***/
//{{{
// Sets the section value if it is present, appends otherwise
//  tip: if sectionName is "!!smth", then "!!!smth" is appended
//
Tiddler.prototype.setSection = function(sectionName,value)
{
	var beginSectionRegExp = new RegExp("(^!{1,6}[ \t]*" + sectionName.escapeRegExp() + "[ \t]*(\n|$))","mg"),
	    sectionTerminatorRegExp = /^!/mg,
	    match = beginSectionRegExp.exec(this.text);

	if(match) // edit existing section
	{
		var sectionTitle = match[1],
		    emptyAtEnd = match[2] != "\n",
		    beforeSection = this.text.substr(0,match.index),
		    sectionAndAfter = this.text.substr(match.index + match[1].length);

		match = sectionTerminatorRegExp.exec(sectionAndAfter);
		var afterSection = match ? sectionAndAfter.substr(match.index) : "";

		this.text = beforeSection + sectionTitle + (emptyAtEnd ? "\n" : "") + value
				+ (afterSection ? ("\n" + afterSection) : "");
	} else // add anew
		this.text = this.text + "\n!"+sectionName + "\n"+value;

	// setting dirty, notifying is not done here
};

// Sets the slice value if it is present, otherwise appends it as |name|value|
//  either after the last slice or to the beginning of the text (if no slices are present)
//
Tiddler.prototype.setSlice = function(sliceName,value)
{
	var replaceSliceSubPart = function(text,part,oldValue)
	{
		if(oldValue == value)
			return text;
		var eOldValue  = oldValue.escapeRegExp(),
		    eSliceName = sliceName.escapeRegExp();

		// "table" notation
		var simplifiedPattern = "^(.*"+eSliceName+".*\\|.*)"+eOldValue+"(.*\\|)$",
		    simplifiedRegExp = new RegExp(simplifiedPattern),
		    newPart = part.replace(simplifiedRegExp,function($0,$1,$2){
			return $1 + value + $2;
		    });
		if(newPart != part)
			return text.replace(part, newPart);

		// "sliceName: sliceValue" notation
		simplifiedPattern = "^(.*"+eSliceName+"\\:[\\s\\t])"+eOldValue+"(.*)$";
		simplifiedRegExp = new RegExp(simplifiedPattern),
		newPart = part.replace(simplifiedRegExp,function($0,$1,$2){
			return $1 + value + $2;
		});
		if(newPart != part)
			return text.replace(part, newPart);
	};
	// modification of TiddlyWiki.prototype.slicesRE to process "|..sliceName..||" syntax
	// empty slices in the "sliceName:" notation are not supported
	var re = /(?:^([\'\/]{0,2})~?([\.\w]+)\:\1[\t\x20]*([^\n]+)[\t\x20]*$)|(?:^\|\x20?([\'\/]{0,2})~?([^\|\s\:\~\'\/]|(?:[^\|\s~\'\/][^\|\n\f\r]*[^\|\s\:\'\/]))\:?\4[\x20\t]*\|[\t\x20]*([^\n\t\x20]?(?:[^\n]*[^\n\t\x20])?)[\t\x20]*\|$)/gm;
	re.lastIndex = 0;
	var m = re.exec(this.text);
	while(m) {
		if(m[2]) {
			if(m[2] == sliceName) {
				this.text = replaceSliceSubPart(this.text,m[0],m[3]);
				break;
			}
		} else {
			if(m[5] == sliceName) {
				this.text = replaceSliceSubPart(this.text,m[0],m[6]);
				break;
			}
		}
		m = re.exec(this.text);
	}

	if(!m || !m[2] && !m[5]) // if the slice is not present
	{
		// append after the last slice/to the start of text (adapted from GridPlugin)
		var matches = this.text.match(re),
		    lastSlice = matches ? matches[matches.length-1] : null,
		    where = lastSlice ? this.text.indexOf(lastSlice)+lastSlice.length : 0;

		this.text = this.text.substr(0,where)+
			(lastSlice ? '\n|%0|%1|' : '|%0|%1|\n').format(sliceName,value)+
			this.text.substr(where);
	}

	// recalc "stored" slices for this tiddler:
	delete store.slices[this.title];

	// setting dirty, notifying is not done here
};

config.macros.insertEditable = {
	handler: function(place,macroName,params,wikifier,paramString,tiddler)
	{
		// parse and attach params to DOM
		var pParams   = paramString.parseParams("tiddler",null,true,false,true),
		    cell      = params.contains("cell"),
		    fill      = cell || params.contains("fillElement"),
		    wrapper   = fill ? place : createTiddlyElement(place,"span"),
		    tidName   = getParam(pParams,"tiddler",""),
		    partName  = getParam(pParams,"container","t"),
		    applyOnEnter = getParam(pParams,"applyOnEnter"),
		    selectOptions = getParam(pParams,"options","[]");
		eval('selectOptions = '+selectOptions);

		wrapper.options = {
			tiddler:	tidName ? store.fetchTiddler(tidName) : tiddler,
//# store tiddler name so that we can create it if it doesn't exist
			part:		partName,
			viewType:	getParam(pParams,"viewType","wikified"),
			selectOptions:	selectOptions,
			withButton:	params.contains("button") || window.isOpenedOnTouchScreen(), // default for touch screens
			defaultShowText:  getParam(pParams,"showIfNoValue"),
			preprocess:	getParam(pParams,"preprocess",""),
			size:		getParam(pParams,"size",""),
			fill:		fill,
			transparentEmpty: cell || params.contains("transparentEmpty"),
			saveOnApply:	params.contains("saveOnApply"),
			applyOnEnter:	(partName[0] == ":") || (partName[0] == "!") ||
					(applyOnEnter === undefined ?
					params.contains("applyOnEnter") : applyOnEnter),
			keepOnBlur:	params.contains("keepOnBlur"),
			noNotify_partial: cell || params.contains("noNotify"),
			noedit:		params.contains("noedit")
		};

		// when "transcluding", use same stack and nest limit to prevent looping
		var stack = config.macros.tiddler.tiddlerStack,
		    title = wrapper.options.tiddler ? wrapper.options.tiddler.title : '';
		if(!config.options.transclusionsNestLimit && (stack.indexOf(title) != -1)
		   || config.options.transclusionsNestLimit &&
		     (stack.length+1 > config.options.transclusionsNestLimit))
		{
			createTiddlyError(wrapper,
				"insertEditable: maximum transcluding depth reached");
			return;
		}
		stack.push(title);
		try {

			this.turnViewMode(wrapper);

		} finally {
			stack.pop();
		}
	},
	getData: function(tiddler, part)
	{
		var partName = part.substr(1);
		switch(part[0]) {
			case "t":
				return tiddler.text || "";
			case "!":
				return tiddler.title;
			case "#":
				return (tiddler.getSection ? tiddler.getSection(partName)
					: store.getTiddlerText(tiddler.title+"##"+partName))
						|| "";
			case ":":
				return (tiddler.getSlice ? tiddler.getSlice(partName)
					: store.getTiddlerText(tiddler.title+"::"+partName))
						|| "";
			case "@":
				return store.getValue(tiddler,partName) || "";
		}
	},
	setData: function(tiddler, part, value, noNotify_partial)
	{
		var partName = part.substr(1);
		//if(!tiddler)
			//# deal with the case when tiddler doesn't exist yet

		switch(part[0]) {
			case "t":
				store.saveTiddler(tiddler,null,value);
				break;
			case "!":
				store.saveTiddler(tiddler,value); // requires my fix to .sT
				break;
			case "#":
				tiddler.setSection(partName,value);
				store.setDirty(true);
				// refresh display of the corresponding tiddler:
				if(!noNotify_partial) store.notify(tiddler.title,true);
				break;
			case ":":
				tiddler.setSlice(partName,value);
				store.setDirty(true);
				if(!noNotify_partial) store.notify(tiddler.title,true);
				break;
			case "@":
				store.setValue(tiddler,partName,value);
				break;
		}
		//# change the "modifier/d" fields?
	},
	turnViewMode: function(place)
	{
		if(!place.options.tiddler && place.options.part[0] != "c")
			return;
//# wtf is this "c" as first character?
		//# may be add warning; also, do the same for unsupported container types

		var value  = this.getData(place.options.tiddler, place.options.part) ||
				place.options.defaultShowText || "",
		    fill   = place.options.fill,
		    noedit = place.options.noedit,
		    classEmpty = place.options.transparentEmpty ? "transparentEmptyViewer"
				 : (place.options.viewType == "select"? "":"emptyViewer");
 		var preprocessScript = place.options.preprocess;
 		if(preprocessScript) {
			var fullScript =
				"place.options.preprocessFunc = function(text){"+
				"var q = \"'\"\n"+
				preprocessScript+
				"\nreturn text;};";
			eval(fullScript);
			value = place.options.preprocessFunc(value);
		}
 		if(fill)
			place.style.padding = (place.options.initialPadding !== undefined)
 				? place.options.initialPadding
				: place.style.padding;
		if(fill && !noedit && !value)
			place.classList.add(classEmpty);
		var html = '<span'+
		(!fill && !value ? ' class="'+classEmpty+'"' : '')+
		'></span>';
		place.innerHTML = html;
		place.onclick = function(e)
		{
			// prevent editor-containing popup closing etc:
			if(e.stopPropagation) e.stopPropagation();
			e.cancelBubble = true;
			// prevent editing when clicking links, buttons, etc; if noedit:
			if(noedit || e.target.tagName.match(new RegExp("^(a|img|button|"+
			    "input|textarea|select|option|canvas)$", "i")))
				return true;
			// prevent editing on text selection
			var selection = "";
			if (window.getSelection) {
				selection = window.getSelection().toString();
			}
			// cross-browser enough https://stackoverflow.com/q/10478/3995261
			if(selection) return true;

			place.classList.remove(classEmpty);
			config.macros.insertEditable.turnEditMode(this);
			return false;
		}
		var container = fill ? place : place.firstChild;
		switch(place.options.viewType) {
			case "plain":
				createTiddlyText(container,value);
				break;
			case "html":
				container.innerHTML = value;
				break;
			case "select":
				var select = createTiddlyElement(container, 'select'),
				    options = place.options.selectOptions, i,
				    selectedIndex = -1;

				// populate with options
				for(i = 0; i < options.length; i++) {
					createTiddlyElement(select,'option',null,null,
					    options[i].label, {value:options[i].option});
					if(options[i].option == value)
						selectedIndex = i;
				}
				if(selectedIndex != -1)
					select.selectedIndex = selectedIndex;
				else {
					// indicate that value is not a suggested option
					jQuery(select).addClass('unsuggested');
					jQuery(select).find('option').addClass('normal');
					// but show it as a selected "option"
					var currentOption = jQuery('<option>'+ value +'</option>')
						.prependTo(select);
					select.selectedIndex = 0;
				}

				select.onchange = function(e) {

					// once wrong option is changed to an expected one
					if(jQuery(select).hasClass('unsuggested')
					   && select.selectedIndex != 0)
						jQuery(select).removeClass('unsuggested')
							.find(':first-child').remove();
					
					var newValue = e.target.value;
					config.macros.insertEditable.setData( place.options.tiddler, place.options.part, newValue, place.options.noNotify_partial);
					if(place.options.saveOnApply)
						autoSaveChanges();
				};
				break;
			case "wikified":
			default:
				wikify(value,container,null,place.options.tiddler);
		}
	},
	turnEditMode: function(place)
	{
		// add the edit area
		var initialValue = this.getData(place.options.tiddler,place.options.part),
		    rowslimit = 1,
		    size = place.options.size;
		place.innerHTML = (size == "minimal" || size == "min") ?
			  '<input type="text" class="mini-inline-editor"></input>'
			:('<textarea class="inline-editor"' +
			  ' style="height: '+rowslimit+'.1em;'+
			  (size == "max" ? 'width:100%;' : 
			   (place.options.fill ? 'width:98%;' : ''))+'"' +
			  '></textarea>');
		if(place.options.fill) {
			place.options.initialPadding = place.style.padding;
			place.style.padding = "0";
		}
		var editarea = place.firstChild, button;
		editarea.value = initialValue;

		// define apply/cancel helpers
		var turnOffEditing = function()
		    {
			config.macros.insertEditable.turnViewMode(place);
			jQuery('html').off("click",onClick);
		    },
		    applyChanges = function()
		    {
			config.macros.insertEditable.setData(place.options.tiddler, place.options.part, editarea.value, place.options.noNotify_partial);
			turnOffEditing();
			if(place.options.saveOnApply)
				autoSaveChanges();
		    };

		if(place.options.withButton)
			button = createTiddlyButton(place,"save",null,applyChanges);
		// on click outside the edit area, switch to the view mode
		var onClick = function(e) {
			if(place.options.keepOnBlur || jQuery(e.target).parents()/*and self*/.addBack().is(place))
				return;
			turnOffEditing();
		};
		place.onclick = null;
		jQuery('html').click(onClick);
//# avoid creating a "global" handler? (to remove extra code from .onkeydown handler)
		editarea.onkeydown = function(ev)
		{
			var e = ev || window.event;
			if(e.key === 'Escape')
				// for now, don't check if was changed
				turnOffEditing()
			if((e.which == $enter) && (e.ctrlKey || place.options.applyOnEnter))
			{
				applyChanges();
				e.cancelBubble = true;
				if(e.stopPropagation) e.stopPropagation();
				return false;
			}
		};
		editarea.oninput = function(){
			adjustHeightToContent.apply(this);
			if(editarea.value != initialValue)
				editarea.classList.add('inline-editor_dirty');
			else
				editarea.classList.remove('inline-editor_dirty')
		}

		// set initial "state"
		editarea.focus();
		if(!window.isOpenedOnTouchScreen()) // with FF for Android, better not to
			editarea.select();
		adjustHeightToContent.apply(editarea);
	}
};

// define styles
setStylesheet(
	'.emptyViewer { color: #ddd; background-color: #ddd; }\n'+
	'.transparentEmptyViewer { color: rgba(0,0,0,0); background-color: rgba(0,0,0,0); }\n'+
	'.emptyViewer:before, .transparentEmptyViewer:before { content: "__" }\n'+
	'.mini-inline-editor { width: 1.5em; }\n'+
	'@-moz-document url-prefix() {.inline-editor { font-family: Consolas !important; font-size: 100% !important; }}\n'+
	'.unsuggested { background-color: #ffdddd; }\n'+
	'option.normal { background-color: white; }',
"StyleSheetInsertEditable");
//# think about better styling (no "__" when copying)

// =========== Extras ===========
// hijack edit macro to make tiddler titles editable inline
(function(){
/* config.macros.chkEditableTitles = (config.macros.chkEditableTitles === undefined) ?
	!('ontouchstart' in window) : config.macros.chkEditableTitles;
	// default for non-touch screens for now

 if(!config.macros.chkEditableTitles)
	return;

 config.macros.view.iep_orig_handler = config.macros.view.handler;
 config.macros.view.handler = function(place,macroName,params,wikifier,paramString,tiddler) {

	if(readOnly || !(tiddler instanceof Tiddler) || tiddler.isReadOnly() ||
	   params[0] != "title" || !place.classList.contains("title"))
	// the last is a hack for avoiding insertEditable in the <<list>> macro
		return this.iep_orig_handler.apply(this,arguments);

	wikify('<<insertEditable container:"!" size:max viewType:plain>>',place,null,tiddler);
	//story.setDirty(tiddler.title,true); ?
 };
*/
})()
//}}}
/***
!!!Sets API and macros
***/
//{{{
// overwrite filterTiddlers to enable different kinds of hijacking
//# to be incorporated into the core
TiddlyWiki.prototype.filterTiddlers = function(filter,results)
{
	var re = /([^\s\[\]]+)|(?:\[([ \w\.\-]+)\[([^\]]+)\]\])|(?:\[\[([^\]]+)\]\])/mg;

	results = results || [];
	var match, handler;
	if(filter)
		while(match = re.exec(filter)) {
			handler = ( match[1] || match[4] ) ? 'tiddler' :
					config.filters[match[2]] ? match[2] : 'field';
			results = config.filters[handler].call(this,results,match);
		}

	return results;
};

// extendable set of elementary sets definitions
// "?" is for the belongCheck, "+" - for addAction getter, "-" - for dropAction getter
config.elementarySets =
{
	tiddler: {
		"?": function(title,tiddler) {
			return tiddler.title == title;
		},
		"+": function(title) {
			return ""; // no action for now
		},
		"-": function(title) {
			return ""; // no action for now
		}
	},
	tag: {
		"?": function(tagName,tiddler) {
			return tiddler.tags.contains(tagName);
		},
		"+": function(tagName) {
			//# check the absence of ",,","::",";;"
			return "+tag.."+tagName;
		},
		"-": function(tagName) {
			//# check the absence of ",,","::",";;"
			return "-tag.."+tagName;
		}
	}
}

config.macros.defineSet =
{
	sets: {},

	add: function (setName, setDefinition, setTags)
	{
		if(this.sets[setName])
			displayMessage("the set \""+setName+"\" will be redifined");
		//# this behaviour may be changed if necessary

		this.sets[setName] = { definition: setDefinition, tags: setTags };
	},

	// this returns a function(tiddler) which checks if the tiddler is in the set;
	//  if there's some "do" parts, it ignores them if forceFunc and
	//  returns an array of tiddlers instead of a function otherwise
	//
	getIsInSet: function (setDefinition,forceFunc)
	{
		var returnFunc = true, definedPart, resultRecursive,
		    parts = setDefinition.parts, type, i,
		    singleTokenRE = /(?:(\w*)\[((?:[^\]]|(?:\]\]))*)\])/,
		    tokenMatch, tokenType, tokenValue;
//# precalc setDefinition.parts[i].definedPart in .parseSetDefinition? (although func/not func stuff ..)

		// for each part..
		for(i = 0; i < parts.length; i++)
		{
			definedPart = null;

			// process tokens first
			if(parts[i].token)
			{
				tokenMatch = singleTokenRE.exec(parts[i].token);
//# single tokens first; .oO when multiple are needed, implement parsing
				if(!tokenMatch) continue;
				tokenType  = tokenMatch[1];
				tokenValue = tokenMatch[2];
				definedPart = {
					type:  tokenType,
					value: tokenValue
				};

				// process elementaries with corresponding handlers
				if(config.elementarySets[tokenType]) {
					definedPart.checkTiddler = function(tiddler) {
						return config.elementarySets[this.type]["?"]
							(this.value, tiddler);
					};
					parts[i].definedPart = definedPart;
					continue;
				}

				//# process non-elementary tokens,
				//  for "set" and "setsTagged", launch recursively,
				//  if returns an array of tids instead of a function
				//  and !forceFunc, set returnFunc = false
			// in contrast to inline sets, named sets can cause infinite loops..
			}

			// next, process "sets" (defined for brackets)
			if(parts[i].set)
			{
				resultRecursive = this.getIsInSet(parts[i].set,forceFunc);
				parts[i].definedPart = resultRecursive instanceof Function ?
					{ checkTiddler: resultRecursive } : resultRecursive;
				// in the latter case resultRecursive is an array with tids
				continue;
			}

			//# for each "do" if !forceFunc, /skip/ it;
			//  set returnFunc = false otherwise
		}

		if(returnFunc) {
		// combine checks from parts via setDefinition.combine (example: "+4*1-3+5")
			return function(tiddler) {
				var re = /([\+\-\*])(\d+)/g, m, isGood = false, val;
//console.log(".combine: "+setDefinition.combine+", parts:");console.log(parts);
//console.log("find definedParts in parts[i].definedPart");
				while(m = re.exec(setDefinition.combine)) {
					i = parseInt(m[2]);
					val = parts[i].definedPart.checkTiddler(tiddler);
//console.log(i+": val is "+val+", isGood is "+isGood);
					switch(m[1])
					{
						case "+": isGood = isGood ||  val; break;
						case "*": isGood = isGood &&  val; break;
						case "-": isGood = isGood && !val; break;
					}
				}
				return isGood;
			};
		} //else
			//# build and return an array of tiddlers
	},
	getIsInNamedSet: function (setName,forceFunc)
	{
		var set = this.sets[setName];
		if(!set) return null;
		return this.getIsInSet(set.definition,forceFunc);
	},
	getSetTiddlers: function (setDefinition,results)
	{
		results = results || [];

		var check = this.getIsInSet(setDefinition);
		if(!check)
			return results;

		var newTids = [];
		if(check instanceof Function)
			store.forEachTiddler(function(tName,tiddler) {
				if(check(tiddler)) newTids.push(tiddler);
			});
		else
			// check is not a function, but an array of tiddlers
			newTids = check;

		if(setDefinition.sortField) {
			var getComparableCounter = function(tiddler)
			{
				var c = tiddler.getSCounter(setDefinition.sortField);
				return (c != 0 && !c)? -1 : c; // undefined up
			};
			newTids.sort(function(tiddlerA, tiddlerB)
			{
				return getComparableCounter(tiddlerA)
					- getComparableCounter(tiddlerB);
			});
		}

		for(var i = 0; i < newTids.length; i++)
			results.pushUnique(newTids[i]);

		return results;
	},
	getNamedSetTiddlers: function (setName,results)
	{
		var set = this.sets[setName];
		if(!set) return results;
		return this.getSetTiddlers(set.definition,results);
	},
	calcActionStepsInDefinition: function (setDefinition)
	{
		var parts = setDefinition.parts, i,
		    singleTokenRE = /(?:(\w*)\[((?:[^\]]|(?:\]\]))*)\])/,
		    tokenMatch, tokenType, tokenValue;

		// for each part
		for(i = 0; i < parts.length; i++)
		{
			// create descriptions of 2 actions sequences:
			// one adds a tiddler to the set, another removes the tiddler from it

			// process tokens first
			if(parts[i].token)
			{
				tokenMatch = singleTokenRE.exec(parts[i].token);
//# single tokens first; .oO when multiple are needed, implement parsing
				if(!tokenMatch) continue;
				tokenType  = tokenMatch[1];
				tokenValue = tokenMatch[2];
				
				// process elementaries with corresponding handlers
				if(config.elementarySets[tokenType]) {
//# add "don't recalc if already calced"
					parts[i].addAction = config.elementarySets[tokenType]
						["+"](tokenValue);
					parts[i].dropAction= config.elementarySets[tokenType]
						["-"](tokenValue);
					continue;
				}

				//# process non-elementary tokens,
				//# ...
			}

			// next, process "sets" (defined for brackets)
			if(parts[i].set)
			{
//# add "don't recalc if already calced"
				parts[i].addAction = this.getAddToSetAction(parts[i].set);
				parts[i].dropAction= this.getDropFromSetAction(parts[i].set);
				continue;
			}

			//# do anything about "do"s?
		}
		return;
	},
	getAddToSetAction: function (setDefinition)
	{
		// combine the already calced actions into one
		var re = /([\+\-\*])(\d+)/g, m, i, partActions, actions = "";
		while(m = re.exec(setDefinition.combine))
		{
			i = parseInt(m[2]);
			partActions = setDefinition.parts[i];
			switch(m[1])
			{
				case "+":
					if(actions || !partActions.addAction) continue;
					// unless that's the ~first action~, do nothing
					// (we suppose that if one describes a set like
					// "this OR that", than add action adds to "this"
					actions = partActions.addAction;
				break;
				case "*":
					if(!partActions.addAction) continue;
					if(actions) actions += ",,";
					actions += partActions.addAction;
				break;
				case "-":
					if(!partActions.dropAction) continue;
					if(actions) actions += ",,";
					actions += partActions.dropAction;
				break;
			}
		}
		return actions;
	},
	getAddToNamedSetAction: function (setName)
	{
		var set = this.sets[setName];
		if(!set) return null;
		return this.getAddToSetAction(set.definition);
	},
	getDropFromSetAction: function (setDefinition)
	{
		// combine the already calced actions into one
		var re = /([\+\-\*])(\d+)/g, m, i, partActions, actions = "";
		while(m = re.exec(setDefinition.combine))
		{
			i = parseInt(m[2]);
			partActions = setDefinition.parts[i];
			switch(m[1])
			{
				// case "*": do nothing (if a tiddler is droped from "a",
				// it is dropped from "a OR b" 
				// case "-": same (consider "a" and "a AND NOT b")
				case "+":
					if(!partActions.dropAction) continue;
					if(actions) actions += ",,";
					actions += partActions.dropAction;
				break;
			}
		}
		return actions;
	},
	getDropFromNamedSetAction: function (setName)
	{
		var set = this.sets[setName];
		if(!set) return null;
		return this.getDropFromSetAction(set.definition);
	},
	getSortFieldForNamedSet: function (setName)
	{
		return this.sets[setName] ? this.sets[setName].definition.sortField : null;
	},

	parseSetDefinition: function (text)
	{
		var set = { parts: [], combine: null, sortField: null };

		// remember tokens (..[..]..[..]...), substitute them with their numbers
		var tokenRegExp = /(?:\w*\[(?:[^\]]|(?:\]\]))*\])+/, // "]]" = escaped "]"
		    tokenMatch, origTokenText, tokenText,
		    sortFieldRegExp = /sortField\[(.*)\]/, sortFieldMatch;

		while(tokenMatch = tokenRegExp.exec(text))
		{
			origTokenText = tokenMatch[0];
			tokenText = origTokenText[0]=="[" ?
				("tiddler"+origTokenText) : origTokenText;
			sortFieldMatch = sortFieldRegExp.exec(tokenText);
			if(sortFieldMatch) {
				text = text.replace(origTokenText,"");
				set.sortField = sortFieldMatch[1];
			} else {
				text = text.replace(origTokenText,set.parts.length);
				set.parts.push({ token: tokenText });
			}
		}

		// find first-level brackets, add definitions, substitute in text
		var openPosition = text.indexOf("("), i, level = 0, closePosition, setText;
		while(openPosition > -1)
		{
			// find closing bracket position
			level = 1; i = openPosition+1;
			while(level > 0) {
				if(text[i] == "(")
					level++;
				if(text[i] == ")")
					level--;
				i++;
			}
			closePosition = i;

			// add definition, parse it recursively, subsititute
			setText = text.substring(openPosition+1, closePosition-1)
					// substitute numbers in setText back with tokens:
					.replace(/\d+/g,function(match){
						return set.parts[parseInt(match)].token
					});

			set.parts.push({ set: this.parseSetDefinition(setText) });
			text = text.substring(0,openPosition) + (set.parts.length-1)
				+ text.substring(closePosition);

			// find next open
			openPosition = text.indexOf("(");
		}

		// find <num> DO <num>, add them to parts, substitute
		var doRegExp = /(\d+)\s+DO\s+(\d+)/m, doMatch, target, action;
		while(doMatch = doRegExp.exec(text))
		{
			action = parseInt(doMatch[2]); target = parseInt(doMatch[1]);
			action = set.parts[action].token;
			target = set.parts[target].token || target;

			// add the definition part, substitute the DO expression in the text
			set.parts.push({ do: action , to: target });
			text = text.replace(doMatch[0],set.parts.length-1);
		}

		set.combine = "+"+text.replace(/\s+AND\s+/g,"*")
				 .replace(/\s+NOT\s+/g,"-")
				.replace(/\s+OR\s+/g,"+")
				.replace(/^ +/,"").replace(/ +$/,"").replace(/ +/g,"+");

		this.calcActionStepsInDefinition(set);

		return set;
	},
	handler: function(place,macroName,params,wikifier,paramString,tiddler)
	{
		// parse params
		var parsedParams = paramString.parseParams("name",null,true,false,true),
		    setName = getParam(parsedParams,"name"),
		    setText = getParam(parsedParams,"tids"),
		    setSortField = getParam(parsedParams,"sortField",""),
		    setTagsLine = getParam(parsedParams,"tags",""),
		    setTags = setTagsLine.readBracketedList();
		if(!setName || !setText)
			return;
		if(setSortField) setText += " sortField["+ setSortField +"]";
		var setDefinition = this.parseSetDefinition(setText);

		// show macro text
		var w = wikifier, macroTWcode = w.source.substring(w.matchStart,w.nextMatch),
		    hide = getFlag(parsedParams, "hide", false) || params.contains('hide');
		if (!hide)
			createTiddlyText(createTiddlyElement(place,"code"),macroTWcode);

		// define the set
		this.add(setName, setDefinition, setTags);
	}
};
// if SharedTiddlersPlugin is installed, make sets "include-aware"
var stp = config.extensions.SharedTiddlersPlugin;
if(stp)
	stp.useForReallyEachTiddler(config.macros.defineSet,"getSetTiddlers");

// hijack filterTiddlers so that if there's "set:..." part with an optional terminator
//  ":set", then that part is parsed as a definition of a set
TiddlyWiki.prototype.ds_orig_filterTiddlers = TiddlyWiki.prototype.filterTiddlers;
TiddlyWiki.prototype.filterTiddlers = function(filter,results)
{
	var beginSetMark = "set:", endSetMark = " modify:";

	// set definition starts with "set:", if no such thing, use ordinary filtering
	if(filter.indexOf(beginSetMark) != 0)
		return this.ds_orig_filterTiddlers(filter,results);

	// add tiddlers from the set
	results = results || [];
	var modifyPos = filter.indexOf(endSetMark), filterAsWell = (modifyPos != "-1"),
	    setDef = filterAsWell ? filter.substring(4,modifyPos) : filter.substr(4),
	    tids = config.macros.defineSet.getSetTiddlers(
		config.macros.defineSet.parseSetDefinition(setDef),results);

	// if necessary, apply the additional filters, return
	if(!filterAsWell)
		return results;
	filter = filter.substr(modifyPos + endSetMark.length);
	return this.ds_orig_filterTiddlers(filter,results);
};
config.filters.set = function(results,match)
{
	var setName = match[3];
	return config.macros.defineSet.getNamedSetTiddlers(setName,results);
};

//-------------------------------------------------------------------------------
// wikify SetsList on startup
//
var readSetsList = function()
{
	if(!window.store)
		return setTimeout(readSetsList,100);

	var setsList = store.fetchTiddler("SetsList"),
	    setsListText = setsList ? setsList.text : "";

	if(setsListText)
		wikify(setsListText,document.createElement("div"),null,setsList);
};
setTimeout(readSetsList,100);
//# test why this first timeout is needed (copied from CTP, STP)
//}}}
SetManagerPlugin + SavingWithRenamingFix + SetFieldPlugin
<<include "node: ForEachTiddlerPluginRepo" filters:"[[SetManagerPlugin installer and updater]] [[SetManagerPlugin]] [[SetFieldPlugin]]" import:4 noRefresh>>
<<include "node: ForEachTiddlerPluginRepo" filters:"[[SetManagerPluginInfo]]" noRefresh>>

{{DDn{should be "include from here if unavailable from core tweaks node" instead}}}
<<include "node: CoreTweaksRepo" filters:"[[SavingWithRenamingFix]]" import:4 noRefresh>>
/***
|Requires|ForEachTiddlerPlugin SetManagerPlugin|
|Version|0.4|
этот плагин предназначен для дополнительных elementary sets, switch actions и др. необязательных расширений SetManagerPlugin
* ForEachTiddlerPlugin нужен, пока {{{.getSlice}}} и {{{.getSection}}} не определены в ядре

to do<<tiddler [[to do list template##main]] with:"set extras todo" with:orderSEtodo noedit>>
***/
//{{{
(function(){
if(!config.macros.itemMenu)
	return;

config.macros.itemMenu.actionStepsWithoutArguments["markChanged"] = function(tiddler)
{ // for coordinator
	tiddler.modifier = config.options.txtUserName;
	tiddler.modified = new Date();
};

config.elementarySets.hasPart =
{
	"?": function(param,tiddler)
	{
		var regExpText, re, type;

		switch(param.substr(0,2)) {
			case config.textPrimitives.sectionSeparator: // ##
				return !!(tiddler.getSection(param.substr(2)));
			case config.textPrimitives.sliceSeparator:   // ::
				return !!(tiddler.getSlice(param.substr(2)));
			case "@@":
				return !!(tiddler.fields[param.substr(2).toLowerCase()]);
			case "r@": // regExp (for tiddler.text) mode
			case "R@":
			case "t@": // title mode
			case "T@":
				regExpText = store.getTiddlerText(param.substr(2));
				type = param.substr(0,1);
				if(!regExpText) {
					if(type == "r" || type == "t")
						return true; // "forgiving mode", nothing is filtered out in this case
					else
						throw("RegExp for checkTiddler is not found in " + param.substr(2));
//# test this case out
				}
				// no break here
			case "r[":
			case "R[":
			case "t[":
			case "T[":
				if(!regExpText) {
					regExpText = param.substr(2);
					type = param.substr(0,1);
				}
				if(type == "r" || type == "t") {
					try {
						re = new RegExp(regExpText);
					} catch(e) {
						return false; // "forgiving mode"
					}
				} else
					re = new RegExp(regExpText);

				if(type == "r" || type == "R")
					return !!(tiddler.text.match(re));
				else
					return !!(tiddler.title.match(re));
		}
		return false;
	},
	"+": function(param) {
		return ""; // for now, no idea what to do here; this won't matter frequently
	},
	"-": function(param) {
		return ""; // for now, no idea what to do here; this won't matter frequently
	}
};

config.macros.itemMenu.actionStepsWithoutArguments["-tags"] = function(tiddler) {
	tiddler.tags = [];
};
config.elementarySets.taggedOnly = 
config.elementarySets.oTag = {
	"?": function(tag,tiddler) {
		return (tiddler.tags.length == 1) && (!tag || tag == tiddler.tags[0]);
	},
	"+": function(tag) {
		return "-tags,,+tag.."+tag;
	},
	"-": function(tag) { return "-tag.."+tag; }
};

config.elementarySets.unclassified = {
//# implemented "as was"; may be scanning the whole tree instead
	"?": function(metaTag,tiddler) {

		var tags = [], tag, i;
		for(i = 0; i < tiddler.tags.length; i++) {

			tag = store.fetchTiddler(tiddler.tags[i]);
			if(tag && tag.tags.contains(metaTag))
				return false;
		}
		return true;
	},
//# implement
	"+": function(metaTag) {
		return ""; /*<addToSetAction> - show "available" tags, ask for a tag to add
				(may be new; in that case create a tag-tid with metaTag) */
	},
	"-": function(metaTag) {
		return "" /*<dropFromSetAction> - remove tags tagged with metaTag*/;
	}
};

//config.macros.itemMenu.actionStepsWithArguments["<name>"] = function(tiddler,arg) {
//};
//config.macros.itemMenu.actionStepsWithoutArguments["<name>"] = function(tiddler) {
//};
//config.elementarySets["<name>"] = {
//	"?": function(param,tiddler) { return <isInSet> },
//	"+": function(param) { return <addToSetAction> },
//	"-": function(param) { return <dropFromSetAction> }
//}
})()
//}}}
SetsExtrasPlugin
<<include "node: ForEachTiddlerPluginRepo" filters:"[[SetsExtrasPlugin installer and updater]] [[SetsExtrasPlugin]] [[SetFieldPlugin]]" import:4 noRefresh>>
/***
|Description|Makes upgrading work ~correctly with (at least) Timimi or MTS 1.7.0 and above (tested on 2.6.5,2.9.2,2.9.3 → 2.9.3,2.9.4), adds optional upgrade autocheck on start; adds tiddlers and fields sorting so that the changes are easier to review|
|Source     |https://github.com/YakovL/TiddlyWiki_SimplifiedUpgradingPlugin/blob/master/SimplifiedUpgradingPlugin.js|
|Author     |Yakov Litvin|
|Version    |0.6.0|
|License    |[[MIT|https://github.com/YakovL/TiddlyWiki_YL_ExtensionsCollection/blob/master/Common%20License%20(MIT)]]|
Installation of this plugin is standard: create tiddler, paste this as text, tag with {{{systemConfig}}}, save, reload.

To start upgrading, use the usual way: open backstage, the "upgrade" tab and hit the "upgrade" button.

Configuration:
<<option txtWaitSavingSeconds>> "wait saving" interval (seconds) may need adjustments for big ~TWs (otherwise, you should check that after reloading the new version is opened: if not, try to reload again)
<<option chkReloadManually>> reload manually (don't reload automatically after saving upgraded TW)
<<option chkAutocheckUpgradeOnStart>> check for upgrades on start
***/
//{{{
config.options.txtWaitSavingSeconds = config.options.txtWaitSavingSeconds || "5"; // no handler for number options

// a fix for older TWs, like 2.7.1
if(config.macros.upgrade.source == 'http://tiddlywiki-releases.tiddlyspace.com/upgrade')
	config.macros.upgrade.source = 'https://classic.tiddlywiki.com/upgrade/'

var upgradingEventBus = {
	handlers: {},
	// no "off" method, no array of handlers for now
	on: function(name, handler) {
		this.handlers[name] = handler
	},
	fire: function(name, params) {
		if(this.handlers[name]) this.handlers[name](params)
	}
}

config.macros.simplifiedUpgrade = {
	lingo: {
		isBackupCreatedQuestion: "Have you made a backup?",
		makeBackupCall: "Please make sure you have a backup before upgrading",
		unsupportedMtsVersionMessage: "Simplified upgrading in MainTiddlySaver below 1.7.0 is not made to work properly, aborting now",
		failedToLoadCore: "Something went wrong when loading core!",
		simplifiedUpgradingDissallowed: "The new core indicates that simplified upgrading is dangerous, please use import of your TW into a new empty TW instead",
		versionNotNewer: "The available core is not newer than the current one",
		getUpgradeFinishedReloadMessage: function() {
			return "Upgrading finished, " + (config.options.chkReloadManually ?
				"reload page to have the changes applied" :
				"will reload page to have the changes applied")
		},
		upgradeMacro: {
			statusUpgrading: "building upgraded TW and saving...",
			statusUpgradedTwSaved: "upgraded TW saved, should reload now",
			getUpgradeAvailableMessage: function(version) {
				return "An upgrade to TiddlyWiki v" + formatVersion(version) + " is available"
			}
		}
	},
	start: function(newCoreString) {
		// don't upgrade without a backup
		if(!confirm(this.lingo.isBackupCreatedQuestion)) {
			alert(this.lingo.makeBackupCall)
			return
		}

		// once MTS supports upgrading, here we will check MTS version instead [or feature-detect]
		var isMainTiddlyServerUsed = !!window.saveOnlineChanges ||
			(window.tiddlyBackend && tiddlyBackend.version && tiddlyBackend.version.title == 'MainTiddlyServer')
		if(isMainTiddlyServerUsed) {
			// for now, we assume that 1.7.0 supports upgrading (this is a matter of testing), so we don't check tiddlyBackend.version.asString
			var doesMtsSupportUpgrading = !!window.tiddlyBackend
			if(!doesMtsSupportUpgrading) {
				alert(this.lingo.unsupportedMtsVersionMessage)
				return
			}
		}

		var me = this
		if(newCoreString) {
			this.proceedWithLoadedCore(newCoreString)
		}
		else this.getNewCore(function(newCoreString) {
			upgradingEventBus.fire("available-core-loaded")
			me.proceedWithLoadedCore(newCoreString)
		}, this.onCoreLoadFail)
	},
	// onSuccess(newCoreString), onProblem(jqXHR, textStatus, errorThrown)
	getNewCore: function(onSuccess, onProblem) {
		var up = config.macros.upgrade
		var url = up.getSourceURL ? up.getSourceURL() : config.options.txtUpgradeCoreURI || up.source
		ajaxReq({
			type: "GET",
			url: url,
			processData: false,
			success: onSuccess,
			error: onProblem
		})
	},
	onCoreLoadFail: function(jqXHR, textStatus, errorThrown) {
		upgradingEventBus.fire("available-core-loading-failed")
		alert(config.macros.simplifiedUpgrade.lingo.failedToLoadCore)
	},
	getSavingWaitMillisecondsInterval: function() {
		return 1000 * parseFloat(config.options.txtWaitSavingSeconds)
	},
	overrides: {},
	// main idea: make sure loadOriginal or its async analogs will return the new core, then just save
	proceedWithLoadedCore: function(newCoreString) {
		var me = config.macros.simplifiedUpgrade
		if(newCoreString.indexOf("simplifiedUpgradingDisallowed") != -1) {
			alert(me.lingo.simplifiedUpgradingDissallowed)
			return
		}
		var availableVersion = config.macros.upgrade.extractVersion(newCoreString)
		if(compareVersions(version, availableVersion) !== 1) {
			displayMessage(me.lingo.versionNotNewer)
			return
		}

		// MainTiddlyServer: avoid granulated saving (won't change core)
		me.overrides.chkAvoidGranulatedSaving = config.options.chkAvoidGranulatedSaving
		config.options.chkAvoidGranulatedSaving = true

		me.overrides.loadOriginal = loadOriginal
		loadOriginal = function loadOriginal(localPath, callback) {
			if(!callback) return newCoreString
			callback(newCoreString)
		}
		// MTS 1.7.0
		if(window.tiddlyBackend) {
			me.overrides.tiddlyBackend_loadOriginal = tiddlyBackend.loadOriginal
			tiddlyBackend.loadOriginal = function(onSuccess) {
				onSuccess(newCoreString)
			}
		}

		saveChanges()
		// restore overrides
		loadOriginal = me.overrides.loadOriginal
		if(me.overrides.tiddlyBackend_loadOriginal) tiddlyBackend.loadOriginal = me.overrides.tiddlyBackend_loadOriginal
		config.options.chkAvoidGranulatedSaving = me.overrides.chkAvoidGranulatedSaving

		// wait so that saving finishes
		setTimeout(function() {
			upgradingEventBus.fire("upgraded-tw-saved")
			me.finalize()
		}, me.getSavingWaitMillisecondsInterval())
	},
	finalize: function() {
		var me = config.macros.simplifiedUpgrade
		alert(me.lingo.getUpgradeFinishedReloadMessage())
		if(!config.options.chkReloadManually) {
			window.location.reload()
		}
	}
}

merge(config.macros.upgrade, config.macros.simplifiedUpgrade.lingo.upgradeMacro)

config.macros.upgrade.onLoadCore = function(status, params, responseText, url, xhr) {

	var me = config.macros.upgrade
	var w = params
	var errMsg = !status ? me.errorLoadingCore : undefined
	var newVer = me.extractVersion(responseText)
	if(!newVer) errMsg = me.errorCoreFormat
	if(errMsg) {
		w.setButtons([], errMsg)
		alert(errMsg)
		return
	}

	// the overridden bit
	var onStartUpgrade = function(e) {
		w.setButtons([], me.statusUpgrading)
		upgradingEventBus.on("upgraded-tw-saved", function() {
			w.setButtons([], me.statusUpgradedTwSaved)
		})
		config.macros.simplifiedUpgrade.start(responseText)
	}

	var step2 = [me.step2Html_downgrade, me.step2Html_restore, me.step2Html_upgrade][compareVersions(version, newVer) + 1];
	w.addStep(me.step2Title, step2.format([formatVersion(newVer), formatVersion(version)]));
	w.setButtons([
		{ caption: me.startLabel, tooltip: me.startPrompt, onClick: onStartUpgrade },
		{ caption: me.cancelLabel, tooltip: me.cancelPrompt, onClick: me.onCancel }
	])
}

var isBelow2_9_3 = compareVersions(version, { major: 2, minor: 9, revision: 3 }) === 1
var isAbove2_9_3 = compareVersions(version, { major: 2, minor: 9, revision: 3 }) === -1

// support upgrading regardless the whitespace after '{' (extra spaces were in 2.9._)
if(isBelow2_9_3) {
	config.macros.upgrade.extractVersion = function(upgradeFile) {
		var re = /version = \{\s*title: "([^"]+)", major: (\d+), minor: (\d+), revision: (\d+)(, beta: (\d+)){0,1}, date: new Date\("([^"]+)"\)/mg
		var m = re.exec(upgradeFile)
		return !m ? null : {
			title: m[1], major: m[2], minor: m[3], revision: m[4], beta: m[6], date: new Date(m[7])
		}
	}
}

// fix the bug introduced in 2.9.3 and fixed in 2.9.4 version
if(!isAbove2_9_3) {
	// not present before 2.9.2
	config.macros.upgrade.getSourceURL = function() {
		return config.options.txtUpgradeCoreURI || config.macros.upgrade.source
	}

	config.macros.upgrade.onClickUpgrade = function(e)
	{
		var me = config.macros.upgrade
		var w = new Wizard(this)
		if(window.allowSave && !window.allowSave()) {
			alert(me.errorCantUpgrade)
			return false
		}
		if(story.areAnyDirty() || store.isDirty()) {
			alert(me.errorNotSaved)
			return false
		}

		w.setButtons([], me.statusPreparingBackup)
		var localPath = getLocalPath(document.location.toString())
		var backupPath = getBackupPath(localPath, me.backupExtension)
		var original = loadOriginal(localPath)

		w.setButtons([], me.statusSavingBackup)
		var backupSuccess = copyFile(backupPath, localPath) || saveFile(backupPath, original)
		//# fails of backup saving with TF are not reported, resulting in empty TW after upgrade
		if(!backupSuccess) {
			w.setButtons([], me.errorSavingBackup)
			alert(me.errorSavingBackup)
			return false
		}
		w.setValue("backupPath", backupPath)

		w.setButtons([], me.statusLoadingCore)
		var sourceURL = me.getSourceURL()
		ajaxReq({
			type: "GET",
			url: sourceURL,
			processData: false,
			success: function(data, textStatus, jqXHR) {
				me.onLoadCore(true, w, jqXHR.responseText, sourceURL, jqXHR)
			},
			error: function(jqXHR, textStatus, errorThrown) {
				me.onLoadCore(false, w, null, sourceURL, jqXHR)
			}
		})
		return false
	}
}

// auto-checking available upgrade
config.macros.upgrade.init = function() {
	config.macros.simplifiedUpgrade.getNewCore(function(coreAsText) {
		var me = config.macros.upgrade
		var availableVersion = me.extractVersion(coreAsText)
		if(compareVersions(version, availableVersion) !== 1) return
		if(config.options.chkAutocheckUpgradeOnStart) {
			displayMessage(me.getUpgradeAvailableMessage(availableVersion))
		}
	})
}

if(!isAbove2_9_3) {
	SaverBase.prototype.externalize = function(store) {
		var results = [];
		var i, tiddlers = store.getTiddlers("title");
		if(!config.options.chkAvoidSortingAll) {
			tiddlers.sort(function(t1, t2) {
				return t1.title.localeCompare(t2.title)
			});
		}
		for(i = 0; i < tiddlers.length; i++) {
			if(!tiddlers[i].doNotSave())
				results.push(this.externalizeTiddler(store, tiddlers[i]));
		}
		return results.join("\n");
	};

	TW21Saver.prototype.externalizeTiddler = function(store, tiddler)
	{
		try {
			var usePre = config.options.chkUsePreForStorage;
			var created = tiddler.created;
			var modified = tiddler.modified;
			var tags = tiddler.getTags();
			var attributes =
				(tiddler.creator ? ' creator="' + tiddler.creator.htmlEncode() + '"' : "") +
				(tiddler.modifier ? ' modifier="' + tiddler.modifier.htmlEncode() + '"' : "") +
				((usePre && created == version.date) ? "" : ' created="' + created.convertToYYYYMMDDHHMM() + '"') +
				((usePre && modified == created) ? "" : ' modified="' + modified.convertToYYYYMMDDHHMM() + '"') +
				((!usePre || tags) ? ' tags="' + tags.htmlEncode() + '"' : "");
			//# todo: check if these changes (sort extended attributes so that the order is always the same) affect performance, commit
			var extendedAttributes = [];
			store.forEachField(tiddler, function(tiddler, fieldName, value) {
				if(typeof value != "string")
					value = "";
				// don't store fields from the temp namespace
				if(!fieldName.match(/^temp\./))
					extendedAttributes.push('%0="%1"'.format([fieldName, value.escapeLineBreaks().htmlEncode()]));
			}, true);
			if(!config.options.chkAvoidSortingAll) {
				extendedAttributes.sort();
			}
			//# avoid closing div tags for _
			return ('<div %0="%1"%2%3>%4</' + 'div>').format([
				usePre ? "title" : "tiddler",
				tiddler.title.htmlEncode(),
				attributes,
				' ' + extendedAttributes.join(' '),
				usePre ? "\n<pre>" + tiddler.text.htmlEncode() + "</pre>\n" : tiddler.text.escapeLineBreaks().htmlEncode()
			]);
		} catch (ex) {
			throw exceptionText(ex, config.messages.tiddlerSaveError.format([tiddler.title]));
		}
	};
}
//}}}