From e17bad9baabebbfde3c73574dd36920091414ff2 Mon Sep 17 00:00:00 2001 From: Yakov Litvin Date: Wed, 20 Mar 2024 12:54:21 +0300 Subject: [PATCH] infra: update index.html to 2.10.1 --- index.html | 592 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 331 insertions(+), 261 deletions(-) diff --git a/index.html b/index.html index 1db3600..d6eb786 100644 --- a/index.html +++ b/index.html @@ -3,7 +3,7 @@ @@ -117,11 +117,15 @@
-
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>>
+
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]]
 
@@ -186,6 +190,8 @@ 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]];} @@ -201,14 +207,14 @@ .headerForeground a {font-weight:normal; color:[[ColorPalette::PrimaryPale]];} .tabSelected { - color:[[ColorPalette::PrimaryDark]]; - background:[[ColorPalette::TertiaryPale]]; + 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 {color:[[ColorPalette::PrimaryDark]]; background:[[ColorPalette::TertiaryPale]]; border:1px solid [[ColorPalette::TertiaryLight]];} +.tabContents {border:1px solid [[ColorPalette::TertiaryLight]];} .tabContents .button {border:0;} #sidebar {} @@ -225,9 +231,10 @@ .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::SecondaryPale]] [[ColorPalette::SecondaryDark]] [[ColorPalette::SecondaryDark]] [[ColorPalette::SecondaryPale]]; + border-color:[[ColorPalette::SecondaryDark]]; } .wizard .button:hover {color:[[ColorPalette::Foreground]]; background:[[ColorPalette::Background]];} .wizard .button:active { @@ -247,8 +254,8 @@ .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]]; } +.tw-icon line { stroke: [[ColorPalette::TertiaryDark]]; } +.messageToolbar__button:hover .tw-icon line { stroke: [[ColorPalette::Foreground]]; } .popup { background: [[ColorPalette::Background]]; @@ -279,10 +286,10 @@ .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, .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;} +.tagging .button, .tagged .button { border:none; } .footer {color:[[ColorPalette::TertiaryLight]];} .selected .footer {color:[[ColorPalette::TertiaryMid]];} @@ -311,8 +318,8 @@ .highlight, .marked {background:[[ColorPalette::SecondaryLight]];} -.editor input {border:1px solid [[ColorPalette::PrimaryMid]];} -.editor textarea {border:1px solid [[ColorPalette::PrimaryMid]]; width:100%;} +.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]];} @@ -356,7 +363,7 @@ 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;} +.txtOptionInput {width:11em; border-width: 1px; } #contentWrapper .chkOptionInput {border:0;} @@ -405,13 +412,15 @@ .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 .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__icon { height: 1em; width: 1em; } /* width for IE */ +.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;} @@ -425,7 +434,7 @@ .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;} +.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;} @@ -438,14 +447,13 @@ .toolbar {text-align:right; font-size:.9em;} -.tiddler {padding:1em 1em 0;} - -.missing .viewer,.missing .title {font-style:italic;} +.tiddler { padding: 1em; } -.title {font-size:1.6em; font-weight:bold;} +.title { font-size: 1.6em; font-weight: bold; } +.subtitle { font-size: 1.1em; } -.missing .subtitle {display:none;} -.subtitle {font-size:1.1em;} +.missing .viewer, .missing .title { font-style: italic; } +.missing .subtitle { display: none; } .tiddler .button {padding:0.2em 0.4em;} @@ -495,9 +503,9 @@ #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;} +#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-left:3em; } +#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;} @@ -1835,6 +1843,9 @@ //-- window.tw = { + assets: { + icons: {} + }, io: {}, textUtils: {} }; @@ -1879,6 +1890,7 @@ config.options = { chkAnimate: true, chkAutoSave: false, + chkBackstage: false, chkCaseSensitiveSearch: false, chkConfirmDelete: true, chkDisplayInstrumentation: false, @@ -1893,6 +1905,7 @@ chkRemoveExtraMarkers: false, // #162 chkSaveBackups: true, chkSaveEmptyTemplate: false, + chkSliderOptionsPanel: false, chkToggleLinks: false, chkUsePreForStorage: true, // Whether to use
 format for storage
 	txtBackupFolder: "",
@@ -1901,7 +1914,8 @@
 	txtMainTab: "tabTimeline",
 	txtMaxEditRows: "30",
 	txtMoreTab: "moreTabAll",
-	txtTheme: ""
+	txtTheme: "",
+	txtUpgradeCoreURI: ""
 };
 config.optionsDesc = {};
 
@@ -2112,7 +2126,7 @@
 	importTask: { text: "import", tooltip: "Import tiddlers and plugins " +
 		"from other TiddlyWiki files and servers", content: '<>' },
 	tweak: { text: "tweak", tooltip: "Tweak the appearance and behaviour of TiddlyWiki", content: '<>' },
-	upgrade: { text: "upgrade", tooltip: "Upgrade TiddlyWiki core code", content: '<>' },
+	upgrade: { text: "upgrade", tooltip: "Upgrade TiddlyWiki core", content: '<>' },
 	plugins: { text: "plugins", tooltip: "Manage installed plugins", content: '<>' }
 });
 
@@ -2457,12 +2471,14 @@
 });
 
 merge(config.macros.upgrade, {
-	wizardTitle: "Upgrade TiddlyWiki core code",
-	step1Title: "Update or repair this TiddlyWiki to the latest release",
-	step1Html: "You are about to upgrade to the latest release of the TiddlyWiki core code " +
-		"(from %1). Your content will be preserved across the upgrade.

" + - "Note that core upgrades have been known to interfere with older plugins. If you run into problems with upgrading, " + - "see http://www.tiddlywiki.org/wiki/CoreUpgrades", + wizardTitle: "Upgrade TiddlyWiki", + step1Title: "Update or repair TiddlyWiki core to the latest release", + step1Html: "You are about to upgrade TiddlyWiki core to the latest release " + + "(from %1). " + + "Your content will be preserved across the upgrade.

" + + "Note that core upgrades have been known to interfere with older plugins. " + + "If you run into problems with upgrading, read how to handle them " + + "here.", errorCantUpgrade: "Unable to upgrade this TiddlyWiki. You can only perform upgrades on TiddlyWiki files stored locally", errorNotSaved: "You must save changes before you can perform an upgrade", step2Title: "Confirm the upgrade details", @@ -2475,6 +2491,11 @@ statusPreparingBackup: "Preparing backup", statusSavingBackup: "Saving backup file", errorSavingBackup: "There was a problem saving the backup file", + errorVerifyingBackup: "Failed to verify the backup was saved. " + + "This is either because it wasn't saved to the moment of the check or " + + "loading file doesn't work with your saver (and it is needed for " + + "the next step of upgrading). To upgrade your TiddlyWiki, you can use " + + "other methods listed at %0", statusLoadingCore: "Loading core code", errorLoadingCore: "Error loading the core code", errorCoreFormat: "Error with the new core code", @@ -2636,8 +2657,7 @@ return (document.location.protocol == "file:"); }; -// Whether to use the JavaSaver applet -var useJavaSaver = window.isLocal() && (config.browser.isSafari || config.browser.isOpera); +var useJavaSaver = false; // Allow preemption code a chance to tweak config and useJavaSaver [Preemption] if (window.tweakConfig) window.tweakConfig(); @@ -4456,7 +4476,7 @@ config.macros.search.handler = function(place, macroName, params, wikifier, paramString, tiddler) { params = paramString.parseParams("anon", null, false, false, false); - createTiddlyButton(place, this.label, this.prompt, this.onClick, "searchButton"); + createTiddlyButton(place, this.label, this.prompt, this.onClick, "button searchButton"); var attributes = { size: this.sizeTextbox, accessKey: getParam(params, "accesskey", this.accessKey), @@ -4616,6 +4636,9 @@ tiddler: tiddler.title }); if(className) jQuery(btn).addClass(className); + if(commandName === 'permalink') { + jQuery(btn).attr('href', config.commands.permalink.getUrl(tiddler.title)); + } }; config.macros.toolbar.isCommandEnabled = function(command, tiddler) @@ -4802,6 +4825,10 @@ return false; }; +config.commands.permalink.getUrl = function(title) { + var hash = story.getPermaViewHash([title]); + return window.location.href.replace(/#.*/, hash); +}; config.commands.permalink.handler = function(event, src, title) { var hash = story.getPermaViewHash([title]); @@ -6223,7 +6250,15 @@ { var editor = this.getTiddlerField(title, field); var tags = editor.value.readBracketedList(); - tags.setItem(tag, mode); + + var i = tags.indexOf(tag); + if(mode == 0) mode = (i == -1) ? +1 : -1; + if(mode == +1) { + if(i == -1) tags.push(tag); + } else if(mode == -1) { + if(i != -1) tags.splice(i, 1); + } + editor.value = String.encodeTiddlyLinkList(tags); }; @@ -6773,7 +6808,7 @@ file = this.files[0].fileName; // REQUIRES PRIVILEGES.. NULL otherwise } catch (ex) { // non-priv fallback: combine filename with path to current document - var path = getLocalPath(document.location.href); + var path = tw.io.getOriginalLocalPath(); var slashpos = path.lastIndexOf('/'); if (slashpos == -1) slashpos = path.lastIndexOf('\\'); if (slashpos != -1) path = path.substr(0, slashpos + 1); // remove filename, leave trailing slash @@ -7065,6 +7100,8 @@ //-- Upgrade macro //-- +config.macros.upgrade.docsUrl = 'https://classic.tiddlywiki.com/#HowToUpgrade'; + config.macros.upgrade.getSourceURL = function() { return config.options.txtUpgradeCoreURI || config.macros.upgrade.source; @@ -7085,7 +7122,11 @@ { var w = new Wizard(); w.createWizard(place, this.wizardTitle); - w.addStep(this.step1Title, this.step1Html.format([this.getSourceURL(), this.getSourceURL()])); + w.addStep(this.step1Title, this.step1Html.format([ + this.getSourceURL(), + this.getSourceURL().replace(/^https:\/\//, ''), + this.docsUrl + ])); w.setButtons([{ caption: this.upgradeLabel, tooltip: this.upgradePrompt, @@ -7107,26 +7148,36 @@ } w.setButtons([], me.statusPreparingBackup); - var localPath = getLocalPath(document.location.toString()); + var localPath = tw.io.getOriginalLocalPath(); var backupPath = getBackupPath(localPath, me.backupExtension); var original = loadOriginal(localPath); w.setButtons([], me.statusSavingBackup); - var backupSuccess = copyFile(backupPath, localPath) || saveFile(backupPath, original); - if(!backupSuccess) { + var backupSuccessOrPending = copyFile(backupPath, localPath) || saveFile(backupPath, original); + if(!backupSuccessOrPending) { w.setButtons([], me.errorSavingBackup); alert(me.errorSavingBackup); return false; } - w.setValue("backupPath", backupPath); - w.setButtons([], me.statusLoadingCore); - var sourceURL = me.getSourceURL(); - me.loadLatestCore(function(data, textStatus, jqXHR) { - me.onLoadCore(true, w, jqXHR.responseText, sourceURL, jqXHR); - }, function(jqXHR, textStatus, errorThrown) { - me.onLoadCore(false, w, null, sourceURL, jqXHR); + // make sure both the backup is saved and tw.io.loadFile works before proceeding + tw.io.loadFile(backupPath, function(backupContent) { + if(!backupContent) { + w.setButtons([], me.errorVerifyingBackup.format([me.docsUrl])); + return; + } + + w.setValue("backupPath", backupPath); + + w.setButtons([], me.statusLoadingCore); + var sourceURL = me.getSourceURL(); + me.loadLatestCore(function(data, textStatus, jqXHR) { + me.onLoadCore(true, w, jqXHR.responseText, sourceURL, jqXHR); + }, function(jqXHR, textStatus, errorThrown) { + me.onLoadCore(false, w, null, sourceURL, jqXHR); + }); }); + return false; }; @@ -7156,7 +7207,7 @@ config.macros.upgrade.onStartUpgrade = function(wizard, newCoreHtml) { wizard.setButtons([], config.macros.upgrade.statusSavingCore); - var localPath = getLocalPath(document.location.toString()); + var localPath = tw.io.getOriginalLocalPath(); saveFile(localPath, newCoreHtml); wizard.setButtons([], config.macros.upgrade.statusReloadingCore); @@ -7281,7 +7332,9 @@ if(e.checked) selectedRows.push(e.getAttribute("rowName")); }); jQuery(listWrapper).empty(); + var plugins = installedPlugins.slice(0); + // not all plugins are installed, gather info about those, too var i, tiddler, p; var configTiddlers = store.getTaggedTiddlers("systemConfig"); for(i = 0; i < configTiddlers.length; i++) { @@ -7291,11 +7344,15 @@ p = getPluginInfo(tiddler); p.executed = false; p.log.splice(0, 0, this.skippedText); + plugins.push(p); + } + + for(i = 0; i < plugins.length; i++) { + p = plugins[i]; p.size = p.tiddler.text ? p.tiddler.text.length : 0; p.forced = p.tiddler.isTagged("systemConfigForce"); p.disabled = p.tiddler.isTagged("systemConfigDisable"); - p.Selected = selectedRows.indexOf(p.title) != -1; - plugins.push(p); + p.Selected = selectedRows.indexOf(plugins[i].title) != -1; } if(plugins.length == 0) { @@ -7362,10 +7419,7 @@ var btn = createTiddlyButton(toolbar, '', config.messages.messageClose.tooltip, clearMessage, "button messageToolbar__button"); - btn.innerHTML = '' + - ' ' + - ' ' + - ''; + btn.innerHTML = tw.assets.icons.closeSvg; // inline SVG is unsupported in old FireFox if(window.HTMLUnknownElement && btn.firstChild instanceof window.HTMLUnknownElement) { btn.innerHTML = config.messages.messageClose.text; @@ -7481,9 +7535,7 @@ if(e.getAttribute && (e.tagName ? e.tagName != "IFRAME" : true)) type = e.getAttribute("refresh"); var refresher = config.refreshers[type]; - var refreshed = false; - if(refresher != undefined) - refreshed = refresher(e, changeList); + var refreshed = refresher ? refresher(e, changeList) : false; if(e.hasChildNodes() && !refreshed) refreshElements(e, changeList); } @@ -7491,8 +7543,8 @@ function applyHtmlMacros(root, tiddler) { - var e = root.firstChild; - while(e) { + for(var e = root.firstChild; !!e; e = nextChild) { + // macros can manipulate DOM, so we remember nextChild before invokeMacro var nextChild = e.nextSibling; if(e.getAttribute) { var macro = e.getAttribute("macro"); @@ -7507,9 +7559,7 @@ invokeMacro(e, macro, params, null, tiddler); } } - if(e.hasChildNodes()) - applyHtmlMacros(e, tiddler); - e = nextChild; + if(e.hasChildNodes()) applyHtmlMacros(e, tiddler); } } @@ -7590,6 +7640,23 @@ //-- Option handling //-- +tw.options = { + defaults: {}, + define: function(name, defaultValue, description) { + this.defaults[name] = defaultValue; + if(config.options[name] === undefined) config.options[name] = defaultValue; + if(description) config.optionsDesc[name] = description; + }, + hasDefaultValue: function(name) { + return config.options[name] == this.defaults[name] || + config.options[name] === undefined; + } +}; + +for(var name in config.options) { + tw.options.define(name, config.options[name], config.optionsDesc[name]); +} + config.optionHandlers = { 'txt': { get: function(name) { return encodeCookie(config.options[name].toString()) }, @@ -7699,6 +7766,7 @@ { var key, cookies = {}; for(key in config.options) { + if(tw.options.hasDefaultValue(key)) continue; var value = getOption(key); value = value == null ? 'false' : value; cookies[key] = value; @@ -7918,10 +7986,8 @@ // Give the user a chance to save changes before exitting function checkUnsavedChanges() { - if(store && store.isDirty && store.isDirty() && window.hadConfirmExit === false) { - if(confirm(config.messages.unsavedChangesWarning)) - saveChanges(); - } + if(!(store && store.isDirty && store.isDirty()) || window.hadConfirmExit !== false) return; + if(confirm(config.messages.unsavedChangesWarning)) saveChanges(); } function updateLanguageAttribute(s) @@ -8042,50 +8108,46 @@ return content; } -// Save this tiddlywiki with the pending changes -function saveChanges(onlyIfDirty, tiddlers) +// reconstruct the local path to TW itself +tw.io.getOriginalLocalPath = function() { - if(onlyIfDirty && !store.isDirty()) return; + return getLocalPath(document.location.toString()); +}; - clearMessage(); - var t0 = new Date(); - var msg = config.messages; - if(!window.allowSave()) { - alert(msg.notFileUrlError); - if(store.tiddlerExists(msg.saveInstructions)) - story.displayTiddler(null, msg.saveInstructions); - return; - } +// Save tiddlywiki (but not backup or anything else) +tw.io.knownSaveMainFailures = { + failedToLoadOriginal: 1, + invalidFile: 2 +}; - var originalPath = document.location.toString(); - var localPath = getLocalPath(originalPath); +// Save this tiddlywiki with the pending changes +tw.io.saveMainAndReport = function(callback) +{ + var localPath = tw.io.getOriginalLocalPath(); var onLoadOriginal = function(original) { if(original == null) { alert(msg.cantSaveError); if(store.tiddlerExists(msg.saveInstructions)) story.displayTiddler(null, msg.saveInstructions); - return; + + return callback(false, { + reason: tw.io.knownSaveMainFailures.failedToLoadOriginal + }); } var posDiv = locateStoreArea(original); if(!posDiv) { alert(msg.invalidFileError.format([localPath])); - return; + + return callback(false, { + reason: tw.io.knownSaveMainFailures.invalidFile + }); } config.saveByDownload = false; config.saveByManualDownload = false; - saveMain(localPath, original, posDiv); - - var co = config.options; - if (!config.saveByDownload && !config.saveByManualDownload) { - if(co.chkSaveBackups) saveBackup(localPath, original); - if(co.chkSaveEmptyTemplate) saveEmpty(localPath, original, posDiv); - if(co.chkGenerateAnRssFeed) saveRss(localPath); - } - - if(co.chkDisplayInstrumentation) - displayMessage("saveChanges " + (new Date() - t0) + " ms"); + // chkPreventAsyncSaving is checked inside saveMain + saveMain(localPath, original, posDiv, callback); }; if(!config.options.chkPreventAsyncSaving) { @@ -8096,20 +8158,64 @@ var original = loadOriginal(localPath); onLoadOriginal(original); } +}; + +function saveChanges(onlyIfDirty, tiddlers) +{ + if(onlyIfDirty && !store.isDirty()) return; + + clearMessage(); + var t0 = new Date(); + var msg = config.messages; + if(!window.allowSave()) { + alert(msg.notFileUrlError); + if(store.tiddlerExists(msg.saveInstructions)) + story.displayTiddler(null, msg.saveInstructions); + return; + } + + tw.io.saveMainAndReport(function postSave(savedOrPending, details) { + var co = config.options; + if (!config.saveByDownload && !config.saveByManualDownload && details && details.original) { + var localPath = tw.io.getOriginalLocalPath(); + if(co.chkSaveBackups) saveBackup(localPath, details.original); + if(co.chkSaveEmptyTemplate) saveEmpty(localPath, details.original); + if(co.chkGenerateAnRssFeed) saveRss(localPath); + } + + if(co.chkDisplayInstrumentation) + displayMessage("saveChanges " + (new Date() - t0) + " ms"); + }); } -function saveMain(localPath, original, posDiv) +function saveMain(localPath, original, posDiv, callback) { - try { - var revised = updateOriginal(original, posDiv, localPath); - var saved = saveFile(localPath, revised); - if(!saved) { - tw.io.onSaveMainFail(); + var reportStatusAndHandle = function(successOrPending, localPath, revised) { + if(successOrPending) { + tw.io.onSaveMainSuccess(config.saveByDownload ? + getDataURI(revised) : "file://" + localPath, + revised, original); } else { - tw.io.onSaveMainSuccess(config.saveByDownload ? getDataURI(revised) : "file://" + localPath, revised, original); + tw.io.onSaveMainFail(); } + }; + try { + var revised = updateOriginal(original, posDiv, localPath); + + if(!callback || config.options.chkPreventAsyncSaving) { + var savedOrPending = tw.io.saveFile(localPath, revised); + reportStatusAndHandle(savedOrPending, localPath, revised); + if(callback) callback(savedOrPending, { + original: original + }); + } else tw.io.saveFile(localPath, revised, function(success, details) { + reportStatusAndHandle(success, localPath, revised); + details.original = original; + callback(success, details); + }); } catch (ex) { tw.io.onSaveMainFail(ex); + if(callback) callback(false, { original: original }); } } @@ -8143,6 +8249,12 @@ function saveEmpty(localPath, original, posDiv) { + posDiv = posDiv || locateStoreArea(original); + if(!posDiv) { + alert(config.messages.emptyFailed); + return; + } + var emptyPath, slashPosition; if((slashPosition = localPath.lastIndexOf("/")) != -1) emptyPath = localPath.substr(0, slashPosition) + "/"; @@ -8294,6 +8406,32 @@ return r; }; +// A placeholder method that can be overwritten/decorated by savers. +// In such a case, it's required to call callback on both success and fail. +// See details about the callback in tw.io.saveFile. +tw.io.asyncSaveFile = tw.io.asyncSaveFile || function(fileUrl, content, callback) +{ + callback(false, { reason: 'Async saving is not implemented' }); +}; + +// The general save method to use +// ============================== +// If callback is set, tries to save in an async fashion and do callback(success: boolean, details: object) +// ⚠️ Some savers within window.saveFile, like download saving or Timimi don't care about the callback, +// so it can be called before actual saving is done (or even give false positives). +tw.io.saveFile = function(fileUrl, content, callback) +{ + if(!callback) return saveFile(fileUrl, content); + + tw.io.asyncSaveFile(fileUrl, content, function(success, details) { + if(success) callback(success, details); + else { + result = saveFile(fileUrl, content); + callback(result, {}); + } + }); +}; + // Load a file from filesystem [Preemption] window.loadFile = window.loadFile || function(fileUrl) { @@ -8327,7 +8465,9 @@ } }; -// if callback is set, tries to load in an async fashion and do callback(result, details) +// The general load method to use +// ============================== +// If callback is set, tries to load in an async fashion and do callback(result, details) tw.io.loadFile = function(fileUrl, callback) { if(!callback) return loadFile(fileUrl); @@ -8353,10 +8493,8 @@ // Remove the filename, if present. Use trailing slash (i.e. "foo\bar\") if no filename. var pos = path.lastIndexOf("\\"); - if(pos == -1) - pos = path.lastIndexOf("/"); - if(pos != -1) - path = path.substring(0, pos + 1); + if(pos == -1) pos = path.lastIndexOf("/"); + if(pos != -1) path = path.substring(0, pos + 1); // Walk up the path until we find a folder that exists var scan = [path]; @@ -8467,102 +8605,6 @@ } } -function javaUrlToFilename(url) -{ - var f = "//localhost"; - if(url.indexOf(f) == 0) - return url.substring(f.length); - var i = url.indexOf(":"); - return i > 0 ? url.substring(i - 1) : url; -} - -/* - * in between when the applet has been started - * and the user has given permission to run the applet - * we get an applet object, but it doesn't have the methods - * we expect yet. - */ -var LOG_TIDDLYSAVER = true; -function logTiddlySaverException(msg, ex) -{ - var applet = document.applets['TiddlySaver']; - console.log(msg + ": " + ex); - if (LOG_TIDDLYSAVER && applet) { - try { - console.log(msg + ": " + applet.getLastErrorMsg()); - console.log(msg + ": " + applet.getLastErrorStackTrace()); - } catch (ex) {} - } -} - -function javaDebugInformation() -{ - var applet = document.applets['TiddlySaver']; - var what = [ - ["Java Version", applet.getJavaVersion], - ["Last Exception", applet.getLastErrorMsg], // #156 - ["Last Exception Stack Trace", applet.getLastErrorStackTrace], - ["System Properties", applet.getSystemProperties] ]; - - function formatItem (description, method) { - try { - result = String(method.call(applet)); - } catch (ex) { - result = String(ex); - } - return description + ": " + result; - } - - return jQuery.map(what, function (item) { return formatItem.apply(this, item) }) - .join('\n\n'); -} - -function javaSaveFile(filePath, content) -{ - if(!filePath) return null; - var applet = document.applets['TiddlySaver']; - if(applet) { - try { - return applet.saveFile(javaUrlToFilename(filePath), "UTF-8", content); - } catch(ex) { - logTiddlySaverException("javaSaveFile", ex); - } - } - try { - var s = new java.io.PrintStream(new java.io.FileOutputStream(javaUrlToFilename(filePath))); - s.print(content); - s.close(); - } catch(ex2) { - return null; - } - return true; -} - -function javaLoadFile(filePath) -{ - if(!filePath) return null; - var applet = document.applets['TiddlySaver']; - if(applet) { - try { - var value = applet.loadFile(javaUrlToFilename(filePath), "UTF-8"); - return !value ? null : String(value); - } catch(ex) { - logTiddlySaverException("javaLoadFile", ex); - } - } - var content = []; - try { - var r = new java.io.BufferedReader(new java.io.FileReader(javaUrlToFilename(filePath))); - var line; - while((line = r.readLine()) != null) - content.push(String(line)); - r.close(); - } catch(ex2) { - return null; - } - return content.join("\n"); -} - function HTML5DownloadSaveFile(filePath, content) { var link = document.createElement("a"); @@ -8599,12 +8641,13 @@ // construct data URI (using base64 encoding to preserve multi-byte encodings) function getDataURI(data) { - if (config.browser.isIE) - return "data:text/html," + encodeURIComponent(data); - else + return config.browser.isIE ? + "data:text/html," + encodeURIComponent(data) : + // manualConvertUnicodeToUTF8 was moved here from convertUnicodeToFileFormat - // in 2.9.1 it was used only for FireFox but happened to fix download saving non-ASCII in Chrome & Safari as well - return "data:text/html;base64," + encodeBase64(manualConvertUnicodeToUTF8(data)); + // In 2.9.1, it was used only for FireFox but happened to fix + // download saving non-ASCII in Chrome & Safari as well (see 949aff6) + "data:text/html;base64," + encodeBase64(manualConvertUnicodeToUTF8(data)); } function encodeBase64(data) @@ -9039,7 +9082,8 @@ return true; } catch (ex) { ; } - try { // local file I/O (IE, FF with TiddlyFox, Chrome/Safari with TiddlySaver, etc.) + // local file I/O (IE, FF with security.fileuri.strict_origin_policy:false, etc.) + try { var data = loadFile(getLocalPath(args.url)); if (data) success(data); else failure("loadFile"); @@ -9124,7 +9168,8 @@ v = v || version; return v.major + "." + v.minor + "." + v.revision + (v.alpha ? " (alpha " + v.alpha + ")" : "") + - (v.beta ? " (beta " + v.beta + ")" : ""); + (v.beta ? " (beta " + v.beta + ")" : "") + + (v.nightly ? " (nightly " + v.nightly + ")" : ""); } function compareVersions(v1, v2) @@ -9808,7 +9853,7 @@ insertSpacer(this.footElem); } if(typeof status == "string") { - createTiddlyElement(this.footElem, "span", null, "status", status); + createTiddlyElement(this.footElem, "span", null, "status").innerHTML = status; } }; @@ -10085,18 +10130,6 @@ //-- Augmented methods for the JavaScript Array() object //-- -// For IE up to 8 (https://caniuse.com/?search=indexOf) -if(!Array.indexOf) { - Array.prototype.indexOf = function(item, from) - { - if(!from) from = 0; - for(var i = from; i < this.length; i++) { - if(this[i] === item) return i; - } - return -1; - }; -} - // Find an entry in a given field of the members of an array Array.prototype.findByField = function(field, value) { @@ -10112,21 +10145,6 @@ return this.indexOf(item) != -1; }; -// Adds, removes or toggles a particular value within an array -// value - value to add -// mode - +1 to add value, -1 to remove value, 0 to toggle it -Array.prototype.setItem = function(value, mode) -{ - var p = this.indexOf(value); - if(mode == 0) - mode = (p == -1) ? +1 : -1; - if(mode == +1) { - if(p == -1) this.push(value); - } else if(mode == -1) { - if(p != -1) this.splice(p, 1); - } -}; - // Return whether one of a list of values exists in an array Array.prototype.containsAny = function(items) { @@ -10162,19 +10180,6 @@ if(p != -1) this.splice(p, 1); }; -// For IE up to 8 (https://caniuse.com/?search=indexOf) -if(!Array.prototype.map) { - Array.prototype.map = function(fn, thisObj) - { - var scope = thisObj || window; - var i, j, a = []; - for(i = 0, j = this.length; i < j; ++i) { - a.push(fn.call(scope, this[i], i, this)); - } - return a; - }; -} - //-- //-- Augmented methods for the JavaScript String() object //-- @@ -10622,6 +10627,12 @@ //-- DOM utilities - many derived from www.quirksmode.org //-- +tw.assets.icons.closeSvg = + '' + + ' ' + + ' ' + + ''; + function drawGradient(place, horiz, loColors, hiColors) { if(!hiColors) hiColors = loColors; @@ -11103,14 +11114,57 @@ createTiddlyElement(place, "br"); }; -// Find an entry in an array. Returns the array index or null +// Find an item in an array. If a predicate is provided, use the native Array.find (return the item); +// otherwise (for backwards compatibility) treat the argument as an item to find (return the index or null) // @Deprecated: Use indexOf instead -Array.prototype.find = function(item) +Array.prototype.orig_find = Array.prototype.find; +Array.prototype.find = function(itemOrPredicate) { - var i = this.indexOf(item); + if(itemOrPredicate instanceof Function) return Array.prototype.orig_find.apply(this, arguments); + var i = this.indexOf(itemOrPredicate); return i == -1 ? null : i; }; +// Adds, removes or toggles a particular value within an array +// value - value to add +// mode - +1 to add value, -1 to remove value, 0 to toggle it +// @Deprecated: No direct substitution +Array.prototype.setItem = function(value, mode) +{ + var i = this.indexOf(value); + if(mode == 0) mode = (i == -1) ? +1 : -1; + if(mode == +1) { + if(i == -1) this.push(value); + } else if(mode == -1) { + if(i != -1) this.splice(i, 1); + } +}; + +// For IE up to 8 (https://caniuse.com/?search=indexOf) +if(!Array.prototype.map) { + Array.prototype.map = function(fn, thisObj) + { + var scope = thisObj || window; + var i, j, a = []; + for(i = 0, j = this.length; i < j; ++i) { + a.push(fn.call(scope, this[i], i, this)); + } + return a; + }; +} + +// For IE up to 8 (https://caniuse.com/?search=indexOf) +if(!Array.prototype.indexOf) { + Array.prototype.indexOf = function(item, from) + { + if(!from) from = 0; + for(var i = from; i < this.length; i++) { + if(this[i] === item) return i; + } + return -1; + }; +} + // Load a tiddler from an HTML DIV. The caller should make sure to later call Tiddler.changed() // @Deprecated: Use store.getLoader().internalizeTiddler instead Tiddler.prototype.loadFromDiv = function(divRef, title) @@ -11119,7 +11173,7 @@ }; // Format the text for storage in an HTML DIV -// @Deprecated Use store.getSaver().externalizeTiddler instead. +// @Deprecated: Use store.getSaver().externalizeTiddler instead. Tiddler.prototype.saveToDiv = function() { return store.getSaver().externalizeTiddler(store, this); @@ -11149,6 +11203,19 @@ story.displayTiddler(srcElement, title, template, animate); } +// @Deprecated: Java IO is no longer supported; +// these "empty" versions are only left for the tiny chance of backwards +// compatibility issues and will be removed completely in the future +function javaSaveFile(filePath, content) +{ + return null; +} + +function javaLoadFile(filePath) +{ + return null; +} + // @Deprecated: Use functions on right hand side directly instead var createTiddlerPopup = Popup.create; var scrollToTiddlerPopup = Popup.show; @@ -11354,8 +11421,11 @@