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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* html .tiddler {height:1%;}

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

hr {height:1px;}

dt {font-weight:bold;}

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

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

#contentWrapper .chkOptionInput {border:0;}

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


a {text-decoration:none;}

.externalLink {text-decoration:underline;}

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

.tiddler { padding: 1em; }

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

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

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

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

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

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

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

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

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

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

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

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

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

.whenBackstage {display:none;}
.backstageVisible .whenBackstage {display:block;}
/*}}}*/
/***
StyleSheet for use when a translation requires any css style changes.
This StyleSheet can be used directly by languages such as Chinese, Japanese and Korean which need larger font sizes.
***/
/*{{{*/
body {font-size:0.8em;}
#sidebarOptions {font-size:1.05em;}
#sidebarOptions a {font-style:normal;}
#sidebarOptions .sliderPanel {font-size:0.95em;}
.subtitle {font-size:0.8em;}
.viewer table.listView {font-size:0.95em;}
/*}}}*/
/*{{{*/
@media print {
  #mainMenu, #sidebar, #messageArea, .toolbar, #backstageButton, #backstageArea { display: none !important; }
  #displayArea { margin: 1em 1em 0em; }
}
/*}}}*/
<!--{{{-->
<div class='toolbar' role='navigation' macro='toolbar [[ToolbarCommands::ViewToolbar]]'></div>
<div class='title' macro='view title'></div>
<div class='subtitle'><span macro='view modifier link'></span>, <span macro='view modified date'></span> (<span macro='message views.wikified.createdPrompt'></span> <span macro='view created date'></span>)</div>
<div class='tagging' macro='tagging'></div>
<div class='tagged' macro='tags'></div>
<div class='viewer' macro='view text wikified'></div>
<div class='tagClear'></div>
<!--}}}-->
JumpKeysPlugin
ExtensionsExplorerPlugin
TiddlerInFilePlugin
/***
|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.

!!!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]].

***/
//{{{
// 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: {
		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
		}
	},
	//# use getAvailableRepositories to get lists of extensions
	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
	},
	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
				}
				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)
				}
			}

			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) {
		if(!extension) return
		if(extension.text) {
			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

		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?
				}
				// import preparation
				extensionTiddler.tags.pushUnique('systemConfig')
			break;

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

			//# add _ tag for themes?
		}

		// 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,
					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
//}}}
<<external [[JumpKeysPlugin]] file:"JumpKeysPlugin.js" plugin:true>>
/***
|''Name''|InnerExternalLinkPlugin|
|''Version''|0.9.1|
|''Author''|Yakov Litvin|
***/
//{{{
config.extensions.InnerExternalLinkPlugin_orig_createExternalLink = createExternalLink;
createExternalLink = function(place,url,label) {

	var permaLinkRegExp = /([^#]+)#(?:(?:([^%\[]+))|(?:\[\[([^\]]+)\]\])|(?:%5B%5B([^\]]+)%5D%5D))/,
		// better to use decoding
	    match = permaLinkRegExp.exec(url),
	    siteUrlText = store.getTiddlerText("SiteUrl"),
	    pageLocation = window.location,
	    urlRegExp = /^(([^:\/\?#]+):)?(\/\/([^\/\?#]*))?([^\?#]*)(\?([^#]*))?(#(.*))?/,
	    matched = false;

	if(match) {
		var bareUrl = match[1],
		    tiddlerName = match[4]? match[4] : (match[3]? match[3] : match[2]);
		tiddlerName = decodeURIComponent(tiddlerName);
	} else
		return config.extensions.InnerExternalLinkPlugin_orig_createExternalLink(place,url,label);

	if(siteUrlText == bareUrl)
		matched = true;

	match = urlRegExp.exec(pageLocation.toString());
	if(match[1]+match[3]+match[5] == bareUrl)
		matched = true;

	// may be additional normalizing (stripping the protocol etc, encoding/decoding) should be added

	if(matched)
		return createTiddlyLink(place, tiddlerName, label);
	else
		return config.extensions.InnerExternalLinkPlugin_orig_createExternalLink(place,url,label);
};
//}}}
[[JumpKeysPlugin]]
/***
|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]));
		}
	};
}
//}}}
to quickly navigate between tiddlers via keyboard and more
~JumpKeysPlugin
https://yakovl.github.io/TiddlyWiki_JumpKeysPlugin/
/***
|Description |Allows to store any number of tiddlers as external files and more|
|Source      |https://github.com/YakovL/TiddlyWiki_TiddlerInFilePlugin/blob/master/TiddlerInFilePlugin.js|
|Author      |Yakov Litvin|
|Version     |1.1.3*|
|License     |[[MIT|https://github.com/YakovL/TiddlyWiki_YL_ExtensionsCollection/blob/master/Common%20License%20(MIT)]]|
!!!Usage
Once the plugin is installed (copy - tag {{{systemConfig}}} - reload) storing tiddlers in files is done via 2 steps:
# list (describe) those in [[ExternalTiddlersList]] by writing {{{<<external>>}}} macros there
# if the file exists and the tiddler doesn't, reload TW (external tiddler will be loaded on startup);<br>if the tiddler exists and the file doesn't, just save your TW

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

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

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

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

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

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

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

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

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

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

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

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

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

	if(isAbsolutePath(subPath)) return subPath

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

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

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

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

		return config.macros.external.orig_deleteTiddler.apply(this, arguments)
	}
}
//}}}
|~ViewToolbar|closeTiddler closeOthers +editTiddler jump permalink > fields references deleteTiddler|
|~EditToolbar|+saveTiddler -cancelTiddler references deleteTiddler|