mirror of
https://github.com/Jermolene/TiddlyWiki5.git
synced 2026-01-09 10:41:45 -08:00
Instead support startup modules that stipulate which platform they require. Also include docs updates and fixes to fullscreen plugin
361 lines
12 KiB
JavaScript
Executable file
361 lines
12 KiB
JavaScript
Executable file
/*\
|
|
title: $:/core/modules/startup.js
|
|
type: application/javascript
|
|
module-type: startup
|
|
|
|
This is the main application logic for both the client and server
|
|
|
|
\*/
|
|
(function(){
|
|
|
|
/*jslint node: true, browser: true */
|
|
/*global $tw: false */
|
|
"use strict";
|
|
|
|
// Export name and synchronous status
|
|
exports.name = "startup";
|
|
exports.after = ["load-modules"];
|
|
exports.synchronous = true;
|
|
|
|
// Set to `true` to enable performance instrumentation
|
|
var PERFORMANCE_INSTRUMENTATION = false;
|
|
|
|
// Time (in ms) that we defer refreshing changes to draft tiddlers
|
|
var DRAFT_TIDDLER_TIMEOUT = 400;
|
|
|
|
// Default story and history lists
|
|
var DEFAULT_STORY_TITLE = "$:/StoryList";
|
|
var DEFAULT_HISTORY_TITLE = "$:/HistoryList";
|
|
|
|
// Default tiddlers
|
|
var DEFAULT_TIDDLERS_TITLE = "$:/DefaultTiddlers";
|
|
|
|
// Favicon tiddler
|
|
var FAVICON_TITLE = "$:/favicon.ico";
|
|
|
|
var widget = require("$:/core/modules/widgets/widget.js");
|
|
|
|
exports.startup = function() {
|
|
var modules,n,m,f,commander;
|
|
if($tw.browser) {
|
|
$tw.browser.isIE = (/msie|trident/i.test(navigator.userAgent));
|
|
}
|
|
$tw.version = $tw.utils.extractVersionInfo();
|
|
// Set up the performance framework
|
|
$tw.perf = new $tw.Performance(PERFORMANCE_INSTRUMENTATION);
|
|
// Kick off the language manager and switcher
|
|
$tw.language = new $tw.Language();
|
|
$tw.languageSwitcher = new $tw.PluginSwitcher({
|
|
wiki: $tw.wiki,
|
|
pluginType: "language",
|
|
controllerTitle: "$:/language",
|
|
defaultPlugins: [
|
|
"$:/languages/en-US"
|
|
]
|
|
});
|
|
// Kick off the theme manager
|
|
$tw.themeManager = new $tw.PluginSwitcher({
|
|
wiki: $tw.wiki,
|
|
pluginType: "theme",
|
|
controllerTitle: "$:/theme",
|
|
defaultPlugins: [
|
|
"$:/themes/tiddlywiki/snowwhite",
|
|
"$:/themes/tiddlywiki/vanilla"
|
|
]
|
|
});
|
|
// Clear outstanding tiddler store change events to avoid an unnecessary refresh cycle at startup
|
|
$tw.wiki.clearTiddlerEventQueue();
|
|
// Open startup tiddlers
|
|
openStartupTiddlers();
|
|
// Set up the syncer object
|
|
$tw.syncer = new $tw.Syncer({wiki: $tw.wiki});
|
|
// Host-specific startup
|
|
if($tw.browser) {
|
|
// Set up our beforeunload handler
|
|
window.addEventListener("beforeunload",function(event) {
|
|
var confirmationMessage = undefined;
|
|
if($tw.syncer.isDirty()) {
|
|
confirmationMessage = "You have unsaved changes in TiddlyWiki";
|
|
event.returnValue = confirmationMessage; // Gecko
|
|
}
|
|
return confirmationMessage;
|
|
});
|
|
// Install the popup manager
|
|
$tw.popup = new $tw.utils.Popup({
|
|
rootElement: document.body
|
|
});
|
|
// Install the animator
|
|
$tw.anim = new $tw.utils.Animator();
|
|
// Create a root widget for attaching event handlers. By using it as the parentWidget for another widget tree, one can reuse the event handlers
|
|
$tw.rootWidget = new widget.widget({
|
|
type: "widget",
|
|
children: []
|
|
},{
|
|
wiki: $tw.wiki,
|
|
document: document
|
|
});
|
|
// Install the modal message mechanism
|
|
$tw.modal = new $tw.utils.Modal($tw.wiki);
|
|
$tw.rootWidget.addEventListener("tw-modal",function(event) {
|
|
$tw.modal.display(event.param);
|
|
});
|
|
// Install the notification mechanism
|
|
$tw.notifier = new $tw.utils.Notifier($tw.wiki);
|
|
$tw.rootWidget.addEventListener("tw-notify",function(event) {
|
|
$tw.notifier.display(event.param);
|
|
});
|
|
// Install the scroller
|
|
$tw.pageScroller = new $tw.utils.PageScroller();
|
|
$tw.rootWidget.addEventListener("tw-scroll",function(event) {
|
|
$tw.pageScroller.handleEvent(event);
|
|
});
|
|
// Listen for the tw-home message
|
|
$tw.rootWidget.addEventListener("tw-home",function(event) {
|
|
displayDefaultTiddlers();
|
|
});
|
|
// Install the save action handlers
|
|
$tw.rootWidget.addEventListener("tw-save-wiki",function(event) {
|
|
$tw.syncer.saveWiki({
|
|
template: event.param,
|
|
downloadType: "text/plain"
|
|
});
|
|
});
|
|
$tw.rootWidget.addEventListener("tw-auto-save-wiki",function(event) {
|
|
$tw.syncer.saveWiki({
|
|
method: "autosave",
|
|
template: event.param,
|
|
downloadType: "text/plain"
|
|
});
|
|
});
|
|
$tw.rootWidget.addEventListener("tw-download-file",function(event) {
|
|
$tw.syncer.saveWiki({
|
|
method: "download",
|
|
template: event.param,
|
|
downloadType: "text/plain"
|
|
});
|
|
});
|
|
// Listen out for login/logout/refresh events in the browser
|
|
$tw.rootWidget.addEventListener("tw-login",function() {
|
|
$tw.syncer.handleLoginEvent();
|
|
});
|
|
$tw.rootWidget.addEventListener("tw-logout",function() {
|
|
$tw.syncer.handleLogoutEvent();
|
|
});
|
|
$tw.rootWidget.addEventListener("tw-server-refresh",function() {
|
|
$tw.syncer.handleRefreshEvent();
|
|
});
|
|
// Install the crypto event handlers
|
|
$tw.rootWidget.addEventListener("tw-set-password",function(event) {
|
|
$tw.passwordPrompt.createPrompt({
|
|
serviceName: "Set a new password for this TiddlyWiki",
|
|
noUserName: true,
|
|
submitText: "Set password",
|
|
canCancel: true,
|
|
callback: function(data) {
|
|
if(data) {
|
|
$tw.crypto.setPassword(data.password);
|
|
}
|
|
return true; // Get rid of the password prompt
|
|
}
|
|
});
|
|
});
|
|
$tw.rootWidget.addEventListener("tw-clear-password",function(event) {
|
|
$tw.crypto.setPassword(null);
|
|
});
|
|
// Ensure that $:/isEncrypted is maintained properly
|
|
$tw.wiki.addEventListener("change",function(changes) {
|
|
if($tw.utils.hop(changes,"$:/isEncrypted")) {
|
|
$tw.crypto.updateCryptoStateTiddler();
|
|
}
|
|
});
|
|
// Set up the favicon
|
|
var faviconLink = document.getElementById("faviconLink"),
|
|
setFavicon = function() {
|
|
var tiddler = $tw.wiki.getTiddler(FAVICON_TITLE);
|
|
if(tiddler) {
|
|
faviconLink.setAttribute("href","data:" + tiddler.fields.type + ";base64," + tiddler.fields.text);
|
|
}
|
|
};
|
|
setFavicon();
|
|
$tw.wiki.addEventListener("change",function(changes) {
|
|
if($tw.utils.hop(changes,FAVICON_TITLE)) {
|
|
setFavicon();
|
|
}
|
|
});
|
|
// Set up location hash update
|
|
$tw.wiki.addEventListener("change",function(changes) {
|
|
if($tw.utils.hop(changes,DEFAULT_STORY_TITLE) || $tw.utils.hop(changes,DEFAULT_HISTORY_TITLE)) {
|
|
updateLocationHash();
|
|
}
|
|
});
|
|
// Listen for changes to the browser location hash
|
|
window.addEventListener("hashchange",function() {
|
|
if(window.location.hash !== $tw.locationHash) {
|
|
$tw.locationHash = window.location.hash;
|
|
openStartupTiddlers({defaultToCurrentStory: true});
|
|
}
|
|
},false)
|
|
// Set up the styles
|
|
var styleTemplateTitle = "$:/core/ui/PageStylesheet",
|
|
styleParser = $tw.wiki.parseTiddler(styleTemplateTitle);
|
|
$tw.styleWidgetNode = $tw.wiki.makeWidget(styleParser,{document: $tw.fakeDocument});
|
|
$tw.styleContainer = $tw.fakeDocument.createElement("style");
|
|
$tw.styleWidgetNode.render($tw.styleContainer,null);
|
|
$tw.styleElement = document.createElement("style");
|
|
$tw.styleElement.innerHTML = $tw.styleContainer.textContent;
|
|
document.head.insertBefore($tw.styleElement,document.head.firstChild);
|
|
$tw.wiki.addEventListener("change",$tw.perf.report("styleRefresh",function(changes) {
|
|
if($tw.styleWidgetNode.refresh(changes,$tw.styleContainer,null)) {
|
|
$tw.styleElement.innerHTML = $tw.styleContainer.textContent;
|
|
}
|
|
}));
|
|
// Display the $:/PageMacros tiddler to kick off the display
|
|
renderPage();
|
|
// Fix up the link between the root widget and the page container
|
|
$tw.rootWidget.domNodes = [$tw.pageContainer];
|
|
$tw.rootWidget.children = [$tw.pageWidgetNode];
|
|
// If we're being viewed on a data: URI then give instructions for how to save
|
|
if(document.location.protocol === "data:") {
|
|
$tw.utils.dispatchCustomEvent(document,"tw-modal",{
|
|
param: "$:/language/Modals/SaveInstructions"
|
|
});
|
|
}
|
|
} else {
|
|
// On the server, start a commander with the command line arguments
|
|
commander = new $tw.Commander(
|
|
$tw.boot.argv,
|
|
function(err) {
|
|
if(err) {
|
|
console.log("Error: " + err);
|
|
}
|
|
},
|
|
$tw.wiki,
|
|
{output: process.stdout, error: process.stderr}
|
|
);
|
|
commander.execute();
|
|
}
|
|
};
|
|
|
|
/*
|
|
Process the location hash to open the specified tiddlers. Options:
|
|
defaultToCurrentStory: If true, the current story is retained as the default, instead of opening the default tiddlers
|
|
*/
|
|
function openStartupTiddlers(options) {
|
|
options = options || {};
|
|
// Decode the hash portion of our URL
|
|
var target,
|
|
storyFilter;
|
|
if($tw.locationHash.length > 1) {
|
|
var hash = $tw.locationHash.substr(1),
|
|
split = hash.indexOf(":");
|
|
if(split === -1) {
|
|
target = decodeURIComponent(hash.trim());
|
|
} else {
|
|
target = decodeURIComponent(hash.substr(0,split).trim());
|
|
storyFilter = decodeURIComponent(hash.substr(split + 1).trim());
|
|
}
|
|
}
|
|
// If a target tiddler was specified add it to the history stack
|
|
if(target && target !== "") {
|
|
// The target tiddler doesn't need double square brackets, but we'll silently remove them if they're present
|
|
if(target.indexOf("[[") === 0 && target.substr(-2) === "]]") {
|
|
target = target.substr(2,target.length - 4);
|
|
}
|
|
$tw.wiki.addToHistory(target);
|
|
}
|
|
// Use the story filter specified in the hash, or the default tiddlers
|
|
if(!storyFilter || storyFilter === "") {
|
|
if(options.defaultToCurrentStory) {
|
|
var currStoryList = $tw.wiki.getTiddlerList(DEFAULT_STORY_TITLE);
|
|
storyFilter = $tw.utils.stringifyList(currStoryList);
|
|
} else {
|
|
storyFilter = $tw.wiki.getTiddlerText(DEFAULT_TIDDLERS_TITLE);
|
|
}
|
|
}
|
|
var storyList = $tw.wiki.filterTiddlers(storyFilter);
|
|
// If the target tiddler isn't included then splice it in at the top
|
|
if(target && storyList.indexOf(target) === -1) {
|
|
storyList.unshift(target);
|
|
}
|
|
// Save the story list
|
|
$tw.wiki.addTiddler({title: DEFAULT_STORY_TITLE, text: "", list: storyList},$tw.wiki.getModificationFields());
|
|
}
|
|
|
|
/*
|
|
Helper to display the default tiddlers
|
|
*/
|
|
function displayDefaultTiddlers() {
|
|
$tw.wiki.addTiddler({title: DEFAULT_STORY_TITLE, text: "", list: getDefaultTiddlers()},$tw.wiki.getModificationFields());
|
|
}
|
|
|
|
function getDefaultTiddlers() {
|
|
var defaultTiddlersTiddler = $tw.wiki.getTiddler(DEFAULT_TIDDLERS_TITLE),
|
|
defaultTiddlers = [];
|
|
if(defaultTiddlersTiddler) {
|
|
defaultTiddlers = $tw.wiki.filterTiddlers(defaultTiddlersTiddler.fields.text);
|
|
}
|
|
return defaultTiddlers;
|
|
}
|
|
|
|
function updateLocationHash() {
|
|
var storyList = $tw.wiki.getTiddlerList(DEFAULT_STORY_TITLE),
|
|
historyList = $tw.wiki.getTiddlerData(DEFAULT_HISTORY_TITLE,[]);
|
|
var targetTiddler = "";
|
|
if(historyList.length > 0) {
|
|
targetTiddler = historyList[historyList.length-1].title;
|
|
}
|
|
$tw.locationHash = "#" + encodeURIComponent(targetTiddler) + ":" + encodeURIComponent($tw.utils.stringifyList(storyList));
|
|
// Only change the location hash if we must, thus avoiding unnecessary onhashchange events
|
|
if(window.location.hash !== $tw.locationHash) {
|
|
window.location.hash = $tw.locationHash;
|
|
}
|
|
}
|
|
|
|
/*
|
|
Main render function for PageMacros, which includes the PageTemplate
|
|
*/
|
|
function renderPage() {
|
|
// Parse and render the template
|
|
var templateTitle = "$:/core/ui/PageMacros",
|
|
parser = $tw.wiki.parseTiddler(templateTitle);
|
|
$tw.perf.report("mainRender",function() {
|
|
$tw.pageWidgetNode = $tw.wiki.makeWidget(parser,{document: document, parentWidget: $tw.rootWidget});
|
|
$tw.pageContainer = document.createElement("div");
|
|
$tw.utils.addClass($tw.pageContainer,"tw-page-container-wrapper");
|
|
document.body.insertBefore($tw.pageContainer,document.body.firstChild);
|
|
$tw.pageWidgetNode.render($tw.pageContainer,null);
|
|
})();
|
|
// Prepare refresh mechanism
|
|
var deferredChanges = Object.create(null),
|
|
timerId;
|
|
function refresh() {
|
|
// Process the refresh
|
|
$tw.pageWidgetNode.refresh(deferredChanges,$tw.pageContainer,null);
|
|
deferredChanges = Object.create(null);
|
|
}
|
|
// Add the change event handler
|
|
$tw.wiki.addEventListener("change",$tw.perf.report("mainRefresh",function(changes) {
|
|
// Check if only drafts have changed
|
|
var onlyDraftsHaveChanged = true;
|
|
for(var title in changes) {
|
|
var tiddler = $tw.wiki.getTiddler(title);
|
|
if(!tiddler || !tiddler.hasField("draft.of")) {
|
|
onlyDraftsHaveChanged = false;
|
|
}
|
|
}
|
|
// Defer the change if only drafts have changed
|
|
if(timerId) {
|
|
clearTimeout(timerId);
|
|
}
|
|
timerId = null;
|
|
if(onlyDraftsHaveChanged) {
|
|
timerId = setTimeout(refresh,DRAFT_TIDDLER_TIMEOUT);
|
|
$tw.utils.extend(deferredChanges,changes);
|
|
} else {
|
|
$tw.utils.extend(deferredChanges,changes);
|
|
refresh();
|
|
}
|
|
}));
|
|
}
|
|
|
|
})();
|