Commit 22226a32 authored by Dele Olajide's avatar Dele Olajide

ofmeet plugin - Added Candy and Fastpath

parent 73572704
......@@ -49,6 +49,12 @@
Openfire Meetings Plugin Changelog
</h1>
<p><b>0.0.2</b> -- Nov 30th, 2014</p>
<ul>
<li>Added Candy and Fastpath.</li>
</ul>
<p><b>0.0.1</b> -- Nov 27th, 2014</p>
<ul>
......
......@@ -5,8 +5,8 @@
<name>ofmeet</name>
<description>Openfire Meetings</description>
<author></author>
<version>0.0.1</version>
<date>27/11/2014</date>
<version>0.0.2</version>
<date>30/11/2014</date>
<minServerVersion>3.10.0</minServerVersion>
<adminconsole>
......
......@@ -108,7 +108,9 @@ function connect(jid, password) {
}
// BAO
connection = new Openfire.Connection(document.getElementById('boshURL').value || config.bosh || '/http-bind');
//connection.rawInput = function (data) { console.log('RECV: ' + data); };
//connection.rawOutput = function (data) { console.log('SEND: ' + data); };
if (nickname) {
connection.emuc.addDisplayNameToPresence(nickname);
}
......@@ -1383,25 +1385,8 @@ $(document).ready(function () {
$(window).bind('beforeunload', function () {
if (connection && connection.connected) {
// ensure signout
$.ajax({
type: 'POST',
url: config.bosh,
async: false,
cache: false,
contentType: 'application/xml',
data: "<body rid='" + (connection.rid || connection._proto.rid)
+ "' xmlns='http://jabber.org/protocol/httpbind' sid='"
+ (connection.sid || connection._proto.sid)
+ "' type='terminate'><presence xmlns='jabber:client' type='unavailable'/></body>",
success: function (data) {
console.log('signed out');
console.log(data);
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
console.log('signout error', textStatus + ' (' + errorThrown + ')');
}
});
connection.send($pres({type: "unavailable"})) // BAO
connection.disconnect();
}
disposeConference(true);
if(APIConnector.isEnabled())
......
......@@ -10,58 +10,64 @@
<script type="text/javascript" src="candy/libs/libs.bundle.js"></script>
<script type="text/javascript" src="candy/libs/strophe.openfire.js"></script>
<script type="text/javascript" src="candy/candy.bundle.js"></script>
<script type="text/javascript" src="candy/candyshop/colors/candy.js"></script>
<link rel="stylesheet" type="text/css" href="candy/candyshop/colors/candy.css" />
<script type="text/javascript" src="candy/candyshop/notifications/candy.js"></script>
<script type="text/javascript" src="candy/candyshop/notifyme/candy.js"></script>
<link rel="stylesheet" type="text/css" href="candy/candyshop/notifyme/candy.css" />
<script type="text/javascript" src="candy/candyshop/ofmeet/candy.js"></script>
<link rel="stylesheet" type="text/css" href="candy/candyshop/ofmeet/candy.css" />
<script type="text/javascript" src="candy/candyshop/fastpath/candy.js"></script>
<script type="text/javascript" src="candy/candyshop/slash-commands/slash-commands.js"></script>
<script type="text/javascript" src="candy/candyshop/timeago/candy.js"></script>
<link rel="stylesheet" type="text/css" href="candy/candyshop/timeago/candy.css" />
<script type="text/javascript" src="candy/candyshop/typingnotifications/typingnotifications.js"></script>
<link rel="stylesheet" type="text/css" href="candy/candyshop/typingnotifications/typingnotifications.css" />
<script type="text/javascript">
$(document).ready(function() {
Candy.init('http://tlvdly02:7070/http-bind/', {
$("<div>").attr("id","candy").attr("style","width: 100%; height: 100%;").appendTo("body");
Candy.init(config.bosh, {
core: {
// only set this to true if developing / debugging errors
debug: false,
autojoin: ['fxdesk@conference.tlvdly02']
autojoin: true
},
view: { assets: 'candy/res/' }
});
Candy.Core.connect("tlvdly02");
});
var authenticatedUser = false;
var connection = null;
window.addEventListener("load", function()
{
connection = new Openfire.Connection(config.bosh, "ofmeet");
var jid = config.hosts.domain;
var jid = config.hosts.domain;
if (config.userName)
{
jid = config.userName + "@" + config.hosts.domain;
authenticatedUser = true;
}
connection.connect(jid, null, function (status, msg)
{
console.log('Strophe status changed to', Strophe.getStatusString(status));
if (status === Strophe.Status.CONNECTED) {
CandyShop.OfMeet.init();
CandyShop.SlashCommands.defaultConferenceDomain = config.hosts.muc;
CandyShop.SlashCommands.init();
CandyShop.Timeago.init();
CandyShop.TypingNotifications.init();
CandyShop.Colors.init();
CandyShop.Notifications.init();
} else if (status === Strophe.Status.CONNFAIL) {
Candy.Core.connect(jid, "null");
CandyShop.NotifyMe.init();
CandyShop.Fastpath.init();
} else if (status === Strophe.Status.DISCONNECTED) {
} else if (status === Strophe.Status.AUTHFAIL) {
}
});
});
window.addEventListener("unload", function ()
{
connection.disconnect();
});
</script>
</head>
<body>
<iframe id="ofmeet" src="index.html?r=dele123" style="width: 100%; height: 50%;"></iframe>
<div id="candy" style="width: 100%; height: 50%;"></div>
</body>
</html>
docs
example/.htaccess
.DS_Store
._*
.idea
.ndproj/Data
.ndproj/Menu.txt
node_modules
\ No newline at end of file
[submodule "libs/jquery-i18n"]
path = libs/jquery-i18n
url = git://github.com/recurser/jquery-i18n.git
[submodule "libs/strophejs"]
path = libs/strophejs
url = git://github.com/strophe/strophejs.git
[submodule "libs/strophejs-plugins"]
path = libs/strophejs-plugins
url = git://github.com/strophe/strophejs-plugins.git
[submodule "libs/mustache.js"]
path = libs/mustache.js
url = git://github.com/janl/mustache.js.git
{
"node": true,
"curly": true,
"eqeqeq": true,
"latedef": true,
"newcap": true,
"noarg": true,
"undef": true,
"unused": true,
"trailing": true,
"maxdepth": 4
}
\ No newline at end of file
Format: 1.52
# This is the Natural Docs languages file for this project. If you change
# anything here, it will apply to THIS PROJECT ONLY. If you'd like to change
# something for all your projects, edit the Languages.txt in Natural Docs'
# Config directory instead.
# You can prevent certain file extensions from being scanned like this:
# Ignore Extensions: [extension] [extension] ...
#-------------------------------------------------------------------------------
# SYNTAX:
#
# Unlike other Natural Docs configuration files, in this file all comments
# MUST be alone on a line. Some languages deal with the # character, so you
# cannot put comments on the same line as content.
#
# Also, all lists are separated with spaces, not commas, again because some
# languages may need to use them.
#
# Language: [name]
# Alter Language: [name]
# Defines a new language or alters an existing one. Its name can use any
# characters. If any of the properties below have an add/replace form, you
# must use that when using Alter Language.
#
# The language Shebang Script is special. It's entry is only used for
# extensions, and files with those extensions have their shebang (#!) lines
# read to determine the real language of the file. Extensionless files are
# always treated this way.
#
# The language Text File is also special. It's treated as one big comment
# so you can put Natural Docs content in them without special symbols. Also,
# if you don't specify a package separator, ignored prefixes, or enum value
# behavior, it will copy those settings from the language that is used most
# in the source tree.
#
# Extensions: [extension] [extension] ...
# [Add/Replace] Extensions: [extension] [extension] ...
# Defines the file extensions of the language's source files. You can
# redefine extensions found in the main languages file. You can use * to
# mean any undefined extension.
#
# Shebang Strings: [string] [string] ...
# [Add/Replace] Shebang Strings: [string] [string] ...
# Defines a list of strings that can appear in the shebang (#!) line to
# designate that it's part of the language. You can redefine strings found
# in the main languages file.
#
# Ignore Prefixes in Index: [prefix] [prefix] ...
# [Add/Replace] Ignored Prefixes in Index: [prefix] [prefix] ...
#
# Ignore [Topic Type] Prefixes in Index: [prefix] [prefix] ...
# [Add/Replace] Ignored [Topic Type] Prefixes in Index: [prefix] [prefix] ...
# Specifies prefixes that should be ignored when sorting symbols in an
# index. Can be specified in general or for a specific topic type.
#
#------------------------------------------------------------------------------
# For basic language support only:
#
# Line Comments: [symbol] [symbol] ...
# Defines a space-separated list of symbols that are used for line comments,
# if any.
#
# Block Comments: [opening sym] [closing sym] [opening sym] [closing sym] ...
# Defines a space-separated list of symbol pairs that are used for block
# comments, if any.
#
# Package Separator: [symbol]
# Defines the default package separator symbol. The default is a dot.
#
# [Topic Type] Prototype Enders: [symbol] [symbol] ...
# When defined, Natural Docs will attempt to get a prototype from the code
# immediately following the topic type. It stops when it reaches one of
# these symbols. Use \n for line breaks.
#
# Line Extender: [symbol]
# Defines the symbol that allows a prototype to span multiple lines if
# normally a line break would end it.
#
# Enum Values: [global|under type|under parent]
# Defines how enum values are referenced. The default is global.
# global - Values are always global, referenced as 'value'.
# under type - Values are under the enum type, referenced as
# 'package.enum.value'.
# under parent - Values are under the enum's parent, referenced as
# 'package.value'.
#
# Perl Package: [perl package]
# Specifies the Perl package used to fine-tune the language behavior in ways
# too complex to do in this file.
#
#------------------------------------------------------------------------------
# For full language support only:
#
# Full Language Support: [perl package]
# Specifies the Perl package that has the parsing routines necessary for full
# language support.
#
#-------------------------------------------------------------------------------
# The following languages are defined in the main file, if you'd like to alter
# them:
#
# Text File, Shebang Script, C/C++, C#, Java, JavaScript, Perl, Python,
# PHP, SQL, Visual Basic, Pascal, Assembly, Ada, Tcl, Ruby, Makefile,
# ActionScript, ColdFusion, R, Fortran
# If you add a language that you think would be useful to other developers
# and should be included in Natural Docs by default, please e-mail it to
# languages [at] naturaldocs [dot] org.
Format: 1.52
# This is the Natural Docs topics file for this project. If you change anything
# here, it will apply to THIS PROJECT ONLY. If you'd like to change something
# for all your projects, edit the Topics.txt in Natural Docs' Config directory
# instead.
# If you'd like to prevent keywords from being recognized by Natural Docs, you
# can do it like this:
# Ignore Keywords: [keyword], [keyword], ...
#
# Or you can use the list syntax like how they are defined:
# Ignore Keywords:
# [keyword]
# [keyword], [plural keyword]
# ...
#-------------------------------------------------------------------------------
# SYNTAX:
#
# Topic Type: [name]
# Alter Topic Type: [name]
# Creates a new topic type or alters one from the main file. Each type gets
# its own index and behavior settings. Its name can have letters, numbers,
# spaces, and these charaters: - / . '
#
# Plural: [name]
# Sets the plural name of the topic type, if different.
#
# Keywords:
# [keyword]
# [keyword], [plural keyword]
# ...
# Defines or adds to the list of keywords for the topic type. They may only
# contain letters, numbers, and spaces and are not case sensitive. Plural
# keywords are used for list topics. You can redefine keywords found in the
# main topics file.
#
# Index: [yes|no]
# Whether the topics get their own index. Defaults to yes. Everything is
# included in the general index regardless of this setting.
#
# Scope: [normal|start|end|always global]
# How the topics affects scope. Defaults to normal.
# normal - Topics stay within the current scope.
# start - Topics start a new scope for all the topics beneath it,
# like class topics.
# end - Topics reset the scope back to global for all the topics
# beneath it.
# always global - Topics are defined as global, but do not change the scope
# for any other topics.
#
# Class Hierarchy: [yes|no]
# Whether the topics are part of the class hierarchy. Defaults to no.
#
# Page Title If First: [yes|no]
# Whether the topic's title becomes the page title if it's the first one in
# a file. Defaults to no.
#
# Break Lists: [yes|no]
# Whether list topics should be broken into individual topics in the output.
# Defaults to no.
#
# Can Group With: [type], [type], ...
# Defines a list of topic types that this one can possibly be grouped with.
# Defaults to none.
#-------------------------------------------------------------------------------
# The following topics are defined in the main file, if you'd like to alter
# their behavior or add keywords:
#
# Generic, Class, Interface, Section, File, Group, Function, Variable,
# Property, Type, Constant, Enumeration, Event, Delegate, Macro,
# Database, Database Table, Database View, Database Index, Database
# Cursor, Database Trigger, Cookie, Build Target
# If you add something that you think would be useful to other developers
# and should be included in Natural Docs by default, please e-mail it to
# topics [at] naturaldocs [dot] org.
Topic Type: Private Member
Plural: Private Members
Keywords:
privatemember
Topic Type: Private Function
Plural: Private Functions
Keywords:
privatefunction
Alter Topic Type: Event
Scope: Always global
......@@ -165,7 +165,7 @@ Candy.Core = function(self, Strophe, $) {
_addNamespaces();
// Connect to BOSH/Websocket service
//_connection = new Strophe.Connection(_service);
_connection = new Openfire.Connection(_service);
_connection = new Openfire.Connection(_service); // BAO
_connection.rawInput = self.rawInput.bind(self);
_connection.rawOutput = self.rawOutput.bind(self);
// set caps node
......
# Colors
Send and receive colored messages.
This plugin uses an own format of messages (`foobar` becomes e.g. `|c:1|foobar`).
If you'd like to use XHTML for formatting messages (this ensures third-party clients could also
display it properly), please use [colors-html](https://github.com/candy-chat/candy-plugins/tree/master/colors-xhtml).
![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) {
_numColors = numColors ? numColors : 8;
self.applyTranslations();
$(Candy).on('candy:view.message.before-send', function(e, args) {
if(_currentColor > 0 && $.trim(args.message) !== '') {
args.message = '|c:'+ _currentColor +'|' + args.message;
}
});
$(Candy).on('candy:view.message.before-render', function(e, args) {
args.templateData.message = args.templateData.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).on('candy:core.chat.connection', function(obj, data)
{
switch(data.status)
{
case Strophe.Status.CONNECTED:
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');
nickname = Strophe.escapeNode(Candy.Core.getUser().getNick());
GetWorkgroups();
break;
}
return true;
});
}
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 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);
if (CandyShop.OfMeet)
{
CandyShop.OfMeet.showOfMeet(roomJid);
} else {
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));
# Notifications
Send HTML5 Notifications when a message is received and the window is not in focus. This only works with webkit browsers.
## Usage
To enable *Notifications* you have to include its JavaScript code and stylesheet:
```HTML
<script type="text/javascript" src="candyshop/notifications/candy.js"></script>
```
Call its `init()` method after Candy has been initialized:
```JavaScript
Candy.init('/http-bind/');
CandyShop.Notifications.init();
Candy.Core.connect();
```
It is possible to configure the Plugin.
```JavaScript
CandyShop.Notifications.init({
notifyNormalMessage: false, // Send a notification for every message. Defaults to false
notifyPersonalMessage: true, // Send a notification if the user is mentioned. (Requires NotfiyMe Plugin) Defaults to true
closeTime: 3000 // Close notification after X milliseconds. Zero means it doesn't close automaticly. Defaults to 3000
});
```
\ No newline at end of file
/*
* HTML5 Notifications
* @version 1.0
* @author Jonatan Männchen <jonatan@maennchen.ch>
* @author Melissa Adamaitis <madamei@mojolingo.com>
*
* Notify user if new messages come in.
*/
var CandyShop = (function(self) { return self; }(CandyShop || {}));
CandyShop.Notifications = (function(self, Candy, $) {
/** Object: _options
* Options for this plugin's operation
*
* Options:
* (Boolean) notifyNormalMessage - Notification on normalmessage. Defaults to false
* (Boolean) notifyPersonalMessage - Notification for private messages. Defaults to true
* (Boolean) notifyMention - Notification for mentions. Defaults to true
* (Integer) closeTime - Time until closing the Notification. (0 = Don't close) Defaults to 3000
* (String) title - Title to be used in notification popup. Set to null to use the contact's name.
* (String) icon - Path to use for image/icon for notification popup.
*/
var _options = {
notifyNormalMessage: false,
notifyPersonalMessage: true,
notifyMention: true,
closeTime: 3000,
title: null,
icon: window.location.origin + '/' + Candy.View.getOptions().assets + '/img/favicon.png'
};
/** Function: init
* Initializes the notifications plugin.
*
* Parameters:
* (Object) options - The options to apply to this plugin
*
* @return void
*/
self.init = function(options) {
// apply the supplied options to the defaults specified
$.extend(true, _options, options);
// Just init if notifications are supported
if (window.Notification) {
// Setup Permissions (has to be kicked on with some user-events)
jQuery(document).one('click keydown', self.setupPermissions);
// Add Listener for Notifications
$(Candy).on('candy:view.message.after-show', self.handleOnShow);
}
};
/** Function: checkPermissions
* Check if the plugin has permission to send notifications.
*
* @return boid
*/
self.setupPermissions = function() {
// Check if permissions is given
if (window.Notification !== 0) { // 0 is PERMISSION_ALLOWED
// Request for it
window.Notification.requestPermission();
}
};
/** Function: handleOnShow
* Descriptions
*
* Parameters:
* (Array) args
*
* @return void
*/
self.handleOnShow = function(e, args) {
// Check if window has focus, so no notification needed
if (!document.hasFocus()) {
if(_options.notifyNormalMessage ||
(self.mentionsMe(args.message) && _options.notifyMention) ||
(_options.notifyPersonalMessage && Candy.View.Pane.Chat.rooms[args.roomJid].type === 'chat')) {
// Create the notification.
var title = !_options.title ? args.name : _options.title ,
notification = new window.Notification(title, {
icon: _options.icon,
body: args.message
});
// Close it after 3 Seconds
if(_options.closeTime) {
window.setTimeout(function() { notification.close(); }, _options.closeTime);
}
}
}
};
self.mentionsMe = function(message) {
var message = message.toLowerCase(),
nick = Candy.Core.getUser().getNick().toLowerCase(),
cid = Strophe.getNodeFromJid(Candy.Core.getUser().getJid()).toLowerCase(),
jid = Candy.Core.getUser().getJid().toLowerCase();
if (message.indexOf(nick) === -1 &&
message.indexOf(cid) === -1 &&
message.indexOf(jid) === -1) {
return false;
}
return true;
};
return self;
}(CandyShop.Notifications || {}, Candy, jQuery));
# Notify me plugin
This plugin will notify users when their names are mentioned and prefixed with a specific token
### Usage
<script type="text/javascript" src="path_to_plugins/notifyme/candy.js"></script>
<link rel="stylesheet" type="text/css" href="path_to_plugins/notifyme/candy.css" />
...
CandyShop.NotifyMe.init();
### Configuration options
`nameIdentifier` - String - The identifier to look for in a string. Defaults to `'@'`
`playSound` - Boolean - Whether to play a sound when the username is mentioned. Defaults to `true`
`highlightInRoom` - Boolean - Whether to highlight the username when it is mentioned. Defaults to `true`
### Example configurations
// Highlight my name when it's prefixed with a '+'
CandyShop.NotifyMe.init({
nameIdentifier: '+',
playSound: false
});
// Highlight and play a sound if my name is prefixed with a '-'
CandyShop.NotifyMe.init({
nameIdentifier: '-'
});
\ No newline at end of file
.candy-notifyme-highlight {
background: #FFFF00;
}
\ No newline at end of file
/** File: candy.js
* Candy - Chats are not dead yet.
*
* Authors:
* - Troy McCabe <troy.mccabe@geeksquad.com>
*
* Copyright:
* (c) 2012 Geek Squad. All rights reserved.
*/
var CandyShop = (function(self) { return self; }(CandyShop || {}));
/** Class: CandyShop.NotifyMe
* Notifies with a sound and highlights the text in the chat when a nick is called out
*/
CandyShop.NotifyMe = (function(self, Candy, $) {
/** Object: _options
* Options for this plugin's operation
*
* Options:
* (String) nameIdentifier - Prefix to append to a name to look for. '@' now looks for '@NICK', '' looks for 'NICK', etc. Defaults to '@'
* (Boolean) playSound - Whether to play a sound when identified. Defaults to true
* (Boolean) highlightInRoom - Whether to highlight the name in the room. Defaults to true
*/
var _options = {
nameIdentifier: '@',
playSound: true,
highlightInRoom: true
};
/** Function: init
* Initialize the NotifyMe plugin
* Bind to beforeShow, play sound and higlight if specified
*
* Parameters:
* (Object) options - The options to apply to this plugin
*/
self.init = function(options) {
// apply the supplied options to the defaults specified
$.extend(true, _options, options);
// get the nick from the current user
var nick = Candy.Core.getUser().getNick();
// make it what is searched
// search for <identifier>name in the whole message
var searchTerm = _options.nameIdentifier + nick;
// bind to the beforeShow event
$(Candy).on('candy:view.message.before-show', function(e, args) {
var searchRegExp = new RegExp('^(.*)(\s?' + searchTerm + ')', 'ig');
// if it's in the message and it's not from me, do stuff
// I wouldn't want to say 'just do @{MY_NICK} to get my attention' and have it knock...
if (searchRegExp.test(args.message) && args.name != nick) {
// play the sound if specified
if (_options.playSound) {
Candy.View.Pane.Chat.Toolbar.playSound();
}
// Save that I'm mentioned in args
args.forMe = true;
}
return args.message;
});
// bind to the beforeShow event
$(Candy).on('candy:view.message.before-render', function(e, args) {
var searchRegExp = new RegExp('^(.*)(\s?' + searchTerm + ')', 'ig');
// if it's in the message and it's not from me, do stuff
// I wouldn't want to say 'just do @{MY_NICK} to get my attention' and have it knock...
if (searchRegExp.test(args.templateData.message) && args.templateData.name != nick) {
// highlight if specified
if (_options.highlightInRoom) {
args.templateData.message = args.templateData.message.replace(searchRegExp, '$1<span class="candy-notifyme-highlight">' + searchTerm + '</span>');
}
}
});
};
return self;
}(CandyShop.NotifyMe || {}, Candy, jQuery));
\ No newline at end of file
#video-modal {
background: #eee;
width: 1024px;
height: 768px;
padding: 20px 5px;
color: #333;
font-size: 16px;
position: fixed;
left: 0px;
top: 0px;
margin-left: 0px;
margin-top: 0px;
text-align: center;
display: none;
z-index: 100;
border: 5px solid #888;
border-radius: 5px;
box-shadow: 0 0 5px black;
}
#video-modal-overlay {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
z-index: 90;
background-image: url(/ofmeet/candy/res/img/overlay.png);
}
#video-modal iframe {
width: 100%;
height: 100%;
}
#video-modal video-modal-body {
width: 100%;
height: 100%;
}
#video-modal .close {
position: absolute;
right: 0;
display: none;
padding: 0 5px;
margin: -17px 3px 0 0;
color: #999;
border-radius: 3px;
}
#video-modal .close:hover {
color: #333;
background-color: #aaa;
}
\ No newline at end of file
var CandyShop = (function(self) { return self; }(CandyShop || {}));
CandyShop.OfMeet = (function(self, Candy, $) {
self.init = function() {
var html = '<li id="ofmeet-control-icon" data-tooltip="Openfire Meetings"><img id="ofmeet-control" src="/ofmeet/candy/candyshop/ofmeet/webcam.png"></span></li>';
$('#emoticons-icon').after(html);
$('#ofmeet-control-icon').click(function(event)
{
var roomJid = Candy.View.getCurrent().roomJid;
Candy.Core.Action.Jabber.Room.Leave(roomJid);
self.showOfMeet(roomJid);
});
var html2 = '<div id="video-modal"><a id="video-modal-cancel" class="close" href="#">×</a><span id="video-modal-body"></span></div><div id="video-modal-overlay"></div>';
$(html2).appendTo("body");
$("#video-modal").css("height", window.innerHeight - 40);
$("#video-modal").css("width", window.innerWidth - 20);
$("#video-modal-overlay").hide();
$(window).resize(function ()
{
$("#video-modal").css("height", window.innerHeight - 40);
$("#video-modal").css("width", window.innerWidth - 20);
});
};
self.showOfMeet = function(roomJid)
{
$("#video-modal-cancel").show().click(function(e)
{
$("#video-modal").fadeOut("fast", function() {
$("#video-modal-body").text("");
$("#video-modal-overlay").hide();
});
e.preventDefault();
window.location.reload();
});
var room = Strophe.getNodeFromJid(roomJid);
$("#video-modal").stop(false, true);
$("#video-modal").fadeIn("fast");
$("#video-modal-body").html('<iframe id="ofmeet" src="/ofmeet/index.html?r=' + room + '"></iframe>');
$("#video-modal-overlay").show();
return true;
};
return self;
}(CandyShop.OfMeet || {}, Candy, jQuery));
Copyright (C) 2014 Mojo Lingo LLC
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.
# Slash Commands Plugin
A plugin to provide a command-line interface to Candy actions.
## Examples
To use any of the following, just type them into the chat input text area. Note that any commands which are room-specific (`/topic`, `/kick`, etc) will work on/for the current room only.
### Room Management
* `/join room [password]` - Joins the MUC room "room" with an optional password
* `/part` - Leaves the current MUC room
* `/clear` - Clears the scrollback in the current room
* `/topic This will be the new topic` - Sets the topic for the current room to "This will be the new topic"
### Presence
* `/available`
* `/away`
* `/dnd` - Do Not Disturb
## Todo
* `/kick username` - Ejects the user "username" from the current room. Must be a MUC admin for this to work
* `/invite username[@domain]` - Invites the user "username" to the current room. If the optional domain is not provided, it is assumed to be the same domain as the current user
## Configuration
For the commands that work on rooms (such as `/join`) you can specify the default domain to be suffixed to the room name:
```JavaScript
CandyShop.SlashCommands.defaultConferenceDomain = 'muc.example.com';
```
If unset, it defaults to the user's XMPP domain prefixed with "conference."
## Usage
Include the JavaScript file::
```HTML
<script type="text/javascript" src="candyshop/slash-commands/slash-commands.js"></script>
```
To enable the Slash Commands Plugin, just add one of the ´init´ methods to your bootstrap:
```JavaScript
CandyShop.SlashCommands.init();
```
/** File: candy.js
* Make several Candy actions accessible via the message box when prefixed with a slash "/"
*
* Authors:
* - Ben Klang <bklang@mojolingo.com>
*
* Contributors:
* - Troy McCabe <troy.mccabe@geeksquad.com>
* - Jonatan Männchen <jonatan.maennchen@amiadogroup.com>
*
* Copyright:
* - (c) 2014 Mojo Lingo LLC. All rights reserved.
*/
var CandyShop = (function(self) { return self; }(CandyShop || {}));
CandyShop.SlashCommands = (function(self, Candy, $) {
/** Object: about
* About SlashCommands plugin
*
* Contains:
* (String) name - Candy Plugin SlashCommands
* (Float) version - andy Plugin Available Rooms version
*/
self.about = {
name: 'Candy Plugin SlashCommands',
version: '0.1.0'
};
self.commands = [
'join',
'part',
'clear',
'topic',
'available',
'away',
'dnd',
];
self.defaultConferenceDomain = null;
/** Function: init
* Initializes the Slash Commands plugin with the default settings.
*/
self.init = function(){
$(Candy).on('candy:view.connection.status-5', function() {
// When connected to the server, default the conference domain if unspecified
if (!self.defaultConferenceDomain) {
self.defaultConferenceDomain = "@conference." + Candy.Core.getConnection().domain;
}
// Ensure we have a leading "@"
if (self.defaultConferenceDomain.indexOf('@') == -1) {
self.defaultConferenceDomain = "@" + self.defaultConferenceDomain;
}
});
$(Candy).bind('candy:view.message.before-send', function(e, args) {
try {
// (strip colors)
var input = args.message.replace(/\|c:\d+\|/, '');
if (input[0] == '/') {
var match = input.match(/^\/([^\s]+)(?:\s+(.*))?$/m);
if (match !== null) {
var command = match[1];
var data = match[2];
// Match only whitelisted commands
if ($.inArray(command, self.commands) != -1) {
self[command](data);
} else {
// TODO: Better way to notify the user of the invalid command
alert("Invalid command: " + command);
}
}
args.message = '';
}
} catch (ex) {
// Without an exception catcher, the page will reload and the user will be logged out
Candy.Core.log(ex);
}
});
};
/** Function: join
* Joins a room
*
* Parameters:
* (String) args The name of the room and the optional password, separated by a space
*/
self.join = function(args) {
args = args.split(' ');
var room = args[0];
var password = args[1];
if(typeof room != 'undefined' && room !== '') {
if(room.indexOf("@") == -1) {
room += self.defaultConferenceDomain;
}
if (typeof password !== 'undefined' && password !== '') {
Candy.Core.Action.Jabber.Room.Join(room, password);
} else {
Candy.Core.Action.Jabber.Room.Join(room);
}
}
};
/** Function: part
* Exits the current chat room
*
*/
self.part = function() {
Candy.Core.Action.Jabber.Room.Leave(self.currentRoom());
};
/** Function: topic
* Sets the topic (subject) for the current chat room
*
* Parameters:
* (String) topic The new topic for the room
*/
self.topic = function(topic) {
Candy.Core.Action.Jabber.Room.Admin.SetSubject(self.currentRoom(), topic);
};
/** Function: clear
* Clear the current room's scrollback
*/
self.clear = function() {
$('.room-pane:visible').find('.message-pane').empty();
};
/** Function: available
* Change the current user's XMPP status to "available" with an optional message
* Parameters:
* (String) message Optional message to set with availability
*/
self.available = function(message) {
// TODO: The message field is currently unsupported by Candy.Core.Action.Jabber.Presence
Candy.Core.Action.Jabber.Presence();
};
/** Function: away
* Change the current user's XMPP status to "away" with an optional message
* Parameters:
* (String) message Optional message to set with availability
*/
self.away = function(message) {
// TODO: The message field is currently unsupported by Candy.Core.Action.Jabber.Presence
Candy.Core.Action.Jabber.Presence(null, $build('show', 'away'));
};
/** Function: dnd
* Change the current user's XMPP status to "dnd" with an optional message
* Parameters:
* (String) message Optional message to set with availability
*/
self.dnd = function(message) {
// TODO: The message field is currently unsupported by Candy.Core.Action.Jabber.Presence
Candy.Core.Action.Jabber.Presence(null, $build('show', 'dnd'));
};
/** Function: currentRoom
* Helper function to get the current room
*/
self.currentRoom = function() {
return Candy.View.getCurrent().roomJid;
};
return self;
}(CandyShop.SlashCommands || {}, Candy, jQuery));
#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 li 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'] = '<li><small><abbr title="{{time}}">{{time}}</abbr></small><div class="adminmessage"><span class="label">{{sender}}</span><span class="spacer">▸</span>{{subject}} {{message}}</div></li>';
Candy.View.Template.Chat['infoMessage'] = '<li><small><abbr title="{{time}}">{{time}}</abbr></small><div class="infomessage"><span class="spacer">•</span>{{subject}} {{message}}</div></li>';
Candy.View.Template.Room['subject'] = '<li><small><abbr title="{{time}}">{{time}}</abbr></small><div class="subject"><span class="label">{{roomName}}</span><span class="spacer">▸</span>{{_roomSubject}} {{subject}}</div></li>';
Candy.View.Template.Message['item'] = '<li><small><abbr title="{{time}}">{{time}}</abbr></small><div><a class="label" href="#" class="name">{{displayName}}</a><span class="spacer">▸</span>{{{message}}}</div></li>';
Candy.Util.localizedTime = function(dateTime) {
if (dateTime === undefined) {
return undefined;
}
var date = Candy.Util.iso8601toDate(dateTime);
return date.format($.i18n._('isoDateTime'));
};
var applyTimeago = function(e, args) {
var $elem = args.element ? $('abbr', args.element) : $('abbr');
$elem.timeago();
};
$(Candy).on('candy:view.message.after-show', applyTimeago);
$(Candy).on('candy:view.room.after-subject-change', applyTimeago);
// the following handlers run timeago() on all <abbr> tags
$(Candy).on('candy:core.presence.room', applyTimeago);
$(Candy).on('candy:view.chat.admin-message', applyTimeago);
};
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
# Typing Notifications
A plugin for Candy Chat to enable typing notifications to show up. Fully compatible with the lefttabs plugin.
## Todo
It would be nice to extend this to groupchat as well. Currenly only working for private chat. (Simpler.)
![Typing Notifications - Regular](screenshot1.png)
![Typing Notifications - Left Tabs](screenshot2.png)
## Usage
Include the JavaScript and CSS files:
```HTML
<script type="text/javascript" src="candyshop/typingnotifications/typingnotifications.js"></script>
<link rel="stylesheet" type="text/css" href="candyshop/typingnotifications/typingnotifications.css" />
```
To enable this typing notifications plugin, add its `init` method after you `init` Candy, but before `Candy.connect()`:
```JavaScript
CandyShop.TypingNotifications.init();
```
/**
* TypingNotifications CSS
*
* Author: Melissa Adamaitis <madamei@mojolingo.com>
*/
.message-pane-wrapper {
padding-bottom: 5px;
}
.typing-notification-area {
position: fixed;
bottom: 34px;
color: #ADADAD;
font-style: italic;
margin-left: 7px;
font-size: 0.8em;
}
/** File: typingnotifications.js
* Candy Plugin Typing Notifications
* Author: Melissa Adamaitis <madamei@mojolingo.com>
*/
var CandyShop = (function(self) { return self; }(CandyShop || {}));
CandyShop.TypingNotifications = (function(self, Candy, $) {
/** Object: about
*
* Contains:
* (String) name - Candy Plugin Typing Notifications
* (Float) version - Candy Plugin Typing Notifications
*/
self.about = {
name: 'Candy Plugin Typing Notifications',
version: '1.0'
};
/**
* Initializes the Typing Notifications plugin with the default settings.
*/
self.init = function(){
// After a room is added, make sure to tack on a little div that we can put the typing notification into.
$(Candy).on('candy:view.private-room.after-open', function(ev, obj){
self.addTypingNotificationDiv(obj);
});
// When a typing notification is recieved, display it.
$(Candy).on('candy:core.message.chatstate', function(ev, obj) {
var pane, chatstate_string;
pane = Candy.View.Pane.Room.getPane(obj.roomJid);
chatstate_string = self.getChatstateString(obj.chatstate, obj.name);
$(pane).find('.typing-notification-area').html(chatstate_string);
return true;
});
};
self.getChatstateString = function(chatstate, name) {
switch (chatstate) {
case 'paused': return name + ' has entered text.';
case 'inactive': return name + ' is away from the window.';
case 'composing': return name + ' is composing...';
case 'gone': return name + ' has closed the window.';
default: return '';
}
};
self.addTypingNotificationDiv = function(obj){
var pane_html = Candy.View.Pane.Room.getPane(obj.roomJid),
typing_notification_div_html = '<div class="typing-notification-area"></div>';
$(pane_html).find('.message-form-wrapper').append(typing_notification_div_html);
};
return self;
}(CandyShop.TypingNotifications || {}, Candy, jQuery));
......@@ -31,7 +31,7 @@
* A new Openfire object.
*/
Openfire.Connection = function(url, iFrameId)
Openfire.Connection = function(url)
{
if (!window.WebSocket)
{
......@@ -84,32 +84,7 @@ Openfire.Connection = function(url, iFrameId)
this[k] = new F();
this[k].init(this);
}
}
var iframe = document.getElementById(iFrameId);
if (iframe)
{
this.iframeWin = iframe.contentWindow;
var that = this;
window.addEventListener('message', function(event)
{
that._ws.send(event.data);
});
console.log("Openfire Connection in parent mode");
} else
if (window.parent && window != window.parent)
{
console.log("Openfire Connection in child mode");
window.addEventListener('message', this._onmessage.bind(this));
} else {
console.log("Openfire Connection in normal mode");
}
}
}
Openfire.Connection.prototype = {
......@@ -214,17 +189,13 @@ Openfire.Connection.prototype = {
this._changeConnectStatus(Strophe.Status.CONNECTING, null);
this.url = this.protocol + "//" + this.host + "/ofmeetws/server?username=" + this.username + "&password=" + this.pass + "&resource=" + this.resource;
this._ws = new WebSocket(this.url, "xmpp");
if (window.parent && window == window.parent)
{
this._ws = new WebSocket(this.url, "xmpp");
this._ws.onopen = this._onopen.bind(this);
this._ws.onmessage = this._onmessage.bind(this);
this._ws.onclose = this._onclose.bind(this);
window.openfireWebSocket = this;
}
this._ws.onopen = this._onopen.bind(this);
this._ws.onmessage = this._onmessage.bind(this);
this._ws.onclose = this._onclose.bind(this);
window.openfireWebSocket = this;
this.jid = this.jid.indexOf("@") < 0 ? this.resource + "@" + this.jid : this.jid;
......@@ -249,18 +220,13 @@ Openfire.Connection.prototype = {
try {
this._changeConnectStatus(Strophe.Status.CONNECTED, null);
if (this.iframeWin)
{
this.iframeWin.connection._onopen();
}
} catch (e) {
throw Error("User connection callback caused an exception: " + e);
}
if (this._ws) this.interval = setInterval (function() {window.openfireWebSocket.sendRaw(" ")}, 10000 );
this.interval = setInterval (function() {window.openfireWebSocket.sendRaw(" ")}, 10000 );
},
/** Function: attach
......@@ -360,7 +326,7 @@ Openfire.Connection.prototype = {
sendRaw: function(xml) {
if(!this.connected) {
if(!this.connected || this._ws == null) {
throw Error("Not connected, cannot send packets.");
}
......@@ -370,9 +336,7 @@ Openfire.Connection.prototype = {
this.rawOutput(xml);
}
if (this._ws) this._ws.send(xml);
if (window.parent && window != window.parent) parent.postMessage(xml, '*');
this._ws.send(xml);
},
......@@ -391,7 +355,7 @@ Openfire.Connection.prototype = {
send: function(elem)
{
if(!this.connected) {
if(!this.connected || this._ws == null) {
throw Error("Not connected, cannot send packets.");
}
......@@ -419,10 +383,7 @@ Openfire.Connection.prototype = {
}
this.rawOutput(toSend);
if (this._ws) this._ws.send(toSend);
if (window.parent && window != window.parent) parent.postMessage(toSend, '*');
this._ws.send(toSend);
},
/** Function: flush
......@@ -648,14 +609,14 @@ Openfire.Connection.prototype = {
disconnect: function(reason) {
if(!this.connected) {
if(!this.connected || this._ws == null) {
return;
}
this._changeConnectStatus(Strophe.Status.DISCONNECTING, reason);
Strophe.info("Disconnect was called because: " + reason);
if (this._ws) this._ws.close();
this._ws.close();
},
......@@ -766,13 +727,10 @@ Openfire.Connection.prototype = {
this.addTimeds = [];
this.addHandlers = [];
if (this._ws)
if(this._ws.readyState != this._ws.CLOSED)
{
if(this._ws.readyState != this._ws.CLOSED)
{
this._ws.close();
}
}
this._ws.close();
}
},
/**
......@@ -782,12 +740,7 @@ Openfire.Connection.prototype = {
*/
_onmessage: function(packet)
{
if (this.iframeWin)
{
this.iframeWin.postMessage(packet.data, "*");
}
{
var elem;
try {
......
<html>
<head>
<title>Fastpath</title>
<script src="js/jquery.min.js"></script>
<script src="js/md5.js"></script>
<script src="js/base64.js"></script>
<script src="js/strophe.js"></script>
<script src="js/strophe.openfire.js"></script>
<script src="js/fastpath.js"></script>
<link rel="shortcut icon" href="favicon.ico"/>
<link rel="stylesheet" href="css/fastpath.css">
</head>
<body>
<button onclick="joinWorkgroup('programming', {username: 'John Doe', email: 'john@large', question: 'what is the meaning of life'})" id="fastpath">Offline</button>
</body>
</html>
// This code was written by Tyler Akins and has been placed in the
// public domain. It would be nice if you left this header intact.
// Base64 code from Tyler Akins -- http://rumkin.com
var Base64 = (function () {
var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var obj = {
/**
* Encodes a string in base64
* @param {String} input The string to encode in base64.
*/
encode: function (input) {
var output = "";
var chr1, chr2, chr3;
var enc1, enc2, enc3, enc4;
var i = 0;
do {
chr1 = input.charCodeAt(i++);
chr2 = input.charCodeAt(i++);
chr3 = input.charCodeAt(i++);
enc1 = chr1 >> 2;
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
enc4 = chr3 & 63;
if (isNaN(chr2)) {
enc3 = enc4 = 64;
} else if (isNaN(chr3)) {
enc4 = 64;
}
output = output + keyStr.charAt(enc1) + keyStr.charAt(enc2) +
keyStr.charAt(enc3) + keyStr.charAt(enc4);
} while (i < input.length);
return output;
},
/**
* Decodes a base64 string.
* @param {String} input The string to decode.
*/
decode: function (input) {
var output = "";
var chr1, chr2, chr3;
var enc1, enc2, enc3, enc4;
var i = 0;
// remove all characters that are not A-Z, a-z, 0-9, +, /, or =
input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
do {
enc1 = keyStr.indexOf(input.charAt(i++));
enc2 = keyStr.indexOf(input.charAt(i++));
enc3 = keyStr.indexOf(input.charAt(i++));
enc4 = keyStr.indexOf(input.charAt(i++));
chr1 = (enc1 << 2) | (enc2 >> 4);
chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
chr3 = ((enc3 & 3) << 6) | enc4;
output = output + String.fromCharCode(chr1);
if (enc3 != 64) {
output = output + String.fromCharCode(chr2);
}
if (enc4 != 64) {
output = output + String.fromCharCode(chr3);
}
} while (i < input.length);
return output;
}
};
return obj;
})();
var connection = null;
var userForm = null;
$(document).ready(function ()
{
connection = new Openfire.Connection(window.location.protocol + "//" + window.location.host + '/http-bind/');
connection.resource = Math.random().toString(36).substr(2, 20);
connection.rawInput = function (data) { console.log('RECV: ' + data); };
connection.rawOutput = function (data) { console.log('SEND: ' + data); };
connection.connect(window.location.hostname, null, function (status)
{
if (status == Strophe.Status.CONNECTED)
{
console.log('connected');
connection.send($pres());
connection.addHandler(onMessage, 'http://jabber.org/protocol/workgroup', 'message');
$('#fastpath').html('Online');
} else {
console.log('status', status);
}
});
});
function joinWorkgroup(workgroup, form)
{
userForm = form;
if (connection != null && connection.connected)
{
var iq = $iq({to: workgroup + "@workgroup." + connection.domain, type: 'set'}).c('join-queue', {xmlns: 'http://jabber.org/protocol/workgroup'});
iq.c('queue-notifications').up();
iq.c('x', {xmlns: 'jabber:x:data', type: 'submit'});
var items = Object.getOwnPropertyNames(form)
for (var i=0; i< items.length; i++)
{
iq.c('field', {var: items[i]}).c('value').t(form[items[i]]).up().up();
}
iq.up();
connection.sendIQ(iq,
function (res) {
console.log('join workgroup ok', res);
},
function (err) {
console.log('join workgroup error', err);
}
);
}
}
function onMessage(message)
{
console.log('onMessage', message);
$(message).find('x').each(function()
{
var xmlns = $(this).attr("xmlns");
if (xmlns == "jabber:x:conference")
{
var roomJid = $(this).attr("jid");
window.location.href = "/ofmeet/?r=" + Strophe.getNodeFromJid(roomJid) + "&n=" + userForm.username + "&q=" + userForm.question;
}
});
return true;
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -31,7 +31,7 @@
* A new Openfire object.
*/
Openfire.Connection = function(url, iFrameId)
Openfire.Connection = function(url)
{
if (!window.WebSocket)
{
......@@ -84,32 +84,7 @@ Openfire.Connection = function(url, iFrameId)
this[k] = new F();
this[k].init(this);
}
}
var iframe = document.getElementById(iFrameId);
if (iframe)
{
this.iframeWin = iframe.contentWindow;
var that = this;
window.addEventListener('message', function(event)
{
that._ws.send(event.data);
});
console.log("Openfire Connection in parent mode");
} else
if (window.parent && window != window.parent)
{
console.log("Openfire Connection in child mode");
window.addEventListener('message', this._onmessage.bind(this));
} else {
console.log("Openfire Connection in normal mode");
}
}
}
Openfire.Connection.prototype = {
......@@ -214,17 +189,13 @@ Openfire.Connection.prototype = {
this._changeConnectStatus(Strophe.Status.CONNECTING, null);
this.url = this.protocol + "//" + this.host + "/ofmeetws/server?username=" + this.username + "&password=" + this.pass + "&resource=" + this.resource;
this._ws = new WebSocket(this.url, "xmpp");
if (window.parent && window == window.parent)
{
this._ws = new WebSocket(this.url, "xmpp");
this._ws.onopen = this._onopen.bind(this);
this._ws.onmessage = this._onmessage.bind(this);
this._ws.onclose = this._onclose.bind(this);
window.openfireWebSocket = this;
}
this._ws.onopen = this._onopen.bind(this);
this._ws.onmessage = this._onmessage.bind(this);
this._ws.onclose = this._onclose.bind(this);
window.openfireWebSocket = this;
this.jid = this.jid.indexOf("@") < 0 ? this.resource + "@" + this.jid : this.jid;
......@@ -249,18 +220,13 @@ Openfire.Connection.prototype = {
try {
this._changeConnectStatus(Strophe.Status.CONNECTED, null);
if (this.iframeWin)
{
this.iframeWin.connection._onopen();
}
} catch (e) {
throw Error("User connection callback caused an exception: " + e);
}
if (this._ws) this.interval = setInterval (function() {window.openfireWebSocket.sendRaw(" ")}, 10000 );
this.interval = setInterval (function() {window.openfireWebSocket.sendRaw(" ")}, 10000 );
},
/** Function: attach
......@@ -360,7 +326,7 @@ Openfire.Connection.prototype = {
sendRaw: function(xml) {
if(!this.connected) {
if(!this.connected || this._ws == null) {
throw Error("Not connected, cannot send packets.");
}
......@@ -370,9 +336,7 @@ Openfire.Connection.prototype = {
this.rawOutput(xml);
}
if (this._ws) this._ws.send(xml);
if (window.parent && window != window.parent) parent.postMessage(xml, '*');
this._ws.send(xml);
},
......@@ -391,7 +355,7 @@ Openfire.Connection.prototype = {
send: function(elem)
{
if(!this.connected) {
if(!this.connected || this._ws == null) {
throw Error("Not connected, cannot send packets.");
}
......@@ -419,10 +383,7 @@ Openfire.Connection.prototype = {
}
this.rawOutput(toSend);
if (this._ws) this._ws.send(toSend);
if (window.parent && window != window.parent) parent.postMessage(toSend, '*');
this._ws.send(toSend);
},
/** Function: flush
......@@ -648,14 +609,14 @@ Openfire.Connection.prototype = {
disconnect: function(reason) {
if(!this.connected) {
if(!this.connected || this._ws == null) {
return;
}
this._changeConnectStatus(Strophe.Status.DISCONNECTING, reason);
Strophe.info("Disconnect was called because: " + reason);
if (this._ws) this._ws.close();
this._ws.close();
},
......@@ -766,13 +727,10 @@ Openfire.Connection.prototype = {
this.addTimeds = [];
this.addHandlers = [];
if (this._ws)
if(this._ws.readyState != this._ws.CLOSED)
{
if(this._ws.readyState != this._ws.CLOSED)
{
this._ws.close();
}
}
this._ws.close();
}
},
/**
......@@ -782,12 +740,7 @@ Openfire.Connection.prototype = {
*/
_onmessage: function(packet)
{
if (this.iframeWin)
{
this.iframeWin.postMessage(packet.data, "*");
}
{
var elem;
try {
......
<html itemscope itemtype="http://schema.org/Product" prefix="og: http://ogp.me/ns#" xmlns="http://www.w3.org/1999/html">
<head>
<script src="/ofmeet/config"></script><!-- BAO -->
<script src="libs/strophe/strophe.js?v=1"></script><!-- BAO -->
<script src="libs/strophe/strophe.openfire.js?v=1"></script><!-- BAO -->
<script>
var authenticatedUser = false;
var connection = null;
window.addEventListener("load", function()
{
connection = new Openfire.Connection(config.bosh, "ofmeet");
var jid = config.hosts.domain;
if (config.userName)
{
jid = config.userName + "@" + config.hosts.domain;
authenticatedUser = true;
}
connection.connect(jid, null, function (status, msg)
{
console.log('Strophe status changed to', Strophe.getStatusString(status));
if (status === Strophe.Status.CONNECTED) {
} else if (status === Strophe.Status.CONNFAIL) {
} else if (status === Strophe.Status.DISCONNECTED) {
} else if (status === Strophe.Status.AUTHFAIL) {
}
});
});
window.addEventListener("unload", function ()
{
connection.disconnect();
});
</script>
</head>
<body>
<iframe id="ofmeet" src="index.html?r=dele123" style="width: 100%; height: 100%;"></iframe>
</body>
</html>
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