Интерфейс на основе candy

master
Alexander Yakovlev 7 years ago
commit 6c69cdffcc

@ -0,0 +1,30 @@
# Chat recall plugin
This plugin will allow the user to navigate through historical messages they've typed using the up and down keys
## Usage
Include the JavaScript file:
```HTML
<script type="text/javascript" src="path_to_plugins/chatrecall/candy.js"></script>
```
Call its `init()` method after Candy has been initialized:
```JavaScript
Candy.init('/http-bind/');
CandyShop.ChatRecall.init();
Candy.Core.connect();
```
## Configuration options
`messagesToKeep` - Integer - The number of messages to store in history. Defaults to 10
## Example configurations
```JavaScript
// Store 25 messages for the user to scroll through
CandyShop.ChatRecall.init({
messagesToKeep: 25
});
```

@ -0,0 +1,116 @@
/** File: candy.js
* Candy - Chats are not dead yet.
*
* Authors:
* - Troy McCabe <troy.mccabe@geeksquad.com>
*
* Copyright:
* (c) 2012 Geek Squad. All rights reserved.
*/
/* global document, Candy, jQuery */
var CandyShop = (function(self) { return self; }(CandyShop || {}));
/** Class: CandyShop.ChatRecall
* Remembers the last {x} messages the user types and allows up and down key recollection
*/
CandyShop.ChatRecall = (function(self, Candy, $) {
/** Object: _options
* Options:
* (Integer) messagesToKeep - How many messages to keep in memory
*/
var _options = {
messagesToKeep: 10
};
/** Array: _messages
* The messages that the user sent
*/
var _messages = [];
/** Integer: _currentMessageIndex
* The current index of the message the user went back to
*/
var _currentMessageIndex = 0;
/** Function: init
* Initialize the ChatRecall plugin
*
* Parameters:
* (Object) options - An options packet to apply to this plugin
*/
self.init = function(options) {
// apply the supplied options to the defaults specified
$.extend(true, _options, options);
// Listen for keydown in the field
$(document).on('keydown', 'input[name="message"]', function(e) {
// switch on the key code
switch (e.which) {
// up arrow
case 38:
// if we're under the cap of max messages and the cap of the messages currently stored, recall
if (_currentMessageIndex < _options.messagesToKeep && _currentMessageIndex < _messages.length) {
// if we're at blank (the bottom), move it up to 0
if (_currentMessageIndex === -1) {
_currentMessageIndex++;
}
// set the value to what we stored
$(this).val(_messages[_currentMessageIndex]);
// if we're under the limits, go ahead and move the tracked position up
if (_currentMessageIndex < _options.messagesToKeep - 1 && _currentMessageIndex < _messages.length - 1) {
_currentMessageIndex++;
}
}
break;
// down arrow
case 40:
// if we're back to the bottom, clear the field
// else move it down
if (_currentMessageIndex === -1) {
$(this).val('');
} else {
// if we're at the cap already, move it down initially (don't want to have to hit it twice)
if (_currentMessageIndex === _options.messagesToKeep - 1 || _currentMessageIndex === _messages.length - 1) {
_currentMessageIndex--;
}
// set the value to the one that's stored
$(this).val(_messages[_currentMessageIndex]);
if (_currentMessageIndex > -1) {
// move the tracked position down
_currentMessageIndex--;
}
}
break;
}
});
// listen before send and add it to the stack
$(Candy).on('candy:view.message.before-send', function(e, data) {
// remove, in case there is the colors plugin, the |c:number| prefix
self.addMessage(data.message.replace(/\|c:\d+\|/i, ''));
});
};
/** Function: addMessage
* Add a message to the front of the stack
* This is stored New[0] -> Old[N]
*
* Parameters:
* (String) message - The message to store
*/
self.addMessage = function(message) {
// pop one off the end if it's too many
if (_messages.length === _options.messagesToKeep) {
_messages.pop();
}
// put the message at pos 0 and move everything else
_messages.unshift(message);
};
return self;
}(CandyShop.ChatRecall || {}, Candy, jQuery));

@ -0,0 +1,43 @@
# Colors using XHTML formatted messages
Send and receive colored messages.
This plugin is based on the [colors](https://github.com/candy-chat/candy-plugins/tree/master/colors) and, contrary
to the `colors` plugin, ensures that third party clients see the colors as well.
![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-xhtml/candy.js"></script>
<link rel="stylesheet" type="text/css" href="candyshop/colors-xhtml/candy.css" />
```
Call its `init()` method after Candy has been initialized:
```javascript
Candy.init('/http-bind/', {
view: {
// make sure you enabled XHTML in order to display it properly.
enableXHTML: true
}
});
// enable Colors-XHTML plugin (default: 8 colors)
CandyShop.ColorsXhtml.init();
Candy.Core.connect();
```
To enable less or more colors enable it with e.g.:
```javascript
var colors = [
'#333', '#fff'
// etc, use as many as you'd like to
];
CandyShop.ColorsXhtml.init(colors);
```
**Please note**: you can't use the `colors` and the `colors-xhtml` plugin together.

@ -0,0 +1,33 @@
#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;
}

@ -0,0 +1,96 @@
/* global Candy, jQuery */
var CandyShop = (function(self) { return self; }(CandyShop || {}));
CandyShop.ColorsXhtml = (function(self, Candy, $) {
var _numColors,
_currentColor = '',
_colors = [
'#333',
'#c4322b',
'#37991e',
'#1654c9',
'#66379b',
'#ba7318',
'#32938a',
'#9e2274'
];
self.init = function(colors) {
if(colors && colors.length) {
_colors = colors;
}
_numColors = _colors.length;
self.applyTranslations();
$(Candy).on('candy:view.message.before-send', function(e, args) {
if(_currentColor !== '' && $.trim(args.message) !== '') {
args.xhtmlMessage = '<span style="color:' + _currentColor + '">' + Candy.Util.Parser.escape(args.message) + '</span>';
}
});
if(Candy.Util.cookieExists('candyshop-colors-xhtml-current')) {
var color = Candy.Util.getCookie('candyshop-colors-xhtml-current');
if(_colors.indexOf(color) !== -1) {
_currentColor = color;
}
}
var html = '<li id="colors-control" data-tooltip="' + $.i18n._('candyshopColorsXhtmlMessagecolor') + '"><span style="color:' + _currentColor + ';background-color:' + _currentColor +'" id="colors-control-indicator"></span></li>';
$('#emoticons-icon').after(html);
$('#colors-control').click(function() {
CandyShop.ColorsXhtml.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 style="color:' + _colors[i] + ';background-color:' + _colors[i] + ';" data-color="' + _colors[i] + '"></span>' + colors;
}
content.html('<li class="colors">' + colors + '</li>');
content.find('span').click(function() {
_currentColor = $(this).attr('data-color');
$('#colors-control-indicator').attr('style', 'color:' + _currentColor + ';background-color:' + _currentColor);
Candy.Util.setCookie('candyshop-colors-xhtml-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].candyshopColorsXhtmlMessagecolor = v;
}
});
};
return self;
}(CandyShop.ColorsXhtml || {}, Candy, jQuery));

Binary file not shown.

After

Width:  |  Height:  |  Size: 816 B

@ -0,0 +1,29 @@
# 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>)`.

@ -0,0 +1,97 @@
#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;
}

@ -0,0 +1,87 @@
/* global Candy, jQuery */
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() {
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));

Binary file not shown.

After

Width:  |  Height:  |  Size: 816 B

@ -0,0 +1,78 @@
# Emphasis
Basic message formatting, with xhtml conversion, compatible with XEP-0071. Standard messages are converted to Textile-style formatting.
Textile, BBCode, and Html Variants are supported:
**bold**
```
*bold*
[b]bold[/b]
<b>bold</b>
<strong>bold</strong>
```
_italic_
```
_italic_
[i]italic[/i]
<i>italic</i>
<em>italic</em>
```
<ins>underlined</ins>
```
+underlined+
[u]underlined[/u]
<u>underlined</u>
<ins>underlined</ins>
```
~~strikethrough~~
```
-strikethrough-
[s]strikethrough[/s]
<s>strinkethough</s>
<del>strikethrough</del>
```
Textile can be escaped like so:
```
==-strikethrough-==
```
This plugin is compatible with colors-xhtml.
## Usage
Include the JavaScript file:
```HTML
<script type="text/javascript" src="candyshop/emphasis/candy.js"></script>
```
Call its `init()` method after Candy has been initialized:
```javascript
Candy.init('/http-bind/', {});
// enable basic textile/BBCode/Html handling
CandyShop.Emphasis.init();
Candy.Core.connect();
```
Optionally, different formats can be disabled.
```javascript
CandyShop.Emphasis.init({ textile: false, bbcode: true, html: true });
```
Or just
```javascript
CandyShop.Emphasis.init({ textile: false });
```

@ -0,0 +1,86 @@
var CandyShop = (function(self) { return self; }(CandyShop || {}));
CandyShop.Emphasis = (function(self, Candy, $) {
// textile, bbcode, old html, escaped old html, new html, escaped new html
var _styles = {
textile: [
{ plain: '==*bold*==', regex: /((^|\s|\>)==\*(.*?)\*==(\s|\<|$))/gm, plain: "$2*$3*$4", xhtml: "$2*$3*$4" },
{ plain: '==_italic_==', regex: /((^|\s|\>)==\_(.*?)\_==(\s|\<|$))/gm, plain: "$2_$3_$4", xhtml: "$2_$3_$4" },
{ plain: '==-strikethrough-==', regex: /((^|\s|\>)==\-(.*?)\-==(\s|\<|$))/gm, plain: "$2-$3-$4", xhtml: "$2-$3-$4" },
{ plain: '==+underline+==', regex: /((^|\s|\>)==\+(.*?)\+==(\s|\<|$))/gm, plain: "$2+$3+$4", xhtml: "$2+$3+$4" },
{ plain: '*bold*', regex: /((^|\s|\>)\*(.*?)\*(\s|\<|$))/gm, plain: "$2*$3*$4", xhtml: "$2<strong>$3</strong>$4" },
{ plain: '_italic_', regex: /((^|\s|\>)\_(.*?)\_(\s|\<|$))/gm, plain: "$2_$3_$4", xhtml: "$2<em>$3</em>$4" },
{ plain: '-strikethrough-', regex: /((^|\s|\>)\-(.*?)\-(\s|\<|$))/gm, plain: "$2-$3-$4", xhtml: "$2<span style='text-decoration: line-through;'>$3</span>$4" },
{ plain: '+underline+', regex: /((^|\s|\>)\+(.*?)\+(\s|\<|$))/gm, plain: "$2+$3+$4", xhtml: "$2<span style='text-decoration: underline;'>$3</span>$4" }
],
bbcode: [
{ plain: '[b]bold[/b]', regex: /(\[b\](.*?)\[\/b\])/igm, plain: "*$2*", xhtml: "<strong>$2</strong>" },
{ plain: '[i]italic[/i]', regex: /(\[i\](.*?)\[\/i\])/igm, plain: "_$2_", xhtml: "<em>$2</em>" },
{ plain: '[s]strikethrough[/s]', regex: /(\[s\](.*?)\[\/s\])/igm, plain: "-$2-", xhtml: "<span style='text-decoration: line-through;'>$2</span>" },
{ plain: '[u]underline[/u]', regex: /(\[u\](.*?)\[\/u\])/igm, plain: "+$2+", xhtml: "<span style='text-decoration: underline;'>$2</span>" }
],
html: [
//handling both escaped an unescaped, just in case.
{ plain: '&lt;b&gt;bold&lt;/b&gt;', regex: /(\&lt;b\&gt;(.*?)\&lt;\/b\&gt;)/igm, plain: "*$2*", xhtml: "<strong>$2</strong>" },
{ plain: '&lt;strong&gt;bold&lt;/strong&gt;', regex: /(\&lt;strong\&gt;(.*?)\&lt;\/strong\&gt;)/igm, plain: "*$2*", xhtml: "<strong>$2</strong>" },
{ plain: '&lt;i&gt;italic&lt;/i&gt;', regex: /(\&lt;i\&gt;(.*?)\&lt;\/i\&gt;)/igm, plain: "_$2_", xhtml: "<em>$2</em>" },
{ plain: '&lt;em&gt;italic&lt;/em&gt;', regex: /(\&lt;em\&gt;(.*?)\&lt;\/em\&gt;)/igm, plain: "_$2_", xhtml: "<em>$2</em>" },
{ plain: '&lt;s&gt;strikethrough&lt;/s&gt;', regex: /(\&lt;s\&gt;(.*?)\&lt;\/s\&gt;)/igm, plain: "-$2-", xhtml: "<span style='text-decoration: line-through;'>$2</span>" },
{ plain: '&lt;del&gt;strikethrough&lt;/del&gt;', regex: /(\&lt;del\&gt;(.*?)\&lt;\/del\&gt;)/igm, plain: "-$2-", xhtml: "<span style='text-decoration: line-through;'>$2</span>" },
{ plain: '&lt;u&gt;underline&lt;/u&gt;', regex: /(\&lt;u\&gt;(.*?)\&lt;\/u\&gt;)/igm, plain: "+$2+", xhtml: "<span style='text-decoration: underline;'>$2</span>" },
{ plain: '&lt;ins&gt;underline&lt;/ins&gt;', regex: /(\&lt;ins\&gt;(.*?)\&lt;\/ins\&gt;)/igm, plain: "+$2+", xhtml: "<span style='text-decoration: underline;'>$2</span>" },
{ plain: '<b>bold</b>', regex: /(\<b\>(.*?)\<\/b\>)/igm, plain: "*$2*", xhtml: "<strong>$2</strong>" },
{ plain: '<strong>bold</strong>', regex: /(\<strong\>(.*?)\<\/strong\>)/igm, plain: "*$2*", xhtml: "<strong>$2</strong>" },
{ plain: '<i>italic</i>', regex: /(\<i\>(.*?)\<\/i\>)/igm, plain: "_$2_", xhtml: "<em>$2</em>" },
{ plain: '<em>italic</em>', regex: /(\<em\>(.*?)\<\/em\>)/igm, plain: "_$2_", xhtml: "<em>$2</em>" },
{ plain: '<s>strikethrough</s>', regex: /(\<s\>(.*?)\<\/s\>)/igm, plain: "-$2-", xhtml: "<span style='text-decoration: line-through;'>$2</span>" },
{ plain: '<del>strikethrough</del>', regex: /(\<del\>(.*?)\<\/del\>)/igm, plain: "-$2-", xhtml: "<span style='text-decoration: line-through;'>$2</span>" },
{ plain: '<u>underline</u>', regex: /(\<u\>(.*?)\<\/u\>)/igm, plain: "+$2+", xhtml: "<span style='text-decoration: underline;'>$2</span>" },
{ plain: '<ins>underline</ins>', regex: /(\<ins\>(.*?)\<\/ins\>)/igm, plain: "+$2+", xhtml: "<span style='text-decoration: underline;'>$2</span>" }
]
};
var _options = {
textile: true,
bbcode: true,
html: true
};
self.init = function( options ) {
// apply the supplied options to the defaults specified
$.extend( true, _options, options );
$(Candy).on( 'candy:view.message.before-send', function(e, args) {
var workingPlainMessage = args.message;
var workingXhtmlMessage = args.message;
if( args.xhtmlMessage ) {
var workingXhtmlMessage = args.xhtmlMessage;
}
$.each( _options, function( optionIndex, optionValue ){
if( optionValue === true ){
$.each( _styles[optionIndex], function( styleIndex, styleValue ){
workingPlainMessage = workingPlainMessage.replace( styleValue.regex, styleValue.plain );
workingXhtmlMessage = workingXhtmlMessage.replace( styleValue.regex, styleValue.xhtml );
});
}
});
if( workingPlainMessage != workingXhtmlMessage) {
// strophe currently requires that xhtml have a root element. Apparently.
// Force one node, with no external text
if( !workingXhtmlMessage.match( /^<.*>$/ ) || $( workingXhtmlMessage ).length != 1 ) {
workingXhtmlMessage = "<span>" + workingXhtmlMessage + "</span>";
}
args.message = workingPlainMessage;
args.xhtmlMessage = workingXhtmlMessage;
}
});
};
return self;
}(CandyShop.Emphasis || {}, Candy, jQuery));

@ -0,0 +1,24 @@
# Left Tabs + Bootstrap3 Icons
A plugin for Candy Chat to enable left tabs with simple Bootstrap3 theme elements.
![Left Tabs + Bootstrap3](screenshot.png)
## Usage
Include the JavaScript and CSS files:
```HTML
<script type="text/javascript" src="candyshop/lefttabs/lefttabs.js"></script>
<link rel="stylesheet" type="text/css" href="candyshop/lefttabs/lefttabs.css" />
```
Remember to include your Bootstrap3 CSS/JS files! They are not included in this plugin. ;)
To enable this Left Tabs plugin, add its `init` method _before_ you `init` Candy:
```JavaScript
CandyShop.LeftTabs.init();
Candy.init('/http-bind', { ...
```
## Compatibility with other plugins
Make sure to `init` it after all other plugins, but before the Candy `init`.
1. CreateRoom

@ -0,0 +1,239 @@
/**
* LeftTabs CSS
*
* @author Melissa Adamaitis <melissa@melissanoelle.com>
*/
/* Message pane/body CSS. */
#chat-rooms {
display: inline-block;
float: right;
margin-left: 50%;
margin-right: 14.6%;
width: 30.5%;
}
.message-pane-wrapper {
float:right;
height: auto;
margin: 0;
position: relative;
width: 100%;
}
.message-pane {
height: 100%;
padding-top: 1px;
width: 69.8%;
overflow-y: scroll;
position: fixed;
}
.message-pane .label {
padding-top: 7px;
}
/* Input form CSS. */
.message-form-wrapper {
float: left;
margin-right:auto;
position: relative;
width: 100%;
}
.message-form {
margin: 0;
position: relative;
width: 100%;
}
.message-form input.submit {
margin: 2px 3px;
position: absolute;
}
/* Roster (right) menu CSS. */
.roster-pane {
background-color: initial;
border: 0;
box-shadow: none;
float:right;
margin: 0;
padding-right: 3px;
padding-top: 2px;
position: fixed;
right: 0;
width: 14%;
}
.roster-pane .label {
color: #555;
font-size: 0.85em;
line-height: 1em;
padding-left: 0;
text-shadow: none;
width: auto;
}
.roster-pane .user {
border: 0;
box-sizing: initial;
box-shadow: none;
font-size: 14px;
padding: 0;
}
.roster-pane .user ul {
float: right;
margin: 0;
position: relative;
}
.roster-pane .user:hover {
background-color: initial;
}
.roster-pane .user:hover .label {
color: #33bbfc;
}
/* Toolbar CSS. (Below roster.) */
#chat-toolbar {
height: 30px;
margin-bottom: 0;
width: 14.5%;
}
#chat-toolbar li {
background-image: none !important;
font-size: 1.25em;
line-height: 1em;
}
#emoticons-icon {
color: #F3E43C;
}
/* Volume. */
#chat-sound-control, #chat-autoscroll-control {
color: #ccc;
}
#chat-sound-control .glyphicon {
display: none;
}
#chat-sound-control .glyphicon.glyphicon-volume-off {
display: initial;
color: #9b1414;
}
#chat-sound-control.checked .glyphicon.glyphicon-volume-off {
display: none;
}
#chat-sound-control.checked .glyphicon.glyphicon-volume-up {
display: block;
}
/* Scroll */
#chat-autoscroll-control {
position: relative;
}
#chat-autoscroll-control .glyphicon.glyphicon-ban-circle {
display: initial;
color: #9b1414;
position: absolute;
left: 0;
}
#chat-autoscroll-control.checked .glyphicon.glyphicon-ban-circle {
display: none;
}
/* Status message */
#chat-statusmessage-control {
position: relative;
color: #6EAEFF;
}
#chat-statusmessage-control .glyphicon.glyphicon-ban-circle {
display: initial;
color: #9b1414;
left: 0;
position: absolute;
}
#chat-statusmessage-control.checked .glyphicon.glyphicon-ban-circle {
display: none;
}
/* Users icon */
.usercount span.glyphicon {
background-color: initial;
}
/* Left menu CSS. */
#left-menu-wrapper {
display: inline-block;
float: left;
position: fixed;
width: 50%;
}
#chat-tabs {
margin: 0;
float: right;
width: 99%;
}
#chat-tabs > li {
margin: 0;
margin-top: 2px;
width: 100% !important;
}
#chat-tabs > li:first-of-type {
margin-top: 0;
}
#chat-tabs a.transition {
display: none;
}
#chat-tabs a.label {
border-radius: 3px 0 0 3px;
text-align: left;
width: 100%;
}
#chat-tabs a.close {
right: -5px;
top: -7px;
}
/* Extra details (badges, non-specific hovers, background colors, etc...) */
#chat-tabs small.unread {
border-radius: 50%;
cursor: default;
height: 17px;
padding: 0;
top: 3px;
text-align: center;
width: 17px;
}
.label[href]:hover, .label[href]:focus {
color: #858585;
}
#chat-pane, #roster-pane {
background: #b0e1f2; /* Old browsers */
background: -moz-linear-gradient(top, #b0e1f2 26%, #81bfe2 100%); /* FF3.6+ */
background: -webkit-gradient(linear, left top, left bottom, color-stop(26%,#b0e1f2), color-stop(100%,#81bfe2)); /* Chrome,Safari4+ */
background: -webkit-linear-gradient(top, #b0e1f2 26%,#81bfe2 100%); /* Chrome10+,Safari5.1+ */
background: -o-linear-gradient(top, #b0e1f2 26%,#81bfe2 100%); /* Opera 11.10+ */
background: -ms-linear-gradient(top, #b0e1f2 26%,#81bfe2 100%); /* IE10+ */
background: linear-gradient(to bottom, #b0e1f2 26%,#81bfe2 100%); /* W3C */
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#b0e1f2', endColorstr='#81bfe2',GradientType=0 ); /* IE6-9 */
}
/* Compatibility with CreateRoom plugin. */
#create-group {
background: #eee;
border-radius: 3px 0 0 3px;
cursor: pointer !important;
float: right;
height: 18px !important;
margin-right: 1px;
margin-top: 9px;
position: initial;
width: 99%;
}
#create-group .click {
font-size: 75%;
font-weight: 700;
line-height: 1;
vertical-align: baseline;
position: initial;
text-align: left;
}
.row {
margin: 0 !important;
}
/* Align tooltip context menu properly in the roster. */
#context-menu {
margin-top: 48px !important;
}

@ -0,0 +1,104 @@
/** File: lefttabs.js
* Candy Plugin Left Tabs + Bootstrap3 Layout
* Author: Melissa Adamaitis <madamei@mojolingo.com>
*/
/* global window, Candy, jQuery */
var CandyShop = (function(self) { return self; }(CandyShop || {}));
CandyShop.LeftTabs = (function(self, Candy, $) {
/**
* Initializes the LeftTabs plugin with the default settings.
*/
self.init = function(){
Candy.View.Template.Chat = {
pane: '<div class="row" id="chat-pane">{{> tabs}}{{> toolbar}}{{> rooms}}</div>{{> modal}}',
rooms: '<div id="chat-rooms" class="rooms"></div>',
tabs: '<div id="left-menu-wrapper"><iframe width="100%" height="510" src="https://www.youtube.com/embed/yG0oBPtyNb0" frameborder="0" allowfullscreen autoplay="1"></iframe></div>',
tab: '<li class="roomtype-{{roomType}}" data-roomjid="{{roomJid}}" data-roomtype="{{roomType}}">' +
'<a href="#" class="label">{{#privateUserChat}}<span class="glyphicon glyphicon-user"></span> {{/privateUserChat}}{{name}}</a>' +
'<a href="#" class="transition"></a><a href="#" class="close">\u00D7</a>' +
'<small class="unread"></small></li>',
modal: '<div id="chat-modal"><a id="admin-message-cancel" class="close" href="#">\u00D7</a>' +
'<span id="chat-modal-body"></span>' +
'<img src="{{resourcesPath}}img/modal-spinner.gif" id="chat-modal-spinner" />' +
'</div><div id="chat-modal-overlay"></div>',
adminMessage: '<li><small>{{time}}</small><div class="adminmessage">' +
'<span class="label">{{sender}}</span>' +
'<span class="spacer">▸</span>{{subject}} {{message}}</div></li>',
infoMessage: '<li><small>{{time}}</small><div class="infomessage">' +
'<span class="spacer">•</span>{{subject}} {{message}}</div></li>',
toolbar: '<ul id="chat-toolbar">' +
'<li id="emoticons-icon" data-tooltip="{{tooltipEmoticons}}"><span class="glyphicon glyphicon-asterisk"></span></li>' +
'<li id="chat-sound-control" class="checked" data-tooltip="{{tooltipSound}}"><span class="glyphicon glyphicon-volume-up"></span><span class="glyphicon glyphicon-volume-off"></span>{{> soundcontrol}}</li>' +
'<li id="chat-autoscroll-control" class="checked" data-tooltip="{{tooltipAutoscroll}}"><span class="glyphicon glyphicon-arrow-down"></span><span class="glyphicon glyphicon-ban-circle"></span></li>' +
'<li class="checked" id="chat-statusmessage-control" data-tooltip="{{tooltipStatusmessage}}"><span class="glyphicon glyphicon-info-sign"></span><span class="glyphicon glyphicon-ban-circle"></span></li>' +
'<li class="context" data-tooltip="{{tooltipAdministration}}"></li>' +
'<li class="usercount" data-tooltip="{{tooltipUsercount}}"><span class="glyphicon glyphicon-user"></span>' +
'<span id="chat-usercount"></span></li></ul>',
soundcontrol: '<script type="text/javascript">var audioplayerListener = new Object();' +
' audioplayerListener.onInit = function() { };' +
'</script><object id="chat-sound-player" type="application/x-shockwave-flash" data="{{resourcesPath}}audioplayer.swf"' +
' width="0" height="0"><param name="movie" value="{{resourcesPath}}audioplayer.swf" /><param name="AllowScriptAccess"' +
' value="always" /><param name="FlashVars" value="listener=audioplayerListener&amp;mp3={{resourcesPath}}notify.mp3" />' +
'</object>',
Context: {
menu: '<div id="context-menu"><i class="arrow arrow-top"></i>' +
'<ul></ul><i class="arrow arrow-bottom"></i></div>',
menulinks: '<li class="{{class}}" id="context-menu-{{id}}">{{label}}</li>',
contextModalForm: '<form action="#" id="context-modal-form">' +
'<label for="context-modal-label">{{_label}}</label>' +
'<input type="text" name="contextModalField" id="context-modal-field" />' +
'<input type="submit" class="button" name="send" value="{{_submit}}" /></form>',
adminMessageReason: '<a id="admin-message-cancel" class="close" href="#">×</a>' +
'<p>{{_action}}</p>{{#reason}}<p>{{_reason}}</p>{{/reason}}'
},
tooltip: '<div id="tooltip"><i class="arrow arrow-top"></i>' +
'<div></div><i class="arrow arrow-bottom"></i></div>'
};
// Make the message pane the full height of the window.
self.heights();
// Make sure that the window heights are the right size after the window is resized.
$(window).resize(function() {
self.heights();
});
// Make sure that the window heights are the right size after a new room is added.
$(Candy).on('candy:view.room.after-add', function() {
self.heights();
if(typeof CandyShop.CreateRoom === "object") {
self.createRoomPluginCompatibility();
}
});
$(Candy).on('candy:view.message.after-show', function(ev, obj) {
if(Candy.View.Pane.Window.autoscroll) {
$('div[data-roomjid="' + obj.roomJid + '"] .message-pane').scrollTop($('div[data-roomjid="' + obj.roomJid + '"] .message-pane').prop('scrollHeight') + $('div[data-roomjid="' + obj.roomJid + '"] .message-form-wrapper').height());
}
});
};
self.heights = function() {
var barless_height = $(window).height() - $('.message-form-wrapper').height();
var message_pane_height = barless_height;
var message_pane_wrapper_height = (barless_height - parseInt($('.message-pane-wrapper').css('padding-bottom')));
if(CandyShop.RoomBar) {
message_pane_height = barless_height - parseInt($('.roombar').css('height'));
$('.message-pane').css('margin-top', parseInt($('.roombar').css('height')) + 'px');
}
$('.message-pane-wrapper').height(message_pane_wrapper_height + 'px');
$('.message-pane').height(message_pane_height + 'px');
$('.roster-pane').height(barless_height + 'px');
};
self.createRoomPluginCompatibility = function() {
$('#create-group-form button').addClass('btn');
$('#create-group-form .close-button').html('<span class="glyphicon glyphicon-remove"></span>');
};
return self;
}(CandyShop.LeftTabs || {}, Candy, jQuery));

@ -0,0 +1,31 @@
# /me Does
Format /me messages, implementing XEP-0245
## Usage
Include the JavaScript file:
```HTML
<script type="text/javascript" src="candyshop/me-does/candy.js"></script>
```
Call its `init()` method after Candy has been initialized:
```javascript
Candy.init('/http-bind/', {});
// enable /me handling
CandyShop.MeDoes.init();
Candy.Core.connect();
```
Now all messages starting with '/me 'will use infoMessage formatting.
```
/me takes screenshot
```
![Color Picker](me-does-screenshot.png)
**Please note**: As `me-does` reroutes message output, it's call to `init()` should happen after the `init()` of most other plugins, including, `inline-images`.

@ -0,0 +1,47 @@
var CandyShop = (function(self) { return self; }(CandyShop || {}));
CandyShop.MeDoes = (function(self, Candy, $) {
// CandyShop.Timeago
self.init = function() {
$(Candy).on("candy:view.message.before-show", function(e, args) {
if (args && args.message && args.message.match(/^\/me /i)) {
var message = args.message.match(/^\/([^\s]+)(?:\s+(.*))?$/m)[2];
self.userInfoMessage(args.roomJid, args.name, message);
return false;
}
});
};
if(CandyShop.Timeago === undefined) {
Candy.View.Template.Chat.userInfoMessage = '<li><small>{{time}}</small><div class="infomessage">' +
'<span class="spacer">•</span>&nbsp;<span><strong>{{name}}</strong>&nbsp;{{{message}}}</span></div></li>';
}
else {
Candy.View.Template.Chat.userInfoMessage = '<li><small><abbr title="{{time}}">{{time}}</abbr></small><div class="infomessage">' +
'<span class="spacer">•</span>&nbsp;<span><strong>{{name}}</strong>&nbsp;{{{message}}}</span></div></li>';
}
//Using logic from real infoMessage function and inserting custom template
self.userInfoMessage = function (roomJid, name, message){
if(Candy.View.getCurrent().roomJid) {
var html = Mustache.to_html(Candy.View.Template.Chat.userInfoMessage, {
name: name,
message: message,
time: Candy.Util.localizedTime(new Date().toGMTString())
});
Candy.View.Pane.Room.appendToMessagePane(roomJid, html);
if (Candy.View.getCurrent().roomJid === roomJid) {
Candy.View.Pane.Room.scrollToBottom(Candy.View.getCurrent().roomJid);
}
$(Candy).triggerHandler('candy:view.message.after-show', {
'message' : message
});
}
};
return self;
}(CandyShop.MeDoes || {}, Candy, jQuery));

@ -0,0 +1,29 @@
# Modify role
Adds **add moderator** and **remove moderator** privilege links to context menu.
![Modify role screenshot](screenshot.png)
## Usage
To enable *Modify role* you have to include its JavaScript code and stylesheet:
```HTML
<script type="text/javascript" src="candyshop/modify-role/candy.js"></script>
<link rel="stylesheet" type="text/css" href="candyshop/modify-role/candy.css" />
```
Call its `init()` method after Candy has been initialized:
```JavaScript
Candy.init('/http-bind/');
// enable ModifyRole plugin
CandyShop.ModifyRole.init();
Candy.Core.connect();
```
## Credits
Thanks to [famfamfam silk icons](http://www.famfamfam.com/lab/icons/silk/) for the icons.
## License
MIT

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

@ -0,0 +1,6 @@
#context-menu .add-moderator {
background-image: url(add-moderator.png);
}
#context-menu .remove-moderator {
background-image: url(remove-moderator.png);
}

@ -0,0 +1,97 @@
/** File: candy.js
* Plugin for modifying roles. Currently implemented: op & deop
*
* Authors:
* - Michael Weibel <michael.weibel@gmail.com>
*
* License: MIT
*
* Copyright:
* (c) 2014 Michael Weibel. All rights reserved.
*/
/* global Candy, jQuery, Strophe, $iq */
var CandyShop = (function(self) { return self; }(CandyShop || {}));
/** Class: CandyShop.ModifyRole
* Remove the ignore option in the roster
*/
CandyShop.ModifyRole = (function(self, Candy, $) {
var modifyRole = function modifyRole(role, roomJid, user) {
var conn = Candy.Core.getConnection(),
nick = user.getNick(),
iq = $iq({
'to': Candy.Util.escapeJid(roomJid),
'type': 'set'
});
iq.c('query', {'xmlns': Strophe.NS.MUC_ADMIN})
.c('item', {'nick': nick, 'role': role});
conn.sendIQ(iq.tree());
};
var applyTranslations = function applyTranslations() {
var addModeratorActionLabel = {
'en' : 'Grant moderator status',
'de' : 'Moderator status geben'
};
var removeModeratorActionLabel = {
'en' : 'Remove moderator status',
'de' : 'Moderator status nehmen'
};
$.each(addModeratorActionLabel, function(k, v) {
if(Candy.View.Translation[k]) {
Candy.View.Translation[k].addModeratorActionLabel = v;
}
});
$.each(removeModeratorActionLabel, function(k, v) {
if(Candy.View.Translation[k]) {
Candy.View.Translation[k].removeModeratorActionLabel = v;
}
});
};
var isOwnerOrAdmin = function(user) {
return ['owner', 'admin'].indexOf(user.getAffiliation()) !== -1;
};
var isModerator = function(user) {
return user.getRole() === 'moderator';
};
/** Function: init
* Initializes the plugin by adding an event which modifies
* the contextmenu links.
*/
self.init = function init() {
applyTranslations();
$(Candy).bind('candy:view.roster.context-menu', function(e, args) {
args.menulinks.addModerator = {
requiredPermission: function(user, me) {
return me.getNick() !== user.getNick() && isOwnerOrAdmin(me) && !isOwnerOrAdmin(user) && !isModerator(user);
},
'class' : 'add-moderator',
'label' : $.i18n._('addModeratorActionLabel'),
'callback' : function(e, roomJid, user) {
modifyRole('moderator', roomJid, user);
}
};
args.menulinks.removeModerator = {
requiredPermission: function(user, me) {
return me.getNick() !== user.getNick() && isOwnerOrAdmin(me) && !isOwnerOrAdmin(user) && isModerator(user);
},
'class' : 'remove-moderator',
'label' : $.i18n._('removeModeratorActionLabel'),
'callback' : function(e, roomJid, user) {
modifyRole('participant', roomJid, user);
}
};
});
};
return self;
}(CandyShop.ModifyRole || {}, Candy, jQuery));

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

@ -0,0 +1,31 @@
# Name completion plugin
This plugin will complete the names of users in the room when a specified key is pressed.
### Usage
<script type="text/javascript" src="path_to_plugins/namecomplete/candy.js"></script>
<link rel="stylesheet" type="text/css" href="path_to_plugins/namecomplete/candy.css" />
...
CandyShop.NameComplete.init();
### Configuration options
`nameIdentifier` - String - The identifier to look for in a string. Defaults to `'@'`
`completeKeyCode` - Integer - The key code of the key to use. Defaults to `9` (tab)
### Example configurations
// complete the name when the user types +nick and hits the right arrow
// +troymcc -> +troymccabe
CandyShop.NameComplete.init({
nameIdentifier: '+',
completeKeyCode: '39'
});
// complete the name when the user types -nick and hits the up arrow
// +troymcc ^ +troymccabe
CandyShop.NameComplete.init({
nameIdentifier: '-',
completeKeyCode: '38'
});

@ -0,0 +1,7 @@
#context-menu li.selected {
background-color: #ccc;
}
#context-menu li.candy-namecomplete-option {
padding: 3px 5px;
}

@ -0,0 +1,260 @@
/** File: candy.js
* Candy - Chats are not dead yet.
*
* Authors:
* - Troy McCabe <troy.mccabe@geeksquad.com>
* - Ben Klang <bklang@mojolingo.com>
*
* Copyright:
* (c) 2012 Geek Squad. All rights reserved.
* (c) 2014 Power Home Remodeling Group. All rights reserved.
*/
/* global document, Candy, jQuery */
var CandyShop = (function(self) { return self; }(CandyShop || {}));
/** Class: CandyShop.NameComplete
* Allows for completion of a name in the roster
*/
CandyShop.NameComplete = (function(self, Candy, $) {
/** Object: _options
* Options:
* (String) nameIdentifier - Prefix to append to a name to look for. '@' now looks for '@NICK', '' looks for 'NICK', etc. Defaults to '@'
* (Integer) completeKeyCode - Which key to use to complete
*/
var _options = {
nameIdentifier: '@',
completeKeyCode: 9
};
/** Array: _nicks
* An array of nicks to complete from
* Populated after 'candy:core.presence'
*/
var _nicks = [];
/** String: _selector
* The selector for the visible message box
*/
var _selector = 'input[name="message"]:visible';
/** Boolean:_autocompleteStarted
* Keeps track of whether we're in the middle of autocompleting a name
*/
var _autocompleteStarted = false;
/** Function: init
* Initialize the NameComplete plugin
* Show options for auto completion of names
*
* Parameters:
* (Object) options - Options to apply to this plugin
*/
self.init = function(options) {
// apply the supplied options to the defaults specified
$.extend(true, _options, options);
// listen for keydown when autocomplete options exist
$(document).on('keypress', _selector, function(e) {
if (e.which === _options.nameIdentifier.charCodeAt()) {
_autocompleteStarted = true;
}
if (_autocompleteStarted) {
// update the list of nicks to grab
self.populateNicks();
// set up the vars for this method
// break it on spaces, and get the last word in the string
var field = $(this);
var msgParts = field.val().split(' ');
var lastWord = new RegExp( "^" + msgParts[msgParts.length - 1] + String.fromCharCode(e.which), "i");
var matches = [];
// go through each of the nicks and compare it
$(_nicks).each(function(index, item) {
// if we have results
if (item.match(lastWord) !== null) {
matches.push(item);
}
});
// if we only have one match, no need to show the picker, just replace it
// else show the picker of the name matches
if (matches.length === 1) {
self.replaceName(matches[0]);
// Since the name will be autocompleted, throw away the last character
e.preventDefault();
} else if (matches.length > 1) {
self.showPicker(matches, field);
}
}
});
};
/** Function: keyDown
* The listener for keydown in the menu
*/
self.keyDown = function(e) {
// get the menu and the content element
var menu = $('#context-menu');
var content = menu.find('ul');
var selected = content.find('li.selected');
if(menu.css('display') === 'none') {
$(document).unbind('keydown', self.keyDown);
return;
}
// switch the key code
switch (e.which) {
// up arrow
case 38:
// down arrow
case 40:
var newEl;
if (e.which === 38) {
// move the selected thing up
newEl = selected.prev();
} else {
// move the selected thing down
newEl = selected.next();
}
// Prevent going off either end of the list
if ($(newEl).length > 0) {