mirror of
https://github.com/Jermolene/TiddlyWiki5
synced 2026-02-24 19:09:50 +00: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.
206 lines
6.6 KiB
JavaScript
206 lines
6.6 KiB
JavaScript
/*\
|
|
title: $:/core/modules/widgets/droppable.js
|
|
type: application/javascript
|
|
module-type: widget
|
|
|
|
Droppable widget
|
|
|
|
\*/
|
|
|
|
"use strict";
|
|
|
|
var Widget = require("$:/core/modules/widgets/widget.js").widget;
|
|
|
|
var DroppableWidget = function(parseTreeNode,options) {
|
|
this.initialise(parseTreeNode,options);
|
|
};
|
|
|
|
/*
|
|
Inherit from the base widget class
|
|
*/
|
|
DroppableWidget.prototype = new Widget();
|
|
|
|
/*
|
|
Render this widget into the DOM
|
|
*/
|
|
DroppableWidget.prototype.render = function(parent,nextSibling) {
|
|
var self = this,
|
|
tag = this.parseTreeNode.isBlock ? "div" : "span",
|
|
domNode;
|
|
// Remember parent
|
|
this.parentDomNode = parent;
|
|
// Compute attributes and execute state
|
|
this.computeAttributes();
|
|
this.execute();
|
|
if(this.droppableTag && $tw.config.htmlUnsafeElements.indexOf(this.droppableTag) === -1) {
|
|
tag = this.droppableTag;
|
|
}
|
|
// Create element and assign classes
|
|
domNode = this.document.createElement(tag);
|
|
this.domNode = domNode;
|
|
this.assignDomNodeClasses();
|
|
// Assign data- attributes and style. attributes
|
|
this.assignAttributes(domNode,{
|
|
sourcePrefix: "data-",
|
|
destPrefix: "data-"
|
|
});
|
|
// Add event handlers
|
|
if(this.droppableEnable) {
|
|
$tw.utils.addEventListeners(domNode,[
|
|
{name: "dragenter", handlerObject: this, handlerMethod: "handleDragEnterEvent"},
|
|
{name: "dragover", handlerObject: this, handlerMethod: "handleDragOverEvent"},
|
|
{name: "dragleave", handlerObject: this, handlerMethod: "handleDragLeaveEvent"},
|
|
{name: "drop", handlerObject: this, handlerMethod: "handleDropEvent"}
|
|
]);
|
|
} else {
|
|
$tw.utils.addClass(this.domNode,this.disabledClass);
|
|
}
|
|
// Insert element
|
|
parent.insertBefore(domNode,nextSibling);
|
|
this.domNodes.push(domNode);
|
|
this.renderChildren(domNode,null);
|
|
// Stack of outstanding enter/leave events
|
|
this.currentlyEntered = [];
|
|
};
|
|
|
|
DroppableWidget.prototype.enterDrag = function(event) {
|
|
if(this.currentlyEntered.indexOf(event.target) === -1) {
|
|
this.currentlyEntered.push(event.target);
|
|
}
|
|
// If we're entering for the first time we need to apply highlighting
|
|
$tw.utils.addClass(this.domNodes[0],"tc-dragover");
|
|
};
|
|
|
|
DroppableWidget.prototype.leaveDrag = function(event) {
|
|
var pos = this.currentlyEntered.indexOf(event.target);
|
|
if(pos !== -1) {
|
|
this.currentlyEntered.splice(pos,1);
|
|
}
|
|
// Remove highlighting if we're leaving externally. The hacky second condition is to resolve a problem with Firefox whereby there is an erroneous dragenter event if the node being dragged is within the dropzone
|
|
if(this.currentlyEntered.length === 0 || (this.currentlyEntered.length === 1 && this.currentlyEntered[0] === $tw.dragInProgress)) {
|
|
this.currentlyEntered = [];
|
|
if(this.domNodes[0]) {
|
|
$tw.utils.removeClass(this.domNodes[0],"tc-dragover");
|
|
}
|
|
}
|
|
};
|
|
|
|
DroppableWidget.prototype.handleDragEnterEvent = function(event) {
|
|
this.enterDrag(event);
|
|
// Tell the browser that we're ready to handle the drop
|
|
event.preventDefault();
|
|
// Tell the browser not to ripple the drag up to any parent drop handlers
|
|
event.stopPropagation();
|
|
return false;
|
|
};
|
|
|
|
DroppableWidget.prototype.handleDragOverEvent = function(event) {
|
|
// Check for being over a TEXTAREA or INPUT
|
|
if(["TEXTAREA","INPUT"].indexOf(event.target.tagName) !== -1) {
|
|
return false;
|
|
}
|
|
// Tell the browser that we're still interested in the drop
|
|
event.preventDefault();
|
|
// Set the drop effect
|
|
event.dataTransfer.dropEffect = this.droppableEffect;
|
|
return false;
|
|
};
|
|
|
|
DroppableWidget.prototype.handleDragLeaveEvent = function(event) {
|
|
this.leaveDrag(event);
|
|
return false;
|
|
};
|
|
|
|
DroppableWidget.prototype.handleDropEvent = function(event) {
|
|
var self = this;
|
|
this.leaveDrag(event);
|
|
// Check for being over a TEXTAREA or INPUT
|
|
if(["TEXTAREA","INPUT"].indexOf(event.target.tagName) !== -1) {
|
|
return false;
|
|
}
|
|
var dataTransfer = event.dataTransfer;
|
|
// Remove highlighting
|
|
$tw.utils.removeClass(this.domNodes[0],"tc-dragover");
|
|
// Try to import the various data types we understand
|
|
if(this.droppableActions) {
|
|
$tw.utils.importDataTransfer(dataTransfer,null,function(fieldsArray) {
|
|
fieldsArray.forEach(function(fields) {
|
|
self.performActions(fields.title || fields.text,event);
|
|
});
|
|
});
|
|
}
|
|
// Send a TitleList to performListActions
|
|
if(this.droppableListActions) {
|
|
$tw.utils.importDataTransfer(dataTransfer,null,function(fieldsArray) {
|
|
var titleList = [];
|
|
fieldsArray.forEach(function(fields) {
|
|
titleList.push(fields.title || fields.text);
|
|
});
|
|
self.performListActions($tw.utils.stringifyList(titleList),event);
|
|
});
|
|
}
|
|
// Tell the browser that we handled the drop
|
|
event.preventDefault();
|
|
// Stop the drop ripple up to any parent handlers
|
|
event.stopPropagation();
|
|
return false;
|
|
};
|
|
|
|
DroppableWidget.prototype.performListActions = function(titleList,event) {
|
|
if(this.droppableListActions) {
|
|
var modifierKey = $tw.keyboardManager.getEventModifierKeyDescriptor(event);
|
|
this.invokeActionString(this.droppableListActions,this,event,{actionTiddlerList: titleList, modifier: modifierKey});
|
|
}
|
|
};
|
|
|
|
DroppableWidget.prototype.performActions = function(title,event) {
|
|
if(this.droppableActions) {
|
|
var modifierKey = $tw.keyboardManager.getEventModifierKeyDescriptor(event);
|
|
this.invokeActionString(this.droppableActions,this,event,{actionTiddler: title, modifier: modifierKey});
|
|
}
|
|
};
|
|
|
|
/*
|
|
Compute the internal state of the widget
|
|
*/
|
|
DroppableWidget.prototype.execute = function() {
|
|
this.droppableActions = this.getAttribute("actions");
|
|
this.droppableListActions = this.getAttribute("listActions");
|
|
this.droppableEffect = this.getAttribute("effect","copy");
|
|
this.droppableTag = this.getAttribute("tag");
|
|
this.droppableEnable = (this.getAttribute("enable") || "yes") === "yes";
|
|
this.disabledClass = this.getAttribute("disabledClass","");
|
|
// Make child widgets
|
|
this.makeChildWidgets();
|
|
};
|
|
|
|
DroppableWidget.prototype.assignDomNodeClasses = function() {
|
|
var classes = this.getAttribute("class","").split(" ");
|
|
classes.push("tc-droppable");
|
|
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
|
|
*/
|
|
DroppableWidget.prototype.refresh = function(changedTiddlers) {
|
|
var changedAttributes = this.computeAttributes();
|
|
if(changedAttributes.tag || changedAttributes.enable || changedAttributes.disabledClass ||
|
|
changedAttributes.actions|| changedAttributes.listActions || changedAttributes.effect) {
|
|
this.refreshSelf();
|
|
return true;
|
|
} else {
|
|
if(changedAttributes["class"]) {
|
|
this.assignDomNodeClasses();
|
|
}
|
|
this.assignAttributes(this.domNodes[0],{
|
|
changedAttributes: changedAttributes,
|
|
sourcePrefix: "data-",
|
|
destPrefix: "data-"
|
|
});
|
|
}
|
|
return this.refreshChildren(changedTiddlers);
|
|
};
|
|
|
|
exports.droppable = DroppableWidget;
|