Welcome to TiddlyWiki created by Jeremy Ruston; Copyright © 2004-2007 Jeremy Ruston, Copyright © 2007-2011 UnaMesa Association
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>>
<!--{{{-->
<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>
<span class='siteSubtitle' refresh='content' tiddler='SiteSubtitle'></span>
</div>
<div class='headerForeground'>
<span class='siteTitle' refresh='content' tiddler='SiteTitle'></span>
<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>
<!--}}}-->
[[Tiddlyhost|https://tiddlyhost.com]] is a hosting service for ~TiddlyWiki.
[[KeyOptionExperiment]]
[[GettingStarted]]
|Source |https://github.com/YakovL/TiddlyWiki_ExtensionsExplorerPlugin/blob/master/ExtensionsCollection.txt|
|Description|This is a central collection for ExtensionsExplorerPlugin. It is meant to gather collections of existing extensions, and also to help new authors make their work more explorable.|
|Version |0.2.2|
Current status is "under construction", meaning that there's a lot of collections and extensions to add. Other things should be considered as well, like quality guidelines, handling forks, etc. Instructions for authors will be published in a separate readme and linked here.
//{{{
[
{
"url": "https://github.com/YakovL/TiddlyWiki_YL_ExtensionsIndex/blob/master/YLExtensionsCollection.txt",
"description": "Extensions created or heavily modified by Yakov Litvin",
"type": "collection"
},
{
"url": "https://github.com/YakovL/TiddlyWiki_abego/blob/master/maintained/AbegoCollection.txt",
"description": "Extensions created by Udo Borkowski",
"type": "collection"
},
{
"url": "https://github.com/YakovL/TiddlyWiki_Extensions/blob/master/TranslationsCollection.txt",
"description": "TiddlyWiki Translations",
"type": "collection"
},
{
"url": "https://github.com/YakovL/TiddlyThemes/blob/master/ThemesCollection.txt",
"description": "TiddlyWiki themes (see also https://yakovl.github.io/TiddlyThemes/)",
"type": "collection"
},
{
"url": "https://github.com/YakovL/TiddlyWiki_Extensions/blob/master/FND/SimpleSearchPlugin.js",
"description": "Displays search results as a simple list of matching tiddlers"
},
{
"url": "https://github.com/PengjuYan/TiddlyWiki_SwitchPalettePlugin/blob/master/SwitchPalettePlugin.js",
"description": "Switches among your color palettes"
},
{
"url": "https://github.com/wangyenshu/TiddlyWikiClassicPluginsArchives/blob/main/TiddlyWikiClassicPluginsArchives.txt",
"description": "Dedicated extensions archives, covering most of the existing plugins (collection per TW, like TiddlyTools or PeachTW)",
"type": "collection"
}
]
//}}}
/***
|Description|checks and reports updates of installed extensions on startup, introduces a macro/backstage button to explore, install and update extensions|
|Version |0.7.0|
|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: {
backstageButtonLabel: "explore extensions",
backstageButtonTooltip: "See if there are any updates or install new ones",
installButtonLabel: "install",
installButtonPrompt: "get and install this extension",
otherActionsPrompt: "show other actions",
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 = /\/([^\/]+)\.(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+(\/\/}}})\s*$/.exec(text)
if(match) try {
const list = JSON.parse(match[2])
return list.map(extension => ({
name: extension.name || this.guessNameByUrl(extension),
...extension
}))
} catch (e) {
console.log(`problems with parsing ${centralSourcesListName}:`, e)
return null
}
},
// reads .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)
}
}
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)
// remove \r originating from Windows
tiddler.text = responseText.replace(/\r\n/g, '\n')
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 && !readOnly)
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: this.lingo.backstageButtonLabel,
tooltip: this.lingo.backstageButtonTooltip,
content: '<<tiddler ExtensionsInBackstage>>',
}
},
handler: function(place, macroName, params, wikifier, paramString) {
// parse param "[type:installed|available]"
const pParams = paramString.parseParams("type", null, true, false, true)
const type = getParam(pParams, "type", "")
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>')
.attr({ 'data-eep-type': type })
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()
const type = jQuery(table).attr('data-eep-type')
// 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', null, 'actionsCell')
const actionsWrapper = createTiddlyElement(actionsCell, 'div', null, 'actionsWrapper')
if(cells.actionElements.length > 0) {
actionsWrapper.appendChild(cells.actionElements[0])
actionsWrapper.firstChild.classList.add('mainButton')
}
if(cells.actionElements.length > 1) {
const { lingo } = config.macros.extensionsExplorer
const otherActionEls = cells.actionElements.slice(1)
createTiddlyButton(actionsWrapper, '▾',
lingo.otherActionsPrompt,
function(event) {
const popup = Popup.create(actionsWrapper)
for(const e of otherActionEls) {
const li = createTiddlyElement(popup, 'li')
li.appendChild(e)
}
popup.style.minWidth = actionsWrapper.offsetWidth + 'px'
Popup.show()
event.stopPropagation()
return false
},
'button otherActionsButton')
}
$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
if(!type || type == 'available') {
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?
if(!type) $tbody.append(jQuery(
`<tr><td colspan="4" style="text-align: center;">Installed</td></tr>`))
if(!type || type == 'installed') {
for(const extensionTiddler of installedExtensionsTiddlers) {
//# limit the width of the table|Description column
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 ? createTiddlyElement(null, 'div', null, 'actionsLabel', 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.ExtensionsInBackstage = `<<tabs txtTabExtensionsExplorer
"check and update" "" ExtensionsExplorer
"explore and install" "" ExtensionsOutThere
contribute "" ContributeToExtensionsEcosystem
>>`
config.shadowTiddlers.ExtensionsExplorer = `<<extensionsExplorer type:installed>>`
config.shadowTiddlers.ExtensionsOutThere = `<<extensionsExplorer type:available>>
Some repositories not yet indexed by EEP that may be worth checking:
|[[TiddlyTools|https://tiddlytools.com/Classic]]|The largest extensions repository created mostly by a single developer, Eric Shulman. Source slice is currently outdated in all the extensions, be sure to change it to the up-to-date urls|
|[[abegoExtensions|https://yakovl.github.io/TiddlyWiki_abego/original/]]|Includes groundshaping plugins like ~ForEachTiddlerPlugin and ~YourSearchPlugin|
|[[MPTW|https://mptw.tiddlyspot.com]]|Introduces various extensions like ~RenameTagsPlugin or ~HideWhenPlugin, and ~TagglyTagging/MPTW as overall "approaches"|
||..more repositories will be added, check the "contribute" tab for more|
Old indexes of existing extensions (EEP is meant to eventually substitute them):
|[[Customize|https://yakovlitvin.pro/TW/TS_backups/customize.tiddlyspace.com%20(24.02.2016).html]]|archive of the big index created by Tobias Beer and contributors|`
config.shadowTiddlers.ContributeToExtensionsEcosystem = `Indexing estensions and repositories for EEP is work in progress. You can suggest changes in [[Github|https://github.com/YakovL/TiddlyWiki_ExtensionsExplorerPlugin]] (via issues or ~PRs) or in the [[Google Group|https://groups.google.com/g/tiddlywikiclassic]].
Things that we encourage you to do include:
✦ Reporting missing repos for the "explore and install" tab (repositories not yet indexed by EEP);
✦ Creating collections and indexing existing extensions (either yours or created by others);
✦ Asking questions about contributing and making it as simple as possible for others.`
config.shadowTiddlers[centralSourcesListName] = '//{{{\n' +
JSON.stringify(config.macros.extensionsExplorer.defaultAvailableExtensions, null, 2) +
'\n//}}}'
config.annotations[centralSourcesListName] =
config.macros.extensionsExplorer.lingo.centralSourcesListAnnotation
// Add styles
const css = `
.actionsLabel, .actionsCell .button {
padding: 0.2em;
display: inline-block;
border: none;
white-space: normal;
}
td.actionsCell {
padding: 0;
}
.actionsWrapper {
white-space: nowrap;
}
.button.mainButton {
padding-left: 0.7em;
}`
const shadowName = 'ExtensionsExplorerStyles'
if(!config.shadowTiddlers[shadowName]) {
config.shadowTiddlers[shadowName] = css
store.addNotification(shadowName, refreshStyles)
store.addNotification("ColorPalette", function(_, doc) { refreshStyles(shadowName, doc) })
}
//}}}
To get started with this blank ~TiddlyWiki, you'll need to modify the following tiddlers:
* [[MainMenu]]: The menu (usually on the left)
Quick setup: EEP, TGSP, SUP, SystemSettings; SiteTitle, SiteSubtitle, DefaultTiddlers
/***
todo:
* make it always save to SystemSettings (add a macro argument?), prevent saving if it's default
* add placeholder and may be tooltip to the input explaining how it works
* adapt to combinations involving only ctrl, shift, and/or alt
* learn if additional modifiers are needed for Mac
test macro: <<option keyMyFunction>>
meta: core: merge config.optionHandlers and config.macros.option.types?
***/
//{{{
// this is needed at least to update SystemSettings
config.optionHandlers.key = config.optionHandlers.txt
// re-read as we extended config.optionHandlers
loadSystemSettings()
config.macros.option.types.key = {
elementType: 'input',
valueField: 'value',
className: 'keyOptionInput',
eventName: 'onkeydown',
create: config.macros.option.genericCreate,
onChange: function(e) {
if(e.preventDefault) e.preventDefault()
// prevent propagation/bubbling
if(e.stopPropagation) e.stopPropagation()
e.returnValue = true // IE
e.cancelBubble = true
// this is a en key (what layout? always QWERTY?)
var key = e.code.substring(0, 3) === 'Key' ? e.code.substring(3) : e.code
var modifierPrefixes = []
if(e.ctrlKey) modifierPrefixes.push('Ctrl+')
if(e.altKey) modifierPrefixes.push('Alt+')
if(e.shiftKey) modifierPrefixes.push('Shift+')
// TODO: any other meta keys for Mac/Unix?
// TODO: don't update on pressing just ctrl/shift/alt?
e.target.value = modifierPrefixes.join('') + key
return config.macros.option.genericOnChange.apply(this, arguments)
}
}
//}}}
/***
|Description|highlights saving button (bold red by default) and the document title (adds a leading "*") when there are unsaved changes (also toggles {{{hasUnsavedChanges}}} class of the root element for hackability)|
|Version |1.5.1|
|Author |Yakov Litvin|
|Source |https://github.com/YakovL/TiddlyWiki_ShowUnsavedPlugin/blob/master/ShowUnsavedPlugin.js|
|License |[[MIT|https://github.com/YakovL/TiddlyWiki_YL_ExtensionsCollection/blob/master/Common%20License%20(MIT)]]|
<<option chkShowDirtyStory>> show unsaved if any tiddler is opened for editing
Styles applied to unsaved TW can be adjusted in StyleSheetUnsaved
***/
//{{{
config.macros.showDirtyPlugin = {
// styles that highlight save button when there's something to save
showDirtyCss: ".saveChangesButton { font-weight: bold; color: red !important; }",
styleSheetName: "suggestSavingOnDirty",
containerClassName: "hasUnsavedChanges",
showDrity: function(dirty) {
const css = store.getTiddlerText('StyleSheetUnsaved')
if(dirty) {
jQuery('html').addClass(this.containerClassName)
setStylesheet(css, this.styleSheetName)
document.title = "*" + getPageTitle()
} else {
jQuery('html').removeClass(this.containerClassName)
removeStyleSheet(this.styleSheetName)
document.title = getPageTitle()
}
},
checkDirty: function() {
return store.isDirty() ||
(config.options.chkShowDirtyStory && story.areAnyDirty())
},
init: function() {
config.shadowTiddlers.StyleSheetUnsaved = this.showDirtyCss
// add the "saveChangesButton" class to the save changes button
config.macros.saveChanges.SCM_orig_handler = config.macros.saveChanges.handler
config.macros.saveChanges.handler = function(place, macroName, params) {
this.SCM_orig_handler.apply(this, arguments)
place.lastChild.classList.add("saveChangesButton")
}
// regularly check and indicate unsaved
setInterval(function() {
const isDirty = config.macros.showDirtyPlugin.checkDirty()
config.macros.showDirtyPlugin.showDrity(isDirty)
}, 500)
}
}
//}}}
/***
|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.7.1|
|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
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])
}
}
}
// not present before 2.9.2
config.macros.upgrade.getSourceURL = config.macros.upgrade.getSourceURL || function() {
return config.options.txtUpgradeCoreURI || config.macros.upgrade.source;
}
// fix the bug introduced in 2.9.3 and fixed in 2.9.4 version;
// since 2.10.0, overriding is necessary to avoid the backup loading check irrelevant for this method
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() {
if(readOnly) return
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))
}
})
}
// sort fields and tiddlers to make html deterministic and avoid unmotivated diffs
// (not introduced into the core yet, nor tested if differs from MTS's granulated saving results)
//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]));
}
};
//}
//}}}
a place for experiments to make TWC more keyboard-friendly
txtUserName: Yakov Litvin
chkSaveBackups: false
chkAutoSave: true
chkInsertTabs: true
/***
|Version|0.1.0|
|Source|https://yllab.tiddlyhost.com#ThostGoodSavingPlugin|
should also: display "save from web" (use original {{{saveChanges}}}) instead of "save to tiddlyhost"
***/
//{{{
window.saveChanges = function(onlyIfDirty, tiddlers) {
config.macros.thostUpload.action()
}
//}}}
/***
|Name |ThostUploadPlugin |
|Description |Support saving to Tiddlyhost.com |
|Version |1.0.1 |
|Date |March 06, 2021 |
|Source |https://github.com/tiddlyhost/tiddlyhost-com/tree/main/rails/tw_content/plugins |
|Author |BidiX, Simon Baird, Yakov Litvin |
|License |BSD open source license |
|~CoreVersion |2.9.2 |
***/
//{{{
version.extensions.ThostUploadPlugin = { major: 1, minor: 0, revision: 1 };
//
// Environment
//
if (!window.bidix) window.bidix = {};
// To change these defaults, create a tiddler named "ThostOptions" with tag
// "systemConfig" and the following content:
// window.bidix = { "editModeAlways": false, "uploadButtonAlways": false };
// Set false if you want the chkHttpReadOnly cookie to decide whether to
// render in read-only mode or edit mode when you're not logged in or when
// the site is being viewed by others. Default true.
if (!("editModeAlways" in bidix)) { bidix.editModeAlways = true; }
// Set false to hide the "upload to tiddlyhost" button when you're not logged
// in or when the site is being viewed by others. Default true.
if (!("uploadButtonAlways" in bidix)) { bidix.uploadButtonAlways = true; }
// For debugging. Default false.
if (!("debugMode" in bidix)) { bidix.debugMode = false; }
//
// Upload Macro
//
config.macros.thostUpload = {
handler: function(place,macroName,params) {
createTiddlyButton(place, "save to tiddlyhost",
"save this TiddlyWiki to a site on Tiddlyhost.com",
this.action, null, null, this.accessKey);
},
action: function(params) {
var siteName = config.options.txtThostSiteName.trim();
if (!siteName) {
alert("Tiddlyhost site name is missing!");
clearMessage();
}
else {
bidix.thostUpload.uploadChanges('https://' + siteName + '.tiddlyhost.com');
}
return false;
}
};
//
// Upload functions
//
if (!bidix.thostUpload) bidix.thostUpload = {};
if (!bidix.thostUpload.messages) bidix.thostUpload.messages = {
invalidFileError: "The original file '%0' does not appear to be a valid TiddlyWiki",
mainSaved: "Main TiddlyWiki file uploaded",
mainFailed: "Failed to upload main TiddlyWiki file. Your changes have not been saved",
loadOriginalHttpPostError: "Can't get original file",
aboutToSaveOnHttpPost: 'About to upload on %0 ...',
storePhpNotFound: "The store script '%0' was not found."
};
bidix.thostUpload.uploadChanges = function(storeUrl) {
var callback = function(status, uploadParams, original, url, xhr) {
if (!status) {
displayMessage(bidix.thostUpload.messages.loadOriginalHttpPostError);
return;
}
if (bidix.debugMode) {
alert(original.substr(0,500)+"\n...");
}
var posDiv = locateStoreArea(original);
if ((posDiv[0] == -1) || (posDiv[1] == -1)) {
alert(config.messages.invalidFileError.format([localPath]));
return;
}
bidix.thostUpload.uploadMain(uploadParams, original, posDiv);
};
clearMessage();
// get original
var uploadParams = [storeUrl];
var originalPath = document.location.toString();
var dest = 'index.html';
displayMessage(bidix.thostUpload.messages.aboutToSaveOnHttpPost.format([dest]));
if (bidix.debugMode) {
alert("about to execute Http - GET on "+originalPath);
}
var r = doHttp("GET", originalPath, null, null, null, null, callback, uploadParams, null);
if (typeof r == "string") {
displayMessage(r);
}
return r;
};
bidix.thostUpload.uploadMain = function(uploadParams, original, posDiv) {
var callback = function(status, params, responseText, url, xhr) {
if (status) {
displayMessage(bidix.thostUpload.messages.mainSaved);
store.setDirty(false);
}
else {
alert(bidix.thostUpload.messages.mainFailed);
displayMessage(bidix.thostUpload.messages.mainFailed);
}
};
var revised = updateOriginal(original, posDiv);
bidix.thostUpload.httpUpload(uploadParams, revised, callback, uploadParams);
};
bidix.thostUpload.httpUpload = function(uploadParams, data, callback, params) {
var localCallback = function(status, params, responseText, url, xhr) {
if (xhr.status == 404) {
alert(bidix.thostUpload.messages.storePhpNotFound.format([url]));
}
var saveNotOk = responseText.charAt(0) != '0';
if (bidix.debugMode || saveNotOk) {
alert(responseText);
}
if (saveNotOk) {
status = null;
}
callback(status, params, responseText, url, xhr);
};
// do httpUpload
var boundary = "---------------------------"+"AaB03x";
var uploadFormName = "UploadPlugin";
// compose headers data
var sheader = "";
sheader += "--" + boundary + "\r\nContent-disposition: form-data; name=\"";
sheader += uploadFormName +"\"\r\n\r\n";
sheader += "backupDir=x" +
";user=x" +
";password=x" +
";uploaddir=x";
if (bidix.debugMode) {
sheader += ";debug=1";
}
sheader += ";;\r\n";
sheader += "\r\n" + "--" + boundary + "\r\n";
sheader += "Content-disposition: form-data; name=\"userfile\"; filename=\"index.html\"\r\n";
sheader += "Content-Type: text/html;charset=UTF-8" + "\r\n";
sheader += "Content-Length: " + data.length + "\r\n\r\n";
// compose trailer data
var strailer = "";
strailer = "\r\n--" + boundary + "--\r\n";
data = sheader + data + strailer;
if (bidix.debugMode) {
alert("about to execute Http - POST on " + uploadParams[0]+ "\n with \n" + data.substr(0,500) + " ... ");
}
var r = doHttp("POST", uploadParams[0], data,
"multipart/form-data; ;charset=UTF-8; boundary=" + boundary, 'x','x', localCallback, params, null);
if (typeof r == "string") {
displayMessage(r);
}
return r;
};
// a fix for versions before 2.9.2 (updateOriginal used conversions irrelevant for Tiddlyhost)
convertUnicodeToFileFormat = function(s) { return s };
//
// Site config
//
bidix.initOption = function(name,value) {
if (!config.options[name]) {
config.options[name] = value;
}
};
merge(config.optionsDesc, {
txtThostSiteName: "Site name for uploads to Tiddlyhost.com",
});
bidix.initOption('txtThostSiteName','hotkeys');
//
// Tiddlyhost stuff
//
bidix.ownerLoggedIn = (config.shadowTiddlers.TiddlyHostIsLoggedIn &&
config.shadowTiddlers.TiddlyHostIsLoggedIn == "yes")
if (bidix.editModeAlways || bidix.ownerLoggedIn) {
// If user is logged in to Tiddlyhost and viewing their own site then
// we disregard the original value of the chkHttpReadOnly cookie
config.options.chkHttpReadOnly = false
// window.readOnly gets set before plugins are loaded, so we need to
// set it here to make sure TW is editable, unlike window.showBackstage
// which is set after
window.readOnly = false
}
if (bidix.uploadButtonAlways || bidix.ownerLoggedIn) {
// Add the 'save to tiddlyhost' button after the regular save button
config.shadowTiddlers.SideBarOptions = config.shadowTiddlers.SideBarOptions
.replace(/(<<saveChanges>>)/,"$1<<thostUpload>>");
}
//}}}
|Source |https://github.com/YakovL/TiddlyWiki_YL_ExtensionsIndex/blob/master/YLExtensionsCollection.txt|
|Version|0.2.7|
//{{{
[
{
"url": "https://github.com/YakovL/TiddlyWiki_YL_ExtensionsIndex/blob/master/YLPrereleasesAndExperimentsCollection.txt",
"description": "Pre-releases and experiments by Yakov Litvin",
"type": "collection"
},
{
"url": "https://github.com/YakovL/TiddlyWiki_DisqusPlugin/blob/master/DisqusPlugin.js",
"description": "Add Disqus threads (comments) to your tiddlers"
},
{
"url": "https://github.com/YakovL/TiddlyWiki_HandsontablePlugin/blob/master/HandsontablePlugin.js",
"description": "Create spreadsheet-like tables with inline editing and keyboard hotkeys for manipulating rows and columns"
},
{
"url": "https://github.com/YakovL/TiddlyWiki_EncryptionPlugin/blob/master/EncryptionPlugin.js",
"description": "Save selected parts of content with encryption"
},
{
"url": "https://github.com/YakovL/TiddlyWiki_TwFormulaPlugin/blob/master/TwFormulaPlugin.js",
"description": "Render beautiful formulas using LaTeX syntax (also provides WYSIWYGish editing with MathQuill)"
},
{
"url": "https://github.com/YakovL/TiddlyWiki_GraphTools/blob/master/VisGraphPlugin.js",
"description": "View and edit graphs in a WYSIWYGish manner with the <<graph>> macro (see additional installation steps inside the plugin)"
},
{
"url": "https://github.com/YakovL/TiddlyWiki_JumpKeysPlugin/blob/master/JumpKeysPlugin.js",
"description": "Jump between tiddlers and do more via hotkeys and a UI similar to what browsers use for tabs"
},
{
"url": "https://github.com/YakovL/TiddlyWiki_QuickSavePlugin/blob/master/QuickSavePlugin.js",
"description": "Save changes via Ctrl + S hotkey (without getting browser default action)"
},
{
"url": "https://github.com/YakovL/TiddlyWiki_ContinuousSavingPlugin/blob/main/ContinuousSavingPlugin.js",
"description": "Get loading and saving work via just one file picking per session"
},
{
"url": "https://github.com/YakovL/TiddlyWiki_ShowUnsavedPlugin/blob/master/ShowUnsavedPlugin.js",
"description": "See when TW has unsaved changes: in the tab/window title (adds '*'), on the saveChanges button (bold red), and more"
},
{
"url": "https://github.com/YakovL/TiddlyWiki_DarkModePlugin/blob/master/DarkModePlugin.js",
"description": "Introduces \"dark mode\" (styles) and switching it by the darkMode macro and operating system settings"
},
{
"url": "https://github.com/YakovL/TiddlyWiki_TiddlerInFilePlugin/blob/master/TiddlerInFilePlugin.js",
"description": "Store specific tiddlers as external files and more"
},
{
"url": "https://github.com/YakovL/TiddlyWiki_ExtraFilters/blob/master/ExtraFiltersPlugin.js",
"description": "Various additional filters, including 'all', 'and', 'not', 'tagTree', 'unclassified', 'taggedOnly', 'hasPart', 'from', 'sortByText', and more"
},
{
"url": "https://github.com/YakovL/TiddlyWiki_FromPlaceToPlacePlugin/blob/master/FromPlaceToPlacePlugin.js",
"description": "Open a tiddler or a page in place of the current one (as opposed to opening in addition) via hotkeys"
},
{
"url": "https://github.com/YakovL/TiddlyWiki_SimplifiedUpgradingPlugin/blob/master/SimplifiedUpgradingPlugin.js",
"description": "Get core upgrading work with savers with I/O limitations, like Timimi, Tiddloid, or MTS 1.7.0 and above; optionally get notified on start if an upgrade is available; have upgrading more git-friendly"
},
{
"url": "https://github.com/YakovL/TiddlyWiki_ExtensionsExplorerPlugin/blob/master/ExtensionsExplorerPlugin.js",
"description": "Get notified about extensions updates; explore, install and update extensions"
},
{
"url": "https://yakovl.github.io/TiddlyWiki_SharedTiddlersPlugin/#SharedTiddlersPlugin",
"description": "Load and use tiddlers from other TiddlyWikis (both content and settings, optionally with importing them)"
},
{
"url": "https://github.com/YakovL/TiddlyWiki_ImageGalleries/blob/master/FancyBox2Plugin.js",
"description": "Create image galleries"
},
{
"url": "https://github.com/YakovL/TiddlyWiki_MarkdeepPlugin/blob/master/MarkdeepPlugin.js",
"description": "Create diagrams using the Markdeep syntax"
}
]
//}}}