mirror of
https://github.com/Jermolene/TiddlyWiki5.git
synced 2026-03-09 08:13:04 -07:00
* Introduced preliminary idea for infinite recurse exception * Better handling of infinite recursion But it could be better still... * the TransclusionError is a proper error Moved the magic number to be on the error's class. Not sure if that's a great idea. * Fixed minor minor issue that came up in conflict The minor fix to the jasmine regexp that escaped a '+' somehow broke some random test. * Removing patch fix for recursion errors * Fixed issue where buttton and other widgets don't clean up * Added release notes for #9548 * Update test-widget.js If I don't fix those indentations, the entire TW codebase will explode or soemthing. * Update test-widget.js These lint problems are wasting my time. * Fixed all core widgets to not leak when renderChildren fails * Updated release notes to reflect what I'm actually fixing * Update test-widget.js Added warning not to use for-of loop for defining tests. The iterating variable needs to have its own method scope, or it risks being the same value for all tests.
295 lines
9.5 KiB
JavaScript
Executable file
295 lines
9.5 KiB
JavaScript
Executable file
/*\
|
|
title: $:/core/modules/widgets/reveal.js
|
|
type: application/javascript
|
|
module-type: widget
|
|
|
|
Reveal widget
|
|
|
|
\*/
|
|
|
|
"use strict";
|
|
|
|
var Widget = require("$:/core/modules/widgets/widget.js").widget;
|
|
|
|
var Popup = require("$:/core/modules/utils/dom/popup.js");
|
|
|
|
var RevealWidget = function(parseTreeNode,options) {
|
|
this.initialise(parseTreeNode,options);
|
|
};
|
|
|
|
/*
|
|
Inherit from the base widget class
|
|
*/
|
|
RevealWidget.prototype = new Widget();
|
|
|
|
/*
|
|
Render this widget into the DOM
|
|
*/
|
|
RevealWidget.prototype.render = function(parent,nextSibling) {
|
|
this.parentDomNode = parent;
|
|
this.computeAttributes();
|
|
this.execute();
|
|
var tag = this.parseTreeNode.isBlock ? "div" : "span";
|
|
if(this.revealTag && $tw.config.htmlUnsafeElements.indexOf(this.revealTag) === -1) {
|
|
tag = this.revealTag;
|
|
}
|
|
var domNode = this.document.createElement(tag);
|
|
this.domNode = domNode;
|
|
this.assignDomNodeClasses();
|
|
if(this.style) {
|
|
domNode.setAttribute("style",this.style);
|
|
}
|
|
parent.insertBefore(domNode,nextSibling);
|
|
this.domNodes.push(domNode);
|
|
this.renderChildren(domNode,null);
|
|
if(!domNode.isTiddlyWikiFakeDom && this.type === "popup" && this.isOpen) {
|
|
this.positionPopup(domNode);
|
|
$tw.utils.addClass(domNode,"tc-popup"); // Make sure that clicks don't dismiss popups within the revealed content
|
|
}
|
|
if(!this.isOpen) {
|
|
domNode.setAttribute("hidden","true");
|
|
}
|
|
};
|
|
|
|
RevealWidget.prototype.positionPopup = function(domNode) {
|
|
domNode.style.position = "absolute";
|
|
domNode.style.zIndex = "1000";
|
|
var left,top;
|
|
switch(this.position) {
|
|
case "left":
|
|
left = this.popup.left - domNode.offsetWidth;
|
|
top = this.popup.top;
|
|
break;
|
|
case "above":
|
|
left = this.popup.left;
|
|
top = this.popup.top - domNode.offsetHeight;
|
|
break;
|
|
case "aboveright":
|
|
left = this.popup.left + this.popup.width;
|
|
top = this.popup.top + this.popup.height - domNode.offsetHeight;
|
|
break;
|
|
case "belowright":
|
|
left = this.popup.left + this.popup.width;
|
|
top = this.popup.top + this.popup.height;
|
|
break;
|
|
case "right":
|
|
left = this.popup.left + this.popup.width;
|
|
top = this.popup.top;
|
|
break;
|
|
case "belowleft":
|
|
left = this.popup.left + this.popup.width - domNode.offsetWidth;
|
|
top = this.popup.top + this.popup.height;
|
|
break;
|
|
case "aboveleft":
|
|
left = this.popup.left - domNode.offsetWidth;
|
|
top = this.popup.top - domNode.offsetHeight;
|
|
break;
|
|
default: // Below
|
|
left = this.popup.left;
|
|
top = this.popup.top + this.popup.height;
|
|
break;
|
|
}
|
|
// if requested, clamp the popup so that it will always be fully inside its parent (the first upstream element with position:relative), as long as the popup is smaller than its parent
|
|
// if position is absolute then clamping is done to the canvas boundary, since there is no "parent"
|
|
if(this.clampToParent !== "none") {
|
|
if(this.popup.absolute) {
|
|
var parentWidth = window.innerWidth,
|
|
parentHeight = window.innerHeight;
|
|
} else {
|
|
var parentWidth = domNode.offsetParent.offsetWidth,
|
|
parentHeight = domNode.offsetParent.offsetHeight;
|
|
}
|
|
var right = left + domNode.offsetWidth,
|
|
bottom = top + domNode.offsetHeight;
|
|
if((this.clampToParent === "both" || this.clampToParent === "right") && right > parentWidth) {
|
|
left = parentWidth - domNode.offsetWidth;
|
|
}
|
|
if((this.clampToParent === "both" || this.clampToParent === "bottom") && bottom > parentHeight) {
|
|
top = parentHeight - domNode.offsetHeight;
|
|
}
|
|
// clamping on left and top sides is taken care of by positionAllowNegative
|
|
}
|
|
if(!this.positionAllowNegative) {
|
|
left = Math.max(0,left);
|
|
top = Math.max(0,top);
|
|
}
|
|
if(this.popup.absolute) {
|
|
// Traverse the offsetParent chain and correct the offset to make it relative to the parent node.
|
|
for(var offsetParentDomNode = domNode.offsetParent; offsetParentDomNode; offsetParentDomNode = offsetParentDomNode.offsetParent) {
|
|
left -= offsetParentDomNode.offsetLeft;
|
|
top -= offsetParentDomNode.offsetTop;
|
|
}
|
|
}
|
|
domNode.style.left = left + "px";
|
|
domNode.style.top = top + "px";
|
|
};
|
|
|
|
/*
|
|
Compute the internal state of the widget
|
|
*/
|
|
RevealWidget.prototype.execute = function() {
|
|
// Get our parameters
|
|
this.state = this.getAttribute("state");
|
|
this.revealTag = this.getAttribute("tag");
|
|
this.type = this.getAttribute("type");
|
|
this.text = this.getAttribute("text");
|
|
this.position = this.getAttribute("position");
|
|
this.positionAllowNegative = this.getAttribute("positionAllowNegative") === "yes";
|
|
// class attribute handled in assignDomNodeClasses()
|
|
this.style = this.getAttribute("style","");
|
|
this["default"] = this.getAttribute("default","");
|
|
this.animate = this.getAttribute("animate","no");
|
|
this.retain = this.getAttribute("retain","no");
|
|
this.openAnimation = this.animate === "no" ? undefined : "open";
|
|
this.closeAnimation = this.animate === "no" ? undefined : "close";
|
|
this.updatePopupPosition = this.getAttribute("updatePopupPosition","no") === "yes";
|
|
this.clampToParent = this.getAttribute("clamp","none");
|
|
// Compute the title of the state tiddler and read it
|
|
this.stateTiddlerTitle = this.state;
|
|
this.stateTitle = this.getAttribute("stateTitle");
|
|
this.stateField = this.getAttribute("stateField");
|
|
this.stateIndex = this.getAttribute("stateIndex");
|
|
this.readState();
|
|
// Construct the child widgets
|
|
var childNodes = this.isOpen ? this.parseTreeNode.children : [];
|
|
this.hasChildNodes = this.isOpen;
|
|
this.makeChildWidgets(childNodes);
|
|
};
|
|
|
|
/*
|
|
Read the state tiddler
|
|
*/
|
|
RevealWidget.prototype.readState = function() {
|
|
// Read the information from the state tiddler
|
|
var state,
|
|
defaultState = this["default"];
|
|
if(this.stateTitle) {
|
|
var stateTitleTiddler = this.wiki.getTiddler(this.stateTitle);
|
|
if(this.stateField) {
|
|
state = stateTitleTiddler ? stateTitleTiddler.getFieldString(this.stateField) || defaultState : defaultState;
|
|
} else if(this.stateIndex) {
|
|
state = stateTitleTiddler ? this.wiki.extractTiddlerDataItem(this.stateTitle,this.stateIndex) || defaultState : defaultState;
|
|
} else if(stateTitleTiddler) {
|
|
state = this.wiki.getTiddlerText(this.stateTitle) || defaultState;
|
|
} else {
|
|
state = defaultState;
|
|
}
|
|
} else {
|
|
state = this.stateTiddlerTitle ? this.wiki.getTextReference(this.state,this["default"],this.getVariable("currentTiddler")) : this["default"];
|
|
}
|
|
if(state === null) {
|
|
state = this["default"];
|
|
}
|
|
switch(this.type) {
|
|
case "popup":
|
|
this.readPopupState(state);
|
|
break;
|
|
case "match":
|
|
this.isOpen = this.text === state;
|
|
break;
|
|
case "nomatch":
|
|
this.isOpen = this.text !== state;
|
|
break;
|
|
case "lt":
|
|
this.isOpen = !!(this.compareStateText(state) < 0);
|
|
break;
|
|
case "gt":
|
|
this.isOpen = !!(this.compareStateText(state) > 0);
|
|
break;
|
|
case "lteq":
|
|
this.isOpen = !(this.compareStateText(state) > 0);
|
|
break;
|
|
case "gteq":
|
|
this.isOpen = !(this.compareStateText(state) < 0);
|
|
break;
|
|
}
|
|
};
|
|
|
|
RevealWidget.prototype.compareStateText = function(state) {
|
|
return state.localeCompare(this.text,undefined,{numeric: true,sensitivity: "case"});
|
|
};
|
|
|
|
RevealWidget.prototype.readPopupState = function(state) {
|
|
this.popup = Popup.parseCoordinates(state);
|
|
// Check if the state matches the location regexp
|
|
if(this.popup) {
|
|
// If so, we're open
|
|
this.isOpen = true;
|
|
} else {
|
|
// If not, we're closed
|
|
this.isOpen = false;
|
|
}
|
|
};
|
|
|
|
RevealWidget.prototype.assignDomNodeClasses = function() {
|
|
var classes = this.getAttribute("class","").split(" ");
|
|
classes.push("tc-reveal");
|
|
this.domNode.className = classes.join(" ").trim();
|
|
};
|
|
|
|
/*
|
|
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
|
|
*/
|
|
RevealWidget.prototype.refresh = function(changedTiddlers) {
|
|
var changedAttributes = this.computeAttributes();
|
|
if(changedAttributes.state || changedAttributes.type || changedAttributes.text || changedAttributes.position || changedAttributes.positionAllowNegative || changedAttributes["default"] || changedAttributes.animate || changedAttributes.stateTitle || changedAttributes.stateField || changedAttributes.stateIndex) {
|
|
this.refreshSelf();
|
|
return true;
|
|
} else {
|
|
var currentlyOpen = this.isOpen;
|
|
this.readState();
|
|
if(this.isOpen !== currentlyOpen) {
|
|
if(this.retain === "yes") {
|
|
this.updateState();
|
|
} else {
|
|
this.refreshSelf();
|
|
return true;
|
|
}
|
|
} else if(this.type === "popup" && this.isOpen && this.updatePopupPosition && (changedTiddlers[this.state] || changedTiddlers[this.stateTitle])) {
|
|
this.positionPopup(this.domNode);
|
|
}
|
|
if(changedAttributes.style) {
|
|
this.domNode.style = this.getAttribute("style","");
|
|
}
|
|
if(changedAttributes["class"]) {
|
|
this.assignDomNodeClasses();
|
|
}
|
|
return this.refreshChildren(changedTiddlers);
|
|
}
|
|
};
|
|
|
|
/*
|
|
Called by refresh() to dynamically show or hide the content
|
|
*/
|
|
RevealWidget.prototype.updateState = function() {
|
|
var self = this;
|
|
// Read the current state
|
|
this.readState();
|
|
// Construct the child nodes if needed
|
|
var domNode = this.domNodes[0];
|
|
if(this.isOpen && !this.hasChildNodes) {
|
|
this.hasChildNodes = true;
|
|
this.makeChildWidgets(this.parseTreeNode.children);
|
|
this.renderChildren(domNode,null);
|
|
}
|
|
// Animate our DOM node
|
|
if(this.isOpen) {
|
|
domNode.removeAttribute("hidden");
|
|
// Position popup after making it visible to ensure correct dimensions
|
|
if(!domNode.isTiddlyWikiFakeDom && this.type === "popup") {
|
|
this.positionPopup(domNode);
|
|
$tw.utils.addClass(domNode,"tc-popup"); // Make sure that clicks don't dismiss popups within the revealed content
|
|
}
|
|
$tw.anim.perform(this.openAnimation,domNode);
|
|
} else {
|
|
$tw.anim.perform(this.closeAnimation,domNode,{callback: function() {
|
|
//make sure that the state hasn't changed during the close animation
|
|
self.readState();
|
|
if(!self.isOpen) {
|
|
domNode.setAttribute("hidden","true");
|
|
}
|
|
}});
|
|
}
|
|
};
|
|
|
|
exports.reveal = RevealWidget;
|