kopia lustrzana https://github.com/c9/core
704 wiersze
27 KiB
JavaScript
704 wiersze
27 KiB
JavaScript
define(function(require, exports, module) {
|
|
main.consumes = [
|
|
"Plugin", "ui", "tabManager", "ace", "anims", "settings", "preferences"
|
|
];
|
|
main.provides = ["tabinteraction"];
|
|
return main;
|
|
|
|
function main(options, imports, register) {
|
|
var Plugin = imports.Plugin;
|
|
var ui = imports.ui;
|
|
var anims = imports.anims;
|
|
var settings = imports.settings;
|
|
var prefs = imports.preferences;
|
|
var tabs = imports.tabManager;
|
|
var aceHandle = imports.ace;
|
|
|
|
var css = require("text!./style.css");
|
|
|
|
/***** Initialization *****/
|
|
|
|
var handle = new Plugin("Ajax.org", main.consumes);
|
|
// var emit = handle.getEmitter();
|
|
|
|
var divSplit, divButton, plusMargin = 11;
|
|
|
|
var loaded = false;
|
|
function load() {
|
|
if (loaded) return false;
|
|
loaded = true;
|
|
|
|
// Insert CSS
|
|
ui.insertCss(css, null, handle);
|
|
|
|
settings.on("read", function() {
|
|
settings.setDefaults("user/tabs", [["autoclosepanes", true]]);
|
|
}, handle);
|
|
|
|
tabs.on("tabCreate", function(e) {
|
|
var tab = e.tab;
|
|
|
|
addInteraction(tab);
|
|
|
|
// Make sure that events are put on the button when the skin changes
|
|
tab.aml.on("$skinchange", function() {
|
|
addInteraction(tab);
|
|
});
|
|
}, handle);
|
|
|
|
tabs.on("tabDestroy", function(e) {
|
|
if (e.tab.meta.$skipAnimation)
|
|
setTimeout(function() { e.tab.pane.unload(); }, 0);
|
|
}, handle);
|
|
|
|
tabs.on("tabAfterClose", function(e) {
|
|
if (e.last && canTabBeRemoved(e.tab.pane, 1)
|
|
&& settings.getBool("user/tabs/@autoclosepanes")) {
|
|
e.tab.meta.$skipAnimation = true;
|
|
}
|
|
}, handle);
|
|
|
|
prefs.add({
|
|
"General": {
|
|
"User Interface": {
|
|
position: 20,
|
|
"Automatically Close Empty Panes": {
|
|
type: "checkbox",
|
|
path: "user/tabs/@autoclosepanes",
|
|
position: 1150
|
|
}
|
|
}
|
|
}
|
|
}, handle);
|
|
|
|
ui.insertCss("* { }", false, handle);
|
|
}
|
|
|
|
function canTabBeRemoved(pane, min) {
|
|
if (!pane || pane.getTabs().length > (min || 0))
|
|
return false;
|
|
|
|
var containers = tabs.containers;
|
|
for (var i = 0; i < containers.length; i++) {
|
|
if (ui.isChildOf(containers[i], pane.aml)) {
|
|
return containers[i]
|
|
.getElementsByTagNameNS(apf.ns.aml, "tab").length > 1;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function addInteraction(plugin) {
|
|
var tab = plugin.aml;
|
|
var button = tab.$button;
|
|
if (!button) return;
|
|
|
|
var offsetX, offsetY, startX, startY, dragWidth;
|
|
var mode, rightPadding, originalTab, btnPlus, pane;
|
|
var started, tabWidth, leftPadding, leftPos, start, initMouse;
|
|
var pages, clean, originalPosition, splitDirection, splitTab;
|
|
|
|
function setOrderMode(toTab, e) {
|
|
if (toTab.isOrderCleaned === false) {
|
|
return setTimeout(function() {
|
|
setOrderMode(toTab, e);
|
|
}, 10);
|
|
}
|
|
|
|
mode = "order";
|
|
clean && clean();
|
|
|
|
var lastPane = pane;
|
|
|
|
// Set new pane
|
|
pane = toTab;
|
|
|
|
// Plus Button
|
|
btnPlus = pane.$ext.querySelector(".plus_tab_button");
|
|
|
|
// Attach tab to pane
|
|
if (e) {
|
|
var curpage = pane.getPage();
|
|
if (curpage) {
|
|
var curbtn = curpage.$button;
|
|
ui.setStyleClass(curbtn, "", ["curbtn"]);
|
|
}
|
|
|
|
ui.setStyleClass(tab.$button, "curbtn");
|
|
}
|
|
|
|
var container = pane.$buttons;
|
|
var nodes = container.childNodes;
|
|
var rect = container.getBoundingClientRect();
|
|
var btn = (pane.getPage() || { $button: button }).$button;
|
|
var diff = ui.getWidthDiff(btn);
|
|
|
|
var leftMargin = parseInt(ui.getStyle(btn, "marginLeft"), 10) || 0;
|
|
var rightMargin = parseInt(ui.getStyle(btn, "marginRight"), 10) || 0;
|
|
var maxWidth = parseInt(ui.getStyle(btn, "maxWidth"), 10) || 150;
|
|
if (maxWidth > 500) maxWidth = 150;
|
|
|
|
leftPos = rect.left;
|
|
pages = pane.getPages();
|
|
leftPadding = parseInt(ui.getStyle(container, "paddingLeft"), 10) || 0;
|
|
rightPadding = (parseInt(ui.getStyle(container, "paddingRight"), 10) || 0) + 24;
|
|
|
|
var addOne = pages.indexOf(tab) == -1;
|
|
var maxTabWidth = Math.min(maxWidth + diff,
|
|
((rect.width - leftPadding - rightPadding + rightMargin)
|
|
/ (pages.length + (addOne ? 1 : 0))) - rightMargin);
|
|
var newTabWidth = maxTabWidth - diff;
|
|
|
|
tabWidth = maxTabWidth + leftMargin + rightMargin;
|
|
|
|
// Get the positions info of the tab buttons
|
|
var info = [];
|
|
for (var i = nodes.length - 1; i >= 0; i--) {
|
|
if ((btn = nodes[i]).nodeType != 1) continue;
|
|
info.push([btn, btn.offsetLeft, btn.offsetTop, btn.offsetWidth]);
|
|
}
|
|
|
|
// Append the button to the button container
|
|
if (e || addOne) {
|
|
pane.$buttons.appendChild(button);
|
|
info.push([button, 0, button.offsetTop, dragWidth]);
|
|
}
|
|
|
|
// Set the info
|
|
var iter;
|
|
while ((iter = info.pop())) {
|
|
btn = iter[0];
|
|
btn.style.left = (iter[1]) + "px";
|
|
btn.style.top = (iter[2]) + "px";
|
|
btn.style.width = (iter[3] - ui.getWidthDiff(btn)) + "px";
|
|
btn.style.margin = 0;
|
|
btn.style.position = "absolute";
|
|
}
|
|
|
|
start = function() {
|
|
// Remove from childNodes of old pane
|
|
var lastIndex = pane.childNodes.indexOf(tab);
|
|
pane.childNodes.remove(tab);
|
|
};
|
|
|
|
if (started)
|
|
start();
|
|
|
|
// Set initial position
|
|
if (e || addOne)
|
|
mouseMoveOrder(e, newTabWidth); //, lastPane == pane);
|
|
|
|
apf.addListener(document, "mousemove", mouseMoveOrder);
|
|
apf.addListener(document, "mouseup", mouseUpOrder);
|
|
|
|
clean = function(change, toTab) {
|
|
if (!toTab)
|
|
toTab = pane;
|
|
toTab.isOrderCleaned = false;
|
|
|
|
if (change !== false) {
|
|
apf.removeListener(document, "mousemove", mouseMoveOrder);
|
|
apf.removeListener(document, "mouseup", mouseUpOrder);
|
|
}
|
|
|
|
if (change === true) {
|
|
var maxTabWidth = Math.min(maxWidth + diff,
|
|
((rect.width - leftPadding - rightPadding + rightMargin + 3)
|
|
/ pane.getPages().length) - rightMargin);
|
|
tabWidth = maxTabWidth + leftMargin + rightMargin;
|
|
|
|
var cb = clean.bind(this, false, toTab);
|
|
return animateTabs(cb, null, maxTabWidth - diff);
|
|
}
|
|
|
|
if (curbtn && curpage.parentNode && curpage == curpage.parentNode.getPage()) {
|
|
ui.setStyleClass(curbtn, "curbtn");
|
|
curbtn = null;
|
|
}
|
|
|
|
for (var i = nodes.length - 1; i >= 0; i--) {
|
|
if ((btn = nodes[i]).nodeType != 1) continue;
|
|
btn.style.left =
|
|
btn.style.top =
|
|
btn.style.width =
|
|
btn.style.margin =
|
|
btn.style.position = "";
|
|
}
|
|
|
|
toTab.isOrderCleaned = true;
|
|
};
|
|
}
|
|
|
|
function setSplitMode(e) {
|
|
mode = "split";
|
|
|
|
// Div that shows where to insert split
|
|
if (!divSplit) {
|
|
divSplit = document.createElement("div");
|
|
divSplit.className = "split-area";
|
|
document.body.appendChild(divSplit);
|
|
}
|
|
|
|
// Remove all pointer events from iframes
|
|
var frames = document.getElementsByTagName("iframe");
|
|
for (var i = 0; i < frames.length; i++)
|
|
frames[i].style.pointerEvents = "none";
|
|
|
|
start = function() {
|
|
// Fixate current position and width
|
|
var rect = button.getBoundingClientRect();
|
|
button.style.left = (rect.left) + "px";
|
|
button.style.top = (rect.top) + "px";
|
|
button.style.width = (dragWidth - ui.getWidthDiff(button)) + "px";
|
|
button.style.position = "absolute";
|
|
|
|
// Attach tab to body
|
|
if (!divButton) {
|
|
divButton = document.createElement("div");
|
|
document.body.appendChild(divButton);
|
|
}
|
|
|
|
var theme = aceHandle.theme || {};
|
|
divButton.className =
|
|
(theme.isDark ? "dark " : "") + (theme.cssClass || "");
|
|
divButton.appendChild(button);
|
|
|
|
// Remove from parent childNodes
|
|
pane.childNodes.remove(tab);
|
|
|
|
ui.setStyleRule("*", "cursor", "default!important");
|
|
};
|
|
|
|
apf.addListener(document, "mousemove", mouseMoveSplit);
|
|
apf.addListener(document, "mouseup", mouseUpSplit);
|
|
|
|
if (started)
|
|
start();
|
|
|
|
clean && clean(true);
|
|
|
|
clean = function() {
|
|
button.style.left =
|
|
button.style.top =
|
|
button.style.width =
|
|
button.style.margin =
|
|
button.style.position = "";
|
|
|
|
divSplit.style.display = "none";
|
|
|
|
ui.setStyleRule("*", "cursor", "");
|
|
|
|
apf.removeListener(document, "mousemove", mouseMoveSplit);
|
|
apf.removeListener(document, "mouseup", mouseUpSplit);
|
|
};
|
|
|
|
if (started) {
|
|
// Set initial position and detect immediate snap
|
|
if (mouseMoveSplit(e) === false)
|
|
return;
|
|
}
|
|
}
|
|
|
|
function finish() {
|
|
if (!initMouse) {
|
|
clean(null, null, true);
|
|
|
|
button.style.zIndex =
|
|
button.style.pointerEvents = "";
|
|
|
|
// Return all pointer events to iframes
|
|
var frames = document.getElementsByTagName("iframe");
|
|
for (var i = 0; i < frames.length; i++)
|
|
frames[i].style.pointerEvents = "";
|
|
}
|
|
|
|
tab.$dragging = false;
|
|
}
|
|
|
|
button.addEventListener("mousedown", function(e) {
|
|
// Tab needs to support ordering
|
|
if (!tab.parentNode.$order || tab.$dragging || e.button)
|
|
return;
|
|
|
|
// APF stuff
|
|
tab.$dragging = true;
|
|
|
|
startX = e.clientX;
|
|
startY = e.clientY;
|
|
|
|
initMouse = function() {
|
|
// Calculate where on the button was clicked
|
|
var rect = button.getBoundingClientRect();
|
|
offsetX = startX - rect.left;
|
|
offsetY = startY - rect.top;
|
|
|
|
// Prepare button for dragging
|
|
button.style.zIndex = 100000;
|
|
button.style.pointerEvents = "none";
|
|
|
|
// Initialize with order mode
|
|
setOrderMode(tab.parentNode, e);
|
|
|
|
initMouse = null;
|
|
};
|
|
|
|
// Use mine
|
|
started = false;
|
|
|
|
// Set current pane
|
|
pane = plugin.pane.aml;
|
|
|
|
// Store original info
|
|
originalTab = pane;
|
|
originalPosition = button.nextSibling;
|
|
dragWidth = button.offsetWidth;
|
|
|
|
apf.addListener(document, "mousemove", mouseMoveOrder);
|
|
apf.addListener(document, "mouseup", mouseUpOrder);
|
|
}, true);
|
|
|
|
function isNotSnapped(e, container) {
|
|
if (!container)
|
|
container = pane.$buttons;
|
|
var rect = container.getBoundingClientRect();
|
|
|
|
var x = e.clientX;
|
|
var y = e.clientY;
|
|
var diff = 10;
|
|
|
|
return (
|
|
x < rect.left - diff ||
|
|
x > rect.left + rect.width + diff ||
|
|
y < rect.top - 5 ||
|
|
y > rect.top + rect.height + diff
|
|
);
|
|
}
|
|
|
|
function showOrderPosition(idx, toWidth, finalize, finish) {
|
|
if (idx < 0) idx = 0;
|
|
|
|
var orderTab = (pages[idx - 1] == tab
|
|
? pages[idx + 1]
|
|
: pages[idx]) || null;
|
|
|
|
// Remove tab from childNodes
|
|
pane.childNodes.remove(tab);
|
|
|
|
if (finalize) {
|
|
// Get new pages with new order
|
|
pages = pane.getPages();
|
|
|
|
// Reparent for real
|
|
var insert = pages[idx] && pages[idx].cloud9tab;
|
|
plugin.attachTo(pane.cloud9pane, insert, true);
|
|
}
|
|
else {
|
|
// If we're already at this position do nothing
|
|
if (orderTab == tab)
|
|
return;
|
|
|
|
// Move tab to new position
|
|
idx = pane.childNodes.indexOf(orderTab);
|
|
if (idx > -1) pane.childNodes.splice(idx, 0, tab);
|
|
else pane.childNodes.push(tab);
|
|
|
|
pane.$buttons.insertBefore(tab.$button,
|
|
orderTab && orderTab.$button || btnPlus);
|
|
}
|
|
|
|
// Patch + button which is changed to "" again
|
|
// btnPlus.style.position = "absolute";
|
|
// btnPlus.style.top = "6px";
|
|
|
|
animateTabs(finish, finalize, toWidth);
|
|
}
|
|
|
|
function animateTabs(finish, includeTab, toWidth) {
|
|
// Get new pages array (with new order)
|
|
pages = pane.getPages();
|
|
pages.push({ $button: btnPlus });
|
|
|
|
// Animate all pages to their right position
|
|
var p, tweens = [], offset = 0;
|
|
for (var i = 0, l = pages.length; i < l; i++) {
|
|
p = pages[i];
|
|
|
|
// Ignore the tab we are dragging
|
|
if (!includeTab && tab === p) {
|
|
if (p.$button.parentNode == document.body)
|
|
offset = 1;
|
|
if (toWidth)
|
|
p.$button.style.width = toWidth + "px";
|
|
continue;
|
|
}
|
|
|
|
var curLeft = p.$button.offsetLeft;
|
|
var toLeft = leftPadding + ((i - offset) * tabWidth)
|
|
+ (!p.localName ? plusMargin : 0);
|
|
|
|
if (toWidth || toLeft != curLeft) {
|
|
var tween = {
|
|
node: p.$button,
|
|
duration: tab === p ? 0.20 : 0.15,
|
|
timingFunction: tab === p
|
|
? "cubic-bezier(.30, .08, 0, 1)"
|
|
: "linear"
|
|
};
|
|
if (includeTab || tab !== p)
|
|
tween.left = toLeft + "px";
|
|
if (toWidth && p.localName)
|
|
tween.width = toWidth + "px";
|
|
|
|
tweens.push(tween);
|
|
}
|
|
}
|
|
|
|
anims.animateMultiple(tweens, function() {
|
|
finish && finish();
|
|
});
|
|
}
|
|
|
|
function mouseMoveOrder(e, toWidth, finalize) {
|
|
if (!e) e = event;
|
|
|
|
if (!started) {
|
|
if (Math.abs(startX - e.clientX) < 4
|
|
&& Math.abs(startY - e.clientY) < 4)
|
|
return;
|
|
started = true;
|
|
initMouse();
|
|
start();
|
|
}
|
|
|
|
if (isNotSnapped(e))
|
|
return setSplitMode(e);
|
|
|
|
button.style.left = (e.clientX - leftPos - offsetX) + "px";
|
|
|
|
var x = button.offsetLeft - leftPadding + (tabWidth / 2);
|
|
var idx = Math.floor(x / tabWidth);
|
|
|
|
showOrderPosition(idx, toWidth, finalize);
|
|
}
|
|
|
|
function mouseUpOrder(e) {
|
|
apf.removeListener(document, "mousemove", mouseMoveOrder);
|
|
apf.removeListener(document, "mouseup", mouseUpOrder);
|
|
|
|
if (!started)
|
|
return finish();
|
|
|
|
button.style.left = (e.clientX - leftPos - offsetX) + "px";
|
|
|
|
var x = button.offsetLeft - leftPadding + (tabWidth / 2);
|
|
var idx = Math.floor(x / tabWidth);
|
|
|
|
// Show final order
|
|
var orderTab = showOrderPosition(idx, null, true, finish);
|
|
|
|
// Activate tab
|
|
plugin.activate();
|
|
|
|
// Remove pane if empty
|
|
if (originalTab && canTabBeRemoved(originalTab.cloud9pane)
|
|
&& settings.getBool("user/tabs/@autoclosepanes"))
|
|
originalTab.cloud9pane.unload();
|
|
}
|
|
|
|
function showSplitPosition(e) {
|
|
var el = document.elementFromPoint(e.clientX, e.clientY);
|
|
var aml = apf.findHost(el);
|
|
|
|
while (aml && aml.localName != "tab")
|
|
aml = aml.parentNode;
|
|
|
|
// If aml is not the pane we seek, lets abort
|
|
if (!aml) {
|
|
divSplit.style.display = "none";
|
|
splitTab = null;
|
|
splitDirection = null;
|
|
return;
|
|
}
|
|
|
|
var tab = (aml.getPage() || {}).cloud9tab;
|
|
var dark = !tab || tab.classList.names.indexOf("dark") > -1;
|
|
divSplit.className = "split-area" + (dark ? " dark" : "");
|
|
|
|
// Find the rotated quarter that we're in
|
|
var rect = aml.$ext.getBoundingClientRect();
|
|
var left = (e.clientX - rect.left) / rect.width;
|
|
var right = 1 - left;
|
|
var top = (e.clientY - rect.top) / rect.height;
|
|
var bottom = 1 - top;
|
|
|
|
// Check whether we're going to dock
|
|
if (!isNotSnapped(e, aml.$buttons)) {
|
|
setOrderMode(aml, e);
|
|
return false;
|
|
}
|
|
|
|
// Cannot split pane that would be removed later
|
|
if (aml.getPages().length === 0) { // && aml == originalTab
|
|
divSplit.style.display = "none";
|
|
splitTab = null;
|
|
splitDirection = null;
|
|
return;
|
|
}
|
|
splitTab = aml;
|
|
|
|
// Anchor to closes side
|
|
var min = Math.min(left, top, right, bottom);
|
|
|
|
// Get buttons height
|
|
var bHeight = pane.$buttons.offsetHeight - 1;
|
|
|
|
// Left
|
|
if (min == left) {
|
|
divSplit.style.left = rect.left + "px";
|
|
divSplit.style.top = (bHeight + rect.top) + "px";
|
|
divSplit.style.width = (rect.width / 2) + "px";
|
|
divSplit.style.height = (rect.height - bHeight) + "px";
|
|
splitDirection = "w";
|
|
}
|
|
// Right
|
|
else if (min == right) {
|
|
divSplit.style.left = rect.left + (rect.width / 2) + "px";
|
|
divSplit.style.top = (bHeight + rect.top) + "px";
|
|
divSplit.style.width = (rect.width / 2) + "px";
|
|
divSplit.style.height = (rect.height - bHeight) + "px";
|
|
splitDirection = "e";
|
|
}
|
|
// Top
|
|
else if (min == top) {
|
|
divSplit.style.left = rect.left + "px";
|
|
divSplit.style.top = (bHeight + rect.top) + "px";
|
|
divSplit.style.width = rect.width + "px";
|
|
divSplit.style.height = ((rect.height / 2) - bHeight) + "px";
|
|
splitDirection = "n";
|
|
}
|
|
// Bottom
|
|
else if (min == bottom) {
|
|
divSplit.style.left = rect.left + "px";
|
|
divSplit.style.top = (rect.top + (rect.height / 2)) + "px";
|
|
divSplit.style.width = rect.width + "px";
|
|
divSplit.style.height = (rect.height / 2) + "px";
|
|
splitDirection = "s";
|
|
}
|
|
|
|
divSplit.style.cursor = splitDirection + "-resize";
|
|
divSplit.style.display = "block";
|
|
}
|
|
|
|
function mouseMoveSplit(e) {
|
|
if (!started) {
|
|
if (Math.abs(startX - e.clientX) < 4
|
|
&& Math.abs(startY - e.clientY) < 4)
|
|
return;
|
|
started = true;
|
|
initMouse();
|
|
start();
|
|
}
|
|
|
|
button.style.left = (e.clientX - offsetX) + "px";
|
|
button.style.top = (e.clientY - offsetY) + "px";
|
|
|
|
return showSplitPosition(e);
|
|
}
|
|
|
|
// var waiting;
|
|
// function schedule(e){
|
|
// if (waiting) {
|
|
// waiting = e;
|
|
// return;
|
|
// }
|
|
|
|
// waiting = e;
|
|
// setTimeout(function(){
|
|
// showSplitPosition(waiting);
|
|
// waiting = false;
|
|
// }, 1000);
|
|
// }
|
|
|
|
function mouseUpSplit(e) {
|
|
button.style.left = (e.clientX - offsetX) + "px";
|
|
button.style.top = (e.clientY - offsetY) + "px";
|
|
|
|
apf.removeListener(document, "mousemove", mouseMoveSplit);
|
|
apf.removeListener(document, "mouseup", mouseUpSplit);
|
|
|
|
showSplitPosition(e);
|
|
|
|
if (splitTab) {
|
|
splitTab = splitTab.cloud9pane;
|
|
var newTab;
|
|
if (splitDirection == "n")
|
|
newTab = splitTab.vsplit();
|
|
else if (splitDirection == "s")
|
|
newTab = splitTab.vsplit(true);
|
|
else if (splitDirection == "w")
|
|
newTab = splitTab.hsplit();
|
|
else if (splitDirection == "e")
|
|
newTab = splitTab.hsplit(true);
|
|
|
|
var oldTab = pane;
|
|
plugin.attachTo(newTab, null, true);
|
|
pane = newTab.aml;
|
|
|
|
if (oldTab && canTabBeRemoved(oldTab.cloud9pane)) {
|
|
oldTab.cloud9pane.unload();
|
|
originalTab = null;
|
|
}
|
|
}
|
|
else {
|
|
tab.parentNode.$buttons.insertBefore(button,
|
|
originalPosition);
|
|
|
|
if (originalTab == tab.parentNode) {
|
|
var idx = tab.parentNode.childNodes.indexOf(tab.nextSibling);
|
|
if (idx == -1)
|
|
tab.parentNode.childNodes.push(tab);
|
|
else
|
|
tab.parentNode.childNodes.splice(idx, 0, tab);
|
|
}
|
|
}
|
|
|
|
// Remove pane if empty
|
|
if (originalTab && originalTab != tab.parentNode
|
|
&& canTabBeRemoved(originalTab.cloud9pane))
|
|
originalTab.cloud9pane.unload();
|
|
|
|
finish();
|
|
}
|
|
}
|
|
|
|
/***** Methods *****/
|
|
|
|
/***** Lifecycle *****/
|
|
|
|
handle.on("load", function() {
|
|
load();
|
|
});
|
|
handle.on("enable", function() {
|
|
|
|
});
|
|
handle.on("disable", function() {
|
|
|
|
});
|
|
handle.on("unload", function() {
|
|
loaded = false;
|
|
});
|
|
|
|
/***** Register and define API *****/
|
|
|
|
/**
|
|
**/
|
|
handle.freezePublicAPI({
|
|
get plusMargin() { return plusMargin; },
|
|
set plusMargin(v) { plusMargin = v; }
|
|
});
|
|
|
|
register(null, {
|
|
tabinteraction: handle
|
|
});
|
|
}
|
|
}); |