mirror of
https://github.com/haiwen/seahub.git
synced 2025-08-17 14:37:58 +00:00
* textcolor: fixed plugin bugs, added translation for zh * image: fixed default.jpg src bug * added 'ru' support for seaf edit * rm aloha-0.22.3 and ununsed files in aloha-0.22.7
1046 lines
27 KiB
JavaScript
Executable File
1046 lines
27 KiB
JavaScript
Executable File
/* sidebar.js is part of Aloha Editor project http://aloha-editor.org
|
|
*
|
|
* Aloha Editor is a WYSIWYG HTML5 inline editing library and editor.
|
|
* Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
|
|
* Contributors http://aloha-editor.org/contribution.php
|
|
*
|
|
* Aloha Editor is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or any later version.
|
|
*
|
|
* Aloha Editor is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
* As an additional permission to the GNU GPL version 2, you may distribute
|
|
* non-source (e.g., minimized or compacted) forms of the Aloha-Editor
|
|
* source code without the copy of the GNU GPL normally required,
|
|
* provided you include this license notice and a URL through which
|
|
* recipients can access the Corresponding Source.
|
|
*/
|
|
/**
|
|
* @todo: - Make the sidebars resizable using drag handles.
|
|
* - Make overlayPage setting settable from external config.
|
|
*/
|
|
|
|
define([
|
|
'aloha/core',
|
|
'jquery',
|
|
'aloha/selection',
|
|
'PubSub'
|
|
], function (
|
|
Aloha,
|
|
$,
|
|
Selection,
|
|
PubSub
|
|
) {
|
|
'use strict';
|
|
|
|
var uid = +(new Date());
|
|
|
|
// Extend jQuery easing animations.
|
|
//debugger;
|
|
if (!$.easing.easeOutExpo) {
|
|
$.extend($.easing, {
|
|
easeOutExpo: function (x, t, b, c, d) {
|
|
return (t == d) ? b + c : c * (-Math.pow(2, -10 * t / d) + 1) + b;
|
|
},
|
|
easeOutElastic: function (x, t, b, c, d) {
|
|
var m = Math,
|
|
s = 1.70158,
|
|
p = 0,
|
|
a = c;
|
|
if (!t) {
|
|
return b;
|
|
}
|
|
if ((t /= d) == 1) {
|
|
return b + c;
|
|
}
|
|
if (!p) {
|
|
p = d * 0.3;
|
|
}
|
|
if (a < m.abs(c)) {
|
|
a = c;
|
|
s = p / 4;
|
|
} else {
|
|
s = p / (2 * m.PI) * m.asin(c / a);
|
|
}
|
|
return a * m.pow(2, -10 * t) * m.sin((t * d - s) * (2 * m.PI) / p) + c + b;
|
|
}
|
|
});
|
|
}
|
|
|
|
var Panel = function Panel(opts) {
|
|
this.id = null;
|
|
this.folds = {};
|
|
this.button = null;
|
|
this.title = $('<div class="aloha-sidebar-panel-title">' + '<span class="aloha-sidebar-panel-title-arrow"></span>' + '<span class="aloha-sidebar-panel-title-text">Untitled</span>' + '</div>');
|
|
this.content = $('<div class="aloha-sidebar-panel-content">' + '<div class="aloha-sidebar-panel-content-inner">' + '<div class="aloha-sidebar-panel-content-inner-text"></div>' + '</div>' + '</div>');
|
|
this.element = null;
|
|
this.effectiveElement = null;
|
|
this.expanded = false;
|
|
this.isActive = true;
|
|
this.init(opts);
|
|
};
|
|
|
|
var Sidebar = function Sidebar(opts) {
|
|
var sidebar = this;
|
|
this.id = 'aloha-sidebar-' + (++uid);
|
|
this.panels = {};
|
|
this.container = $('<div class="aloha-ui aloha-sidebar-bar">' + '<div class="aloha-sidebar-handle">' + '<span class="aloha-sidebar-handle-icon"></span>' + '</div>' + '<div class="aloha-sidebar-inner">' + '<ul class="aloha-sidebar-panels"></ul>' + '</div>' + '</div>');
|
|
this.width = 300;
|
|
this.opened = false;
|
|
this.isOpen = false;
|
|
this.isCompletelyOpen = false;
|
|
this.settings = {
|
|
// We automatically set this to true when we are in IE, where
|
|
// rotating elements using filters causes undesirable rendering
|
|
// ugliness. Our solution is to fallback to swapping icon images.
|
|
// We set this as a sidebar property so that it can overridden by
|
|
// whoever thinks they are smarter than we are.
|
|
rotateIcons: !$.browser.msie,
|
|
overlayPage: true
|
|
};
|
|
|
|
$(function () {
|
|
if (!((typeof Aloha.settings.sidebar !== 'undefined') && Aloha.settings.sidebar.disabled)) {
|
|
sidebar.init(opts);
|
|
}
|
|
});
|
|
};
|
|
|
|
/**
|
|
* The last calculated view port height.
|
|
* @type {number}
|
|
*/
|
|
var previousViewportHeight = null;
|
|
var previousActivePanelIds = null;
|
|
|
|
$.extend(Sidebar.prototype, {
|
|
|
|
// We build as much of the sidebar as we can before appending it to DOM
|
|
// to minimize reflow.
|
|
init: function (opts) {
|
|
var that = this;
|
|
var panels;
|
|
|
|
if (typeof opts === 'object') {
|
|
panels = opts.panels;
|
|
delete opts.panels;
|
|
}
|
|
|
|
$.extend(this, opts);
|
|
|
|
if (typeof panels === 'object') {
|
|
$.each(panels, function () {
|
|
that.addPanel(this, true);
|
|
});
|
|
}
|
|
|
|
var bar = this.container;
|
|
|
|
if (this.position === 'right') {
|
|
bar.addClass('aloha-sidebar-right');
|
|
}
|
|
|
|
bar.hide().appendTo($('body')).click(function () {
|
|
that.barClicked.apply(that, arguments);
|
|
}).find('.aloha-sidebar-panels').width(this.width);
|
|
|
|
// IE7 needs us to explicitly set the container width, since it is
|
|
// unable to determine it on its own.
|
|
bar.width(this.width);
|
|
this.width = bar.width();
|
|
|
|
this.updateHeight();
|
|
this.initToggler();
|
|
|
|
this.container.css(this.position === 'right' ? 'marginRight' : 'marginLeft', -this.width);
|
|
|
|
if (this.opened) {
|
|
this.open(0);
|
|
}
|
|
|
|
this.toggleHandleIcon(this.isOpen);
|
|
this.subscribeToEvents();
|
|
|
|
$(window).resize(function () {
|
|
that.updateHeight();
|
|
that.correctHeight();
|
|
});
|
|
|
|
this.correctHeight();
|
|
},
|
|
|
|
show: function () {
|
|
this.container.css('display', 'block');
|
|
return this;
|
|
},
|
|
|
|
hide: function () {
|
|
this.container.css('display', 'none');
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Determines the effective elements at the current selection.
|
|
* Iterates through all panels and checks whether the panel should be
|
|
* activated for any of the effective elements in the selection.
|
|
*
|
|
* @param {Aloha.RangeObject} range The current selection range.
|
|
*/
|
|
checkActivePanels: function (range) {
|
|
var effective = [];
|
|
|
|
if (typeof range !== 'undefined' && typeof range.markupEffectiveAtStart !== 'undefined') {
|
|
var l = range.markupEffectiveAtStart.length;
|
|
var i;
|
|
for (i = 0; i < l; ++i) {
|
|
effective.push($(range.markupEffectiveAtStart[i]));
|
|
}
|
|
}
|
|
|
|
var that = this;
|
|
|
|
$.each(this.panels, function () {
|
|
that.showActivePanel(this, effective);
|
|
});
|
|
|
|
this.correctHeight();
|
|
},
|
|
|
|
subscribeToEvents: function () {
|
|
var that = this;
|
|
|
|
PubSub.sub('aloha.selection.context-change', function (message) {
|
|
if (that.isOpen) {
|
|
that.checkActivePanels(message.range);
|
|
}
|
|
that.lastRange = message.range;
|
|
});
|
|
|
|
Aloha.bind('aloha-editable-deactivated', function (event, params) {
|
|
if (that.isOpen) {
|
|
that.checkActivePanels();
|
|
}
|
|
that.lastRange = null;
|
|
});
|
|
|
|
this.container.mousedown(function (e) {
|
|
e.originalEvent.stopSelectionUpdate = true;
|
|
Aloha.eventHandled = true;
|
|
});
|
|
|
|
this.container.mouseup(function (e) {
|
|
e.originalEvent.stopSelectionUpdate = true;
|
|
Aloha.eventHandled = false;
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Dynamically set appropriate heights for panels.
|
|
* The height for each panel is determined by the amount of space that
|
|
* is available in the viewport and the number of panels that need to
|
|
* share that space.
|
|
*/
|
|
correctHeight: function () {
|
|
if (!this.isOpen) {
|
|
return;
|
|
}
|
|
if (!this.isCompletelyOpen) {
|
|
this.correctHeightWhenCompletelyOpen = true;
|
|
return;
|
|
}
|
|
|
|
var viewportHeight = $(window).height();
|
|
var activePanelIds = [];
|
|
var panels = [];
|
|
var panelId;
|
|
for (panelId in this.panels) {
|
|
if (this.panels.hasOwnProperty(panelId)) {
|
|
if (this.panels[panelId].isActive) {
|
|
panels.push(this.panels[panelId]);
|
|
activePanelIds.push(panelId);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (0 === panels.length) {
|
|
return;
|
|
}
|
|
|
|
activePanelIds = activePanelIds.sort().join(',');
|
|
|
|
if (previousActivePanelIds === activePanelIds && previousViewportHeight === viewportHeight) {
|
|
return;
|
|
}
|
|
|
|
previousViewportHeight = viewportHeight;
|
|
previousActivePanelIds = activePanelIds;
|
|
|
|
var height = this.container.find('.aloha-sidebar-inner').height();
|
|
var remainingHeight = height - ((panels[0].title.outerHeight() + 10) * panels.length);
|
|
var panel;
|
|
var targetHeight;
|
|
var panelInner;
|
|
var panelText;
|
|
var undone;
|
|
var toadd = 0;
|
|
var math = Math;
|
|
var j;
|
|
|
|
while (panels.length > 0 && remainingHeight > 0) {
|
|
remainingHeight += toadd;
|
|
toadd = 0;
|
|
undone = [];
|
|
|
|
for (j = panels.length - 1; j >= 0; --j) {
|
|
panel = panels[j];
|
|
panelInner = panel.content.find('.aloha-sidebar-panel-content-inner');
|
|
|
|
targetHeight = math.min(
|
|
panelInner.height('auto').height(),
|
|
math.floor(remainingHeight / (j + 1))
|
|
);
|
|
|
|
panelInner.height(targetHeight);
|
|
remainingHeight -= targetHeight;
|
|
panelText = panelInner.find('.aloha-sidebar-panel-content-inner-text');
|
|
|
|
if (panelText.height() > targetHeight) {
|
|
undone.push(panel);
|
|
toadd += targetHeight;
|
|
panelInner.css({
|
|
'overflow-x': 'hidden',
|
|
'overflow-y': 'scroll'
|
|
});
|
|
} else {
|
|
panelInner.css('overflow-y', 'hidden');
|
|
}
|
|
|
|
if (panel.expanded) {
|
|
panel.expand();
|
|
}
|
|
}
|
|
|
|
panels = undone;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Checks whether this panel should be activated (ie: made visible) for
|
|
* any of the elements specified in a given list of elements.
|
|
*
|
|
* We have to add a null object to the list of elements to allow us to
|
|
* check whether the panel should be visible when we have no effective
|
|
* elements in the current selection
|
|
*
|
|
* @param {object} panel The Panel object we will test
|
|
* @param {Array.<jQuery.<HTMLElement>>} elements The effective
|
|
* elements, any of
|
|
* which may activate
|
|
* the panel.
|
|
*/
|
|
showActivePanel: function (panel, elements) {
|
|
elements.push(null);
|
|
|
|
var li = panel.content.parent('li');
|
|
var activeOn = panel.activeOn;
|
|
var effective = $();
|
|
var count = 0;
|
|
var j = elements.length;
|
|
var i;
|
|
|
|
for (i = 0; i < j; ++i) {
|
|
if (activeOn(elements[i])) {
|
|
++count;
|
|
if (elements[i]) {
|
|
$.merge(effective, elements[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (count) {
|
|
panel.activate(effective);
|
|
} else {
|
|
panel.deactivate();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Sets up the functionality, event listeners, and animation of the
|
|
* sidebar handle
|
|
*/
|
|
initToggler: function () {
|
|
var that = this;
|
|
var bar = this.container;
|
|
var icon = bar.find('.aloha-sidebar-handle-icon');
|
|
var toggledClass = 'aloha-sidebar-toggled';
|
|
var bounceTimer;
|
|
var isRight = (this.position === 'right');
|
|
|
|
if (this.opened) {
|
|
this.rotateHandleArrow(isRight ? 0 : 180, 0);
|
|
}
|
|
|
|
// configure the position of the sidebar handle
|
|
$(function () {
|
|
if (typeof Aloha.settings.sidebar !== 'undefined' && Aloha.settings.sidebar.handle && Aloha.settings.sidebar.handle.top) {
|
|
$(bar.find('.aloha-sidebar-handle'))[0].style.top = Aloha.settings.sidebar.handle.top;
|
|
}
|
|
});
|
|
|
|
bar.find('.aloha-sidebar-handle').click(function () {
|
|
if (bounceTimer) {
|
|
clearInterval(bounceTimer);
|
|
}
|
|
|
|
icon.stop().css('marginLeft', 4);
|
|
|
|
if (that.isOpen) {
|
|
$(this).removeClass(toggledClass);
|
|
that.close();
|
|
that.isOpen = false;
|
|
} else {
|
|
$(this).addClass(toggledClass);
|
|
that.open();
|
|
that.isOpen = true;
|
|
}
|
|
}).hover(function () {
|
|
var flag = that.isOpen ? -1 : 1;
|
|
|
|
if (bounceTimer) {
|
|
clearInterval(bounceTimer);
|
|
}
|
|
|
|
icon.stop();
|
|
|
|
$(this).stop().animate(
|
|
isRight ? {
|
|
marginLeft: '-=' + (flag * 5)
|
|
} : {
|
|
marginRight: '-=' + (flag * 5)
|
|
},
|
|
200
|
|
);
|
|
|
|
bounceTimer = setInterval(function () {
|
|
flag *= -1;
|
|
icon.animate(
|
|
isRight ? {
|
|
left: '-=' + (flag * 4)
|
|
} : {
|
|
right: '-=' + (flag * 4)
|
|
},
|
|
300
|
|
);
|
|
}, 300);
|
|
}, function () {
|
|
if (bounceTimer) {
|
|
clearInterval(bounceTimer);
|
|
}
|
|
|
|
icon.stop().css(isRight ? 'left' : 'right', 5);
|
|
|
|
$(this).stop().animate(
|
|
isRight ? {
|
|
marginLeft: 0
|
|
} : {
|
|
marginRight: 0
|
|
},
|
|
600,
|
|
'easeOutElastic'
|
|
);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Rounds the top corners of the first visible panel, and the bottom
|
|
* corners of the last visible panel elements in the panels ul list.
|
|
* @deprecated
|
|
* @fixme: css3
|
|
*/
|
|
roundCorners: function () {
|
|
|
|
var bar = this.container;
|
|
var lis = bar.find('.aloha-sidebar-panels>li:not(.aloha-sidebar-deactivated)');
|
|
var topClass = 'aloha-sidebar-panel-top';
|
|
var bottomClass = 'aloha-sidebar-panel-bottom';
|
|
|
|
bar.find('.aloha-sidebar-panel-top, .aloha-sidebar-panel-bottom').removeClass(topClass).removeClass(bottomClass);
|
|
|
|
lis.first().find('.aloha-sidebar-panel-title').addClass(topClass);
|
|
lis.last().find('.aloha-sidebar-panel-content').addClass(bottomClass);
|
|
},
|
|
|
|
/**
|
|
* Updates the height of the inner div of the sidebar. This is done
|
|
* whenever the viewport is resized.
|
|
*/
|
|
updateHeight: function () {
|
|
var h = $(window).height();
|
|
this.container.height(h).find('.aloha-sidebar-inner').height(h);
|
|
},
|
|
|
|
/**
|
|
* Delegate all sidebar onclick events to the container.
|
|
* Then use handleBarclick method until we bubble up to the first
|
|
* significant element that we can interact with.
|
|
*/
|
|
barClicked: function (ev) {
|
|
this.handleBarclick($(ev.target));
|
|
},
|
|
|
|
/**
|
|
* We handle all click events on the sidebar from here--dispatching
|
|
* calls to which ever methods that should be invoked for the each
|
|
* interaction.
|
|
*/
|
|
handleBarclick: function (el) {
|
|
if (el.hasClass('aloha-sidebar-panel-title')) {
|
|
this.togglePanel(el);
|
|
} else if (!el.hasClass('aloha-sidebar-panel-content') && !el.hasClass('aloha-sidebar-handle') && !el.hasClass('aloha-sidebar-bar')) {
|
|
this.handleBarclick(el.parent());
|
|
}
|
|
},
|
|
|
|
getPanelById: function (id) {
|
|
return this.panels[id];
|
|
},
|
|
|
|
getPanelByElement: function (el) {
|
|
var li = (el[0].tagName === 'LI') ? el : el.parent('li');
|
|
return this.getPanelById(li[0].id);
|
|
},
|
|
|
|
togglePanel: function (el) {
|
|
this.getPanelByElement(el).toggle();
|
|
},
|
|
|
|
/**
|
|
* Animation to rotate the sidebar arrow
|
|
*
|
|
* @param {number} angle The angle two which the arrow should rotate
|
|
* (0 or 180).
|
|
* @param {number|String} duration (Optional) How long the animation
|
|
* should play for.
|
|
*/
|
|
rotateHandleIcon: function (angle, duration) {
|
|
var arr = this.container.find('.aloha-sidebar-handle-icon');
|
|
arr.animate({
|
|
angle: angle
|
|
}, {
|
|
duration: (typeof duration === 'number' || typeof duration === 'string') ? duration : 500,
|
|
easing: 'easeOutExpo',
|
|
step: function (val, fx) {
|
|
arr.css({
|
|
'-o-transform': 'rotate(' + val + 'deg)',
|
|
'-webkit-transform': 'rotate(' + val + 'deg)',
|
|
'-moz-transform': 'rotate(' + val + 'deg)',
|
|
'-ms-transform': 'rotate(' + val + 'deg)'
|
|
// We cannot use Microsoft Internet Explorer filters
|
|
// because Microsoft Internet Explore 8 does not support
|
|
// Microsoft Internet Explorer filters correctly. It
|
|
// breaks the layout
|
|
// filter : 'progid:DXImageTransform.Microsoft.BasicImage(rotation=' + (angle / 90) + ')'
|
|
});
|
|
}
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Sets the handle icon to the "i am opened, click me to close the
|
|
* sidebar" state, or vice versa. The direction of the arrow depends
|
|
* on whether the sidebar is on the left or right, and whether it is
|
|
* in an opened state or not.
|
|
*
|
|
* @param {boolean} isOpen Whether or not the sidebar is in the opened
|
|
* state.
|
|
*/
|
|
toggleHandleIcon: function (isOpen) {
|
|
var isPointingLeft = (this.position === 'right') ^ isOpen;
|
|
|
|
if (this.settings.rotateIcons) {
|
|
this.rotateHandleIcon(isPointingLeft ? 180 : 0, 0);
|
|
} else {
|
|
var icon = this.container.find('.aloha-sidebar-handle-icon');
|
|
|
|
if (isPointingLeft) {
|
|
icon.addClass('aloha-sidebar-handle-icon-left');
|
|
} else {
|
|
icon.removeClass('aloha-sidebar-handle-icon-left');
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Slides the sidebar into view
|
|
*/
|
|
open: function (duration, callback) {
|
|
if (this.isOpen) {
|
|
return this;
|
|
}
|
|
|
|
var isRight = (this.position === 'right');
|
|
var anim = isRight ? {
|
|
marginRight: 0
|
|
} : {
|
|
marginLeft: 0
|
|
};
|
|
var sidebar = this;
|
|
|
|
this.toggleHandleIcon(true);
|
|
this.container.animate(anim, (typeof duration === 'number' || typeof duration === 'string') ? duration : 500, 'easeOutExpo');
|
|
|
|
if (!this.settings.overlayPage) {
|
|
$('body').animate(
|
|
isRight ? {
|
|
marginRight: '+=' + this.width
|
|
} : {
|
|
marginLeft: '+=' + this.width
|
|
},
|
|
500,
|
|
'easeOutExpo',
|
|
function () {
|
|
sidebar.isCompletelyOpen = true;
|
|
if (sidebar.correctHeightWhenCompletelyOpen) {
|
|
sidebar.correctHeight();
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
this.isOpen = true;
|
|
this.correctHeight();
|
|
if (this.lastRange) {
|
|
this.checkActivePanels(this.lastRange);
|
|
}
|
|
$('body').trigger('aloha-sidebar-opened', this);
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Slides that sidebar out of view.
|
|
*/
|
|
close: function (duration, callback) {
|
|
if (!this.isOpen) {
|
|
return this;
|
|
}
|
|
|
|
var isRight = (this.position === 'right');
|
|
var anim = isRight ? {
|
|
marginRight: -this.width
|
|
} : {
|
|
marginLeft: -this.width
|
|
};
|
|
|
|
this.toggleHandleIcon(false);
|
|
this.container.animate(anim, (typeof duration === 'number' || typeof duration === 'string') ? duration : 500, 'easeOutExpo');
|
|
|
|
if (!this.settings.overlayPage) {
|
|
$('body').animate(
|
|
isRight ? {
|
|
marginRight: '-=' + this.width
|
|
} : {
|
|
marginLeft: '-=' + this.width
|
|
},
|
|
500,
|
|
'easeOutExpo'
|
|
);
|
|
}
|
|
|
|
this.isOpen = false;
|
|
this.isCompletelyOpen = false;
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Activates the given panel and passes to it the given element as the
|
|
* the effective that we want it to think activated it.
|
|
*
|
|
* @param {object|String} panel Panel instance or the id of a panel
|
|
* object.
|
|
* @param {jQuery} element Element to pass to the panel as effective
|
|
* element (the element that activated it).
|
|
*/
|
|
activatePanel: function (panel, element) {
|
|
if (typeof panel === 'string') {
|
|
panel = this.getPanelById(panel);
|
|
}
|
|
|
|
if (panel) {
|
|
panel.activate(element);
|
|
}
|
|
|
|
this.roundCorners();
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Invokes the expand method for the given panel so that it expands its
|
|
* height to display its contents
|
|
*
|
|
* @param {object|String} panel Panel instance or the id of a panel
|
|
* object.
|
|
* @param {funtion} callback
|
|
*/
|
|
expandPanel: function (panel, callback) {
|
|
if (typeof panel === 'string') {
|
|
panel = this.getPanelById(panel);
|
|
}
|
|
|
|
if (panel) {
|
|
panel.expand(callback);
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Collapses the panel contents by invoking the given panel's collapse
|
|
* method.
|
|
*
|
|
* @param {object|String} panel Panel instance or the id of a panel
|
|
* object.
|
|
* @param {funtion} callback
|
|
*/
|
|
collapsePanel: function (panel, callback) {
|
|
if (typeof panel === 'string') {
|
|
panel = this.getPanelById(panel);
|
|
}
|
|
|
|
if (panel) {
|
|
panel.collapse(callback);
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Adds a panel to this sidebar instance.
|
|
* We try and build as much of the panel DOM as we can before inserting
|
|
* it into the DOM in order to reduce reflow.
|
|
*
|
|
* @param {object} panel - either a panel instance or an associative
|
|
* array containing settings for the construction
|
|
* of a new panel.
|
|
* @param {boolean} deferRounding - (Optional) If true, the rounding-off
|
|
* of the top most and bottom most panels
|
|
* will not be automatically done. Set
|
|
* this to true when adding a lot of panels
|
|
* at once.
|
|
* @return {object} The newly created panel.
|
|
*/
|
|
addPanel: function (panel, deferRounding) {
|
|
if (!(panel instanceof Panel)) {
|
|
if (!panel.width) {
|
|
panel.width = this.width;
|
|
}
|
|
panel.sidebar = this;
|
|
panel = new Panel(panel);
|
|
}
|
|
|
|
this.panels[panel.id] = panel;
|
|
this.container.find('.aloha-sidebar-panels').append(panel.element);
|
|
this.checkActivePanels(Selection.getRangeObject());
|
|
return panel;
|
|
}
|
|
|
|
});
|
|
|
|
// ------------------------------------------------------------------------
|
|
// Panel prototype
|
|
// ------------------------------------------------------------------------
|
|
$.extend(Panel.prototype, {
|
|
|
|
init: function (opts) {
|
|
this.setTitle(opts.title).setContent(opts.content);
|
|
|
|
delete opts.title;
|
|
delete opts.content;
|
|
|
|
$.extend(this, opts);
|
|
|
|
if (!this.id) {
|
|
this.id = 'aloha-sidebar-' + (++uid);
|
|
}
|
|
|
|
var li = this.element = $('<li id="' + this.id + '">').append(this.title, this.content);
|
|
|
|
if (this.expanded) {
|
|
this.content.height('auto');
|
|
}
|
|
|
|
this.toggleTitleIcon(this.expanded);
|
|
this.coerceActiveOn();
|
|
|
|
// Disable text selection on title element.
|
|
this.title.attr('unselectable', 'on').css('-moz-user-select', 'none').each(function () {
|
|
this.onselectstart = function () {
|
|
return false;
|
|
};
|
|
});
|
|
|
|
if (typeof this.onInit === 'function') {
|
|
this.onInit.apply(this);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @param {boolean} isExpanded Whether or not the panel is in an
|
|
* expanded state.
|
|
*/
|
|
toggleTitleIcon: function (isExpanded) {
|
|
if (this.sidebar.settings.rotateIcons) {
|
|
this.rotateTitleIcon(isExpanded ? 90 : 0);
|
|
} else {
|
|
var icon = this.title.find('.aloha-sidebar-panel-title-arrow');
|
|
|
|
if (isExpanded) {
|
|
icon.addClass('aloha-sidebar-panel-title-arrow-down');
|
|
} else {
|
|
icon.removeClass('aloha-sidebar-panel-title-arrow-down');
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Normalizes the activeOn property into a predicate function.
|
|
*/
|
|
coerceActiveOn: function () {
|
|
if (typeof this.activeOn !== 'function') {
|
|
var activeOn = this.activeOn;
|
|
|
|
this.activeOn = (function () {
|
|
var typeofActiveOn = typeof activeOn,
|
|
fn;
|
|
|
|
if (typeofActiveOn === 'boolean') {
|
|
fn = function () {
|
|
return activeOn;
|
|
};
|
|
} else if (typeofActiveOn === 'undefined') {
|
|
fn = function () {
|
|
return true;
|
|
};
|
|
} else if (typeofActiveOn === 'string') {
|
|
fn = function (el) {
|
|
return el ? el.is(activeOn) : false;
|
|
};
|
|
} else {
|
|
fn = function () {
|
|
return false;
|
|
};
|
|
}
|
|
|
|
return fn;
|
|
}());
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Activates (displays) this panel.
|
|
*/
|
|
activate: function (effective) {
|
|
this.isActive = true;
|
|
this.content.parent('li').show().removeClass('aloha-sidebar-deactivated');
|
|
this.effectiveElement = effective;
|
|
if (typeof this.onActivate === 'function') {
|
|
this.onActivate.call(this, effective);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Hides this panel.
|
|
*/
|
|
deactivate: function () {
|
|
if (!this.isActive) {
|
|
return;
|
|
}
|
|
this.isActive = false;
|
|
this.content.parent('li').hide().addClass('aloha-sidebar-deactivated');
|
|
this.effectiveElement = null;
|
|
},
|
|
|
|
toggle: function () {
|
|
if (this.expanded) {
|
|
this.collapse();
|
|
} else {
|
|
this.expand();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Displays the panel's contents.
|
|
*/
|
|
expand: function (callback) {
|
|
var that = this;
|
|
var el = this.content;
|
|
var old_h = el.height();
|
|
var new_h = el.height('auto').height();
|
|
el.height(old_h).stop().animate(
|
|
{height: new_h},
|
|
500,
|
|
'easeOutExpo',
|
|
function () {
|
|
if (typeof callback === 'function') {
|
|
callback.call(that);
|
|
}
|
|
}
|
|
);
|
|
this.element.removeClass('collapsed');
|
|
this.toggleTitleIcon(true);
|
|
this.expanded = true;
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Hides the panel's contents--leaving only it's header.
|
|
*/
|
|
collapse: function (duration, callback) {
|
|
var that = this;
|
|
this.element.addClass('collapsed');
|
|
this.content.stop().animate(
|
|
{ height: 5 },
|
|
250,
|
|
'easeOutExpo',
|
|
function () {
|
|
if (typeof callback === 'function') {
|
|
callback.call(that);
|
|
}
|
|
}
|
|
);
|
|
this.toggleTitleIcon(false);
|
|
this.expanded = false;
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* May also be called by the Sidebar to update title of panel
|
|
*
|
|
* @param {string} html Markup string, DOM object, or jQuery object.
|
|
*/
|
|
setTitle: function (html) {
|
|
this.title.find('.aloha-sidebar-panel-title-text').html(html);
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* May also be called by the Sidebar to update content of panel
|
|
*
|
|
* @param {string|jQuery.<HTMLElement>|HTMLElement} html Markup string,
|
|
* DOM object, or
|
|
* jQuery object.
|
|
*/
|
|
setContent: function (html) {
|
|
// We do this so that empty panels don't appear collapsed
|
|
if (!html || html === '') {
|
|
html = ' ';
|
|
}
|
|
|
|
this.content.find('.aloha-sidebar-panel-content-inner-text').html(html);
|
|
return this;
|
|
},
|
|
|
|
rotateTitleIcon: function (angle, duration) {
|
|
var arr = this.title.find('.aloha-sidebar-panel-title-arrow');
|
|
arr.animate({
|
|
angle: angle
|
|
}, {
|
|
duration: (typeof duration === 'number') ? duration : 500,
|
|
easing: 'easeOutExpo',
|
|
step: function (val, fx) {
|
|
arr.css({
|
|
'-o-transform': 'rotate(' + val + 'deg)',
|
|
'-webkit-transform': 'rotate(' + val + 'deg)',
|
|
'-moz-transform': 'rotate(' + val + 'deg)',
|
|
'-ms-transform': 'rotate(' + val + 'deg)'
|
|
// filter : 'progid:DXImageTransform.Microsoft.BasicImage(rotation=' + (angle / 90) + ')'
|
|
});
|
|
}
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Walks up the ancestors chain for the given effective element, and
|
|
* renders subpanels using the specified renderer function.
|
|
*
|
|
* @param {jQuery.<HTMLElement>} effective The effective element, whose
|
|
* lineage we want to render.
|
|
* @param {function} renderer (Optional) function that will render each
|
|
* element in the parental
|
|
* lineage of the effective
|
|
* element.
|
|
*/
|
|
renderEffectiveParents: function (effective, renderer) {
|
|
var el = effective.first();
|
|
var content = [];
|
|
var path = [];
|
|
var activeOn = this.activeOn;
|
|
var l;
|
|
var pathRev;
|
|
|
|
while (el.length > 0 && !el.is('.aloha-editable')) {
|
|
if (activeOn(el)) {
|
|
path.push('<span>' + el[0].tagName.toLowerCase() + '</span>');
|
|
l = path.length;
|
|
pathRev = [];
|
|
while (l--) {
|
|
pathRev.push(path[l]);
|
|
}
|
|
content.push(
|
|
'<div class="aloha-sidebar-panel-parent">'
|
|
+ '<div class="aloha-sidebar-panel-parent-path">'
|
|
+ pathRev.join('')
|
|
+ '</div>'
|
|
+ '<div class="aloha-sidebar-panel-parent-content'
|
|
+ 'aloha-sidebar-opened">'
|
|
+ ((typeof renderer === 'function') ? renderer(el) : '----') + '</div>' + '</div>'
|
|
);
|
|
}
|
|
el = el.parent();
|
|
}
|
|
|
|
this.setContent(content.join(''));
|
|
|
|
$('.aloha-sidebar-panel-parent-path').click(function () {
|
|
var $content = $(this).parent().find('.aloha-sidebar-panel-parent-content');
|
|
if ($content.hasClass('aloha-sidebar-opened')) {
|
|
$content.hide().removeClass('aloha-sidebar-opened');
|
|
} else {
|
|
$content.show().addClass('aloha-sidebar-opened');
|
|
}
|
|
});
|
|
|
|
this.content.height('auto').find('.aloha-sidebar-panel-content-inner').height('auto');
|
|
}
|
|
|
|
});
|
|
|
|
var left = new Sidebar({
|
|
position: 'left',
|
|
width: 250 // TODO define in config
|
|
});
|
|
|
|
var right = new Sidebar({
|
|
position: 'right',
|
|
width: 250 // TODO define in config
|
|
});
|
|
|
|
Aloha.Sidebar = {
|
|
left: left,
|
|
right: right
|
|
};
|
|
|
|
return Aloha.Sidebar;
|
|
});
|