Commit 75785ab9 authored by Dele Olajide's avatar Dele Olajide

Merge pull request #121 from akrherz/deljitsu

Remove JitsiVideoBridge Plugin as requested by jitsi.org. It is to be replaced by new plugin "Jitsi Meet" from jitsi.org directly
parents 6a105ae8 dfb8b00c
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title>Jitsi Video Bridge Plugin Changelog</title>
<style type="text/css">
BODY {
font-size : 100%;
}
BODY, TD, TH {
font-family : tahoma, verdana, arial, helvetica, sans-serif;
font-size : 0.8em;
}
H2 {
font-size : 10pt;
font-weight : bold;
padding-left : 1em;
}
A:hover {
text-decoration : none;
}
H1 {
font-family : tahoma, arial, helvetica, sans-serif;
font-size : 1.4em;
font-weight: bold;
border-bottom : 1px #ccc solid;
padding-bottom : 2px;
}
TT {
font-family : courier new;
font-weight : bold;
color : #060;
}
PRE {
font-family : courier new;
font-size : 100%;
}
</style>
</head>
<body>
<h1>
Jitsi Video Bridge Plugin Changelog
</h1>
<p><b>1.3.2</b> -- Oct 30th, 2014</p>
<ul>
<li>Added support for clustering</li>
</ul>
<p><b>1.3.1</b> -- May 10th, 2014</p>
<ul>
<li>Added Etherpad for document sharing. Requires NodeJs plugin for Openfire to be installed</li>
<li>Added TogetherJs for application sharing</li>
<li>Enable editing of local and public host ip address parameters from admin web console. required for to support NAT</li>
</ul>
<p><b>1.3.0</b> -- March 14th, 2014</p>
<ul>
<li>OF-749: Upgraded bouncy castle libraries from 1.49 to 1.50</li>
<li>Jisti Videobridge plugin: Added latest jitmeet web conference application</li>
<li>Jisti Videobridge plugin: Implememted SIP registration, incoming and outgoing calls</li>
<li>Jisti Videobridge plugin: Added new rayo commands "invite"and "uninvite". Added events "inviteaccepted" and "invitecompleted"</li>
<li>Jisti Videobridge plugin: Implememted media recording</li>
<li>Ofmeet: Implememted feature to invite telphone user to conference</li>
<li>Ofmeet: Implememted feature to accept incoming telphone user to conference</li>
<li>Ofmeet: Implememted feature to record and save conference audio as a .au file</li>
<li>Ofmeet: Implememted feature to record and save conference video as a .webm file (windows only)</li>
</ul>
<p><b>1.2.2</b> -- Feb 18th, 2014</p>
<ul>
<li>Added username/password protection on web applications</li>
</ul>
<p><b>1.2.1</b> -- Feb 17th, 2014</p>
<ul>
<li>Updated Spark plugin and added source code to project files</li>
</ul>
<p><b>1.2</b> -- Feb 15th, 2014</p>
<ul>
<li>Added latest jitmeet web conference application</li>
<li>Restructured web apps folder</li>
<li>Added logging of XMPP messages in web browser console</li>
<li>Fixed warining message about channle expiry</li>
<li>jitsivideobridge: Updated to https://github.com/jitsi/jitsi-videobridge/commit/a63c8c0dd901e1f9a6f164264338d3a4a89e97fd</li>
</ul>
<p><b>1.1.1</b> -- Feb 9th, 2014</p>
<ul>
<li>Added support for PDF Presentations</li>
</ul>
<p><b>1.1</b> -- Jan 27th, 2014</p>
<ul>
<li>OF-716 Added to Openfire plugins.</li>
</ul>
<p><b>1.0</b> -- Apr 12, 2013</p>
<ul>
<li>Initial release. </li>
</ul>
</body>
</html>
Some of the files here, notably libjitsi.jar, are OSGi bundles from the Jitsi
project. In Jitsi they are generated in the 'sc-bundles' directory by the
'bundles' ant target. To update the files here, run 'ant rebuild' in Jitsi and
then copy the files here, e.g.:
for f in *.jar; do
if [ -e ../../jitsi/sc-bundles/$f ]; then
cp ../../jitsi/sc-bundles/$f .;
fi;
done
handlers= java.util.logging.ConsoleHandler
java.util.logging.ConsoleHandler.level = ALL
java.util.logging.ConsoleHandler.formatter = net.java.sip.communicator.util.ScLogFormatter
.level=INFO
# FIXME: remove once RTT issue is fixed
org.jitsi.impl.neomedia.MediaStreamStatsImpl.level=WARNING
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8" ?>
<plugin>
<author>jitsi.org and igniterealtime.org</author>
<class>org.jitsi.videobridge.openfire.PluginImpl</class>
<description>Integrates Jitsi Video Bridge into Openfire.</description>
<licenseType>LGPL</licenseType>
<minServerVersion>3.10.0</minServerVersion>
<name>Jitsi Video Bridge</name>
<version>1.3.2</version>
<adminconsole>
<tab id="tab-server">
<sidebar id="siderbar-jvb"
name="${plugin.title}">
<item id="jitsi-videobridge-settings"
name="${plugin.title}"
description="${plugin.title.description}"
url="jitsi-videobridge.jsp"/>
</sidebar>
</tab>
</adminconsole>
</plugin>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title>Jitsi Video Bridge Plugin Readme</title>
<style type="text/css">
BODY {
font-size : 100%;
}
BODY, TD, TH {
font-family : tahoma, verdana, arial, helvetica, sans-serif;
font-size : 0.8em;
}
H2 {
font-size : 10pt;
font-weight : bold;
}
A:hover {
text-decoration : none;
}
H1 {
font-family : tahoma, arial, helvetica, sans-serif;
font-size : 1.4em;
font-weight: bold;
border-bottom : 1px #ccc solid;
padding-bottom : 2px;
}
TT {
font-family : courier new;
font-weight : bold;
color : #060;
}
PRE {
font-family : courier new;
font-size : 100%;
}
#datatable TH {
color : #fff;
background-color : #2A448C;
text-align : left;
}
#datatable TD {
background-color : #FAF6EF;
}
#datatable .name {
background-color : #DCE2F5;
}
</style>
</head>
<body>
<h1>
Jitsi Video Bridge Plugin Readme
</h1>
<h2>Overview</h2>
<p>
The Jitsi Video Bridge Plugin is an XMPP server component that allows for
multiuser video communication. Unlike the expensive dedicated hardware videobridges,
Jitsi Videobridge does not mix the video channels into a composite video stream,
but only relays the received video channels to all call participants.
Therefore, while it does need to run on a server with good network bandwidth,
CPU horsepower is not that critical for performance. It also includes some useful web conference applications.
</p>
<h2>Known Issues</h2>
<p>
The first video conference creating after restarting Openfire may fail. Work around is remove all participants from the room and try again or use a new room.
</p>
<h2>Installation</h2>
<ol>
<li>Copy the jitsivideobridge.jar file to the OPENFIRE_HOME/plugins directory.</li>
<li>Restart Openfire.</li>
<li>Configure the admin properties page.</li>
</ol>
<h2>Configuration</h2>
Under Server settings -> Jitsi Videobridge tab you can configure various parameters.
<h2>How to use</h2>
<p>
Make sure:
<ul>
<li>you are using Google Chrome as your web browser</li>
<li>you have a webcam installed and ready for use for each user</li>
<li>you have opened ports 50000 - 60000 (or whatever you configured) on your openfire server</li>
</ul>
</p>
<h2>OfMeet</h2>
To run the ofmeet video conference application, point your browser at https://your_server:7443/jitsi/apps/ofmeet
<p/>
<h2>Candy</h2>
To run the Candy web application with multi-user video, point your browser at https://your_server:7443/jitsi/apps/candy
<p/>
<h2>Spark</h2>
To download the Spark plugin, point your browser at https://your_server:7443/jitsi/apps/spark/jitsivideobridge-plugin.jar
<p/>
<h2>JitMeet</h2>
To run the jitmeet video conference application, point your browser at https://your_server:7443/jitsi/apps/jitmeet
<p/>
You will need Google Chrome as your default browser to use the Spark plugin
</body>
</html>
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
<servlet>
<servlet-name>proxy</servlet-name>
<servlet-class>org.jitsi.videobridge.openfire.HttpProxy</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>proxy</servlet-name>
<url-pattern>/proxy</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>config</servlet-name>
<servlet-class>org.jitsi.videobridge.openfire.Config</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>config</servlet-name>
<url-pattern>/config</url-pattern>
</servlet-mapping>
</web-app>
\ No newline at end of file
Credits
=======
- [famfamfam silk icons](http://www.famfamfam.com/lab/icons/silk/) is a smooth, free icon set, containing over 700 16-by-16 pixel icons.
- [Simple Smileys](http://simplesmileys.org) are beautifully simple emoticons.
- [Flash MP3 Player](http://flash-mp3-player.net/players/js) is a very simple flash audio player used by Candy for audio notifications.
- [Colin Snover](http://zetafleet.com/blog/javascript-dateparse-for-iso-8601) provides a fix for browsers not supporting latest Date.parse().
- [Ben Cherry](http://www.adequatelygood.com/2010/3/JavaScript-Module-Pattern-In-Depth) wrote a great article about the JS module pattern.
- [Amiado Group](http://www.amiadogroup.com) allowed us to make Candy freely available for everyone! :)
\ No newline at end of file
Candy — a JavaScript-based multi-user chat client
==================================================
Visit the official project page: http://candy-chat.github.com/candy
Features
--------
- Focused on real-time multi-user chatting
- Easy to configure, easy to run, easy to use
- Highly customizable
- 100% well-documented JavaScript source code
- Built for Jabber (XMPP), using famous technologies
- Used and approved in a productive environment with up to 400 concurrent users
- Works with all major web browsers including IE7
Plugins
-------
If you wish to add new functionality (to your candy installation) or contribute plugins, take a look at our [plugin repository](http://github.com/candy-chat/candy-plugins).
Support & Community
-------------------
Take a look at our [FAQ](https://github.com/candy-chat/candy/wiki/Frequently-Asked-Questions). If it doesn't solve your questions, you're welcome to join our [Mailinglist on Google Groups](http://groups.google.com/group/candy-chat).
You don't need to have a Gmail account for it.
[![githalytics.com alpha](https://cruel-carlota.pagodabox.com/a41a8075608abeaf99db685d7ef29cf6 "githalytics.com")](http://githalytics.com/candy-chat/candy)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Candy - Chats are not dead yet</title>
<link rel="shortcut icon" href="../res/img/favicon.png" type="image/gif" />
<link rel="stylesheet" type="text/css" href="../res/default.css" />
<link rel="stylesheet" type="text/css" href="../plugins/colors/candy.css" />
<link rel="stylesheet" type="text/css" href="../plugins/roomPanel/default.css" />
<link rel="stylesheet" type="text/css" href="../plugins/timeago/candy.css" />
<link rel="stylesheet" type="text/css" href="../plugins/videobridge/candy.css" />
<script type="text/javascript" src="../libs/jquery.min.js"></script>
<script type="text/javascript" src="../libs/libs.min.js"></script>
<script type="text/javascript" src="../libs/strophe-connection-websockets.js"></script>
<script type="text/javascript" src="../libs/candy.bundle.js"></script>
<script type="text/javascript" src="../plugins/colors/candy.js"></script>
<script type="text/javascript" src="../plugins/roomPanel/roomPanel.js"></script>
<script type="text/javascript" src="../plugins/timeago/candy.js"></script>
<script type="text/javascript" src="../plugins/videobridge/candy.js"></script>
<script type="text/javascript" src="../plugins/videobridge/jingle.sdp.js"></script>
<script type="text/javascript" src="../plugins/fastpath/candy.js"></script>
<script type="text/javascript">
function urlParam(name)
{
var results = new RegExp('[\\?&]' + name + '=([^&#]*)').exec(window.location.href);
if (!results) { return undefined; }
return results[1] || undefined;
};
function toggleFullScreen()
{
var videoElement = document.getElementById("largeVideo");
if (!document.mozFullScreen && !document.webkitFullScreen)
{
if (videoElement.mozRequestFullScreen) {
videoElement.mozRequestFullScreen();
} else {
videoElement.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT);
}
} else {
if (document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else {
document.webkitCancelFullScreen();
}
}
}
$(document).ready(function() {
Candy.init('/http-bind/', {
core: { debug: false, websockets: true},
view: { resources: '../res/' }
});
var username = urlParam("username");
var password = urlParam("password");
CandyShop.RoomPanel.init({
// domain that hosts the muc rooms, only required if autoDetectRooms is enabled
mucDomain: 'conference.' + window.location.hostname,
// show room list if all rooms are closed, default value is true. [optional]
showIfAllTabClosed: true,
// detect rooms before showing list, default value is true. [optional]
autoDetectRooms: true,
// how long in seconds before refreshing room list, default value is 600. [optional]
roomCacheTime: 600
});
CandyShop.Colors.init();
CandyShop.Timeago.init();
CandyShop.Videobridge.init();
CandyShop.Fastpath.init();
if (username)
Candy.Core.connect(username + "@" + window.location.hostname, password, username);
else
Candy.Core.connect();
});
</script>
</head>
<body>
<div class="fade_line"></div>
<div id="candy"></div>
<div class="fade_line"></div>
<video id="largeVideo" onDblClick="toggleFullScreen();" autoplay oncontextmenu="return false;"></video>
<div id="remoteVideos">
<video id="localVideo" autoplay oncontextmenu="return false;" muted/>
</div>
</body>
</html>
<html>
<head><title></title>
<meta http-equiv="refresh" content="0;URL=example/index.html">
</head>
<body>
</body>
</html>
/*
* Date Format 1.2.3
* (c) 2007-2009 Steven Levithan <stevenlevithan.com>
* MIT license
*
* Includes enhancements by Scott Trenda <scott.trenda.net>
* and Kris Kowal <cixar.com/~kris.kowal/>
*
* Accepts a date, a mask, or a date and a mask.
* Returns a formatted version of the given date.
* The date defaults to the current date/time.
* The mask defaults to dateFormat.masks.default.
*
* @link http://blog.stevenlevithan.com/archives/date-time-format
*/
var dateFormat = function () {
var token = /d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloSZ]|"[^"]*"|'[^']*'/g,
timezone = /\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g,
timezoneClip = /[^-+\dA-Z]/g,
pad = function (val, len) {
val = String(val);
len = len || 2;
while (val.length < len) val = "0" + val;
return val;
};
// Regexes and supporting functions are cached through closure
return function (date, mask, utc) {
var dF = dateFormat;
// You can't provide utc if you skip other args (use the "UTC:" mask prefix)
if (arguments.length == 1 && Object.prototype.toString.call(date) == "[object String]" && !/\d/.test(date)) {
mask = date;
date = undefined;
}
// Passing date through Date applies Date.parse, if necessary
date = date ? new Date(date) : new Date;
if (isNaN(date)) throw SyntaxError("invalid date");
mask = String(dF.masks[mask] || mask || dF.masks["default"]);
// Allow setting the utc argument via the mask
if (mask.slice(0, 4) == "UTC:") {
mask = mask.slice(4);
utc = true;
}
var _ = utc ? "getUTC" : "get",
d = date[_ + "Date"](),
D = date[_ + "Day"](),
m = date[_ + "Month"](),
y = date[_ + "FullYear"](),
H = date[_ + "Hours"](),
M = date[_ + "Minutes"](),
s = date[_ + "Seconds"](),
L = date[_ + "Milliseconds"](),
o = utc ? 0 : date.getTimezoneOffset(),
flags = {
d: d,
dd: pad(d),
ddd: dF.i18n.dayNames[D],
dddd: dF.i18n.dayNames[D + 7],
m: m + 1,
mm: pad(m + 1),
mmm: dF.i18n.monthNames[m],
mmmm: dF.i18n.monthNames[m + 12],
yy: String(y).slice(2),
yyyy: y,
h: H % 12 || 12,
hh: pad(H % 12 || 12),
H: H,
HH: pad(H),
M: M,
MM: pad(M),
s: s,
ss: pad(s),
l: pad(L, 3),
L: pad(L > 99 ? Math.round(L / 10) : L),
t: H < 12 ? "a" : "p",
tt: H < 12 ? "am" : "pm",
T: H < 12 ? "A" : "P",
TT: H < 12 ? "AM" : "PM",
Z: utc ? "UTC" : (String(date).match(timezone) || [""]).pop().replace(timezoneClip, ""),
o: (o > 0 ? "-" : "+") + pad(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4),
S: ["th", "st", "nd", "rd"][d % 10 > 3 ? 0 : (d % 100 - d % 10 != 10) * d % 10]
};
return mask.replace(token, function ($0) {
return $0 in flags ? flags[$0] : $0.slice(1, $0.length - 1);
});
};
}();
// Some common format strings
dateFormat.masks = {
"default": "ddd mmm dd yyyy HH:MM:ss",
shortDate: "m/d/yy",
mediumDate: "mmm d, yyyy",
longDate: "mmmm d, yyyy",
fullDate: "dddd, mmmm d, yyyy",
shortTime: "h:MM TT",
mediumTime: "h:MM:ss TT",
longTime: "h:MM:ss TT Z",
isoDate: "yyyy-mm-dd",
isoTime: "HH:MM:ss",
isoDateTime: "yyyy-mm-dd'T'HH:MM:ss",
isoUtcDateTime: "UTC:yyyy-mm-dd'T'HH:MM:ss'Z'"
};
// Internationalization strings
dateFormat.i18n = {
dayNames: [
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat",
"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
],
monthNames: [
"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"
]
};
// For convenience...
Date.prototype.format = function (mask, utc) {
return dateFormat(this, mask, utc);
};
\ No newline at end of file
# Colors
Send and receive colored messages.
![Color Picker](screenshot.png)
## Usage
To enable *Colors* you have to include its JavaScript code and stylesheet:
```HTML
<script type="text/javascript" src="candyshop/colors/candy.js"></script>
<link rel="stylesheet" type="text/css" href="candyshop/colors/candy.css" />
```
Call its `init()` method after Candy has been initialized:
```JavaScript
Candy.init('/http-bind/');
// enable Colors plugin (default: 8 colors)
CandyShop.Colors.init();
Candy.Core.connect();
```
To enable less or more colors just call `CandyShop.Colors.init(<number-of-colors>)`.
#colors-control {
background: no-repeat url('colors-control.png');
position: relative;
}
#colors-control-indicator {
display: inline-block;
height: 6px;
width: 6px;
border: 1px solid white;
position: absolute;
top: 100%;
left: 100%;
margin: -8px 0 0 -8px;
}
#context-menu .colors {
padding-left: 5px;
width: 89px;
white-space: normal;
}
#context-menu .colors:hover {
background-color: inherit;
}
#context-menu .colors span {
display: inline-block;
width: 14px;
height: 14px;
border: 1px solid white;
margin: 3px;
}
.message-pane span.colored {
background-color: transparent !important;
}
.color-0 {
color: #333;
background-color: #333;
}
.color-1 {
color: #c4322b;
background-color: #c4322b;
}
.color-2 {
color: #37991e;
background-color: #37991e;
}
.color-3 {
color: #1654c9;
background-color: #1654c9;
}
.color-4 {
color: #66379b;
background-color: #66379b;
}
.color-5 {
color: #ba7318;
background-color: #ba7318;
}
.color-6 {
color: #32938a;
background-color: #32938a;
}
.color-7 {
color: #9e2274;
background-color: #9e2274;
}
.color-8 {
color: #4C82E4;
background-color: #4C82E4;
}
.color-9 {
color: #7F140E;
background-color: #7F140E;
}
.color-10 {
color: #1C630A;
background-color: #1C630A;
}
.color-11 {
color: #CF55A4;
background-color: #CF55A4;
}
\ No newline at end of file
var CandyShop = (function(self) { return self; }(CandyShop || {}));
CandyShop.Colors = (function(self, Candy, $) {
var _numColors,
_currentColor = 0;
self.init = function(numColors) {
console.log("Colors.init");
_numColors = numColors ? numColors : 8;
self.applyTranslations();
Candy.View.Event.Message.beforeSend = function(message) {
if(_currentColor > 0 && $.trim(message) !== '') {
return '|c:'+ _currentColor +'|' + message;
}
return message;
};
Candy.View.Event.Message.beforeShow = function(args) {
var message = ($.type(args) !== 'string') ? /* Candy >= 1.0.4 */ args.message : /* Candy < 1.0.4 */ args;
return message.replace(/^\|c:([0-9]{1,2})\|(.*)/gm, '<span class="colored color-$1">$2</span>');
};
if(Candy.Util.cookieExists('candyshop-colors-current')) {
var color = parseInt(Candy.Util.getCookie('candyshop-colors-current'), 10);
if(color > 0 && color < _numColors) {
_currentColor = color;
}
}
var html = '<li id="colors-control" data-tooltip="' + $.i18n._('candyshopColorsMessagecolor') + '"><span class="color-' + _currentColor + '" id="colors-control-indicator"></span></li>';
$('#emoticons-icon').after(html);
$('#colors-control').click(function(event) {
CandyShop.Colors.showPicker(this);
});
};
self.showPicker = function(elem) {
elem = $(elem);
var pos = elem.offset(),
menu = $('#context-menu'),
content = $('ul', menu),
colors = '',
i;
$('#tooltip').hide();
for(i = _numColors-1; i >= 0; i--) {
colors = '<span class="color-' + i + '" data-color="' + i + '"></span>' + colors;
}
content.html('<li class="colors">' + colors + '</li>');
content.find('span').click(function() {
_currentColor = $(this).attr('data-color');
$('#colors-control-indicator').attr('class', 'color-' + _currentColor);
Candy.Util.setCookie('candyshop-colors-current', _currentColor, 365);
Candy.View.Pane.Room.setFocusToForm(Candy.View.getCurrent().roomJid);
menu.hide();
});
var posLeft = Candy.Util.getPosLeftAccordingToWindowBounds(menu, pos.left),
posTop = Candy.Util.getPosTopAccordingToWindowBounds(menu, pos.top);
menu.css({'left': posLeft.px, 'top': posTop.px, backgroundPosition: posLeft.backgroundPositionAlignment + ' ' + posTop.backgroundPositionAlignment});
menu.fadeIn('fast');
return true;
};
self.applyTranslations = function() {
var translations = {
'en' : 'Message Color',
'ru' : '???? ?????????',
'de' : 'Farbe für Nachrichten',
'fr' : 'Couleur des messages',
'nl' : 'Berichtkleur',
'es' : 'Color de los mensajes'
};
$.each(translations, function(k, v) {
if(Candy.View.Translation[k]) {
Candy.View.Translation[k].candyshopColorsMessagecolor = v;
}
});
};
return self;
}(CandyShop.Colors || {}, Candy, jQuery));
/**
* Fastpath plugin for Candy
*
*/
var CandyShop = (function(self) { return self; }(CandyShop || {}));
CandyShop.Fastpath = (function(self, Candy, $) {
var connection = null;
var nickname = null;
self.init = function()
{
console.log("Fastpath.init");
Candy.Core.Event.addObserver(Candy.Core.Event.KEYS.CHAT, chatObserver);
}
self.acceptOffer = function()
{
console.log('accepted', window.properties);
connection.send($iq({type: 'set', to: window.properties.workgroupJid}).c('offer-accept', {xmlns: "http://jabber.org/protocol/workgroup", jid: window.properties.jid, id: window.properties.id}));
Candy.View.Pane.Chat.Modal.hide();
}
self.rejectOffer = function()
{
console.log('rejected');
connection.send($iq({type: 'set', to: window.properties.workgroupJid}).c('offer-reject', {xmlns: "http://jabber.org/protocol/workgroup", jid: window.properties.jid, id: window.properties.id}));
Candy.View.Pane.Chat.Modal.hide();
}
var chatObserver =
{
update: function(obj, args)
{
if(args.type === 'connection')
{
switch(args.status)
{
case Strophe.Status.CONNECTING:
connection = Candy.Core.getConnection();
Candy.Core.addHandler(fastpathIqCallback, "http://jabber.org/protocol/workgroup", 'iq', 'set');
Candy.Core.addHandler(fastpathPresCallback, null, 'presence');
Candy.Core.addHandler(fastpathMsgCallback, null, 'message');
break;
case Strophe.Status.CONNECTED:
nickname = Strophe.escapeNode(Candy.Core.getUser().getNick());
GetWorkgroups();
break;
}
}
}
};
var GetWorkgroups = function()
{
var iq = $iq({type: 'get', to: "workgroup." + connection.domain}).c('workgroups', {jid: connection.jid, xmlns: "http://jabber.org/protocol/workgroup"});
connection.sendIQ(iq, function(response)
{
$(response).find('workgroup').each(function()
{
var current = $(this);
var jid = current.attr('jid');
var name = Strophe.getNodeFromJid(jid);
var chatRoom = 'workgroup-' + name + "@conference." + connection.domain;
connection.send($pres({to: jid}).c('agent-status', {'xmlns': "http://jabber.org/protocol/workgroup"}));
Candy.Core.Action.Jabber.Room.Join(chatRoom);
connection.send($pres({to: jid}).c("status").t("Online").up().c("priority").t("1"));
connection.sendIQ($iq({type: 'get', to: jid}).c('agent-status-request', {xmlns: "http://jabber.org/protocol/workgroup"}));
});
});
};
var fastpathIqCallback = function(iq)
{
console.log('fastpathIqCallback', iq);
var iq = $(iq);
var workgroupJid = iq.attr('from');
var workgroup = Strophe.getNodeFromJid(workgroupJid);
connection.send($iq({type: 'result', to: iq.attr('from'), id: iq.attr('id')}));
iq.find('offer').each(function()
{
var id = $(this).attr('id');
var jid = $(this).attr('jid').toLowerCase();
var properties = {id: id, jid: jid, workgroupJid: workgroupJid};
iq.find('value').each(function()
{
var name = $(this).attr('name');
var value = $(this).text();
properties[name] = value;
});
console.log("fastpathIqCallback offer", properties, workgroup);
acceptRejectOffer(workgroup, properties);
});
iq.find('offer-revoke').each(function()
{
id = $(this).attr('id');
jid = $(this).attr('jid').toLowerCase();
console.log("fastpathIqCallback offer-revoke", workgroup);
});
return true;
}
var fastpathPresCallback = function(presence)
{
console.log('fastpathPresCallback', presence);
var presence = $(presence);
if (presence.find('agent-status').length > 0 || presence.find('notify-queue-details').length > 0 || presence.find('notify-queue').length > 0)
{
var from = Candy.Util.unescapeJid(presence.attr('from'));
var nick = Strophe.getNodeFromJid(from);
var workGroup, maxChats, free = true;
presence.find('agent-status').each(function()
{
workGroup = 'workgroup-' + Strophe.getNodeFromJid($(this).attr('jid')) + "@conference." + connection.domain;
presence.find('max-chats').each(function()
{
maxChats = $(this).text();
});
presence.find('chat').each(function()
{
free = false;
var sessionID = $(this).attr('sessionID');
var sessionJid = sessionID + "@conference." + connection.domain;
var sessionHash = (sessionJid);
var userID = $(this).attr('userID');
var startTime = $(this).attr('startTime');
var question = $(this).attr('question');
var username = $(this).attr('username');
var email = $(this).attr('email');
if (workGroup)
{
console.log('agent-status message to ' + workGroup);
var text = "Talking with " + username + " about " + question;
Candy.View.Pane.Message.show(workGroup, nick, text);
}
});
});
presence.find('notify-queue-details').each(function()
{
var workGroup = 'workgroup-' + nick + "@conference." + connection.domain;
var free = true;
presence.find('user').each(function()
{
var jid = $(this).attr('jid');
var position, time, joinTime
$(this).find('position').each(function()
{
position = $(this).text() == "0" ? "first": jQuery(this).text();
});
$(this).find('time').each(function()
{
time = $(this).text();
});
$(this).find('join-time').each(function()
{
joinTime = $(this).text();
});
if (position && time && joinTime)
{
free = false;
console.log('notify-queue-details message to ' + workGroup);
var text = "A caller has been waiting for " + time + " secconds";
Candy.View.Pane.Message.show(workGroup, nick, text);
}
});
});
presence.find('notify-queue').each(function()
{
var workGroup = 'workgroup-' + nick + "@conference." + connection.domain;
var free = true;
var count, oldest, waitTime, status
var room = Candy.View.Pane.Room.getPane(workGroup, '.message-pane')
presence.find('count').each(function()
{
count = jQuery(this).text();
});
presence.find('oldest').each(function()
{
oldest = jQuery(this).text();
});
presence.find('time').each(function()
{
waitTime = jQuery(this).text();
});
presence.find('status').each(function()
{
status = jQuery(this).text();
});
if (count && oldest && waitTime && status)
{
free = false;
console.log('notify-queue message to ' + workGroup);
var text = "There are " + count + " caller(s) waiting for as long as " + waitTime + " seconds";
if (room) Candy.View.Pane.Message.show(workGroup, nick, text);
}
if (free && room) Candy.View.Pane.Message.show(workGroup, nick, "No waiting conversations");
});
}
return true;
}
var fastpathMsgCallback = function(message)
{
console.log('fastpathMsgCallback', message);
var msg = $(message);
msg.find('invite').each(function()
{
var roomJid = msg.attr("from");
var workgroupJid = $(this).attr('from');
msg.find('offer').each(function()
{
var contactJid = $(this).attr('jid');
console.log("fastpathMsgCallback offer", workgroupJid, contactJid, roomJid);
Candy.Core.Action.Jabber.Room.Join(roomJid);
});
});
return true;
}
var acceptRejectOffer = function(workgroup, properties)
{
var form = '<strong>' + workgroup + '</strong>' + '<form class="accept-reject-offer-form">'
var props = Object.getOwnPropertyNames(properties)
for (var i=0; i< props.length; i++)
{
if (props[i] != "id" && props[i] != "jid" && props[i] != "workgroupJid")
form = form + props[i] + " - " + properties[props[i]] + "<p/>";
}
window.properties = properties;
form = form + '<input onclick="CandyShop.Fastpath.acceptOffer()" type="button" value="Accept" />'
+ '<input onclick="CandyShop.Fastpath.rejectOffer()" type="button" value="Reject" />'
+ '</form>'
Candy.View.Pane.Chat.Modal.show(form, true);
}
return self;
}(CandyShop.Fastpath || {}, Candy, jQuery));
# Inline Images
If a user posts a URL to an image, that image gets rendered directly inside of Candy.
![Inline Images](screenshot.png)
## Usage
Include the JavaScript and CSS files:
```HTML
<script type="text/javascript" src="candyshop/inline-images/candy.js"></script>
<link rel="stylesheet" type="text/css" href="candyshop/inline-images/candy.css" />
```
To enable the Inline Images plugin, just add one of the ´init´ methods to your bootstrap:
```JavaScript
// init with default settings:
CandyShop.InlineImages.init();
// customized initialization:
CandyShop.InlineImages.initWithFileExtensions(['png','jpg']); // only recognize PNG and JPG files as image
CandyShop.InlineImages.initWithMaxImageSize(150); // resize images to a maximum edge size of 150px
CandyShop.InlineImages.initWithFileExtensionsAndMaxImageSize(['png','jpg'], 150); // combination of the above examples
```
\ No newline at end of file
.inlineimages-link {
text-decoration:none;
position:relative;
display:inline-block;
}
.inlineimages-link:hover:before {
content: url('overlay.png');
position: absolute;
top: 5px;
left: 5px;
opacity: .8;
}
\ No newline at end of file
/*
* inline-images
* @version 1.0
* @author Manuel Alabor (manuel@alabor.me)
*
* If a user posts a URL to an image, that image gets rendered directly
* inside of Candy.
*/
var CandyShop = (function(self) { return self; }(CandyShop || {}));
CandyShop.InlineImages = (function(self, Candy, $) {
var _fileExtensions = ['png','jpg','jpeg','gif']
,_originalLinkify = Candy.Util.Parser.linkify
,_maxImageSize = 100;
/** Function: init
* Initializes the inline-images plugin with the default settings.
*/
self.init = function() {
Candy.View.Event.Message.beforeShow = handleBeforeShow;
Candy.View.Event.Message.onShow = handleOnShow;
Candy.Util.Parser.linkify = linkify;
};
/** Function: initWithFileExtensions
* Initializes the inline-images plugin with the possibility to pass an
* array with all the file extensions you want to display as image.
*
* Parameters:
* (String array) fileExtensions - Array with extensions (jpg, png, ...)
*/
self.initWithFileExtensions = function(fileExtensions) {
_fileExtensions = fileExtensions;
self.init();
};
/** Function: initWithMaxImageSize
* Initializes the inline-images plugin with the possibility to pass the
* maximum image size for displayed images.
*
* Parameters:
* (int) maxImageSize - Maximum edge size for images
*/
self.initWithMaxImageSize = function(maxImageSize) {
_maxImageSize = maxImageSize;
self.init();
};
/** Function: initWithFileExtensionsAndMaxImageSize
* Initializes the inline-images plugin with the possibility to pass an
* array with all the file extensions you want to display as image and
* the maximum image size for displayed images.
*
* Parameters:
* (String array) fileExtensions - Array with extensions (jpg, png, ...)
* (int) maxImageSize - Maximum edge size for images
*/
self.initWithFileExtensionsAndMaxImageSize = function(fileExtensions, maxImageSize) {
_fileExtensions = fileExtensions;
_maxImageSize = maxImageSize;
self.init();
};
/** Function: handleBeforeShow
* Handles the beforeShow event of a message.
*
* Paramteres:
* (Object) args - {roomJid, element, nick, message}
*
* Returns:
* (String)
*/
var handleBeforeShow = function(args) {
var message = args.message;
var processed = message.replace(/\|[^\|]+\|/, "");
processed = processed.replace(/(^|[^\/])(www\.[^\.]+\.[\S]+(\b|$))/gi, '$1http://$2');
processed = processed.replace(/\b(https?:\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig, replaceCallback);
return processed;
};
/** Function: handleOnShow
* Each time a message gets displayed, this method checks for possible
* image loaders (created by buildImageLoaderSource).
* If there is one, the image "behind" the loader gets loaded in the
* background. As soon as the image is loaded, the image loader gets
* replaced by proper scaled image.
*
* Parameters:
* (Array) args
*/
var handleOnShow = function(args) {
$('.inlineimages-loader').each(function(index, element) {
$(element).removeClass('inlineimages-loader');
var url = $(element).attr('longdesc');
var imageLoader = new Image();
$(imageLoader).load(function() {
var origWidth = this.width;
var origHeight = this.height;
var ratio = Math.min(_maxImageSize / origWidth, _maxImageSize / origHeight);
var width = Math.round(ratio * origWidth);
var height = Math.round(ratio * origHeight);
$(element).replaceWith(buildImageSource(url, width, height))
});
imageLoader.src = url;
});
}
/** Function: linkify
* Is used to overwrite the original Candy.Util.Parser.linkify.
* This implementation prevents the parsing of URL's by the Candy core.
* inline-images handles this on itself by handleBeforeShow.
*
* Parameters:
* (String) text - text to process
*
* Returns:
* (String)
*/
var linkify = function(text) {
return text;
}
/** Function: replaceCallback
* This callback handles matches from the URL regex.
* If the callback detects an image URL, it returns an image with a loading
* indicator. If it is just a common URL, a link-tag gets returned.
*
* Paramters:
* (String) match - matched URL
*
* Returns:
* (String)
*/
var replaceCallback = function(match) {
var result = match;
var dotPosition = match.lastIndexOf(".");
if(dotPosition > -1) {
if(_fileExtensions.indexOf(match.substr(dotPosition+1)) != -1) {
result = buildImageLoaderSource(match);
} else {
result = buildLinkSource(match);
}
}
return result;
}
/** Function: buildImageLoaderSource
* Returns a loader indicator. The handleOnShow method fullfills afterwards
* the effective image loading.
*
* Parameters:
* (String) url - image url
*
* Returns:
* (String)
*/
var buildImageLoaderSource = function(url) {
return '<img class="inlineimages-loader" longdesc="' + url + '" src="candy-plugins/inline-images/spinner.gif" />'
}
/** Function: buildImageSource
* Returns HTML source to show a URL as an image.
*
* Parameters:
* (String) url - image url
*
* Returns:
* (String)
*/
var buildImageSource = function(url, width, height) {
return '<a href="' + url + '" target="_blank" class="inlineimages-link"><img src="' + url + '" width="' + width + '" height="' + height + '"/></a>';
}
/** Function: buildLinkSource
* Returns HTML source to show a URL as a link.
*
* Parameters:
* (String) url - url
*
* Returns:
* (String)
*/
var buildLinkSource = function(url) {
return '<a href="' + url + '" target="_blank">' + url + '</a>';
}
return self;
}(CandyShop.InlineImages || {}, Candy, jQuery));
# Reply Highlighting
To better support conversations in high-activity rooms, this plugin highlights any message that contains "@yourusername"
## Usage
```HTML
<script type="text/javascript" src="candyshop/replies/candy.js"></script>
<link rel="stylesheet" type="text/css" href="candyshop/replies/candy.css" />
```
```JavaScript
CandyShop.Replies.init();
```
.message-pane dt {
height: 13px;
}
.message-pane dd.mention, .message-pane dt.mention {
background-color: #FFF7DE;
}
\ No newline at end of file
/*
* candy-replies-plugin
* @version 0.1 (2013-2-20)
* @author Drew Harry (drew.harry@gmail.com)
*
* Adds @reply highlighting to chat messages to help with high velocity
* conversations.
*/
var CandyShop = (function(self) { return self; }(CandyShop || {}));
CandyShop.Replies = (function(self, Candy, $) {
self.init = function() {
Candy.View.Event.Message.onShow = handleOnShow;
return self;
};
var handleOnShow = function(args) {
var localNick = Candy.Core.getUser().getNick();
var re = new RegExp("@" + localNick + "([ .!><\":\/@-]|$)", 'im');
if(re.test(args.message)) {
var el = args.element;
el.addClass("mention");
el.prev().addClass("mention");
}
}
return self;
}(CandyShop.Replies || {}, Candy, jQuery));
# RoomPanel
Adds Icon show a lists rooms, also allows to show rooms upon connection and when all rooms are closed.
![RoomPanel](screenshot.png)
## Usage
To enable *RoomPanel* you have to include its JavaScript code and stylesheet:
```HTML
<script type="text/javascript" src="candyshop/roomPanel/roomPanel.js"></script>
<link rel="stylesheet" type="text/css" href="candyshop/roomPanel/default.css" />
```
Call its `init()` method after Candy has been initialized:
```JavaScript
Candy.init('/http-bind/');
// enable RoomPanel plugin
CandyShop.RoomPanel.init({
// domain that hosts the muc rooms, only required if autoDetectRooms is enabled
mucDomain: 'conference.yourdomain.com',
// allow you to force a list of rooms, only required if autoDetectRoom is disabled
roomList: [
{
name: 'my room',
jid: 'my-room@conference.yourdomain.com'
},
{
name: 'other room',
jid: 'other-room@conference.yourdomain.com'
}
],
// show room list if all rooms are closed, default value is true. [optional]
showIfAllTabClosed: true,
// detect rooms before showing list, default value is true. [optional]
autoDetectRooms: true,
// how long in seconds before refreshing room list, default value is 600. [optional]
roomCacheTime: 600
});
Candy.Core.connect();
.roomList a {
color: #AAA;
}
#roomPanel-control {
width: 16px;
background: url(images/room.png);
}
var CandyShop = (function(self) {return self;}(CandyShop || {}));
/**
* Class: Shows a list of rooms upon connection and adds a little icon to bring list of rooms
*/
CandyShop.RoomPanel = (function(self, Candy, Strophe, $) {
var _options = {
// domain that hosts the muc rooms, only required if autoDetectRooms is enabled
mucDomain: '',
// allow you to force a list of rooms, only required if autoDetectRoom is disabled
roomList: [],
// show room list if all rooms are closed, default value is true. [optional]
showIfAllTabClosed: true,
// detect rooms before showing list, default value is true. [optional]
autoDetectRooms: true,
// how long in seconds before refreshing room list, default value is 600. [optional]
roomCacheTime: 600
};
var _lastRoomUpdate = 0;
self.init = function(options) {
$.extend(_options, options);
self.applyTranslations();
/* Overwrite candy allTabsClosed function not
* to disconnect when all tags are closed */
if (_options.showIfAllTabClosed) {
Candy.View.Pane.Chat.allTabsClosed = function () {
CandyShop.RoomPanel.showRoomPanel();
return;
};
} //if
var html = '<li id="roomPanel-control" data-tooltip="' + $.i18n._('candyshopRoomPanelListRoom') + '"></li>';
$('#chat-toolbar').prepend(html);
$('#roomPanel-control').click(function() {
CandyShop.RoomPanel.showRoomPanel();
});
Candy.Core.Event.addObserver(Candy.Core.Event.KEYS.CHAT, {update: function(obj, data) {
if (data.type == 'connection') {
if (Strophe.Status.CONNECTED == data.status) {
/* only show room window if not already in a room, timeout is to let some time for auto join to execute */
setTimeout(CandyShop.RoomPanel.showRoomPanelIfAllClosed, 500);
} //if
} //if
return true;
}});
};
self.showRoomPanelIfAllClosed = function() {
var roomCount = 0;
var rooms = Candy.Core.getRooms();
for (k in rooms) {
if (rooms.hasOwnProperty(k)) {
roomCount++;
} //if
} //for
if (roomCount == 0) {
CandyShop.RoomPanel.showRoomPanel();
} //if
}
self.updateRoomList = function (iq) {
var newRoomList = [];
$('item', iq).each(function (index, value) {
var name = $(value).attr('name');
var jid = $(value).attr('jid');
if (typeof name == 'undefined') {
name = jid.split('@')[0];
} //if
newRoomList.push({
name: name,
jid: jid
});
});
_options.roomList = newRoomList;
_lastRoomUpdate = Math.round(new Date().getTime() / 1000);
self.showRoomPanel();
};
self.showRoomPanel = function() {
/* call until connecting modal is gone */
if ($('#chat-modal').is(':visible')) {
setTimeout(CandyShop.RoomPanel.showRoomPanel, 100);
} else {
var timeDiff = Math.round(new Date().getTime() / 1000) - _options.roomCacheTime;
if (_options.autoDetectRooms && timeDiff > _lastRoomUpdate ) {
/* sends a request to get list of rooms user for the room */
var iq = $iq({type: 'get', from: Candy.Core.getUser().getJid(), to: _options.mucDomain , id: 'findRooms1'})
.c('query', {xmlns: Strophe.NS.DISCO_ITEMS});
Candy.Core.getConnection().sendIQ(iq, self.updateRoomList);
} else {
var html = Mustache.to_html(CandyShop.RoomPanel.Template.rooms, {
title: $.i18n._('candyshopRoomPanelChooseRoom'),
roomList: _options.roomList
});
Candy.View.Pane.Chat.Modal.show(html,true);
$('.roomList a').bind('click', function(e) {
var roomJid = this.href.split('#')[1];
Candy.Core.Action.Jabber.Room.Join(roomJid);
Candy.View.Pane.Chat.Modal.hide();
e.preventDefault();
});
} //if
} //if
return true;
};
self.applyTranslations = function() {
var translations = {
'en' : ['List Rooms', 'Choose Room To Join'],
'ru' : ['Список комнат', 'Выберите комнату'],
'de' : ['Verfügbare Räume anzeigen', 'Verfügbare Räume'],
'fr' : ['Choisir une salle', 'Liste des salles'],
'nl' : ['Choose Room To Join', 'List Rooms'],
'es' : ['Choose Room To Join', 'List Rooms'],
};
$.each(translations, function(k, v) {
if(Candy.View.Translation[k]) {
Candy.View.Translation[k].candyshopRoomPanelListRoom = v[0];
Candy.View.Translation[k].candyshopRoomPanelChooseRoom = v[1];
}
});
};
return self;
}(CandyShop.RoomPanel || {}, Candy, Strophe, jQuery));
CandyShop.RoomPanel.Template = (function (self) {
var roomParts = [
'<div class="roomList">',
'<h2>{{title}}</h2>',
'<ul>',
'{{#roomList}}',
'<li><a href="#{{jid}}">{{name}}</a></li>',
'{{/roomList}}',
'</ul>',
'</div>'
];
self.rooms = roomParts.join('');
return self;
})(CandyShop.RoomPanel.Template || {});
#Candy Timeago plugin
This plugin replaces the exact time/date with 'fuzzy timestamps' (e.g. 'less than a minute ago', '2 minutes ago', 'about an hour ago'). The timestamps update dynamically. All the heavy lifting is done by Ryan McGeary's excellent jQuery Timeago plugin (http://timeago.yarp.com/).
##Usage
To enable Timeago include it's JavaScript code and CSS file (after the main Candy script and CSS):
```html
<script type="text/javascript" src="candyshop/timeago/candy.js"></script>
<link rel="stylesheet" type="text/css" href="candyshop/timeago/candy.css" />
```
Then call its init() method after Candy has been initialized:
```html
Candy.init('/http-bind/');
CandyShop.Timeago.init();
Candy.Core.connect();
```
\ No newline at end of file
.message-pane dt {
width: 120px;
padding-right: 10px;
}
.message-pane dt abbr {
border-bottom: none;
}
\ No newline at end of file
/*
* candy-timeago-plugin
* @version 0.1 (2011-07-15)
* @author David Devlin (dave.devlin@gmail.com)
*
* Integrates the jQuery Timeago plugin (http://timeago.yarp.com/) with Candy.
*/
var CandyShop = (function(self) { return self; }(CandyShop || {}));
CandyShop.Timeago = (function(self, Candy, $) {
self.init = function() {
Candy.View.Template.Chat['adminMessage'] = '<dt><abbr title="{{time}}">{{time}}</abbr></dt><dd class="adminmessage"><span class="label">{{sender}}</span>{{subject}} {{message}}</dd>';
Candy.View.Template.Chat['infoMessage'] = '<dt><abbr title="{{time}}">{{time}}</abbr></dt><dd class="infomessage">{{subject}} {{message}}</dd>';
Candy.View.Template.Room['subject'] = '<dt><abbr title="{{time}}">{{time}}</abbr></dt><dd class="subject"><span class="label">{{roomName}}</span>{{_roomSubject}} {{subject}}</dd>';
Candy.View.Template.Message['item'] = '<dt><abbr title="{{time}}">{{time}}</abbr></dt><dd><span class="label"><a href="#" class="name">{{displayName}}</a></span>{{{message}}}</dd>';
Candy.Util.localizedTime = function(dateTime) {
if (dateTime === undefined) {
return undefined;
}
var date = Candy.Util.iso8601toDate(dateTime);
return date.format($.i18n._('isoDateTime'));
};
Candy.View.Event.Message.onShow = function(message) {
$('abbr').timeago();
};
Candy.View.Event.Chat.onAdminMessage = function(message) {
$('abbr').timeago();
};
Candy.View.Event.Room.onSubjectChange = function(message) {
$('abbr').timeago();
};
Candy.View.Event.Room.onPresenceChange = function(message) {
$('abbr').timeago();
};
};
return self;
}(CandyShop.Timeago || {}, Candy, jQuery));
/*
* timeago: a jQuery plugin, version: 0.9.3 (2011-01-21)
* @requires jQuery v1.2.3 or later
*
* Timeago is a jQuery plugin that makes it easy to support automatically
* updating fuzzy timestamps (e.g. "4 minutes ago" or "about 1 day ago").
*
* For usage and examples, visit:
* http://timeago.yarp.com/
*
* Licensed under the MIT:
* http://www.opensource.org/licenses/mit-license.php
*
* Copyright (c) 2008-2011, Ryan McGeary (ryanonjavascript -[at]- mcgeary [*dot*] org)
*/
(function($) {
$.timeago = function(timestamp) {
if (timestamp instanceof Date) {
return inWords(timestamp);
} else if (typeof timestamp === "string") {
return inWords($.timeago.parse(timestamp));
} else {
return inWords($.timeago.datetime(timestamp));
}
};
var $t = $.timeago;
$.extend($.timeago, {
settings: {
refreshMillis: 60000,
allowFuture: false,
strings: {
prefixAgo: null,
prefixFromNow: null,
suffixAgo: "ago",
suffixFromNow: "from now",
seconds: "less than a minute",
minute: "about a minute",
minutes: "%d minutes",
hour: "about an hour",
hours: "about %d hours",
day: "a day",
days: "%d days",
month: "about a month",
months: "%d months",
year: "about a year",
years: "%d years",
numbers: []
}
},
inWords: function(distanceMillis) {
var $l = this.settings.strings;
var prefix = $l.prefixAgo;
var suffix = $l.suffixAgo;
if (this.settings.allowFuture) {
if (distanceMillis < 0) {
prefix = $l.prefixFromNow;
suffix = $l.suffixFromNow;
}
distanceMillis = Math.abs(distanceMillis);
}
var seconds = distanceMillis / 1000;
var minutes = seconds / 60;
var hours = minutes / 60;
var days = hours / 24;
var years = days / 365;
function substitute(stringOrFunction, number) {
var string = $.isFunction(stringOrFunction) ? stringOrFunction(number, distanceMillis) : stringOrFunction;
var value = ($l.numbers && $l.numbers[number]) || number;
return string.replace(/%d/i, value);
}
var words = seconds < 45 && substitute($l.seconds, Math.round(seconds)) ||
seconds < 90 && substitute($l.minute, 1) ||
minutes < 45 && substitute($l.minutes, Math.round(minutes)) ||
minutes < 90 && substitute($l.hour, 1) ||
hours < 24 && substitute($l.hours, Math.round(hours)) ||
hours < 48 && substitute($l.day, 1) ||
days < 30 && substitute($l.days, Math.floor(days)) ||
days < 60 && substitute($l.month, 1) ||
days < 365 && substitute($l.months, Math.floor(days / 30)) ||
years < 2 && substitute($l.year, 1) ||
substitute($l.years, Math.floor(years));
return $.trim([prefix, words, suffix].join(" "));
},
parse: function(iso8601) {
var s = $.trim(iso8601);
s = s.replace(/\.\d\d\d+/,""); // remove milliseconds
s = s.replace(/-/,"/").replace(/-/,"/");
s = s.replace(/T/," ").replace(/Z/," UTC");
s = s.replace(/([\+\-]\d\d)\:?(\d\d)/," $1$2"); // -04:00 -> -0400
return new Date(s);
},
datetime: function(elem) {
// jQuery's `is()` doesn't play well with HTML5 in IE
var isTime = $(elem).get(0).tagName.toLowerCase() === "time"; // $(elem).is("time");
var iso8601 = isTime ? $(elem).attr("datetime") : $(elem).attr("title");
return $t.parse(iso8601);
}
});
$.fn.timeago = function() {
var self = this;
self.each(refresh);
var $s = $t.settings;
if ($s.refreshMillis > 0) {
setInterval(function() { self.each(refresh); }, $s.refreshMillis);
}
return self;
};
function refresh() {
var data = prepareData(this);
if (!isNaN(data.datetime)) {
$(this).text(inWords(data.datetime));
}
return this;
}
function prepareData(element) {
element = $(element);
if (!element.data("timeago")) {
element.data("timeago", { datetime: $t.datetime(element) });
var text = $.trim(element.text());
if (text.length > 0) {
element.attr("title", text);
}
}
return element.data("timeago");
}
function inWords(date) {
return $t.inWords(distance(date));
}
function distance(date) {
return (new Date().getTime() - date.getTime());
}
// fix for IE6 suckage
document.createElement("abbr");
document.createElement("time");
}(jQuery));
\ No newline at end of file
body{
margin:0px;
}
#largeVideo {
display:block;
visibility:hidden;
position:relative;
width:1280px;
height:720px;
margin-left:auto;
margin-right:auto;
/* top:-20px;*/
z-index: 2;
}
#videobridge-control {
background: no-repeat url('webcam_off.png');
position: relative;
}
#videobridge-control.active {
background-image: url(webcam_on.png);
}
#webcam-control {
background-image: url(webcam.png);
}
#webcam-control.muted {
background-image: url(webcam_mute.png);
}
#mic-control {
background-image: url(mic.png);
}
#mic-control.muted {
background-image: url(mic_muted.png);
}
#screen-control {
background-image: url(desktop_off.png);
}
#screen-control.active {
background-image: url(desktop_on.png);
}
#remoteVideos {
display:block;
position:relative;
/* top:-30px;*/
text-align:center;
height:196px;
width:auto;
overflow: hidden;
border:1px solid transparent;
font-size:0;
z-index: 2;
}
#remoteVideos video {
position:relative;
top:38px;
height:160px;
width:auto;
z-index:0;
border:1px solid transparent;
}
#remoteVideos video:hover {
cursor: pointer;
cursor: hand;
transform:scale(1.08, 1.08);
-webkit-transform:scale(1.08, 1.08);
transition-duration: 0.5s;
-webkit-transition-duration: 0.5s;
background-color: #FFFFFF;
-webkit-animation-name: greyPulse;
-webkit-animation-duration: 2s;
-webkit-animation-iteration-count: 1;
-webkit-box-shadow: 0 0 18px #515151;
border:1px solid #FFFFFF;
z-index: 10;
}
#chatspace {
height:46px;
line-height:20px;
font-size:20px;
visibility:hidden;
padding:10px;
margin-left:auto;
margin-right:auto;
}
#chatspace .nick {
font-weight:bold;
}
#chatspace>:first-child{
visibility:hidden;
}
div#spacer {
height:5px;
}
#settings {
display:none;
}
#nowebrtc {
display:none;
}
div#header{
display:block;
position:relative;
width:100%;
height:39px;
z-index: 1;
}
div#left {
display:block;
position: absolute;
left: 0px;
top: 0px;
width: 100px;
height: 39px;
background-repeat:no-repeat;
margin: 0;
padding: 0;
}
div#leftlogo {
position:absolute;
top: 5px;
left: 15px;
background-image:url(jitsilogo.png);
background-repeat:no-repeat;
height: 31px;
width: 68px;
}
div#link {
margin-left: 100px;
margin-right:100px;
height:39px;
line-height:39px;
overflow: hidden;
text-align: center;
vertical-align:middle;
font-size: 14px;
color: #313131;
visibility:hidden;
}
.fade_line {
height: 2px;
background: black;
background: -webkit-gradient(linear, 0 0, 100% 0, from(white), to(white), color-stop(50%, black));
}
div#right {
display:block;
position:absolute;
right: 0px;
top: 0px;
background-repeat:no-repeat;
margin:0;
padding:0;
width:100px;
height:39px;
}
div#rightlogo {
position:absolute;
top: 8px;
right: 15px;
background-image:url(estoslogo.png);
background-repeat:no-repeat;
height: 20px;
width: 62px;
}
#largeVideo {width:1280px;height:720px;margin-left:auto;margin-right:255px;display:block; top:30px}
#localVideo {visibility:hidden; }
#remoteVideos {text-align:center;height:180px;}
#remoteVideos video {width:320;height:180;}
#chatspace {height:46px;line-height:20px;font-size:20px;visibility:hidden;padding:10px;margin-left:auto;margin-right:auto;}
#chatspace .nick {font-weight:bold;}
#chatspace>:first-child{visibility:hidden}
#settings {display:none;}
#header {text-align:center;visibility:hidden;}
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment