Commit 52207f72 authored by Dele Olajide's avatar Dele Olajide Committed by dele

JitsiVideobridge - Added Fastpath video using candy (agent) and ofmeet (user)

git-svn-id: http://svn.igniterealtime.org/svn/repos/openfire/trunk@13858 b35dd754-fafc-0310-a699-88a17e54d16e
parent 786247b5
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
Copyright (c) 2011 Amiado Group AG
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
# Makefile for Candy
# Candy - Chats are not dead yet
#
# Copyright:
# (c) 2011 Amiado Group AG
#
# Authors:
# - Patrick Stadler <patrick.stadler@gmail.com>
# - Michael Weibel <michael.weibel@gmail.com>
#
SHELL=/bin/bash
DOC_DIR = docs
NDPROJ_DIR = .ndproj
SRC_DIR = src
LIBS_DIR = libs
CANDY_BUNDLE = candy.bundle.js
CANDY_BUNDLE_MIN = candy.min.js
CANDY_BUNDLE_LIBRARIES = libs/libs.bundle.js
CANDY_BUNDLE_LIBRARIES_MIN = libs/libs.min.js
CANDY_FILES = $(SRC_DIR)/candy.js $(SRC_DIR)/core.js $(SRC_DIR)/view.js $(SRC_DIR)/util.js $(SRC_DIR)/core/action.js $(SRC_DIR)/core/chatRoom.js $(SRC_DIR)/core/chatRoster.js $(SRC_DIR)/core/chatUser.js $(SRC_DIR)/core/event.js $(SRC_DIR)/view/event.js $(SRC_DIR)/view/observer.js $(SRC_DIR)/view/pane.js $(SRC_DIR)/view/template.js $(SRC_DIR)/view/translation.js
CANDY_LIBS_FILES = $(LIBS_DIR)/strophejs/strophe.js $(LIBS_DIR)/strophejs-plugins/muc/strophe.muc.js $(LIBS_DIR)/mustache.js/mustache.js $(LIBS_DIR)/jquery-i18n/jquery.i18n.js $(LIBS_DIR)/dateformat/dateFormat.js
CANDY_FILES_BUNDLE = $(CANDY_FILES:.js=.bundle)
CANDY_LIBS_FILES_BUNDLE = $(CANDY_LIBS_FILES:.js=.libs-bundle)
all: bundle min
bundle: clean-bundle $(CANDY_FILES_BUNDLE)
%.bundle: %.js
@@echo -n "Bundling" $< "..."
@@cat $< >> $(CANDY_BUNDLE)
@@echo "done"
min: $(CANDY_BUNDLE)
@@echo -n "Compressing" $(CANDY_BUNDLE) "..."
ifdef YUI_COMPRESSOR
@@java -jar $(YUI_COMPRESSOR) --type js $(CANDY_BUNDLE) -o $(CANDY_BUNDLE_MIN) --charset utf-8
@@echo "done ("$(CANDY_BUNDLE_MIN)")"
else
@@echo "aborted"
@@echo "** You can safely use the uncompressed bundle ("$(CANDY_BUNDLE)")"
@@echo "** YUI Compressor is required to build the minified version."
@@echo "** Please set YUI_COMPRESSOR to the path to the jar file."
endif
libs: libs-bundle libs-min
libs-bundle: clean-libs $(CANDY_LIBS_FILES_BUNDLE)
%.libs-bundle: %.js
@@echo -n "Bundling" $< "..."
@@cat $< >> $(CANDY_BUNDLE_LIBRARIES)
@@echo "done"
libs-min: $(CANDY_BUNDLE_LIBRARIES)
@@echo -n "Compressing" $(CANDY_BUNDLE_LIBRARIES) "..."
ifdef YUI_COMPRESSOR
@@java -jar $(YUI_COMPRESSOR) --type js $(CANDY_BUNDLE_LIBRARIES) -o $(CANDY_BUNDLE_LIBRARIES_MIN) --charset utf-8
@@echo "done ("$(CANDY_BUNDLE_LIBRARIES_MIN)")"
else
@@echo "aborted"
@@echo "** You can safely use the uncompressed bundle ("$(CANDY_BUNDLE_LIBRARIES)")"
@@echo "** YUI Compressor is required to build the minified version."
@@echo "** Please set YUI_COMPRESSOR to the path to the jar file."
endif
docs:
@@echo "Building candy documentation ..."
ifdef NATURALDOCS_DIR
@@if [ ! -d $(NDPROJ_DIR) ]; then mkdir $(NDPROJ_DIR); fi
@@if [ ! -d $(DOC_DIR) ]; then mkdir $(DOC_DIR); fi
@@$(NATURALDOCS_DIR)/NaturalDocs -q --exclude-source libs --exclude-source res --exclude-source candy.min.js --exclude-source candy.bundle.js -i . -o html $(DOC_DIR) -p $(NDPROJ_DIR)
@@rm -r $(NDPROJ_DIR)
@@echo "Documentation built."
@@echo
else
@@echo "aborted"
@@echo "** NaturalDocs is required to build the documentation."
@@echo "** Please set NATURALDOCS_DIR to the path to the NaturalDocs executable"
endif
clean: clean-bundle clean-libs
clean-bundle:
@@echo -n "Cleaning bundles ..."
@@rm -f $(CANDY_BUNDLE) $(CANDY_BUNDLE_MIN)
@@echo "done"
clean-libs:
@@echo -n "Cleaning library bundles ..."
@@rm -f $(CANDY_BUNDLE_LIBRARIES) $(CANDY_BUNDLE_LIBRARIES_MIN)
@@echo "done"
clean-docs:
@@echo -n "Cleaning documentation ..."
@@rm -rf $(NDPROJ_DIR) $(DOC_DIR)
@@echo "done"
.PHONY: all docs clean libs
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;
};
$(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();
Candy.Core.connect();
// for testing only
// Candy.Core.connect(username + "@" + window.location.hostname, password, username);
});
</script>
</head>
<body>
<div class="fade_line"></div>
<div id="candy"></div>
<div class="fade_line"></div>
<video id="largeVideo" autoplay oncontextmenu="return false;"></video>
<div id="remoteVideos">
<video id="localVideo" autoplay oncontextmenu="return false;" muted/>
</div>
</body>
</html>
This diff is collapsed.
/*
* 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
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
# 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;
Candy.Core.Action.Jabber.Room.Join(chatRoom);
connection.send($pres({to: jid}).c('agent-status', {'xmlns': "http://jabber.org/protocol/workgroup"}));
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
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";
Candy.View.Pane.Message.show(workGroup, nick, text);
}
if (free) 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);
}
#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.
Simple Smileys is a set of 49 clean, free as in freedom, Public Domain smileys.
For more packages or older versions, visit http://simplesmileys.org
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.
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.
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