mirror of
https://github.com/Jermolene/TiddlyWiki5
synced 2024-11-24 02:27:19 +00:00
84cd296c58
1. Moved some methods out of boot.js because they are not needed until after bootup 2. Added alternate message for editing an overridden shadow tiddler 3. Minor style tweaks
472 lines
14 KiB
JavaScript
Executable File
472 lines
14 KiB
JavaScript
Executable File
/*\
|
|
title: $:/core/modules/widgets/navigator.js
|
|
type: application/javascript
|
|
module-type: widget
|
|
|
|
Navigator widget
|
|
|
|
\*/
|
|
(function(){
|
|
|
|
/*jslint node: true, browser: true */
|
|
/*global $tw: false */
|
|
"use strict";
|
|
|
|
var Widget = require("$:/core/modules/widgets/widget.js").widget;
|
|
|
|
var NavigatorWidget = function(parseTreeNode,options) {
|
|
this.initialise(parseTreeNode,options);
|
|
this.addEventListeners([
|
|
{type: "tw-navigate", handler: "handleNavigateEvent"},
|
|
{type: "tw-edit-tiddler", handler: "handleEditTiddlerEvent"},
|
|
{type: "tw-delete-tiddler", handler: "handleDeleteTiddlerEvent"},
|
|
{type: "tw-save-tiddler", handler: "handleSaveTiddlerEvent"},
|
|
{type: "tw-cancel-tiddler", handler: "handleCancelTiddlerEvent"},
|
|
{type: "tw-close-tiddler", handler: "handleCloseTiddlerEvent"},
|
|
{type: "tw-close-all-tiddlers", handler: "handleCloseAllTiddlersEvent"},
|
|
{type: "tw-close-other-tiddlers", handler: "handleCloseOtherTiddlersEvent"},
|
|
{type: "tw-new-tiddler", handler: "handleNewTiddlerEvent"},
|
|
{type: "tw-import-tiddlers", handler: "handleImportTiddlersEvent"}
|
|
]);
|
|
};
|
|
|
|
/*
|
|
Inherit from the base widget class
|
|
*/
|
|
NavigatorWidget.prototype = new Widget();
|
|
|
|
/*
|
|
Render this widget into the DOM
|
|
*/
|
|
NavigatorWidget.prototype.render = function(parent,nextSibling) {
|
|
this.parentDomNode = parent;
|
|
this.computeAttributes();
|
|
this.execute();
|
|
this.renderChildren(parent,nextSibling);
|
|
};
|
|
|
|
/*
|
|
Compute the internal state of the widget
|
|
*/
|
|
NavigatorWidget.prototype.execute = function() {
|
|
// Get our parameters
|
|
this.storyTitle = this.getAttribute("story");
|
|
this.historyTitle = this.getAttribute("history");
|
|
// Construct the child widgets
|
|
this.makeChildWidgets();
|
|
};
|
|
|
|
/*
|
|
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
|
|
*/
|
|
NavigatorWidget.prototype.refresh = function(changedTiddlers) {
|
|
var changedAttributes = this.computeAttributes();
|
|
if(changedAttributes.story || changedAttributes.history) {
|
|
this.refreshSelf();
|
|
return true;
|
|
} else {
|
|
return this.refreshChildren(changedTiddlers);
|
|
}
|
|
};
|
|
|
|
NavigatorWidget.prototype.getStoryList = function() {
|
|
return this.storyTitle ? this.wiki.getTiddlerList(this.storyTitle) : null;
|
|
};
|
|
|
|
NavigatorWidget.prototype.saveStoryList = function(storyList) {
|
|
var storyTiddler = this.wiki.getTiddler(this.storyTitle);
|
|
this.wiki.addTiddler(new $tw.Tiddler(
|
|
{title: this.storyTitle},
|
|
storyTiddler,
|
|
{list: storyList}
|
|
));
|
|
};
|
|
|
|
NavigatorWidget.prototype.findTitleInStory = function(storyList,title,defaultIndex) {
|
|
var p = storyList.indexOf(title);
|
|
return p === -1 ? defaultIndex : p;
|
|
};
|
|
|
|
NavigatorWidget.prototype.removeTitleFromStory = function(storyList,title) {
|
|
var p = storyList.indexOf(title);
|
|
while(p !== -1) {
|
|
storyList.splice(p,1);
|
|
p = storyList.indexOf(title);
|
|
}
|
|
};
|
|
|
|
NavigatorWidget.prototype.replaceFirstTitleInStory = function(storyList,oldTitle,newTitle) {
|
|
var pos = storyList.indexOf(oldTitle);
|
|
if(pos !== -1) {
|
|
storyList[pos] = newTitle;
|
|
do {
|
|
pos = storyList.indexOf(oldTitle,pos + 1);
|
|
if(pos !== -1) {
|
|
storyList.splice(pos,1);
|
|
}
|
|
} while(pos !== -1);
|
|
} else {
|
|
storyList.splice(0,0,newTitle);
|
|
}
|
|
};
|
|
|
|
NavigatorWidget.prototype.addToStory = function(title,fromTitle) {
|
|
var storyList = this.getStoryList();
|
|
if(storyList) {
|
|
// See if the tiddler is already there
|
|
var slot = this.findTitleInStory(storyList,title,-1);
|
|
// If not we need to add it
|
|
if(slot === -1) {
|
|
// First we try to find the position of the story element we navigated from
|
|
slot = this.findTitleInStory(storyList,fromTitle,-1) + 1;
|
|
// Add the tiddler
|
|
storyList.splice(slot,0,title);
|
|
// Save the story
|
|
this.saveStoryList(storyList);
|
|
}
|
|
}
|
|
};
|
|
|
|
/*
|
|
Add a new record to the top of the history stack
|
|
title: a title string or an array of title strings
|
|
fromPageRect: page coordinates of the origin of the navigation
|
|
*/
|
|
NavigatorWidget.prototype.addToHistory = function(title,fromPageRect) {
|
|
var titles = $tw.utils.isArray(title) ? title : [title];
|
|
// Add a new record to the top of the history stack
|
|
if(this.historyTitle) {
|
|
var historyList = this.wiki.getTiddlerData(this.historyTitle,[]);
|
|
$tw.utils.each(titles,function(title) {
|
|
historyList.push({title: title, fromPageRect: fromPageRect});
|
|
});
|
|
this.wiki.setTiddlerData(this.historyTitle,historyList,{"current-tiddler": titles[titles.length-1]});
|
|
this.wiki.addTiddler(new $tw.Tiddler());
|
|
}
|
|
};
|
|
|
|
/*
|
|
Handle a tw-navigate event
|
|
*/
|
|
NavigatorWidget.prototype.handleNavigateEvent = function(event) {
|
|
this.addToStory(event.navigateTo,event.navigateFromTitle);
|
|
if(!event.navigateSuppressNavigation) {
|
|
this.addToHistory(event.navigateTo,event.navigateFromClientRect);
|
|
}
|
|
return false;
|
|
};
|
|
|
|
// Close a specified tiddler
|
|
NavigatorWidget.prototype.handleCloseTiddlerEvent = function(event) {
|
|
var title = event.param || event.tiddlerTitle,
|
|
storyList = this.getStoryList();
|
|
// Look for tiddlers with this title to close
|
|
this.removeTitleFromStory(storyList,title);
|
|
this.saveStoryList(storyList);
|
|
return false;
|
|
};
|
|
|
|
// Close all tiddlers
|
|
NavigatorWidget.prototype.handleCloseAllTiddlersEvent = function(event) {
|
|
this.saveStoryList([]);
|
|
return false;
|
|
};
|
|
|
|
// Close other tiddlers
|
|
NavigatorWidget.prototype.handleCloseOtherTiddlersEvent = function(event) {
|
|
var title = event.param || event.tiddlerTitle;
|
|
this.saveStoryList([title]);
|
|
return false;
|
|
};
|
|
|
|
// Place a tiddler in edit mode
|
|
NavigatorWidget.prototype.handleEditTiddlerEvent = function(event) {
|
|
function isUnmodifiedShadow(title) {
|
|
// jshint eqnull:true
|
|
var tiddler = $tw.wiki.getTiddler(title);
|
|
return (
|
|
$tw.wiki.isShadowTiddler(title) &&
|
|
tiddler.fields.modified == null
|
|
);
|
|
}
|
|
function confirmEditShadow(title) {
|
|
return confirm($tw.language.getString(
|
|
"ConfirmEditShadowTiddler",
|
|
{variables:
|
|
{title: title}
|
|
}
|
|
));
|
|
}
|
|
var title = event.param || event.tiddlerTitle;
|
|
if(isUnmodifiedShadow(title) && !confirmEditShadow(title)) {
|
|
return false;
|
|
}
|
|
// Replace the specified tiddler with a draft in edit mode
|
|
var draftTiddler = this.makeDraftTiddler(title),
|
|
draftTitle = draftTiddler.fields.title,
|
|
storyList = this.getStoryList();
|
|
this.removeTitleFromStory(storyList,draftTitle);
|
|
this.replaceFirstTitleInStory(storyList,title,draftTitle);
|
|
this.addToHistory(draftTitle,event.navigateFromClientRect);
|
|
this.saveStoryList(storyList);
|
|
return false;
|
|
};
|
|
|
|
// Delete a tiddler
|
|
NavigatorWidget.prototype.handleDeleteTiddlerEvent = function(event) {
|
|
// Get the tiddler we're deleting
|
|
var title = event.param || event.tiddlerTitle,
|
|
tiddler = this.wiki.getTiddler(title),
|
|
storyList = this.getStoryList(),
|
|
originalTitle, confirmationTitle;
|
|
// Check if the tiddler we're deleting is in draft mode
|
|
if(tiddler.hasField("draft.title")) {
|
|
// If so, we'll prompt for confirmation referencing the original tiddler
|
|
originalTitle = tiddler.fields["draft.of"];
|
|
confirmationTitle = originalTitle;
|
|
} else {
|
|
// If not a draft, then prompt for confirmation referencing the specified tiddler
|
|
originalTitle = null;
|
|
confirmationTitle = title;
|
|
}
|
|
// Seek confirmation
|
|
if(!confirm($tw.language.getString(
|
|
"ConfirmDeleteTiddler",
|
|
{variables:
|
|
{title: confirmationTitle}
|
|
}
|
|
))) {
|
|
return false;
|
|
}
|
|
// Delete the original tiddler
|
|
if(originalTitle) {
|
|
this.wiki.deleteTiddler(originalTitle);
|
|
this.removeTitleFromStory(storyList,originalTitle);
|
|
}
|
|
// Delete this tiddler
|
|
this.wiki.deleteTiddler(title);
|
|
// Remove the closed tiddler from the story
|
|
this.removeTitleFromStory(storyList,title);
|
|
this.saveStoryList(storyList);
|
|
// Send a notification event
|
|
this.dispatchEvent({type: "tw-auto-save-wiki"});
|
|
return false;
|
|
};
|
|
|
|
/*
|
|
Create/reuse the draft tiddler for a given title
|
|
*/
|
|
NavigatorWidget.prototype.makeDraftTiddler = function(targetTitle) {
|
|
// See if there is already a draft tiddler for this tiddler
|
|
var drafts = [];
|
|
this.wiki.forEachTiddler({includeSystem: true},function(title,tiddler) {
|
|
if(tiddler.fields["draft.title"] && tiddler.fields["draft.of"] === targetTitle) {
|
|
drafts.push(tiddler);
|
|
}
|
|
});
|
|
if(drafts.length > 0) {
|
|
return drafts[0];
|
|
}
|
|
// Get the current value of the tiddler we're editing
|
|
var tiddler = this.wiki.getTiddler(targetTitle),
|
|
draftTitle = this.generateDraftTitle(targetTitle);
|
|
// Save the initial value of the draft tiddler
|
|
var draftTiddler = new $tw.Tiddler(
|
|
tiddler,
|
|
{
|
|
title: draftTitle,
|
|
"draft.title": targetTitle,
|
|
"draft.of": targetTitle
|
|
},
|
|
this.wiki.getModificationFields()
|
|
);
|
|
this.wiki.addTiddler(draftTiddler);
|
|
return draftTiddler;
|
|
};
|
|
|
|
/*
|
|
Generate a title for the draft of a given tiddler
|
|
*/
|
|
NavigatorWidget.prototype.generateDraftTitle = function(title) {
|
|
var c = 0;
|
|
do {
|
|
var draftTitle = "Draft " + (c ? (c + 1) + " " : "") + "of '" + title + "'";
|
|
c++;
|
|
} while(this.wiki.tiddlerExists(draftTitle));
|
|
return draftTitle;
|
|
};
|
|
|
|
// Take a tiddler out of edit mode, saving the changes
|
|
NavigatorWidget.prototype.handleSaveTiddlerEvent = function(event) {
|
|
var title = event.param || event.tiddlerTitle,
|
|
tiddler = this.wiki.getTiddler(title),
|
|
storyList = this.getStoryList();
|
|
// Replace the original tiddler with the draft
|
|
if(tiddler) {
|
|
var draftTitle = (tiddler.fields["draft.title"] || "").trim(),
|
|
draftOf = (tiddler.fields["draft.of"] || "").trim();
|
|
if(draftTitle) {
|
|
var isRename = draftOf !== draftTitle,
|
|
isConfirmed = true;
|
|
if(isRename && this.wiki.tiddlerExists(draftTitle)) {
|
|
isConfirmed = confirm($tw.language.getString(
|
|
"ConfirmOverwriteTiddler",
|
|
{variables:
|
|
{title: draftTitle}
|
|
}
|
|
));
|
|
} else if(!this.wiki.isDraftModified(title)) {
|
|
event.type = "tw-cancel-tiddler";
|
|
this.dispatchEvent(event);
|
|
} else if(isConfirmed) {
|
|
// Save the draft tiddler as the real tiddler
|
|
this.wiki.addTiddler(new $tw.Tiddler(this.wiki.getCreationFields(),tiddler,{
|
|
title: draftTitle,
|
|
"draft.title": undefined,
|
|
"draft.of": undefined
|
|
},this.wiki.getModificationFields()));
|
|
// Remove the draft tiddler
|
|
this.wiki.deleteTiddler(title);
|
|
// Remove the original tiddler if we're renaming it
|
|
if(isRename) {
|
|
this.wiki.deleteTiddler(draftOf);
|
|
}
|
|
// Replace the draft in the story with the original
|
|
this.replaceFirstTitleInStory(storyList,title,draftTitle);
|
|
this.addToHistory(draftTitle,event.navigateFromClientRect);
|
|
if(draftTitle !== this.storyTitle) {
|
|
this.saveStoryList(storyList);
|
|
}
|
|
// Send a notification event
|
|
this.dispatchEvent({type: "tw-auto-save-wiki"});
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
|
|
// Take a tiddler out of edit mode without saving the changes
|
|
NavigatorWidget.prototype.handleCancelTiddlerEvent = function(event) {
|
|
// Flip the specified tiddler from draft back to the original
|
|
var draftTitle = event.param || event.tiddlerTitle,
|
|
draftTiddler = this.wiki.getTiddler(draftTitle),
|
|
originalTitle = draftTiddler.fields["draft.of"],
|
|
storyList = this.getStoryList();
|
|
if(draftTiddler && originalTitle) {
|
|
// Ask for confirmation if the tiddler text has changed
|
|
var isConfirmed = true;
|
|
if(this.wiki.getTiddlerText(draftTitle) !== this.wiki.getTiddlerText(originalTitle)) {
|
|
isConfirmed = confirm($tw.language.getString(
|
|
"ConfirmCancelTiddler",
|
|
{variables:
|
|
{title: draftTitle}
|
|
}
|
|
));
|
|
}
|
|
// Remove the draft tiddler
|
|
if(isConfirmed) {
|
|
this.wiki.deleteTiddler(draftTitle);
|
|
this.replaceFirstTitleInStory(storyList,draftTitle,originalTitle);
|
|
this.addToHistory(originalTitle,event.navigateFromClientRect);
|
|
this.saveStoryList(storyList);
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
|
|
// Create a new draft tiddler
|
|
NavigatorWidget.prototype.handleNewTiddlerEvent = function(event) {
|
|
// Get the story details
|
|
var storyList = this.getStoryList();
|
|
// Get the template tiddler if there is one
|
|
var templateTiddler = this.wiki.getTiddler(event.param);
|
|
// Create the new tiddler
|
|
var title = this.wiki.generateNewTitle((templateTiddler && templateTiddler.fields.title) || "New Tiddler");
|
|
var tiddler = new $tw.Tiddler(this.wiki.getCreationFields(),{
|
|
text: "Newly created tiddler",
|
|
title: title
|
|
},this.wiki.getModificationFields());
|
|
this.wiki.addTiddler(tiddler);
|
|
// Create the draft tiddler
|
|
var draftTitle = this.generateDraftTitle(title),
|
|
draftTiddler = new $tw.Tiddler({
|
|
text: ""
|
|
},templateTiddler,
|
|
this.wiki.getCreationFields(),
|
|
{
|
|
title: draftTitle,
|
|
"draft.title": title,
|
|
"draft.of": title
|
|
},this.wiki.getModificationFields());
|
|
this.wiki.addTiddler(draftTiddler);
|
|
// Update the story to insert the new draft at the top
|
|
var slot = storyList.indexOf(event.navigateFromTitle);
|
|
storyList.splice(slot + 1,0,draftTitle);
|
|
// Save the updated story
|
|
this.saveStoryList(storyList);
|
|
// Add a new record to the top of the history stack
|
|
this.addToHistory(draftTitle);
|
|
return false;
|
|
};
|
|
|
|
// Import JSON tiddlers
|
|
NavigatorWidget.prototype.handleImportTiddlersEvent = function(event) {
|
|
var self = this;
|
|
// Get the tiddlers
|
|
var tiddlers = [];
|
|
try {
|
|
tiddlers = JSON.parse(event.param);
|
|
} catch(e) {
|
|
}
|
|
// Process each tiddler
|
|
var importedTiddlers = [];
|
|
$tw.utils.each(tiddlers,function(tiddlerFields) {
|
|
var title = tiddlerFields.title;
|
|
// Add it to the store
|
|
var imported = self.wiki.importTiddler(new $tw.Tiddler(
|
|
self.wiki.getCreationFields(),
|
|
self.wiki.getModificationFields(),
|
|
tiddlerFields
|
|
));
|
|
if(imported) {
|
|
importedTiddlers.push(title);
|
|
}
|
|
});
|
|
// Get the story and history details
|
|
var storyList = this.getStoryList(),
|
|
history = [];
|
|
// Create the import report tiddler
|
|
if(importedTiddlers.length === 0) {
|
|
return false;
|
|
}
|
|
var title;
|
|
if(importedTiddlers.length > 1) {
|
|
title = this.wiki.generateNewTitle("$:/temp/ImportReport");
|
|
var tiddlerFields = {
|
|
title: title,
|
|
text: "# [[" + importedTiddlers.join("]]\n# [[") + "]]\n"
|
|
};
|
|
this.wiki.addTiddler(new $tw.Tiddler(
|
|
self.wiki.getCreationFields(),
|
|
tiddlerFields,
|
|
self.wiki.getModificationFields()
|
|
));
|
|
} else {
|
|
title = importedTiddlers[0];
|
|
}
|
|
// Add it to the story
|
|
if(storyList.indexOf(title) === -1) {
|
|
storyList.unshift(title);
|
|
}
|
|
// And to history
|
|
history.push(title);
|
|
// Save the updated story and history
|
|
this.saveStoryList(storyList);
|
|
this.addToHistory(history);
|
|
return false;
|
|
};
|
|
|
|
exports.navigator = NavigatorWidget;
|
|
|
|
})();
|