112 Commits

Author SHA1 Message Date
650c761c51 Fixed dark theme issues 2019-02-25 07:57:45 +01:00
2e07945994 Block YouTube video by default 2019-02-23 19:08:34 +01:00
d9fe86d160 Added first TypeScript typing rules 2019-02-23 19:08:25 +01:00
bbaaf3a5c2 Only allow one window per conversation 2019-02-23 18:36:40 +01:00
0f4d3042c3 Enabled trickling with WebRTC 2019-02-23 18:30:05 +01:00
be0306b309 Close ring screen if user respond to call on another client 2019-02-23 18:27:31 +01:00
c819aaf716 Fix -1 year ago issue 2019-02-02 10:34:50 +01:00
b6e818dc00 Improved call window size 2019-02-02 10:32:37 +01:00
a3f8e2f243 Get number of members per conversation from API 2019-02-02 08:25:12 +01:00
9f3311978d Block calls for browsers that does not support it. 2019-02-02 08:13:06 +01:00
817ef1069c Can specify whether the signaling server is secure or not. 2019-02-02 08:11:11 +01:00
faba9b36cb Fix build system. 2019-01-26 16:46:36 +01:00
5276790afe Fix ring screen issue 2019-01-26 16:07:57 +01:00
8d613d4e57 Avoid stop loops 2019-01-26 16:03:15 +01:00
5a067001e9 Automatically close ring screen if other peer gives up with connection 2019-01-26 15:59:06 +01:00
1a3117a603 Created calls utilities 2019-01-26 15:49:08 +01:00
cbfe141c32 Play song when the phone is ringing 2019-01-26 15:43:26 +01:00
35c4597017 Can play forever songs 2019-01-26 15:33:43 +01:00
289a66e55b Created song player class 2019-01-26 15:26:49 +01:00
8c6e04abd0 Change cursor on call window title 2019-01-26 12:11:52 +01:00
79dfa0511b Made call system responsive 2019-01-26 12:10:41 +01:00
982c40788c Close more properly local streams. 2019-01-26 11:53:43 +01:00
a46a7154ea Improve dark theme 2019-01-26 11:42:46 +01:00
fe2a6c2dfe Can hide local stream 2019-01-26 11:40:39 +01:00
c1053b8041 Improve window position 2019-01-26 11:11:25 +01:00
194b6c60de Can switch to full screen 2019-01-26 10:57:58 +01:00
0b806d5bb2 Made call window draggable 2019-01-26 10:23:57 +01:00
b8865b96f0 Added footer buttons 2019-01-26 09:52:40 +01:00
b62fefc258 Fix issue 2019-01-26 08:15:17 +01:00
590e1d6794 Automatically close call when a user is the last on a call. 2019-01-26 08:14:17 +01:00
ece7a97d42 Follow API update 2019-01-25 19:18:12 +01:00
99fffb839f Each conversation use its own stream to media and audio stream 2019-01-25 19:03:23 +01:00
154f1b98ef Improved video appearance 2019-01-25 19:03:00 +01:00
45a4f552e8 Got a visual ! 2019-01-25 18:49:50 +01:00
ca39a08d07 Remote stream available ! 2019-01-25 18:30:01 +01:00
5eb1c8b274 Added SimplePeer attribution in README 2019-01-25 17:49:36 +01:00
29b5499b85 Created call window body 2019-01-25 15:34:40 +01:00
b08255cd18 Can detect if a call was rejected 2019-01-25 15:24:32 +01:00
be03249f11 Removed useless source code 2019-01-25 10:13:30 +01:00
0a52bd5fe3 Can reopen calls on page reload 2019-01-25 10:08:55 +01:00
9eab6c7e2e Can respond to calls 2019-01-25 09:43:19 +01:00
118cfeee41 Created ring screen 2019-01-25 09:15:34 +01:00
f79ef55e3b Created a function to easily get the name of a conversation 2019-01-24 18:14:53 +01:00
5640580397 Get user media 2019-01-24 14:50:03 +01:00
e90c20c262 Display basic call window 2019-01-24 14:40:36 +01:00
cd4e6ddcb1 Show call button on conversation of two people when available 2019-01-23 15:45:19 +01:00
feb17e3f13 Automatically retrieve calls configuration 2019-01-23 15:19:34 +01:00
e6f4159e53 Fixed dark theme issues 2019-01-21 09:06:27 +01:00
2d88a71b80 Fix issue 2019-01-21 08:30:56 +01:00
6c090b4967 Bumped to 2019 2019-01-21 07:51:16 +01:00
f67670dc0a Fixed little issue 2019-01-20 19:40:29 +01:00
a03fc1a745 Integrated personnal data navigator 2019-01-20 18:04:09 +01:00
32c484b41a Added personnal data navigator build file 2019-01-20 17:17:13 +01:00
c89edd3cd6 Display conversation messages. 2019-01-15 08:25:59 +01:00
4d775494c0 Added all conversation messages 2019-01-15 07:32:07 +01:00
f9c1f37ac6 Display user movies. 2019-01-15 07:17:06 +01:00
0e3aee13bd Display survey responses 2019-01-14 21:19:06 +01:00
4a24d5249c Display user likes 2019-01-14 21:12:06 +01:00
64ead89601 Improved appearance of processed categories 2019-01-14 21:04:10 +01:00
b48b6db028 Display all comments 2019-01-14 21:01:44 +01:00
59e6eb3c30 Improved project structure 2019-01-14 20:51:24 +01:00
8072d1eb3e Display surveys 2019-01-14 20:48:03 +01:00
e1dea40167 Display posts with countdown timer. 2019-01-14 19:26:18 +01:00
f3efb3d390 Show posts with PDFs 2019-01-14 19:22:15 +01:00
2035b85a06 Added post weblink 2019-01-14 19:11:03 +01:00
100032fa86 Can show movies 2019-01-14 18:48:43 +01:00
3da2455945 Display YouTube post 2019-01-14 18:40:28 +01:00
30a96d56ec Begin to display posts 2019-01-14 15:30:42 +01:00
a7a36d8665 Display friends information 2019-01-14 14:36:20 +01:00
a0ef614252 Improved navigation 2019-01-14 14:19:56 +01:00
229b02534e Display user information 2019-01-14 14:13:18 +01:00
f2ab71cf3f Work progress on project UI 2019-01-14 13:17:05 +01:00
497b8f1274 Added export categories 2019-01-14 09:56:46 +01:00
98765057dd Added account export assets 2019-01-14 09:39:09 +01:00
824de4bcdb Added gitignore 2019-01-14 08:27:29 +01:00
72fe0843c4 Fix dark theme issue 2019-01-11 18:27:02 +01:00
584bb42c93 Can update conversation message content. 2019-01-11 18:17:37 +01:00
f8e8454b86 Can delete conversation messages 2019-01-11 17:57:55 +01:00
41354be949 Added easter egg (a pacman game) 2019-01-11 16:34:43 +01:00
457712cd35 Fix HTTPS issue 2019-01-11 15:23:18 +01:00
a0d644469d Added incognito mode 2019-01-11 14:50:06 +01:00
f76d9ba9cd Display bottom links on main menu. 2019-01-11 11:54:22 +01:00
dc7fd44b67 Splited main menu links list and rendering. 2019-01-11 11:38:55 +01:00
fba7937d02 Splited bottom links list and rendering 2019-01-11 11:11:25 +01:00
f418ee25b0 Fix dark theme issue on friends list on mobiles 2019-01-10 17:10:49 +01:00
c05506a2a5 Dark theme persistant on page reload. 2019-01-10 17:07:42 +01:00
5bb4e2ae1f Improved dark theme 2019-01-10 17:01:00 +01:00
fc70840c41 Sceditor auto-expanding on post create form. 2019-01-10 16:47:20 +01:00
dff9831228 Improved Sceditor integration 2019-01-10 16:28:59 +01:00
6d61c0d621 Improve dark theme 2019-01-10 15:53:16 +01:00
b16ca0defd Basic integration of dark theme. 2019-01-10 15:44:39 +01:00
93df9d235d Fixed build issues. 2019-01-10 14:59:12 +01:00
069922b7da Merge remote-tracking branch 'origin/dark-theme-design' 2019-01-10 14:28:40 +01:00
fdabb3e3fc Improved input appearance. 2018-12-27 19:07:39 +01:00
2890b03283 Switched to BBCode language for posts. 2018-12-27 14:02:01 +01:00
f6e2c83dbd Added the possibility to create a detached picker. 2018-12-27 11:18:52 +01:00
c2eba7b3be Fix security breach. 2018-12-08 19:03:53 +01:00
089739e141 Added (w) smiley 2018-12-01 10:46:33 +01:00
5c19d9c04c Play a song when new notifications arrives. 2018-11-24 18:54:02 +01:00
46bb22b17b Display the number of notifications on page title. 2018-11-24 16:01:45 +01:00
d49b04e6fb Do not display JS errors in production mode. 2018-11-24 11:24:31 +01:00
672cbc1409 Form keep focus after sending a message onconversation page. 2018-09-07 10:00:05 +02:00
34f652d3bf Fix issue when responding to a group invitation. 2018-09-02 17:39:00 +02:00
8f65890f29 Added the form to invite a user. 2018-09-02 17:34:30 +02:00
413cc6ddf8 Reorganized groups member page. 2018-09-02 15:05:52 +02:00
ccd0fd03a9 Improve groups appearance 2018-09-02 14:26:20 +02:00
6588b3d13a Add a notice when the user does not belong to any group. 2018-09-02 14:20:32 +02:00
4860ee6623 Upgraded request. 2018-09-02 14:14:21 +02:00
e8df43351f Can force the request to get multiple groups. 2018-09-02 14:14:10 +02:00
1c1dbe8454 Can delete groups. 2018-08-31 10:33:51 +02:00
7e09e0f2f9 Created a method to prompt user password. 2018-08-31 10:25:02 +02:00
c495337aa6 Click on search link collapse toggle menu 2018-08-31 09:06:29 +02:00
167 changed files with 33185 additions and 290 deletions

View File

@ -1,5 +1,5 @@
MIT License
Copyright (c) 2017-2018 Pierre HUBERT
Copyright (c) 2017-2019 Pierre HUBERT
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:

View File

@ -21,8 +21,12 @@ ComunicWeb would not exists without the following technologies developped by the
- jquery.hotkeys
- bootstrap-wysiwyg (https://github.com/steveathon/bootstrap-wysiwyg/)
- wdt-emoji-bundle (http://ned.im/wdt-emoji-bundle)
- PNGLib (http://www.xarg.org/2010/03/generate-client-side-png-files-using-javascript/) (BSD Licence)
- Identicon (http://github.com/stewartlord/identicon.js) (BSD Licence)
- FileSaver.js (http://eligrey.com) (by Eli Grey) (MIT Licence)
- JSZip (https://github.com/Stuk/jszip.git) (MIT Licence)
- JSZip Utils (https://github.com/Stuk/jszip-utils.git) (MIT Licence)
- PNGLib (http://www.xarg.org/2010/03/generate-client-side-png-files-using-javascript/) (BSD License)
- Identicon (http://github.com/stewartlord/identicon.js) (BSD License)
- FileSaver.js (http://eligrey.com) (by Eli Grey) (MIT License)
- JSZip (https://github.com/Stuk/jszip.git) (MIT License)
- JSZip Utils (https://github.com/Stuk/jszip-utils.git) (MIT License)
- SCEditor (BBC WYIWYG editor) (https://github.com/samclarke/SCEditor) (MIT License)
- JavaScript BBCode Parser (https://github.com/Frug/js-bbcode-parser) (MIT License)
- Pacman (https://github.com/daleharvey/pacman) (WTFPL License)
- SimplePeer (https://github.com/feross/simple-peer) (MIT License)

View File

@ -0,0 +1,262 @@
/**
* Signal exchanger web client
*
* @author Pierre HUBERT
*/
class SignalExchangerClient {
/**
* Server domain
*
* @type {String}
*/
//domain;
/**
* Server port
*
* @type {Number}
*/
//port;
/**
* Current client ID
*
* @type {String}
*/
//clientID;
/**
* Socket connection to the server
*
* @type {WebSocket}
*/
//socket;
/**
* Function called in case of error
*
* @type {Function}
*/
//onError = null;
/**
* Function called when the connection is etablished
*
* @type {Function}
*/
//onConnected = null;
/**
* Function called when the connection to the socket is closed
*
* @type {Function}
*/
//onClosed = null;
/**
* Function called when we get a new signal information
*
* @type {Function}
*/
//onSignal = null;
/**
* Function called when we get a ready message notice
*
* @type {Function}
*/
//onReadyMessage = null;
/**
* Construct a client instance
*
* @param {String} domain The name of the signal server
* @param {Number} port The port of the server to use
* @param {String} clientID The ID of current client
* @param {Boolean} secure Specify whether connection to the socket should be secure or not
*/
constructor(domain, port, clientID, secure) {
//Save information
this.domain = domain,
this.port = port;
this.clientID = clientID;
this.socket = new WebSocket((secure ? "wss" : "ws") + "://" + this.domain + ":" + this.port + "/socket");
//Add a few events listeners
this.socket.addEventListener("open", () => {
this.serverConnected();
if(this.onConnected != null)
setTimeout(this.onConnected, 10);
});
this.socket.addEventListener("message", message => {
let data;
try {
data = JSON.parse(message.data);
} catch(e){
console.error("Could not parse message from server!");
return;
}
console.log("New message from socket", data);
this.serverMessage(data);
});
this.socket.addEventListener("error", () => {
if(this.onError != null)
setTimeout(this.onError, 0);
});
this.socket.addEventListener("close", () => {
if(this.onClosed != null)
setTimeout(this.onClosed, 0);
});
}
/**
* Use this method to get the current connection status to the server
*
* @return {Boolean} TRUE if the client is connected to the server / FALSE else
*/
isConnected() {
return this.socket.readyState == WebSocket.OPEN;
}
/**
* Close the connection to the server (if connected)
*/
close() {
if(this.isConnected())
this.socket.close();
}
/**
* Method called once the client is successfully
* connected to the client
*/
serverConnected(){
//Send data to the server to identificate client
this.sendData({
client_id: this.clientID
});
}
/**
* Send ready message to a peer
*
* @param {String} peerID The ID of the target peer for the message
*/
sendReadyMessage(peerID){
this.sendData({
ready_msg: true,
target_id: peerID
});
}
/**
* Send a signal to the server
*
* @param target_id The ID of the target for the signal
* @param content Signal to send to the target
*/
sendSignal(target_id, content){
//Send directly the message to the server
this.sendData({
signal: content,
target_id: target_id
});
//Save the current signal being sent to be able to send
//it again in case of failure
this.pending_signal = content;
this.pending_signal_target = target_id;
}
/**
* Stop to try to send the current signal message in queue
*
* This does not cancel the sending of messages already sent through
* socket
*/
cancelCurrentSignal() {
this.pending_signal = undefined;
this.pending_signal_target = undefined;
}
/**
* Send data to the server
*
* @param {Object} data The data to send to the server
*/
sendData(data){
console.log("Sending data to server", data);
this.socket.send(JSON.stringify(data));
}
/**
* This method is called when the server has sent a new message to this client
*
* @param {Object} message The message sent by the server, as a JSON object
*/
serverMessage(message){
//Check if it is a callback for a pending message
if(message.signal_sent){
if(message.number_of_targets < 1 && this.pending_signal && this.pending_signal_target){
//We have to send the message again
setTimeout(() => {
this.sendSignal(this.pending_signal, this.pending_signal_target);
}, 1000);
}
else {
//Else we can remove from this class information about the signal being sent
this.cancelCurrentSignal();
}
}
//Check if message is a callback for a ready notice
else if(message.ready_message_sent){
if(message.number_of_targets < 1){
//Try to send message again
setTimeout(() => {
this.sendReadyMessage(message.target_id);
}, 1000);
}
}
// Check if message is a ready notice
else if(message.ready_msg){
if(this.onReadyMessage != null)
this.onReadyMessage(message.source_id);
}
// Check if the message is a signal
else if(message.signal){
if(this.onSignal != null)
this.onSignal(message.signal, message.source_id);
}
}
}

View File

@ -0,0 +1,301 @@
/*
* Javascript BBCode Parser Config Options
* @author Philip Nicolcev
* @license MIT License
*/
var parserColors = [ 'gray', 'silver', 'white', 'yellow', 'orange', 'red', 'fuchsia', 'blue', 'green', 'black', '#cd38d9' ];
var parserTags = {
'b': {
openTag: function(params,content) {
return '<b>';
},
closeTag: function(params,content) {
return '</b>';
}
},
'code': {
openTag: function(params,content) {
return '<code>';
},
closeTag: function(params,content) {
return '</code>';
},
noParse: true
},
'color': {
openTag: function(params,content) {
var colorCode = params.substr(1) || "inherit";
BBCodeParser.regExpAllowedColors.lastIndex = 0;
BBCodeParser.regExpValidHexColors.lastIndex = 0;
if ( !BBCodeParser.regExpAllowedColors.test( colorCode ) ) {
if ( !BBCodeParser.regExpValidHexColors.test( colorCode ) ) {
colorCode = "inherit";
} else {
if (colorCode.substr(0,1) !== "#") {
colorCode = "#" + colorCode;
}
}
}
return '<span style="color:' + colorCode + '">';
},
closeTag: function(params,content) {
return '</span>';
}
},
'i': {
openTag: function(params,content) {
return '<i>';
},
closeTag: function(params,content) {
return '</i>';
}
},
'img': {
openTag: function(params,content) {
var myUrl = content;
BBCodeParser.urlPattern.lastIndex = 0;
if ( !BBCodeParser.urlPattern.test( myUrl ) ) {
myUrl = "";
}
return '<img class="bbCodeImage" src="' + myUrl + '">';
},
closeTag: function(params,content) {
return '';
},
content: function(params,content) {
return '';
}
},
'list': {
openTag: function(params,content) {
return '<ul>';
},
closeTag: function(params,content) {
return '</ul>';
},
restrictChildrenTo: ["*", "li"]
},
'noparse': {
openTag: function(params,content) {
return '';
},
closeTag: function(params,content) {
return '';
},
noParse: true
},
'quote': {
openTag: function(params,content) {
return '<blockquote>';
},
closeTag: function(params,content) {
return '</blockquote>';
}
},
's': {
openTag: function(params,content) {
return '<s>';
},
closeTag: function(params,content) {
return '</s>';
}
},
'size': {
openTag: function(params,content) {
var mySize = parseInt(params.substr(1),10) || 0;
if (mySize < 10 || mySize > 20) {
mySize = 'inherit';
} else {
mySize = mySize + 'px';
}
return '<span style="font-size:' + mySize + '">';
},
closeTag: function(params,content) {
return '</span>';
}
},
'u': {
openTag: function(params,content) {
return '<span style="text-decoration:underline">';
},
closeTag: function(params,content) {
return '</span>';
}
},
'url': {
openTag: function(params,content) {
var myUrl;
if (!params) {
myUrl = content.replace(/<.*?>/g,"");
} else {
myUrl = params.substr(1);
}
BBCodeParser.urlPattern.lastIndex = 0;
if ( !BBCodeParser.urlPattern.test( myUrl ) ) {
myUrl = "#";
}
BBCodeParser.urlPattern.lastIndex = 0;
if ( !BBCodeParser.urlPattern.test( myUrl ) ) {
myUrl = "";
}
return '<a href="' + myUrl + '">';
},
closeTag: function(params,content) {
return '</a>';
}
},
//COMUNIC ADD BEGIN
'left': {
openTag: function(params,content) {
return '<p style="text-align: left;">';
},
closeTag: function(params,content) {
return '</p>';
}
},
'center': {
openTag: function(params,content) {
return '<p style="text-align: center;">';
},
closeTag: function(params,content) {
return '</p>';
}
},
'right': {
openTag: function(params,content) {
return '<p style="text-align: right;">';
},
closeTag: function(params,content) {
return '</p>';
}
},
'justify': {
openTag: function(params,content) {
return '<p style="text-align: justify;">';
},
closeTag: function(params,content) {
return '</p>';
}
},
'ul': {
openTag: function(params,content) {
return '<ul>';
},
closeTag: function(params,content) {
return '</ul>';
}
},
'ol': {
openTag: function(params,content) {
return '<ol>';
},
closeTag: function(params,content) {
return '</ol>';
}
},
'li': {
openTag: function(params,content) {
return '<li>';
},
closeTag: function(params,content) {
return '</li>';
}
},
'sup': {
openTag: function(params,content) {
return '<sup>';
},
closeTag: function(params,content) {
return '</sup>';
}
},
'sub': {
openTag: function(params,content) {
return '<sub>';
},
closeTag: function(params,content) {
return '</sub>';
}
},
'ltr': {
openTag: function(params,content) {
return '<div style="text-align: left;">';
},
closeTag: function(params,content) {
return '</div>';
}
},
'rtl': {
openTag: function(params,content) {
return '<div style="text-align: right;">';
},
closeTag: function(params,content) {
return '</div>';
}
},
'table': {
openTag: function(params,content) {
return '<table border="1" style="margin: auto;">';
},
closeTag: function(params,content) {
return '</table>';
}
},
'tr': {
openTag: function(params,content) {
return '<tr>';
},
closeTag: function(params,content) {
return '</tr>';
}
},
'td': {
openTag: function(params,content) {
return '<td>';
},
closeTag: function(params,content) {
return '</td>';
}
},
'hr': {
openTag: function(params,content) {
return '<hr />';
},
closeTag: function(params,content) {
return '';
},
content: function(params, content){
return '';
}
},
//COMUNIC ADD END
};

View File

@ -0,0 +1,152 @@
/*
* Javascript BBCode Parser
* @author Philip Nicolcev
* @license MIT License
*/
var BBCodeParser = (function(parserTags, parserColors) {
'use strict';
var me = {},
urlPattern = /^(?:https?|file|c):(?:\/{1,3}|\\{1})[-a-zA-Z0-9:;@#%&()~_?\+=\/\\\.]*$/,
emailPattern = /[^\s@]+@[^\s@]+\.[^\s@]+/,
fontFacePattern = /^([a-z][a-z0-9_]+|"[a-z][a-z0-9_\s]+")$/i,
tagNames = [],
tagNamesNoParse = [],
regExpAllowedColors,
regExpValidHexColors = /^#?[a-fA-F0-9]{6}$/,
ii, tagName, len;
// create tag list and lookup fields
for (tagName in parserTags) {
if (!parserTags.hasOwnProperty(tagName))
continue;
if (tagName === '*') {
tagNames.push('\\' + tagName);
} else {
tagNames.push(tagName);
if ( parserTags[tagName].noParse ) {
tagNamesNoParse.push(tagName);
}
}
parserTags[tagName].validChildLookup = {};
parserTags[tagName].validParentLookup = {};
parserTags[tagName].restrictParentsTo = parserTags[tagName].restrictParentsTo || [];
parserTags[tagName].restrictChildrenTo = parserTags[tagName].restrictChildrenTo || [];
len = parserTags[tagName].restrictChildrenTo.length;
for (ii = 0; ii < len; ii++) {
parserTags[tagName].validChildLookup[ parserTags[tagName].restrictChildrenTo[ii] ] = true;
}
len = parserTags[tagName].restrictParentsTo.length;
for (ii = 0; ii < len; ii++) {
parserTags[tagName].validParentLookup[ parserTags[tagName].restrictParentsTo[ii] ] = true;
}
}
regExpAllowedColors = new RegExp('^(?:' + parserColors.join('|') + ')$');
/*
* Create a regular expression that captures the innermost instance of a tag in an array of tags
* The returned RegExp captures the following in order:
* 1) the tag from the array that was matched
* 2) all (optional) parameters included in the opening tag
* 3) the contents surrounded by the tag
*
* @param {type} tagsArray - the array of tags to capture
* @returns {RegExp}
*/
function createInnermostTagRegExp(tagsArray) {
var openingTag = '\\[(' + tagsArray.join('|') + ')\\b(?:[ =]([\\w"#\\-\\:\\/= ]*?))?\\]',
notContainingOpeningTag = '((?:(?=([^\\[]+))\\4|\\[(?!\\1\\b(?:[ =](?:[\\w"#\\-\\:\\/= ]*?))?\\]))*?)',
closingTag = '\\[\\/\\1\\]';
return new RegExp( openingTag + notContainingOpeningTag + closingTag, 'i');
}
/*
* Escape the contents of a tag and mark the tag with a null unicode character.
* To be used in a loop with a regular expression that captures tags.
* Marking the tag prevents it from being matched again.
*
* @param {type} matchStr - the full match, including the opening and closing tags
* @param {type} tagName - the tag that was matched
* @param {type} tagParams - parameters passed to the tag
* @param {type} tagContents - everything between the opening and closing tags
* @returns {String} - the full match with the tag contents escaped and the tag marked with \u0000
*/
function escapeInnerTags(matchStr, tagName, tagParams, tagContents) {
tagParams = tagParams || "";
tagContents = tagContents || "";
tagContents = tagContents.replace(/\[/g, "&#91;").replace(/\]/g, "&#93;");
return "[\u0000" + tagName + tagParams + "]" + tagContents + "[/\u0000" + tagName + "]";
}
/*
* Escape all BBCodes that are inside the given tags.
*
* @param {string} text - the text to search through
* @param {string[]} tags - the tags to search for
* @returns {string} - the full text with the required code escaped
*/
function escapeBBCodesInsideTags(text, tags) {
var innerMostRegExp;
if (tags.length === 0 || text.length < 7)
return text;
innerMostRegExp = createInnermostTagRegExp(tags);
while (
text !== (text = text.replace(innerMostRegExp, escapeInnerTags))
);
return text.replace(/\u0000/g,'');
}
/*
* Process a tag and its contents according to the rules provided in parserTags.
*
* @param {type} matchStr - the full match, including the opening and closing tags
* @param {type} tagName - the tag that was matched
* @param {type} tagParams - parameters passed to the tag
* @param {type} tagContents - everything between the opening and closing tags
* @returns {string} - the fully processed tag and its contents
*/
function replaceTagsAndContent(matchStr, tagName, tagParams, tagContents) {
tagName = tagName.toLowerCase();
tagParams = tagParams || "";
tagContents = tagContents || "";
return parserTags[tagName].openTag(tagParams, tagContents) + (parserTags[tagName].content ? parserTags[tagName].content(tagParams, tagContents) : tagContents) + parserTags[tagName].closeTag(tagParams, tagContents);
}
function processTags(text, tagNames) {
var innerMostRegExp;
if (tagNames.length === 0 || text.length < 7)
return text;
innerMostRegExp = createInnermostTagRegExp(tagNames);
while (
text !== (text = text.replace(innerMostRegExp, replaceTagsAndContent))
);
return text;
}
/*
* Public Methods and Properties
*/
me.process = function(text, config) {
text = escapeBBCodesInsideTags(text, tagNamesNoParse);
return processTags(text, tagNames);
};
me.allowedTags = tagNames;
me.urlPattern = urlPattern;
me.emailPattern = emailPattern;
me.regExpAllowedColors = regExpAllowedColors;
me.regExpValidHexColors = regExpValidHexColors;
return me;
})(parserTags, parserColors);

Binary file not shown.

13
assets/3rdparty/pacman/LICENSE vendored Normal file
View File

@ -0,0 +1,13 @@
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.

BIN
assets/3rdparty/pacman/audio/die.mp3 vendored Normal file

Binary file not shown.

BIN
assets/3rdparty/pacman/audio/die.ogg vendored Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
assets/3rdparty/pacman/audio/eating.mp3 vendored Normal file

Binary file not shown.

BIN
assets/3rdparty/pacman/audio/eating.ogg vendored Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
assets/3rdparty/pacman/audio/eatpill.mp3 vendored Normal file

Binary file not shown.

BIN
assets/3rdparty/pacman/audio/eatpill.ogg vendored Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
assets/3rdparty/pacman/audio/siren.mp3 vendored Normal file

Binary file not shown.

BIN
assets/3rdparty/pacman/audio/siren.ogg vendored Normal file

Binary file not shown.

BIN
assets/3rdparty/pacman/audio/vcs_90.mp3 vendored Normal file

Binary file not shown.

BIN
assets/3rdparty/pacman/audio/vcs_90.ogg vendored Normal file

Binary file not shown.

59
assets/3rdparty/pacman/index.html vendored Normal file
View File

@ -0,0 +1,59 @@
<!--
WTFPL License
Origin: https://github.com/daleharvey/pacman
--><!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>HTML5 Pacman</title>
<style type="text/css">
@font-face {
font-family: 'BDCartoonShoutRegular';
src: url('BD_Cartoon_Shout-webfont.ttf') format('truetype');
font-weight: normal;
font-style: normal;
}
#pacman {
height:450px;
width:342px;
margin:0px auto;
}
#shim {
font-family: BDCartoonShoutRegular;
position:absolute;
visibility:hidden
}
body {
width:342px;
margin:0px;
font-family:sans-serif;
overflow: hidden;
}
</style>
</head>
<body>
<div id="pacman"></div>
<script src="pacman.js"></script>
<script src="modernizr-1.5.min.js"></script>
<script>
var el = document.getElementById("pacman");
if (Modernizr.canvas && Modernizr.localstorage &&
Modernizr.audio && (Modernizr.audio.ogg || Modernizr.audio.mp3)) {
window.setTimeout(function () { PACMAN.init(el, "./"); }, 0);
} else {
el.innerHTML = "Sorry, needs a decent browser<br /><small>" +
"(firefox 3.6+, Chrome 4+, Opera 10+ and Safari 4+)</small>";
}
</script>
</body>
</html>

View File

@ -0,0 +1,28 @@
/*!
* Modernizr JavaScript library 1.5
* http://www.modernizr.com/
*
* Copyright (c) 2009-2010 Faruk Ates - http://farukat.es/
* Dual-licensed under the BSD and MIT licenses.
* http://www.modernizr.com/license/
*
* Featuring major contributions by
* Paul Irish - http://paulirish.com
*/
window.Modernizr=function(i,e,I){function C(a,b){for(var c in a)if(m[a[c]]!==I&&(!b||b(a[c],D)))return true}function r(a,b){var c=a.charAt(0).toUpperCase()+a.substr(1);return!!C([a,"Webkit"+c,"Moz"+c,"O"+c,"ms"+c,"Khtml"+c],b)}function P(){j[E]=function(a){for(var b=0,c=a.length;b<c;b++)J[a[b]]=!!(a[b]in n);return J}("autocomplete autofocus list placeholder max min multiple pattern required step".split(" "));j[Q]=function(a){for(var b=0,c,h=a.length;b<h;b++){n.setAttribute("type",a[b]);if(c=n.type!==
"text"){n.value=K;/tel|search/.test(n.type)||(c=/url|email/.test(n.type)?n.checkValidity&&n.checkValidity()===false:n.value!=K)}L[a[b]]=!!c}return L}("search tel url email datetime date month week time datetime-local number range color".split(" "))}var j={},s=e.documentElement,D=e.createElement("modernizr"),m=D.style,n=e.createElement("input"),E="input",Q=E+"types",K=":)",M=Object.prototype.toString,y=" -o- -moz- -ms- -webkit- -khtml- ".split(" "),d={},L={},J={},N=[],u=function(){var a={select:"input",
change:"input",submit:"form",reset:"form",error:"img",load:"img",abort:"img"},b={};return function(c,h){var t=arguments.length==1;if(t&&b[c])return b[c];h=h||document.createElement(a[c]||"div");c="on"+c;var g=c in h;if(!g&&h.setAttribute){h.setAttribute(c,"return;");g=typeof h[c]=="function"}h=null;return t?(b[c]=g):g}}(),F={}.hasOwnProperty,O;O=typeof F!=="undefined"&&typeof F.call!=="undefined"?function(a,b){return F.call(a,b)}:function(a,b){return b in a&&typeof a.constructor.prototype[b]==="undefined"};
d.canvas=function(){return!!e.createElement("canvas").getContext};d.canvastext=function(){return!!(d.canvas()&&typeof e.createElement("canvas").getContext("2d").fillText=="function")};d.geolocation=function(){return!!navigator.geolocation};d.crosswindowmessaging=function(){return!!i.postMessage};d.websqldatabase=function(){var a=!!i.openDatabase;if(a)try{a=!!openDatabase("testdb","1.0","html5 test db",2E5)}catch(b){a=false}return a};d.indexedDB=function(){return!!i.indexedDB};d.hashchange=function(){return u("hashchange",
i)&&(document.documentMode===I||document.documentMode>7)};d.historymanagement=function(){return!!(i.history&&history.pushState)};d.draganddrop=function(){return u("drag")&&u("dragstart")&&u("dragenter")&&u("dragover")&&u("dragleave")&&u("dragend")&&u("drop")};d.websockets=function(){return"WebSocket"in i};d.rgba=function(){m.cssText="background-color:rgba(150,255,150,.5)";return(""+m.backgroundColor).indexOf("rgba")!==-1};d.hsla=function(){m.cssText="background-color:hsla(120,40%,100%,.5)";return(""+
m.backgroundColor).indexOf("rgba")!==-1};d.multiplebgs=function(){m.cssText="background:url(//:),url(//:),red url(//:)";return/(url\s*\(.*?){3}/.test(m.background)};d.backgroundsize=function(){return r("backgroundSize")};d.borderimage=function(){return r("borderImage")};d.borderradius=function(){return r("borderRadius","",function(a){return(""+a).indexOf("orderRadius")!==-1})};d.boxshadow=function(){return r("boxShadow")};d.opacity=function(){var a=y.join("opacity:.5;")+"";m.cssText=a;return(""+m.opacity).indexOf("0.5")!==
-1};d.cssanimations=function(){return r("animationName")};d.csscolumns=function(){return r("columnCount")};d.cssgradients=function(){var a=("background-image:"+y.join("gradient(linear,left top,right bottom,from(#9f9),to(white));background-image:")+y.join("linear-gradient(left top,#9f9, white);background-image:")).slice(0,-17);m.cssText=a;return(""+m.backgroundImage).indexOf("gradient")!==-1};d.cssreflections=function(){return r("boxReflect")};d.csstransforms=function(){return!!C(["transformProperty",
"WebkitTransform","MozTransform","OTransform","msTransform"])};d.csstransforms3d=function(){var a=!!C(["perspectiveProperty","WebkitPerspective","MozPerspective","OPerspective","msPerspective"]);if(a){var b=document.createElement("style"),c=e.createElement("div");b.textContent="@media ("+y.join("transform-3d),(")+"modernizr){#modernizr{height:3px}}";e.getElementsByTagName("head")[0].appendChild(b);c.id="modernizr";s.appendChild(c);a=c.offsetHeight===3;b.parentNode.removeChild(b);c.parentNode.removeChild(c)}return a};
d.csstransitions=function(){return r("transitionProperty")};d.fontface=function(){var a;if(/*@cc_on@if(@_jscript_version>=5)!@end@*/0)a=true;else{var b=e.createElement("style"),c=e.createElement("span"),h,t=false,g=e.body,o,w;b.textContent="@font-face{font-family:testfont;src:url('data:font/ttf;base64,AAEAAAAMAIAAAwBAT1MvMliohmwAAADMAAAAVmNtYXCp5qrBAAABJAAAANhjdnQgACICiAAAAfwAAAAEZ2FzcP//AAMAAAIAAAAACGdseWYv5OZoAAACCAAAANxoZWFk69bnvwAAAuQAAAA2aGhlYQUJAt8AAAMcAAAAJGhtdHgGDgC4AAADQAAAABRsb2NhAIQAwgAAA1QAAAAMbWF4cABVANgAAANgAAAAIG5hbWUgXduAAAADgAAABPVwb3N03NkzmgAACHgAAAA4AAECBAEsAAUAAAKZAswAAACPApkCzAAAAesAMwEJAAACAAMDAAAAAAAAgAACbwAAAAoAAAAAAAAAAFBmRWQAAAAgqS8DM/8zAFwDMwDNAAAABQAAAAAAAAAAAAMAAAADAAAAHAABAAAAAABGAAMAAQAAAK4ABAAqAAAABgAEAAEAAgAuqQD//wAAAC6pAP///9ZXAwAAAAAAAAACAAAABgBoAAAAAAAvAAEAAAAAAAAAAAAAAAAAAAABAAIAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAEACoAAAAGAAQAAQACAC6pAP//AAAALqkA////1lcDAAAAAAAAAAIAAAAiAogAAAAB//8AAgACACIAAAEyAqoAAwAHAC6xAQAvPLIHBADtMrEGBdw8sgMCAO0yALEDAC88sgUEAO0ysgcGAfw8sgECAO0yMxEhESczESMiARDuzMwCqv1WIgJmAAACAFUAAAIRAc0ADwAfAAATFRQWOwEyNj0BNCYrASIGARQGKwEiJj0BNDY7ATIWFX8aIvAiGhoi8CIaAZIoN/43KCg3/jcoAWD0JB4eJPQkHh7++EY2NkbVRjY2RgAAAAABAEH/+QCdAEEACQAANjQ2MzIWFAYjIkEeEA8fHw8QDxwWFhwWAAAAAQAAAAIAAIuYbWpfDzz1AAsEAAAAAADFn9IuAAAAAMWf0i797/8zA4gDMwAAAAgAAgAAAAAAAAABAAADM/8zAFwDx/3v/98DiAABAAAAAAAAAAAAAAAAAAAABQF2ACIAAAAAAVUAAAJmAFUA3QBBAAAAKgAqACoAWgBuAAEAAAAFAFAABwBUAAQAAgAAAAEAAQAAAEAALgADAAMAAAAQAMYAAQAAAAAAAACLAAAAAQAAAAAAAQAhAIsAAQAAAAAAAgAFAKwAAQAAAAAAAwBDALEAAQAAAAAABAAnAPQAAQAAAAAABQAKARsAAQAAAAAABgAmASUAAQAAAAAADgAaAUsAAwABBAkAAAEWAWUAAwABBAkAAQBCAnsAAwABBAkAAgAKAr0AAwABBAkAAwCGAscAAwABBAkABABOA00AAwABBAkABQAUA5sAAwABBAkABgBMA68AAwABBAkADgA0A/tDb3B5cmlnaHQgMjAwOSBieSBEYW5pZWwgSm9obnNvbi4gIFJlbGVhc2VkIHVuZGVyIHRoZSB0ZXJtcyBvZiB0aGUgT3BlbiBGb250IExpY2Vuc2UuIEtheWFoIExpIGdseXBocyBhcmUgcmVsZWFzZWQgdW5kZXIgdGhlIEdQTCB2ZXJzaW9uIDMuYmFlYzJhOTJiZmZlNTAzMiAtIHN1YnNldCBvZiBKdXJhTGlnaHRiYWVjMmE5MmJmZmU1MDMyIC0gc3Vic2V0IG9mIEZvbnRGb3JnZSAyLjAgOiBKdXJhIExpZ2h0IDogMjMtMS0yMDA5YmFlYzJhOTJiZmZlNTAzMiAtIHN1YnNldCBvZiBKdXJhIExpZ2h0VmVyc2lvbiAyIGJhZWMyYTkyYmZmZTUwMzIgLSBzdWJzZXQgb2YgSnVyYUxpZ2h0aHR0cDovL3NjcmlwdHMuc2lsLm9yZy9PRkwAQwBvAHAAeQByAGkAZwBoAHQAIAAyADAAMAA5ACAAYgB5ACAARABhAG4AaQBlAGwAIABKAG8AaABuAHMAbwBuAC4AIAAgAFIAZQBsAGUAYQBzAGUAZAAgAHUAbgBkAGUAcgAgAHQAaABlACAAdABlAHIAbQBzACAAbwBmACAAdABoAGUAIABPAHAAZQBuACAARgBvAG4AdAAgAEwAaQBjAGUAbgBzAGUALgAgAEsAYQB5AGEAaAAgAEwAaQAgAGcAbAB5AHAAaABzACAAYQByAGUAIAByAGUAbABlAGEAcwBlAGQAIAB1AG4AZABlAHIAIAB0AGgAZQAgAEcAUABMACAAdgBlAHIAcwBpAG8AbgAgADMALgBiAGEAZQBjADIAYQA5ADIAYgBmAGYAZQA1ADAAMwAyACAALQAgAHMAdQBiAHMAZQB0ACAAbwBmACAASgB1AHIAYQBMAGkAZwBoAHQAYgBhAGUAYwAyAGEAOQAyAGIAZgBmAGUANQAwADMAMgAgAC0AIABzAHUAYgBzAGUAdAAgAG8AZgAgAEYAbwBuAHQARgBvAHIAZwBlACAAMgAuADAAIAA6ACAASgB1AHIAYQAgAEwAaQBnAGgAdAAgADoAIAAyADMALQAxAC0AMgAwADAAOQBiAGEAZQBjADIAYQA5ADIAYgBmAGYAZQA1ADAAMwAyACAALQAgAHMAdQBiAHMAZQB0ACAAbwBmACAASgB1AHIAYQAgAEwAaQBnAGgAdABWAGUAcgBzAGkAbwBuACAAMgAgAGIAYQBlAGMAMgBhADkAMgBiAGYAZgBlADUAMAAzADIAIAAtACAAcwB1AGIAcwBlAHQAIABvAGYAIABKAHUAcgBhAEwAaQBnAGgAdABoAHQAdABwADoALwAvAHMAYwByAGkAcAB0AHMALgBzAGkAbAAuAG8AcgBnAC8ATwBGAEwAAAAAAgAAAAAAAP+BADMAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAQACAQIAEQt6ZXJva2F5YWhsaQ==')}";
e.getElementsByTagName("head")[0].appendChild(b);c.setAttribute("style","font:99px _,arial,helvetica;position:absolute;visibility:hidden");if(!g){g=s.appendChild(e.createElement("fontface"));t=true}c.innerHTML="........";c.id="fonttest";g.appendChild(c);h=c.offsetWidth*c.offsetHeight;c.style.font="99px testfont,_,arial,helvetica";a=h!==c.offsetWidth*c.offsetHeight;var v=function(){if(g.parentNode){a=j.fontface=h!==c.offsetWidth*c.offsetHeight;s.className=s.className.replace(/(no-)?fontface\b/,"")+
(a?" ":" no-")+"fontface"}};setTimeout(v,75);setTimeout(v,150);addEventListener("load",function(){v();(w=true)&&o&&o(a);setTimeout(function(){t||(g=c);g.parentNode.removeChild(g);b.parentNode.removeChild(b)},50)},false)}j._fontfaceready=function(p){w||a?p(a):(o=p)};return a||h!==c.offsetWidth};d.video=function(){var a=e.createElement("video"),b=!!a.canPlayType;if(b){b=new Boolean(b);b.ogg=a.canPlayType('video/ogg; codecs="theora"');b.h264=a.canPlayType('video/mp4; codecs="avc1.42E01E"');b.webm=a.canPlayType('video/webm; codecs="vp8, vorbis"')}return b};
d.audio=function(){var a=e.createElement("audio"),b=!!a.canPlayType;if(b){b=new Boolean(b);b.ogg=a.canPlayType('audio/ogg; codecs="vorbis"');b.mp3=a.canPlayType("audio/mpeg;");b.wav=a.canPlayType('audio/wav; codecs="1"');b.m4a=a.canPlayType("audio/x-m4a;")||a.canPlayType("audio/aac;")}return b};d.localStorage=function(){return"localStorage"in i&&i.localStorage!==null};d.sessionStorage=function(){try{return"sessionStorage"in i&&i.sessionStorage!==null}catch(a){return false}};d.webworkers=function(){return!!i.Worker};
d.applicationCache=function(){var a=i.applicationCache;return!!(a&&typeof a.status!="undefined"&&typeof a.update=="function"&&typeof a.swapCache=="function")};d.svg=function(){return!!e.createElementNS&&!!e.createElementNS("http://www.w3.org/2000/svg","svg").createSVGRect};d.smil=function(){return!!e.createElementNS&&/SVG/.test(M.call(e.createElementNS("http://www.w3.org/2000/svg","animate")))};d.svgclippaths=function(){return!!e.createElementNS&&/SVG/.test(M.call(e.createElementNS("http://www.w3.org/2000/svg",
"clipPath")))};for(var z in d)if(O(d,z))N.push(((j[z.toLowerCase()]=d[z]())?"":"no-")+z.toLowerCase());j[E]||P();j.addTest=function(a,b){a=a.toLowerCase();if(!j[a]){b=!!b();s.className+=" "+(b?"":"no-")+a;j[a]=b;return j}};m.cssText="";D=n=null;(function(){var a=e.createElement("div");a.innerHTML="<elem></elem>";return a.childNodes.length!==1})()&&function(a,b){function c(f,k){if(o[f])o[f].styleSheet.cssText+=k;else{var l=t[G],q=b[A]("style");q.media=f;l.insertBefore(q,l[G]);o[f]=q;c(f,k)}}function h(f,
k){for(var l=new RegExp("\\b("+w+")\\b(?!.*[;}])","gi"),q=function(B){return".iepp_"+B},x=-1;++x<f.length;){k=f[x].media||k;h(f[x].imports,k);c(k,f[x].cssText.replace(l,q))}}for(var t=b.documentElement,g=b.createDocumentFragment(),o={},w="abbr|article|aside|audio|canvas|command|datalist|details|figure|figcaption|footer|header|hgroup|keygen|mark|meter|nav|output|progress|section|source|summary|time|video",v=w.split("|"),p=[],H=-1,G="firstChild",A="createElement";++H<v.length;){b[A](v[H]);g[A](v[H])}g=
g.appendChild(b[A]("div"));a.attachEvent("onbeforeprint",function(){for(var f,k=b.getElementsByTagName("*"),l,q,x=new RegExp("^"+w+"$","i"),B=-1;++B<k.length;)if((f=k[B])&&(q=f.nodeName.match(x))){l=new RegExp("^\\s*<"+q+"(.*)\\/"+q+">\\s*$","i");g.innerHTML=f.outerHTML.replace(/\r|\n/g," ").replace(l,f.currentStyle.display=="block"?"<div$1/div>":"<span$1/span>");l=g.childNodes[0];l.className+=" iepp_"+q;l=p[p.length]=[f,l];f.parentNode.replaceChild(l[1],l[0])}h(b.styleSheets,"all")});a.attachEvent("onafterprint",
function(){for(var f=-1,k;++f<p.length;)p[f][1].parentNode.replaceChild(p[f][0],p[f][1]);for(k in o)t[G].removeChild(o[k]);o={};p=[]})}(this,e);j._enableHTML5=true;j._version="1.5";s.className=s.className.replace(/\bno-js\b/,"")+" js";s.className+=" "+N.join(" ");return j}(this,this.document);

1270
assets/3rdparty/pacman/pacman.js vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,3 @@
/* SCEditor v2.1.3 | (C) 2017, Sam Clarke | sceditor.com/license */
!function(e){"use strict";var t="sce-autodraft-"+location.pathname+location.search;function i(e){localStorage.removeItem(e||t)}e.plugins.autosave=function(){var a,e=this,o=t,r=864e5,n=function(e){localStorage.setItem(o,JSON.stringify(e))},s=function(){return JSON.parse(localStorage.getItem(o))};e.init=function(){var e=(a=this).opts&&a.opts.autosave||{};n=e.save||n,s=e.load||s,o=e.storageKey||o,r=e.expires||r,function(){for(var e=0;e<localStorage.length;e++){var t=localStorage.key(e);if(/^sce\-autodraft\-/.test(t)){var a=JSON.parse(localStorage.getItem(o));a&&a.time<Date.now()-r&&i(t)}}}()},e.signalReady=function(){for(var e=a.getContentAreaContainer();e;){if(/form/i.test(e.nodeName)){e.addEventListener("submit",i.bind(null,o),!0);break}e=e.parentNode}var t=s();t&&(a.sourceMode(t.sourceMode),a.val(t.value,!1),a.focus(),t.sourceMode?a.sourceEditorCaret(t.caret):a.getRangeHelper().restoreRange()),n({caret:this.sourceEditorCaret(),sourceMode:this.sourceMode(),value:a.val(null,!1),time:Date.now()})},e.signalValuechangedEvent=function(e){n({caret:this.sourceEditorCaret(),sourceMode:this.sourceMode(),value:e.detail.rawValue,time:Date.now()})}},e.plugins.autosave.clear=i}(sceditor);

View File

@ -0,0 +1,3 @@
/* SCEditor v2.1.3 | (C) 2017, Sam Clarke | sceditor.com/license */
!function(u,e){"use strict";var a=e.dom,c=/(^|\s)(?:https?:\/\/)?(?:www\.)?(?:youtu\.be\/|youtube\.com\/watch\?v=)([^"&?\/ ]{11})(?:\&[\&_\?0-9a-z\#]+)?(\s|$)/i;e.plugins.autoyoutube=function(){this.signalPasteRaw=function(e){if(!a.closest(this.currentNode(),"code")&&(e.html||e.text)){var t=u.createElement("div");e.html?t.innerHTML=e.html:t.textContent=e.text,function e(t){for(var o,n=t.firstChild;n;){if(3===n.nodeType){var i=n.nodeValue,r=n.parentNode,s=i.match(c);s&&(r.insertBefore(u.createTextNode(i.substr(0,s.index)+s[1]),n),r.insertBefore(a.parseHTML('<iframe width="560" height="315" frameborder="0" src="https://www.youtube-nocookie.com/embed/'+(o=s[2])+'" data-youtube-id="'+o+'" allowfullscreen></iframe>'),n),n.nodeValue=s[3]+i.substr(s.index+s[0].length))}else a.is(n,"code")||e(n);n=n.nextSibling}}(t),e.html=t.innerHTML}}}}(document,sceditor);

View File

@ -0,0 +1,3 @@
/* SCEditor v2.1.3 | (C) 2017, Sam Clarke | sceditor.com/license */
!function(n){"use strict";var v="data:image/gif;base64,R0lGODlhlgBkAPABAH19ffb29iH5BAAKAAAAIf4aQ3JlYXRlZCB3aXRoIGFqYXhsb2FkLmluZm8AIf8LTkVUU0NBUEUyLjADAQAAACwAAAAAlgBkAAAC1YyPqcvtD6OctNqLs968+w+G4kiW5omm6sq27gvH8kzX9o3n+s73/g8MCofEovGITCqXzKbzCY1Kp9Sq9YrNarfcrvcLDovH5LL5jE6r1+y2+w2Py+f0uv2OvwD2fP6iD/gH6Pc2GIhg2JeQSNjGuLf4GMlYKIloefAIUEl52ZmJyaY5mUhqyFnqmQr6KRoaMKp66hbLumpQ69oK+5qrOyg4a6qYV2x8jJysvMzc7PwMHS09TV1tfY2drb3N3e39DR4uPk5ebn6Onq6+zt7u/g4fL99UAAAh+QQACgAAACwAAAAAlgBkAIEAAAB9fX329vYAAAAC3JSPqcvtD6OctNqLs968+w+G4kiW5omm6sq27gvH8kzX9o3n+s73/g8MCofEovGITCqXzKbzCY1Kp9Sq9YrNarfcrvcLDovH5LL5jE6r1+y2+w2Py+f0uv2OvwD2fP4iABgY+CcoCNeHuJdQyLjIaOiWiOj4CEhZ+SbZd/nI2RipqYhQOThKGpAZCuBZyArZprpqSupaCqtaazmLCRqai7rb2av5W5wqSShcm8fc7PwMHS09TV1tfY2drb3N3e39DR4uPk5ebn6Onq6+zt7u/g4fLz9PX29/j5/vVAAAIfkEAAoAAAAsAAAAAJYAZACBAAAAfX199vb2AAAAAuCUj6nL7Q+jnLTai7PevPsPhuJIluaJpurKtu4Lx/JM1/aN5/rO9/4PDAqHxKLxiEwql8ym8wmNSqfUqvWKzWq33K73Cw6Lx+Sy+YxOq9fstvsNj8vn9Lr9jr8E9nz+AgAYGLjQVwhXiJgguAiYgGjo9tinyCjoKLn3hpmJUGmJsBmguUnpCXCJOZraaXoKShoJe9DqehCqKlnqiZobuzrbyvuIO8xqKpxIPKlwrPCbBx0tPU1dbX2Nna29zd3t/Q0eLj5OXm5+jp6uvs7e7v4OHy8/T19vf4+fr7/P379UAAAh+QQACgAAACwAAAAAlgBkAIEAAAB9fX329vYAAAAC4JSPqcvtD6OctNqLs968+w+G4kiW5omm6sq27gvH8kzX9o3n+s73/g8MCofEovGITCqXzKbzCY1Kp9Sq9YrNarfcrvcLDovH5LL5jE6r1+y2+w2Py+f0uv2OvwT2fP6iD7gAMEhICAeImIAYiFDoOPi22KcouZfw6BhZGUBZeYlp6LbJiTD6CQqg6Vm6eQqqKtkZ24iaKtrKunpQa9tmmju7Wwu7KFtMi3oYDMzompkHHS09TV1tfY2drb3N3e39DR4uPk5ebn6Onq6+zt7u/g4fLz9PX29/j5+vv8/f31QAADs=",c=void 0!==window.FileReader,u=/data:[^;]+;base64,/i;function g(e){for(var t=e.substr(5,e.indexOf(";")-5),n=atob(e.substr(e.indexOf(",")+1)),r=new Uint8Array(n.length),i=0;i<n.length;i++)r[i]=n[i].charCodeAt(0);try{return new Blob([r],{type:t})}catch(e){return null}}n.plugins.dragdrop=function(){if(c){var A,r,o,i,a,s=0;this.signalReady=function(){A=(r=this).opts.dragdrop||{},o=A.handleFile,i=r.getContentAreaContainer().parentNode,a=i.appendChild(n.dom.parseHTML('<div class="sceditor-dnd-cover" style="display: none"><p>'+r._("Drop files here")+"</p></div>").firstChild),i.addEventListener("dragover",e),i.addEventListener("dragleave",d),i.addEventListener("dragend",d),i.addEventListener("drop",t),r.getBody().addEventListener("dragover",e),r.getBody().addEventListener("drop",d)},this.signalPasteHtml=function(e){if(!("handlePaste"in A)||A.handlePaste){var t=document.createElement("div");t.innerHTML=e.val;for(var n=t.querySelectorAll("img"),r=0;r<n.length;r++){var i=n[r];if(u.test(i.src)){var a=g(i.src);a&&l(a)?o(a,f(i)):i.parentNode.removeChild(i)}}e.val=t.innerHTML}}}function d(){a.style.display="none",i.className=i.className.replace(/(^| )dnd( |$)/g,"")}function l(e){return!("application/x-moz-file"!==e.type&&A.allowedTypes&&A.allowedTypes.indexOf(e.type)<0)&&(!A.isAllowed||A.isAllowed(e))}function f(e){var n=document.createElement("img");function t(e){var t=r.getBody().ownerDocument.getElementById(n.id);t&&("string"==typeof e&&t.insertAdjacentHTML("afterend",e),t.parentNode.removeChild(t))}return n.src=v,n.className="sceditor-ignore",n.id="sce-dragdrop-"+s++,function(){return e?e.parentNode.replaceChild(n,e):r.wysiwygEditorInsertHtml(n.outerHTML),{insert:function(e){t(e)},cancel:t}}}function e(e){for(var t=e.dataTransfer,n=t.files.length||!t.items?t.files:t.items,r=0;r<n.length;r++)if("string"===n[r].kind)return;"none"===a.style.display&&(a.style.display="block",i.className+=" dnd"),e.preventDefault()}function t(e){var t=e.dataTransfer,n=t.files.length||!t.items?t.files:t.items;d();for(var r=0;r<n.length;r++){if("string"===n[r].kind)return;l(n[r])&&o(n[r],f())}e.preventDefault()}}}(sceditor);

View File

@ -0,0 +1,3 @@
/* SCEditor v2.1.3 | (C) 2017, Sam Clarke | sceditor.com/license */
!function(i){"use strict";i.plugins.format=function(){var n,a,c={p:"Paragraph",h1:"Heading 1",h2:"Heading 2",h3:"Heading 3",h4:"Heading 4",h5:"Heading 5",h6:"Heading 6",address:"Address",pre:"Preformatted Text"};this.init=function(){var e=this.opts,t=e.paragraphformat;e.format&&"bbcode"===e.format||(t&&(t.tags&&(c=t.tags),t.excludeTags&&t.excludeTags.forEach(function(e){delete c[e]})),this.commands.format||(this.commands.format={exec:a,txtExec:a,tooltip:"Format Paragraph"}),e.toolbar===i.defaultOptions.toolbar&&(e.toolbar=e.toolbar.replace(",color,",",color,format,")))},n=function(e,t){e.sourceMode()?e.insert("<"+t+">","</"+t+">"):e.execCommand("formatblock","<"+t+">")},a=function(e){var o=this,r=document.createElement("div");i.utils.each(c,function(t,a){var e=document.createElement("a");e.className="sceditor-option",e.textContent=a.name||a,e.addEventListener("click",function(e){o.closeDropDown(!0),a.exec?a.exec(o):n(o,t),e.preventDefault()}),r.appendChild(e)}),o.createDropDown(e,"format",r)}}}(sceditor);

View File

@ -0,0 +1,3 @@
/* SCEditor v2.1.3 | (C) 2017, Sam Clarke | sceditor.com/license */
!function(t){"use strict";var i=t.utils.extend;t.plugins.plaintext=function(){var e=!0;this.init=function(){var t=this.commands,n=this.opts;n&&n.plaintext&&n.plaintext.addButton&&(e=n.plaintext.enabled,t.pastetext=i(t.pastetext||{},{state:function(){return e?1:0},exec:function(){e=!e}}))},this.signalPasteRaw=function(t){if(e){if(t.html&&!t.text){var n=document.createElement("div");n.innerHTML=t.html,t.text=n.innerText}t.html=null}}}}(sceditor);

View File

@ -0,0 +1,2 @@
/* SCEditor v2.1.3 | (C) 2017, Sam Clarke | sceditor.com/license */

View File

@ -0,0 +1,3 @@
/* SCEditor v2.1.3 | (C) 2017, Sam Clarke | sceditor.com/license */
!function(e){"use strict";sceditor.plugins.undo=function(){var r,o,e=this,u=0,a=50,n=[],c=[],s=!1,l=function(e){s=!0,o=e.value,r.sourceMode(e.sourceMode),r.val(e.value,!1),r.focus(),e.sourceMode?r.sourceEditorCaret(e.caret):r.getRangeHelper().restoreRange(),s=!1};e.init=function(){a=(r=this).undoLimit||a,r.addShortcut("ctrl+z",e.undo),r.addShortcut("ctrl+shift+z",e.redo),r.addShortcut("ctrl+y",e.redo)},e.undo=function(){var e=c.pop(),t=r.val(null,!1);return e&&!n.length&&t===e.value&&(e=c.pop()),e&&(n.length||n.push({caret:r.sourceEditorCaret(),sourceMode:r.sourceMode(),value:t}),n.push(e),l(e)),!1},e.redo=function(){var e=n.pop();return c.length||(c.push(e),e=n.pop()),e&&(c.push(e),l(e)),!1},e.signalReady=function(){var e=r.val(null,!1);o=e,c.push({caret:this.sourceEditorCaret(),sourceMode:this.sourceMode(),value:e})},e.signalValuechangedEvent=function(e){var t=e.detail.rawValue;0<a&&c.length>a&&c.shift(),!s&&o&&o!==t&&(n.length=0,(u+=function(e,t){var r,o,u,a,n=e.length,c=t.length,s=Math.max(n,c);for(r=0;r<s&&e.charAt(r)===t.charAt(r);r++);for(u=n<c?c-n:0,a=c<n?n-c:0,o=s-1;0<=o&&e.charAt(o-u)===t.charAt(o-a);o--);return o-r+1}(o,t))<20||u<50&&!/\s$/g.test(e.rawValue)||(c.push({caret:r.sourceEditorCaret(),sourceMode:r.sourceMode(),value:t}),u=0,o=t))}}}();

View File

@ -0,0 +1,3 @@
/* SCEditor v2.1.3 | (C) 2017, Sam Clarke | sceditor.com/license */
!function(t,r){"use strict";var e=t.plugins;function n(c){if(c._scePatched)return c;var t=function(){for(var t=[],e=0;e<arguments.length;e++){var n=arguments[e];n&&n.nodeType?t.push(r(n)):t.push(n)}return c.apply(this,t)};return t._scePatched=!0,t}function c(t){if(t._scePatched)return t;var e=function(){return r(t.apply(this,arguments))};return e._scePatched=!0,e}var o=t.command.set;if(t.command.set=function(t,e){return e&&"function"==typeof e.exec&&(e.exec=n(e.exec)),e&&"function"==typeof e.txtExec&&(e.txtExec=n(e.txtExec)),o.call(this,t,e)},e.bbcode){var a=e.bbcode.bbcode.set;e.bbcode.bbcode.set=function(t,e){return e&&"function"==typeof e.format&&(e.format=n(e.format)),a.call(this,t,e)}}var i=t.create;t.create=function(t,e){if(i.call(this,t,e),t&&t._sceditor){var n=t._sceditor;n.getBody=c(n.getBody),n.getContentAreaContainer=c(n.getContentAreaContainer)}}}(sceditor,jQuery);

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
/*! SCEditor | (C) 2011-2013, Sam Clarke | sceditor.com/license */body,code:before,html,p,table{margin:0;padding:0;font-family:Verdana,Arial,Helvetica,sans-serif;font-size:14px;color:#111;line-height:1.25;overflow:visible}html{height:100%}.ios{overflow:auto;-webkit-overflow-scrolling:touch}.ios body{position:relative;overflow:auto}body{min-height:100%;word-wrap:break-word}body.placeholder::before{content:attr(placeholder);color:#555;font-style:italic}ol,ul{margin-top:0;margin-bottom:0;padding-top:0;padding-bottom:0}table,td{border:1px dotted #000;empty-cells:show}table td{min-width:5px}code{display:block;background:#f1f1f1;white-space:pre;padding:1em;text-align:left;margin:.25em 0;direction:ltr}blockquote{background:#fff7d9;margin:.25em 0;border-left:.3em solid #f4e59f;padding:.5em .5em .5em .75em}blockquote cite{font-weight:700;display:block;font-size:1em;margin:0 -.5em .25em -.75em;padding:0 .5em .15em .75em;border-bottom:1px solid #f4e59f}h1,h2,h3,h4,h5,h6{padding:0;margin:0}div,p{min-height:1.25em}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

BIN
assets/audio/notif_song.mp3 Normal file

Binary file not shown.

BIN
assets/audio/notif_song.ogg Normal file

Binary file not shown.

BIN
assets/audio/phone_ring.mp3 Normal file

Binary file not shown.

BIN
assets/audio/phone_ring.ogg Normal file

Binary file not shown.

View File

@ -0,0 +1,551 @@
/*! SCEditor | (C) 2011-2016, Sam Clarke | sceditor.com/license */
/**
* Default SCEditor
* http://www.sceditor.com/
*
* Copyright (C) 2011-16, Sam Clarke
*
* SCEditor is licensed under the MIT license:
* http://www.opensource.org/licenses/mit-license.php
*
* This version of the theme has been adapated by Pierre HUBERT for Comunic
* Modifications: Copyright (c) 2018-2019 Pierre HUBERT (MIT License)
*/
div.sceditor-grip,
.sceditor-button div {
background-image: url("famfamfam.png");
background-repeat: no-repeat;
width: 16px;
height: 16px;
}
.sceditor-button-youtube div {
background-position: 0px 0px;
}
.sceditor-button-link div {
background-position: 0px -16px;
}
.sceditor-button-unlink div {
background-position: 0px -32px;
}
.sceditor-button-underline div {
background-position: 0px -48px;
}
.sceditor-button-time div {
background-position: 0px -64px;
}
.sceditor-button-table div {
background-position: 0px -80px;
}
.sceditor-button-superscript div {
background-position: 0px -96px;
}
.sceditor-button-subscript div {
background-position: 0px -112px;
}
.sceditor-button-strike div {
background-position: 0px -128px;
}
.sceditor-button-source div {
background-position: 0px -144px;
}
.sceditor-button-size div {
background-position: 0px -160px;
}
.sceditor-button-rtl div {
background-position: 0px -176px;
}
.sceditor-button-right div {
background-position: 0px -192px;
}
.sceditor-button-removeformat div {
background-position: 0px -208px;
}
.sceditor-button-quote div {
background-position: 0px -224px;
}
.sceditor-button-print div {
background-position: 0px -240px;
}
.sceditor-button-pastetext div {
background-position: 0px -256px;
}
.sceditor-button-paste div {
background-position: 0px -272px;
}
.sceditor-button-outdent div {
background-position: 0px -288px;
}
.sceditor-button-orderedlist div {
background-position: 0px -304px;
}
.sceditor-button-maximize div {
background-position: 0px -320px;
}
.sceditor-button-ltr div {
background-position: 0px -336px;
}
.sceditor-button-left div {
background-position: 0px -352px;
}
.sceditor-button-justify div {
background-position: 0px -368px;
}
.sceditor-button-italic div {
background-position: 0px -384px;
}
.sceditor-button-indent div {
background-position: 0px -400px;
}
.sceditor-button-image div {
background-position: 0px -416px;
}
.sceditor-button-horizontalrule div {
background-position: 0px -432px;
}
.sceditor-button-format div {
background-position: 0px -448px;
}
.sceditor-button-font div {
background-position: 0px -464px;
}
.sceditor-button-emoticon div {
background-position: 0px -480px;
}
.sceditor-button-email div {
background-position: 0px -496px;
}
.sceditor-button-date div {
background-position: 0px -512px;
}
.sceditor-button-cut div {
background-position: 0px -528px;
}
.sceditor-button-copy div {
background-position: 0px -544px;
}
.sceditor-button-color div {
background-position: 0px -560px;
}
.sceditor-button-code div {
background-position: 0px -576px;
}
.sceditor-button-center div {
background-position: 0px -592px;
}
.sceditor-button-bulletlist div {
background-position: 0px -608px;
}
.sceditor-button-bold div {
background-position: 0px -624px;
}
div.sceditor-grip {
background-position: 0px -640px;
width: 10px;
height: 10px;
}
.rtl div.sceditor-grip {
background-position: 0px -650px;
}
/**
* SCEditor
* http://www.sceditor.com/
*
* Copyright (C) 2017, Sam Clarke (samclarke.com)
*
* SCEditor is licensed under the MIT license:
* http://www.opensource.org/licenses/mit-license.php
*/
/*---------------------------------------------------
LESS Elements 0.7
---------------------------------------------------
A set of useful LESS mixins
More info at: http://lesselements.com
---------------------------------------------------*/
.sceditor-container {
display: -ms-flexbox;
display: flex;
-ms-flex-direction: column;
flex-direction: column;
position: relative;
/*background: #fff;
border: 1px solid #d9d9d9;
font-size: 13px;
font-family: Arial, "Helvetica Neue", Helvetica, sans-serif;
color: #333;
line-height: 1;
font-weight: bold;
height: 250px;
border-radius: 4px;
background-clip: padding-box;*/
width: 100%;
min-height: 100px;
font-size: 14px;
line-height: 18px;
border: 1px solid #dddddd;
margin-bottom: 10px;
}
.sceditor-container *,
.sceditor-container *:before,
.sceditor-container *:after {
-webkit-box-sizing: content-box;
-moz-box-sizing: content-box;
box-sizing: content-box;
}
.sceditor-container,
.sceditor-container div,
div.sceditor-dropdown,
div.sceditor-dropdown div {
padding: 0;
margin: 0;
z-index: 3;
}
.sceditor-container iframe,
.sceditor-container textarea {
display: block;
-ms-flex: 1 1 0%;
flex: 1 1 0%;
line-height: 1.25;
border: 0;
outline: none;
font-family: Verdana, Arial, Helvetica, sans-serif;
font-size: 14px;
color: #111;
padding: 0;
margin: 0px;
resize: none;
background: #fff;
height: auto !important;
width: auto !important;
width: calc(100% - 10px) !important;
min-height: 1px;
}
.sceditor-container textarea {
margin: 7px 5px;
}
div.sceditor-dnd-cover {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
background: rgba(255, 255, 255, 0.2);
border: 5px dashed #aaa;
z-index: 200;
font-size: 2em;
text-align: center;
color: #aaa;
}
div.sceditor-dnd-cover p {
position: relative;
top: 45%;
pointer-events: none;
}
div.sceditor-resize-cover {
position: absolute;
top: 0;
left: 0;
background: #000;
width: 100%;
height: 100%;
z-index: 10;
opacity: 0.3;
}
div.sceditor-grip {
overflow: hidden;
width: 10px;
height: 10px;
cursor: pointer;
position: absolute;
bottom: 0;
right: 0;
z-index: 3;
line-height: 0;
}
div.sceditor-grip.has-icon {
background-image: none;
}
.sceditor-maximize {
position: fixed;
top: 0;
left: 0;
height: 100% !important;
width: 100% !important;
border-radius: 0;
background-clip: padding-box;
z-index: 2000;
}
html.sceditor-maximize,
body.sceditor-maximize {
height: 100%;
width: 100%;
padding: 0;
margin: 0;
overflow: hidden;
}
.sceditor-maximize div.sceditor-grip {
display: none;
}
.sceditor-maximize div.sceditor-toolbar {
border-radius: 0;
background-clip: padding-box;
}
/**
* Dropdown styleing
*/
div.sceditor-dropdown {
position: absolute;
border: 1px solid #ccc;
background: #fff;
z-index: 4000;
padding: 10px;
font-weight: normal;
font-size: 15px;
border-radius: 2px;
background-clip: padding-box;
box-shadow: 1px 2px 4px rgba(0, 0, 0, 0.2);
}
div.sceditor-dropdown *,
div.sceditor-dropdown *:before,
div.sceditor-dropdown *:after {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
div.sceditor-dropdown a,
div.sceditor-dropdown a:link {
color: #333;
}
div.sceditor-dropdown form {
margin: 0;
}
div.sceditor-dropdown label {
display: block;
font-weight: bold;
color: #3c3c3c;
padding: 4px 0;
}
div.sceditor-dropdown input,
div.sceditor-dropdown textarea {
font-family: Arial, "Helvetica Neue", Helvetica, sans-serif;
outline: 0;
padding: 4px;
border: 1px solid #ccc;
border-top-color: #888;
margin: 0 0 .75em;
border-radius: 1px;
background-clip: padding-box;
}
div.sceditor-dropdown textarea {
padding: 6px;
}
div.sceditor-dropdown input:focus,
div.sceditor-dropdown textarea:focus {
border-color: #aaa;
border-top-color: #666;
box-shadow: inset 0 1px 5px rgba(0, 0, 0, 0.1);
}
div.sceditor-dropdown .button {
font-weight: bold;
color: #444;
padding: 6px 12px;
background: #ececec;
border: solid 1px #ccc;
border-radius: 2px;
background-clip: padding-box;
cursor: pointer;
margin: .3em 0 0;
}
div.sceditor-dropdown .button:hover {
background: #f3f3f3;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15);
}
div.sceditor-font-picker,
div.sceditor-fontsize-picker,
div.sceditor-format {
padding: 6px 0;
}
div.sceditor-color-picker {
padding: 4px;
}
div.sceditor-emoticons,
div.sceditor-more-emoticons {
padding: 0;
}
.sceditor-pastetext textarea {
border: 1px solid #bbb;
width: 20em;
}
.sceditor-emoticons img,
.sceditor-more-emoticons img {
padding: 0;
cursor: pointer;
margin: 2px;
}
.sceditor-more {
border-top: 1px solid #bbb;
display: block;
text-align: center;
cursor: pointer;
font-weight: bold;
padding: 6px 0;
}
.sceditor-dropdown a:hover {
background: #eee;
}
.sceditor-fontsize-option,
.sceditor-font-option,
.sceditor-format a {
display: block;
padding: 7px 10px;
cursor: pointer;
text-decoration: none;
color: #222;
}
.sceditor-fontsize-option {
padding: 7px 13px;
}
.sceditor-color-column {
float: left;
}
.sceditor-color-option {
display: block;
border: 2px solid #fff;
height: 18px;
width: 18px;
overflow: hidden;
}
.sceditor-color-option:hover {
border: 1px solid #aaa;
}
/**
* Toolbar styleing
*/
div.sceditor-toolbar {
flex-shrink: 0;
overflow: hidden;
padding: 3px 5px 2px;
/*background: #f7f7f7;
border-bottom: 1px solid #c0c0c0;*/
line-height: 0;
text-align: left;
user-select: none;
border-radius: 3px 3px 0 0;
background-clip: padding-box;
}
div.sceditor-group {
display: inline-block;
/*background: #ddd;*/
margin: 1px 5px 1px 0;
padding: 1px;
/*border-bottom: 1px solid #aaa;*/
border-radius: 3px;
background-clip: padding-box;
}
.sceditor-button {
float: left;
cursor: pointer;
padding: 3px 5px;
width: 22px;
height: 20px;
background-clip: padding-box;
background: #ddd;
display: flex !important;
align-items: center;
justify-content: center;
}
.sceditor-button:first-child {
border-top-left-radius: 3px;
border-bottom-left-radius: 3px;
}
.sceditor-button:last-child {
border-top-right-radius: 3px;
border-bottom-right-radius: 3px;
}
.sceditor-button:hover,
.sceditor-button:active,
.sceditor-button.active {
background: #fff;
box-shadow: inset 1px 1px 0 rgba(0,0,0,0.3), inset -1px 0 rgba(0,0,0,0.3), inset 0 -1px 0 rgba(0,0,0,0.2);
}
.sceditor-button:active {
background: #fff;
box-shadow: inset 1px 1px 0 rgba(0,0,0,0.3), inset -1px 0 rgba(0,0,0,0.3), inset 0 -1px 0 rgba(0,0,0,0.2), inset 0 0 8px rgba(0,0,0,0.3);
}
.sceditor-button.disabled:hover {
background: inherit;
cursor: default;
box-shadow: none;
}
.sceditor-button,
.sceditor-button div {
display: block;
}
.sceditor-button svg {
display: inline-block;
height: 12px;
width: 12px;
margin: 2px 0;
fill: #111;
text-decoration: none;
pointer-events: none;
line-height: 1;
}
.sceditor-button.disabled svg {
fill: #888;
}
.sceditor-button div {
display: inline-block;
margin: 2px 0;
padding: 0;
overflow: hidden;
line-height: 0;
font-size: 0;
color: transparent;
}
.sceditor-button.has-icon div {
display: none;
}
.sceditor-button.disabled div {
opacity: 0.3;
}
.text .sceditor-button,
.text .sceditor-button div,
.sceditor-button.text,
.sceditor-button.text div,
.text-icon .sceditor-button,
.text-icon .sceditor-button div,
.sceditor-button.text-icon,
.sceditor-button.text-icon div {
display: inline-block;
width: auto;
line-height: 16px;
font-size: 1em;
color: inherit;
text-indent: 0;
}
.text-icon .sceditor-button.has-icon div,
.sceditor-button.has-icon div,
.text .sceditor-button div,
.sceditor-button.text div {
padding: 0 2px;
background: none;
}
.text .sceditor-button svg,
.sceditor-button.text svg {
display: none;
}
.text-icon .sceditor-button div,
.sceditor-button.text-icon div {
padding: 0 2px 0 20px;
}
.rtl div.sceditor-toolbar {
text-align: right;
}
.rtl .sceditor-button {
float: right;
}
.rtl div.sceditor-grip {
right: auto;
left: 0;
}

View File

@ -23,4 +23,16 @@ a {
.a:focus, .a:hover {
color: #72afd2;
}
.cursor-pointer {
cursor: pointer;
}
/**
* Sceditor iframe
*/
.sceditor-iframe-body {
padding: 5px;
padding-bottom: 0px;
}

View File

@ -0,0 +1,158 @@
/**
* Calls window stylesheet
*
* @author Pierre HUBERT
*/
#callsTarget {
position: fixed;
width: 100%;
height: 100%;
top: 0px;
visibility: hidden;
z-index: 1000;
}
.call-window {
width: 300px;
min-width: 300px;
min-height: 174px;
position: absolute;
top: 100px;
right: 10px;
z-index: 100;
border: 1px black solid;
display: flex;
flex-direction: column;
background-color: #000000b3;
visibility: visible;
}
.call-window .call-window-body{
flex: 1;
display: flex;
max-height: calc(100% - 62px);
}
/**
* Toolbar
*/
.call-toolbar {
height: 20px;
background-color: #000000b3;
color: white;
display: flex;
align-items: center;
padding-left: 10px;
}
.call-toolbar > i:first-child {
margin-right: 6px;
}
.call-title {
flex: 1;
cursor: move;
}
/**
* Loading message
*/
.loading-message-container {
position: absolute;
min-height: 112px;
width: 100%;
display: flex;
background: 1px #0009;
flex-direction: column;
justify-content: space-around;
text-align: center;
font-size: 150%;
color: white;
}
/**
* Stream target
*/
.call-window .streams-target {
flex: 1;
display: flex;
flex-wrap: wrap;
flex-direction: row;
max-width: 100%;
}
.call-window .streams-target video {
flex: 1;
flex-shrink: 1;
max-height: 100%;
}
/**
* Footer
*/
.call-window .call-footer {
height: 40px;
display: flex;
justify-content: space-around;
align-items: center;
}
.call-window .call-footer .call-option-button {
color: #fff6;
flex: 1;
text-align: center;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.call-window .call-footer .call-option-button.selected {
color: white;
}
.call-window .call-footer .call-option-button:hover {
background-color: #ffffff4d;
}
.call-window .call-footer .call-option-button:active {
background-color: #fff3;
}
.call-window .call-footer .call-option-button.hang-up-button {
color: #dd4b39;
}
/**
* Responsive mode
*/
@media screen and (max-width: 730px) {
#callsTarget {
z-index: 1030;
}
.streams-target {
flex-direction: column !important;
}
.streams-target video {
max-width: 100%;
}
.call-window {
position: fixed;
width: 100%;
height: 100%;
top: 0px !important;
left: 0px !important;
}
}

View File

@ -0,0 +1,33 @@
/**
* Ring screen stylesheet
*
* @author Pierre HUBERT
*/
.ring-call-container {
width: 100%;
height: 100%;
position: fixed;
top: 0px;
left: 0px;
background-color: #011932e6;
z-index: 1030;
display: flex;
justify-content: center;
align-items: center;
}
.call-message-box {
background-color: #fff6;
color: white;
padding: 10px;
border-radius: 5px;
min-width: 200px;
text-align: center;
font-size: 150%;
}
.ring-call-container .response-buttons {
display: flex;
justify-content: space-around;
}

View File

@ -4,6 +4,10 @@
* @author Pierre HUBERT
*/
/**
* Main definition
*/
#conversationsElem .box {
width: 250px;
height: 350px;
@ -15,6 +19,10 @@
margin-bottom: 0px;
}
#conversationsElem .chat-window .box-title {
font-size: 15px !important;
}
/**
* Conversations create message form
*/
@ -115,4 +123,8 @@
#conversationsElem .direct-chat-msg.not-last-message-from-user .direct-chat-img {
margin-bottom: -5px;
}
#conversationsElem .direct-chat-msg.open .dropdown-menu {
margin-top: -20px;
}

View File

@ -0,0 +1,23 @@
/**
* Incognito mode stylesheet
*
* @author Pierre HUBERT
*/
#incognito-block {
position: fixed;
left: 10px;
bottom: 54px;
text-align: center;
background-color: #001F3F;
padding: 10px;
color: white;
}
#incognito-block i {
font-size: 200%;
}
#incognito-block span {
display: block;
}

View File

@ -0,0 +1,16 @@
/**
* Pacman stylesheet
*
* @author Pierre HUBERT
*/
.pacman-iframe {
width: 342px;
height: 426px;
margin: auto;
display: block;
}
.pacman-iframe + p {
text-align: center;
}

View File

@ -17,6 +17,9 @@
margin-bottom: 10px;
}
.post-form .new-message-content-container {
margin-bottom: 15px;
}
/**
* Message type chooser

View File

@ -63,6 +63,20 @@
text-align: center;
}
.post .post-youtube.post-youtube-placeholder {
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-around;
background-color: #3c8dbc;
color: white;
border-radius: 5px;
}
.post .post-youtube.post-youtube-placeholder .title {
font-size: 150%;
}
.post .post-youtube {
width: 100%;
height: 300px;

View File

@ -1,8 +1,8 @@
/**
* ComunicWeb dark theme
*
* @author Pierre HUBERT
*/
* ComunicWeb dark theme
*
* @author Pierre HUBERT
*/
/**
* General definitions
@ -17,6 +17,10 @@
--white: silver;
}
body {
background-color: var(--black6);
}
p, h1, h2, h3, h4, h5, h6 {
color: var(--white);
}
@ -54,6 +58,14 @@ p, h1, h2, h3, h4, h5, h6 {
}
/**
* Callouts
*/
.callout.callout-info {
background-color: var(--black-5) !important;
}
/**
* Forms
*/
@ -227,6 +239,12 @@ fieldset[disabled] .form-control {
}
}
@media screen and (max-width: 513px) {
#friendsList {
background-color: var(--black5);
}
}
#friendsList h4 {
color: var(--white);
}
@ -236,8 +254,8 @@ fieldset[disabled] .form-control {
}
/**
* Emoji picker
*/
* Emoji picker
*/
.wdt-emoji-popup {
background-color: var(--black6);
border: 1px var(--black6) solid;
@ -271,6 +289,50 @@ fieldset[disabled] .form-control {
background-color: var(--black4) !important;;
}
/**
* Sceditor
*/
.sceditor-iframe-body {
background-color: var(--black5);
color: var(--white);
}
.sceditor-container textarea {
background-color: var(--black5);
color: var(--white);
}
.sceditor-button {
background-color: var(--black6);
}
.sceditor-button svg {
fill: var(--black4);
}
.sceditor-button:hover,
.sceditor-button:active,
.sceditor-button.active {
background-color: var(--black4);
color: white;
}
.sceditor-button:hover svg,
.sceditor-button:active svg,
.sceditor-button.active svg {
fill: var(--black6);
}
div.sceditor-dropdown {
background-color: var(--black5);
}
div.sceditor-dropdown input {
background-color: var(--black4);
}
/**
* Conversations
*/
@ -292,11 +354,16 @@ fieldset[disabled] .form-control {
color: var(--black5);
}
.select2-dropdown {
background-color: var(--black5);
.select2-dropdown, .select2-selection,
.select2-search__field {
background-color: var(--black5) !important;
color: var(--white);
}
.select2-selection__rendered {
color: var(--white) !important;
}
.select2-container--default .select2-results__option--highlighted[aria-selected] {
background-color: var(--black4);
color: var(--white);
@ -307,6 +374,10 @@ fieldset[disabled] .form-control {
color: var(--white);
}
.conversation-settings-pane {
background-color: var(--black5) !important;
}
/**
* Home page
*/
@ -379,6 +450,10 @@ fieldset[disabled] .form-control {
filter: brightness(100%);
}
.post-youtube-placeholder {
background-color: var(--black6) !important;
}
.box-comments .username,
.box-comments .comment-content {
color: var(--white);
@ -399,6 +474,10 @@ fieldset[disabled] .form-control {
color: var(--black4);
}
.post-form-choice span span {
color: inherit;
}
.post-form .post-form-choice input:checked ~ span {
color: var(--white);
}
@ -431,8 +510,8 @@ fieldset[disabled] .form-control {
/**
* Search page
*/
* Search page
*/
.nav > li > a:hover,
.nav > li > a:active,
.nav > li > a:focus {
@ -440,8 +519,8 @@ fieldset[disabled] .form-control {
}
/**
* Conversation page
*/
* Conversation page
*/
.conversations-page-container a {
background-color: var(--black6) !important;;
}
@ -480,4 +559,68 @@ fieldset[disabled] .form-control {
.big-box-conversation .direct-chat-text a {
background-color: transparent !important;
color: inherit !important;
}
/**
* Groups logo
*/
img[src$="groups_logo/default.png"] {
filter: invert(1) brightness(50%);
}
/**
* Read only friends list
*/
.friends-list-ro .friend a:hover .friends-name {
color: var(--black4);
}
/**
* Account created page
*/
.page_account_created .message_container .message {
background-color: var(--black5);
}
/**
* Call system
*/
.call-window {
border: 1px var(--black4) solid;
}
.ring-call-container {
background-color: var(--black5);
}
.call-message-box {
background-color: var(--black6);
}
/**
* Groups list page
*/
.groups-main-page {
color: var(--white);
}
/**
* Groups members page
*/
.group-members-page {
color: var(--white);
}
/**
* Pick a movie modal
*/
.pick-movie-modal {
color: var(--white);
}
/**
* Group settings page
*/
.group-settings-container label {
color: var(--white);
}

View File

@ -11,6 +11,11 @@
padding-top: 50px;
}
.groups-main-page .no-group-notice {
margin-top: 10px;
text-align: center;
}
.groups-main-page .group-item {
text-align: justify;
margin-top: 10px;

View File

@ -18,6 +18,11 @@
text-align: center;
}
.group-members-page .invite-user-form {
margin-top: 30px;
margin-bottom: 30px;
}
.group-members-page .member {
margin-bottom: 10px;
}

View File

@ -35,4 +35,13 @@
margin: auto;
margin-bottom: 10px;
display: block;
}
.group-settings-container .delete-group-link-container {
text-align: center;
padding: 20px;
}
.group-settings-container .delete-group-link-container a {
color: black;
}

35
assets/custom_ts/Utils.d.ts vendored Normal file
View File

@ -0,0 +1,35 @@
/**
* Typescript typing rules for Utils
*
* @author Pierre HUBERT
*/
declare interface CreateElem2Args {
type : string,
appendTo ?: HTMLElement,
insertBefore ?: HTMLElement,
insertAsFirstChild ?: HTMLElement,
class ?: string,
id ?: string,
title ?: string,
src ?: string,
href ?: string,
name ?: string,
elemType ?: string,
value ?: string,
placeholder ?: string,
innerHTML ?: string,
innerLang ?: string,
innerHTMLprefix ?: string,
disabled ?: boolean,
}
declare function createElem(nodeType : string, appendTo : string) : HTMLElement;
declare function createElem2(infos : CreateElem2Args) : HTMLElement;
declare function byId(id : string) : HTMLElement;
declare function emptyElem(target : HTMLElement) : void;
declare function checkMail(emailAddress : string) : boolean;

View File

@ -33,6 +33,10 @@ ComunicWeb.common.api = {
}
//Enable incognito mode if required
if(ComunicWeb.components.incognito.management.isEnabled())
params.incognito = true;
//Prepare data to send in request
var count = 0;
var datas = "";
@ -90,7 +94,11 @@ ComunicWeb.common.api = {
data.append('userToken2', tokens.token2);
}
}
}
//Enable incognito mode if required
if(ComunicWeb.components.incognito.management.isEnabled())
data.append("incognito", true);
//Create request
var apiXHR = new XMLHttpRequest();

View File

@ -21,6 +21,11 @@ ComunicWeb.common.date = {
* @return {String} The generated date
*/
diffToStr: function(difference){
//Check if difference is less than one second
if(difference < 0)
difference = 0;
//Calculate seconds
var seconds = difference-Math.floor(difference/60)*60;
var difference = (difference - seconds)/60;

View File

@ -109,6 +109,10 @@ ComunicWeb.common.error.pageNotFound = function(additionnalData, targetElement){
*/
ComunicWeb.common.error.syntaxtError = function(error, additional){
//Do not do anything in production mode
if(ComunicWeb.__config.productionMode == true)
return;
//Create a modal dialog to report error
var dialog = ComunicWeb.common.messages.createDialogSkeleton({
type: "danger",

View File

@ -118,6 +118,12 @@ var ComunicWeb = {
* Prompt the user to input a string
*/
inputString: function(title, message, defaultValue, callback){},
/**
* Prompt the user to enter his password
*/
promptPassword: function(info){},
},
/**
@ -222,6 +228,22 @@ var ComunicWeb = {
getAndShowJSONtemplate: function(targetElem, templateURI, additionalData, afterParsingJSONtemplate, cleanContainer){},
},
/**
* Page title management
*/
pageTitle: {
/**
* Set a new title to the page
*/
setTitle: function(title){},
/**
* Set new number of notifications
*/
setNotificationsNumber: function(number){}
},
/**
* Functions to check data input in forms
*/
@ -570,6 +592,13 @@ var ComunicWeb = {
*/
bottom: {
/**
* Bottom links
*/
links: [
//TODO : implement
],
/**
* Main bottom script file
*/
@ -781,7 +810,14 @@ var ComunicWeb = {
*/
unreadDropdown: {
//TODO : implementd
}
},
/**
* Conversation message editor
*/
messageEditor: {
//TODO : implement
},
},
/**
@ -1016,6 +1052,13 @@ var ComunicWeb = {
//TODO : implement
},
/**
* Notification song system
*/
song: {
//TODO : implement
},
/**
* Notifications utilities
*/
@ -1065,6 +1108,108 @@ var ComunicWeb = {
},
},
/**
* Dark Theme component
*/
darkTheme: {
//TODO : implement
},
/**
* Incognito mode component
*/
incognito: {
/**
* Keyboard catcher
*/
keyboard: {
//TODO : implement
},
/**
* Incognito management
*/
management: {
//TODO : implement
},
/**
* UI management
*/
ui: {
//TODO : implement
},
},
/**
* Calls component
*/
calls: {
/**
* Calls configuration
*/
__config: undefined,
/**
* Calls interface
*/
interface: {
//TODO : implement
},
/**
* Calls controller
*/
controller: {
//TODO : implement
},
/**
* Call window
*/
callWindow: {
//TODO : implement
},
/**
* Current calls list
*/
currentList: {
//TODO : implement
},
/**
* User media getter
*/
userMedia: {
//TODO : implement
},
/**
* Ring screen
*/
ringScreen: {
//TODO : implement
},
/**
* Calls utilities
*/
utils: {
//TODO : implement
},
},
/**
* Easter egg : pacman
*/
pacman: {
//TODO : implement
},
},
/**

View File

@ -382,4 +382,85 @@ ComunicWeb.common.messages.inputString = function(title, message, defaultValue,
//Show the modal
$(modal).modal('show');
}
/**
* Prompt the user to input his password
*
* @param {Object} info Additionnal information
*/
ComunicWeb.common.messages.promptPassword = function(info){
var dialog = ComunicWeb.common.messages.createDialogSkeleton({
type: "danger",
title: "Password required"
});
$(dialog.modal).modal("show");
//Create modal close function
var closeModal = function(e, password){
$(dialog.modal).modal("hide");
emptyElem(dialog.modal);
dialog.modal.remove();
//Callback
if(info.callback)
info.callback(password);
};
dialog.cancelButton.addEventListener("click", closeModal);
dialog.closeModal.addEventListener("click", closeModal);
//Set dialog body
var passwordForm = createElem2({
appendTo: dialog.modalBody,
type: "div"
});
createElem2({
appendTo: passwordForm,
type: "p",
innerHTML: "We need your password to continue."
});
//Create pasword input group
var inputGroup = createElem2({
appendTo: passwordForm,
type: "div",
class: "input-group input-group-sm"
});
//Create password input
var passwordInput = createElem2({
appendTo: inputGroup,
type: "input",
class: "form-control",
elemType: "password"
});
//Create input group
var inputGroupContainer = createElem2({
appendTo: inputGroup,
type: "span",
class: "input-group-btn"
});
//Add submit button
var submitButton = createElem2({
appendTo: inputGroupContainer,
type: "button",
class: "btn btn-danger",
innerHTML: "Confirm deletion"
});
submitButton.addEventListener("click", function(e){
//Check given password
var password = passwordInput.value;
if(password.length < 4)
return notify("Please check given password !", "danger");
//Close modal
closeModal(null, password);
});
}

View File

@ -196,7 +196,7 @@ ComunicWeb.common.page = {
}
//Change page title
document.title = pageInfos.pageTitle;
ComunicWeb.common.pageTitle.setTitle(pageInfos.pageTitle);
//Change page URL, if required
if(additionnalData.no_url_update ? !additionnalData.no_url_update : true)
@ -277,6 +277,11 @@ ComunicWeb.common.page = {
//Call the method related to the page
eval(pageInfos.methodHandler + ("(additionnalData, pageTarget);"));
//Propagate information
SendEvent("openPage", {
page: pageURI
});
//Success
return true;

View File

@ -0,0 +1,53 @@
/**
* Page title management
*
* @author Pierre HUBERT
*/
ComunicWeb.common.pageTitle = {
/**
* Current page title
*/
_curr_title: "Comunic",
/**
* Current number of notifications
*/
_curr_notifications_number: 0,
/**
* Set a new title to the page
*
* @param {string} title The new title for the page
*/
setTitle: function(title){
this._curr_title = title;
this.__refresh();
},
/**
* Set new number of notifications
*
* @param {number} number The new number of notifications
*/
setNotificationsNumber: function(number){
this._curr_notifications_number = number;
this.__refresh();
},
/**
* Refresh document title
*/
__refresh: function(){
var title = "";
if(this._curr_notifications_number > 0)
title += "(" + this._curr_notifications_number + ") ";
title += this._curr_title;
document.title = title;
}
}

View File

@ -167,7 +167,8 @@ function getInfoGroup(id, callback){
*
* @param {Array} IDs The IDs of the groups to get information about
* @param {Function} callback Callback to call once we have information about the group
* @param {Boolean} force TRUE to force the request (ignore cache)
*/
function getInfoMultipleGroups(IDs, callback){
ComunicWeb.components.groups.info.getInfoMultiple(IDs, callback);
function getInfoMultipleGroups(IDs, callback, force){
ComunicWeb.components.groups.info.getInfoMultiple(IDs, callback, force);
}

View File

@ -0,0 +1,51 @@
/**
* Song player
*
* @author Pierre HUBERT
*/
class SongPlayer {
/**
* Initialize a new SongPlayer instance
*
* @param {String[]} sources The list of sources to exploit for the song
*/
constructor(sources){
this.songElem = document.createElement("audio");
//Process the list of sources
for (var index = 0; index < sources.length; index++) {
var url = sources[index];
var source = document.createElement("source");
source.src = url;
this.songElem.appendChild(source);
}
}
/**
* Play audio just once
*/
playOnce(){
this.songElem.loop = false;
this.songElem.play();
}
/**
* Play song forever
*/
playForever(){
this.songElem.loop = true;
this.songElem.play();
}
/**
* Stop song
*/
stop(){
this.songElem.pause();
this.songElem.currentTime = 0;
}
}

View File

@ -48,6 +48,21 @@ ComunicWeb.common.system = {
*/
ComunicWeb.common.langs.initLanguages();
/**
* Initialize incognito mode detection
*/
ComunicWeb.components.incognito.management.init();
/**
* Refresh dark theme mode
*/
ComunicWeb.components.darkTheme.refresh();
/**
* Initialize call system
*/
ComunicWeb.components.calls.controller.init();
/**
* What to do after login refresh
*/

View File

@ -40,6 +40,7 @@ function createElem(nodeType, appendTo){
* @info {String} placeholder The placeholder of the new element
* @info {String} innerHTML Specify the html content of the newly created element
* @info {String} innerLang Specify the key of the lang to use to fill the element
* @info {String} innerHTMLprefix Specify prefix to add at the begining of the content of the element
* @info {boolean} disabled Set whether the field should be disabled or not (input only)
* @return {HTMLElement} The newly created element
*/
@ -105,6 +106,9 @@ function createElem2(infos){
if(infos.innerLang)
newElem.innerHTML = lang(infos.innerLang);
if(infos.innerHTMLprefix)
newElem.innerHTML = infos.innerHTMLprefix + newElem.innerHTML;
//Set field state
if(infos.disabled)
@ -416,6 +420,10 @@ function checkString(value){
*/
function removeHtmlTags(input){
//Check if input string is empty
if(input == null || typeof input !== "string")
return "";
//Prepare update
var output = input;
@ -435,6 +443,25 @@ function removeHtmlTags(input){
return output;
}
/**
* Replace all line break with paragraph tags
*
* @param {string} input Input string to convert
* @return {string} Generated string
*/
function lineBreakToPTags(input){
//Check if the string is empty
if(input == null || input == "")
return input;
//Change string
while(input.includes("\n"))
input = input.replace("\n", "</p><p>");
return "<p>"+input+"</p>";
}
/**
* Check a URL validity
*
@ -592,4 +619,138 @@ function dataURItoBlob(dataURI){
return new Blob([ia], {type: mimeString});
}
/**
* Satinize some HTML source code by removing all javascript event detectors
* from it
*
* @param {string} html The source code to update
* @return {string} Secured html
*/
function removeJavascriptEventsFromHTML(html){
//Check if the string to check is null (we will consider
//at safe in this case)
if(html == null)
return html;
//Search for unexceptable references
while(html.match(/on[a-zA-Z ]+=/i) != null){
var match = html.match(/on[a-zA-Z ]+=/i)[0];
html = html.replace(match, match.replace("on", "o<block></block>n"))
}
return html;
}
/**
* Get and return the DOM of a specified iframe
*
* @param {HTMLIFrameElement} iframe The iframe to process
* @return {HTMLDocument} DOM of the iframe
*/
function GetIframeDOM(iframe){
return iframe.contentWindow
? iframe.contentWindow.document
: iframe.contentDocument;
}
/**
* Initialize styles for a sceditor textarea
*
* @param {HTMLTextAreaElement} textarea Target textarea element that
* have sceditor initialized
*/
function ApplySceditorStyle(textarea){
//Get iframe DOM
var iframeDOM = GetIframeDOM(textarea.parentNode.getElementsByTagName("iframe")[0]);
//Apply stylesheets
document.querySelectorAll("link[rel='stylesheet']").forEach(function(entry){
//Skip the entry if it is disabled
if(entry.disabled)
return;
var elem = iframeDOM.createElement("link");
elem.rel = "stylesheet";
elem.href = entry.href;
iframeDOM.head.appendChild(elem);
});
//Apply new styles to body
iframeDOM.body.className += " sceditor-iframe-body";
}
/**
* Send a new javascript event
*
* @param {String} name The name of the event to create
* @param {Object} details Information about the event to create
*/
function SendEvent(name, details){
var event = new CustomEvent(name, {
detail: details,
bubbles: true,
cancelable: false
});
document.dispatchEvent(event);
}
/**
* Request full screen for an HTML element
*
* Note : this function must be called inside of an event of
* the user like a click, otherwise it will not work
*
* This function source code is based on this StackOverFlow Answer
* https://stackoverflow.com/a/32100295/3781411
*
* @param {HTMLElement} elem The element for which we want
* full screen
*/
function RequestFullScreen(element){
if (
document.fullscreenElement ||
document.webkitFullscreenElement ||
document.mozFullScreenElement ||
document.msFullscreenElement
) {
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else if (document.webkitExitFullscreen) {
document.webkitExitFullscreen();
} else if (document.msExitFullscreen) {
document.msExitFullscreen();
}
} else {
if (element.requestFullscreen) {
element.requestFullscreen();
} else if (element.mozRequestFullScreen) {
element.mozRequestFullScreen();
} else if (element.webkitRequestFullscreen) {
element.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
} else if (element.msRequestFullscreen) {
element.msRequestFullscreen();
}
}
}
/**
* Check out whether Comunic has grabbed full screen or not
*
* @return {Boolean} TRUE if the window is in full screen / FALSE else
*/
function IsFullScreen(){
return document.fullscreenElement ||
document.webkitFullscreenElement ||
document.mozFullScreenElement ||
document.msFullscreenElement;
}

View File

@ -25,18 +25,51 @@ ComunicWeb.components.account.export.worker = {
ComunicWeb.components.account.export.ui.updateMessage("Got text data");
ComunicWeb.components.account.export.ui.updateProgress(10);
//Parse data
ComunicWeb.components.account.export.worker.parse(result);
//Get explorer
ComunicWeb.components.account.export.worker.getExplorer(result);
});
},
/**
* Parse account text data into ZIP file
* Second step for export : Get and open personnal data explorer
*
* @param {Object} data Text data about the account
* @param {Object} data Text data about the account (data not modified at this stage)
*/
parse: function(data){
getExplorer: function(data){
ComunicWeb.components.account.export.ui.updateMessage("Getting data explorer");
ComunicWeb.components.account.export.ui.updateProgress(15);
JSZipUtils.getBinaryContent(ComunicWeb.__config.assetsURL+"zip/personnal-data-export-navigator.zip", function(err, file){
if(err != null){
ComunicWeb.debug.logMessage("Could not get personnal data export navigator!");
ComunicWeb.components.account.export.ui.exportFatalError(e);
return;
}
JSZip.loadAsync(file).then(function(zip){
//Ready to parse data
ComunicWeb.components.account.export.worker.parse(data, zip);
}).catch(function(){
ComunicWeb.debug.logMessage("Could not parse personnal data export navigator!");
ComunicWeb.components.account.export.ui.exportFatalError(e);
return;
});
});
},
/**
* Parse account text data into ZIP file
*
* @param {Object} data Text data about the account
* @param {ZIP} zip The ZIP object to fill
*/
parse: function(data, zip){
//Get UI shorcut
var ui = ComunicWeb.components.account.export.ui;
@ -77,9 +110,6 @@ ComunicWeb.components.account.export.worker = {
//Determine the list of files to download
var files_list = this._generate_files_list(data);
//Create zip file
var zip = new JSZip();
//Add raw json file
zip.file("source.json", JSON.stringify(data));

View File

@ -0,0 +1,23 @@
/**
* Comunic bottom links list
*
* @author Pierre HUBERT
*/
ComunicWeb.components.bottom.links = [
//Language selector
{
innerLang: "bottom_bar_action_language",
icon: "fa-globe",
onclick: function(){ComunicWeb.components.langPicker.show();}
},
//About Comunic
{
innerLang: "bottom_bar_action_about",
icon: "fa-question-circle",
href: ComunicWeb.__config.aboutWebsiteURL,
target: "_blank"
}
];

View File

@ -43,27 +43,26 @@ ComunicWeb.components.bottom.main = {
innerHTML: "Comunic &nbsp; &nbsp; "
});
//Put the language selector link on the right
var langLink = createElem2({
appendTo: leftElements,
type: "a",
innerHTML: "<i class='fa fa-globe'></i> Language"
});
langLink.onclick = function(){
ComunicWeb.components.langPicker.show();
};
ComunicWeb.components.bottom.links.forEach(function(link){
add_space(leftElements);
add_space(leftElements);
var linkEl = createElem2({
appendTo: leftElements,
type: "a",
href: link.href,
innerHTML: link.innerHTML,
innerLang: link.innerLang,
innerHTMLprefix: "<i class='fa "+link.icon+"'></i> "
});
//Add about link
var aboutLink = createElem2({
appendTo: leftElements,
type: "a",
innerHTML: "<i class='fa fa-question-circle'></i> About",
href: ComunicWeb.__config.aboutWebsiteURL
if(link.target)
linkEl.setAttribute("target", link.target);
if(link.onclick)
linkEl.onclick = link.onclick;
add_space(leftElements);
add_space(leftElements);
});
aboutLink.setAttribute("target", "_blank");
}
}

View File

@ -0,0 +1,838 @@
/**
* Single call window management
*
* @author Pierre HUBERT
*/
ComunicWeb.components.calls.callWindow = {
/**
* Initialize a call
*
* @param {Object} info Information about the call to initialize
*/
initCall: function(info){
/**
* Initialize call object
*/
var call = {
info: info,
/**
* @type {String}
*/
localPeerID: undefined,
/**
* @type {Boolean}
*/
open: true,
/**
* @type {Boolean}
*/
stopped: false,
window: {},
streams: {},
/**
* @type {MediaStream}
*/
localStream: undefined,
/**
* @type {HTMLVideoElement}
*/
localStreamVideo: undefined,
/**
* @type {SignalExchangerClient}
*/
signalClient: undefined
};
/**
* Initialize utilities
*/
/**
* Get member information based on call ID
*
* @param {String} id The ID of the peer to process
* @return Information about the peer / empty object
* if no peer found
*/
call.getMemberByCallID = function(id){
var memberInfo = undefined;
call.info.members.forEach(function(member){
if(member.user_call_id == id)
memberInfo = member;
});
return memberInfo;
}
/**
* Check out whether local stream audio is enabled or not
*
* @return {Boolean} TRUE if local stream is enabled / FALSE else
*/
call.isLocalAudioEnabled = function(){
//Default behaviour : local stream not enabled
if(!call.localStream)
return true;
return call.localStream.getAudioTracks()[0].enabled;
}
/**
* Set audio mute status of local stream
*
* @param {Boolean} enabled New enabled status
*/
call.setLocalAudioEnabled = function(enabled){
if(call.localStream)
call.localStream.getAudioTracks()[0].enabled = enabled;
}
/**
* Check if the local video is enabled or not
*
* @return {Boolean} TRUE to mute video / FALSE else
*/
call.isLocalVideoEnabled = function(){
//Default behaviour : video not enabled
if(!call.localStream)
return true;
return call.localStream.getVideoTracks()[0].enabled;
}
/**
* Update mute status of local video stream
*
* @param {Boolean} enabled New mute status
*/
call.setLocalVideoEnabled = function(enabled){
if(call.localStream)
call.localStream.getVideoTracks()[0].enabled = enabled;
}
/**
* Set local stream video visibility
*
* @param {Boolean} visible TRUE to make it visible / FALSE else
*/
call.setLocalStreamVisibility = function(visible){
if(call.localStreamVideo)
call.localStreamVideo.style.display = visible ? "block" : "none";
}
/**
* Get local stream visibility
*
* @return {Boolean} TRUE if local stream is visible / FALSE else
*/
call.isLocalStreamVisible = function(){
if(!call.localStreamVideo)
return true;
return call.localStreamVideo.style.display !== "none";
}
/**
* We have to begin to draw conversation UI
*/
var callContainer = createElem2({
appendTo: byId("callsTarget") ? byId("callsTarget") : byId("wrapper"), //If call target is not found, add call in page wrapper
type: "div",
class: "call-window"
});
call.window.container = callContainer;
//Add toolbar
call.window.toolbar = createElem2({
appendTo: callContainer,
type: "div",
class: "call-toolbar",
innerHTML: "<i class='fa fa-phone'>"
});
//Call title
call.window.title = createElem2({
appendTo: call.window.toolbar,
type: "div",
class: "call-title",
innerHTML: "Loading..."
});
/**
* Update the title of the call
*/
call.setTitle = function(title){
call.window.title.innerHTML = title;
}
//Add close button
call.window.closeButton = createElem2({
appendTo: call.window.toolbar,
type: "button",
class: "btn btn-box-tool close-btn",
innerHTML: "<i class='fa fa-times'></i>"
});
//Make close button lives
call.close = function(){
//Avoid to call this several times
if(call.stopped)
return;
call.open = false;
call.stopped = true;
callContainer.remove();
//Close sockets connections too
ComunicWeb.components.calls.callWindow.stop(call);
}
call.window.closeButton.addEventListener("click", function(){
//Check if the call is in full screen mode
if(IsFullScreen())
RequestFullScreen(null)
else
call.close();
});
//Get information about related conversation to get the name of the call
ComunicWeb.components.conversations.utils.getNameForID(info.conversation_id, function(name){
if(!name)
return notify("Could not get information about related conversation!", "danger");
call.setTitle(name);
});
//Call box body
call.window.body = createElem2({
appendTo: callContainer,
type: "div",
class: "call-window-body"
});
//Call videos target
call.window.videosTarget = createElem2({
appendTo: call.window.body,
type: "div",
class: "streams-target"
});
/**
* Create loading message area
*/
call.window.loadingMessageContainer = createElem2({
insertBefore: call.window.body.firstChild,
type: "div",
class: "loading-message-container",
innerHTML: "<i class='fa fa-clock-o'></i>"
});
call.window.loadingMessageContent = createElem2({
appendTo: call.window.loadingMessageContainer,
type: "div",
class: "message",
innerHTML: "Loading..."
});
/**
* Set loading message visiblity
*
* @param {Boolean} visible TRUE to make it visible / FALSE else
*/
call.setLoadingMessageVisibility = function(visible){
call.window.loadingMessageContainer.style.display = visible ? "flex" : "none";
}
/**
* Update call loading message
*
* @param {String} message The new message to show to the
* users
*/
call.setLoadingMessage = function(message){
call.setLoadingMessageVisibility(true);
call.window.loadingMessageContent.innerHTML = message;
}
//Call footer
call.window.footer = createElem2({
appendTo: callContainer,
type: "div",
class: "call-footer"
});
/**
* This function is used to toggle selection state
* of one of the call toolbar button
*
* @param {HTMLElement} btn Target button
* @param {Boolean} selected Selection state of the button
*/
var togglButtonSelectedStatus = function(btn, selected){
if(!selected){
while(btn.className.includes(" selected"))
btn.className = btn.className.replace(" selected", "");
}
else if(selected && !btn.className.includes(" selected"))
btn.className += " selected";
}
var buttonsList = [
//Show current user camera
{
icon: "fa-eye",
selected: true,
onclick: function(btn){
call.setLocalStreamVisibility(!call.isLocalStreamVisible());
togglButtonSelectedStatus(btn, call.isLocalStreamVisible());
}
},
//Mute button
{
icon: "fa-microphone",
selected: true,
onclick: function(btn){
call.setLocalAudioEnabled(!call.isLocalAudioEnabled());
togglButtonSelectedStatus(btn, call.isLocalAudioEnabled());
}
},
//Hang up button
{
icon: "fa-phone",
class: "hang-up-button",
selected: false,
onclick: function(){
call.close();
}
},
//Stop video button
{
icon: "fa-video-camera",
selected: true,
onclick: function(btn){
call.setLocalVideoEnabled(!call.isLocalVideoEnabled());
togglButtonSelectedStatus(btn, call.isLocalVideoEnabled());
console.log(call);
}
},
//Full screen button
{
icon: "fa-expand",
selected: false,
onclick: function(btn){
RequestFullScreen(callContainer);
setTimeout(function(){
togglButtonSelectedStatus(btn, IsFullScreen());
}, 1000);
}
}
];
//Add buttons
buttonsList.forEach(function(button){
var buttonEl = createElem2({
appendTo: call.window.footer,
type: "div",
class: "call-option-button",
innerHTML: "<i class='fa " + button.icon + "'></i>"
});
//Add button optionnal class
if(button.class)
buttonEl.className += " " + button.class;
buttonEl.addEventListener("click", function(){
button.onclick(buttonEl);
});
togglButtonSelectedStatus(buttonEl, button.selected);
});
/**
* Make the call window draggable
*/
function checkWindowMinPosition(){
if(window.innerHeight < callContainer.style.top.replace("px", ""))
callContainer.style.top = "0px";
if(window.innerWidth < callContainer.style.left.replace("px", ""))
callContainer.style.left = "0px";
if(callContainer.style.left.replace("px", "") < 0)
callContainer.style.left = "0px";
if(callContainer.style.top.replace("px", "") < 49)
callContainer.style.top = "50px";
}
//Enable dragging
{
var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
call.window.title.onmousedown = function(e){
e = e || window.event;
e.preventDefault();
//Check if the window is currently in full screen mode
if(IsFullScreen())
return;
//get the mouse cursor position at startup
pos3 = e.clientX;
pos4 = e.clientY;
document.onmouseup = closeDragElement;
document.onmousemove = elementDrag;
}
function elementDrag(e){
e = e || window.event;
e.preventDefault();
//Calculate new cursor position
pos1 = pos3 - e.clientX;
pos2 = pos4 - e.clientY;
pos3 = e.clientX;
pos4 = e.clientY;
//Set element new position
callContainer.style.top = (callContainer.offsetTop - pos2) + "px";
callContainer.style.left = (callContainer.offsetLeft - pos1) + "px";
checkWindowMinPosition();
}
function closeDragElement(){
//Stop moving when mouse button is released
document.onmouseup = null;
document.onmousemove = null;
}
}
window.addEventListener("resize", function(){
checkWindowMinPosition();
});
//Load user media
call.setLoadingMessage("Waiting for your microphone and camera...");
ComunicWeb.components.calls.userMedia.get().then(function(stream){
//Check if connection has already been closed
if(!call.open)
return;
call.localStream = stream;
//Initialize signaling server connection
ComunicWeb.components.calls.callWindow.initializeConnectionToSignalingServer(call);
//Add local stream to the list of visible stream
call.localStreamVideo = ComunicWeb.components.calls.callWindow.addVideoStream(call, true, stream);
//Mark as connecting
call.setLoadingMessage("Connecting...");
return true;
}).catch(function(e){
console.error("Get user media error: ", e);
call.setLoadingMessageVisibility(false);
return notify("Could not get your microphone and camera!", "danger");
});
/**
* Start to automaticaly refresh information about the call
*/
var interval = setInterval(function(){
//Check if call is not visible anymore
if(!callContainer.isConnected){
call.close();
return;
}
if(!call.open)
return clearInterval(interval);
ComunicWeb.components.calls.callWindow.refreshInfo(call);
}, 4000);
},
/**
* Initialize connection to signaling server
*
* @param {Object} call Information about the call
*/
initializeConnectionToSignalingServer: function(call) {
//Get current user call ID
call.info.members.forEach(function(member){
if(member.userID == userID())
call.localPeerID = member.user_call_id;
});
//Create client instance and connect to server
var config = ComunicWeb.components.calls.controller.getConfig();
call.signalClient = new SignalExchangerClient(
config.signal_server_name,
config.signal_server_port,
call.localPeerID,
config.is_signal_server_secure
);
/**
* Error when connecting to signaling server
*/
call.signalClient.onError = function(){
call.setLoadingMessage("Could not connect to signaling server!");
call.open = false;
};
/**
* Connection to signaling server is not supposed to close
*/
call.signalClient.onClosed = function(){
call.setLoadingMessage("Connection to signaling server closed!");
call.open = false;
}
/**
* A remote peer sent a ready notice
*/
call.signalClient.onReadyMessage = function(peerID){
ComunicWeb.components.calls.callWindow.readyToInitialize(call, peerID);
}
/**
* A remote peer sent a signal
*/
call.signalClient.onSignal = function(signal, peerID){
ComunicWeb.components.calls.callWindow.receivedSignal(call, peerID, signal);
}
},
/**
* Refresh at a regular interval information about the call
*
* @param {Object} call Call Root object
*/
refreshInfo: function(call){
ComunicWeb.components.calls.interface.getInfo(call.info.id, function(result){
if(result.error)
return notify("Could not get information about the call!", "danger");
call.info = result;
ComunicWeb.components.calls.callWindow.gotNewCallInfo(call);
});
},
/**
* This method get called each time information about the call
* are refreshed on the server
*
* @param {Object} call Information about the call
*/
gotNewCallInfo: function(call) {
//Check if we are connected to signaling server and we have got local
//streams
if(!call.signalClient || !call.signalClient.isConnected() || !call.localStream)
return;
//Check if all other members rejected call
var allDisconnected = ComunicWeb.components.calls.utils.hasEveryoneLeft(call.info);
//Check if all call peer rejected the call
if(allDisconnected){
call.setLoadingMessage("Conversation terminated.");
setTimeout(function(){
call.close();
}, 5000);
return;
}
//Process the connection to each accepted member
call.info.members.forEach(function(member){
//Ignores all not accepted connection
if(member.userID == userID() || member.status !== "accepted")
return;
//Check if we have not peer information
if(!call.streams.hasOwnProperty("peer-" + member.userID)){
//If the ID of the current user is bigger than the remote
//peer user ID, we wait a ready signal from him
if(member.userID < userID())
return;
//Else we have to create peer
ComunicWeb.components.calls.callWindow.createPeerConnection(call, member, false);
}
});
},
/**
* Create a peer connection
*
* @param {Object} call Information about the peer
* @param {Object} member Information about the member
* @param {Boolean} isInitiator Specify whether current user is the
* initiator of the connection or not
*/
createPeerConnection: function(call, member, isInitiator){
var peerConnection = {
peer: undefined
};
call.streams["peer-" + member.userID] = peerConnection;
//Get call configuration
var config = ComunicWeb.components.calls.controller.getConfig();
//Create peer
var peer = new SimplePeer({
initiator: isInitiator,
stream: call.localStream,
trickle: true,
config: {
'iceServers': [
{ urls: config.stun_server },
{"url": config.turn_server,
"credential": config.turn_username,
"username": config.turn_password}
]
}
});
peerConnection.peer = peer;
//Add a function to remove connection
peerConnection.removePeerConnection = function(){
peer.destroy();
delete call.streams["peer-" + member.userID];
if(peerConnection.video)
peerConnection.video.remove();
}
peer.on("error", function(err){
console.error("Peer error !", err, member);
peerConnection.removePeerConnection();
});
peer.on("signal", function(data){
console.log('SIGNAL', JSON.stringify(data));
call.signalClient.sendSignal(member.user_call_id, JSON.stringify(data));
});
peer.on("message", function(message){
console.log("Message from remote peer: " + message);
});
peer.on("close", function(){
peerConnection.removePeerConnection();
});
peer.on("stream", function(stream){
ComunicWeb.components.calls.callWindow.streamAvailable(call, member, stream);
});
//If this peer does not initialize connection, inform other peer we are ready
if(!isInitiator)
call.signalClient.sendReadyMessage(member.user_call_id);
},
/**
* This method is called when a remote peers notify it is ready to
* establish connection
*
* @param {Object} call Information about the call
* @param {String} peerID Remote peer ID
*/
readyToInitialize: function(call, peerID){
var member = call.getMemberByCallID(peerID);
if(member == undefined)
return;
//It the user with the smallest ID who send the ready message
//else it would mess everything up
if(member.userID > userID())
return;
this.createPeerConnection(call, member, true);
},
/**
* This method is called when we received a remote signal
*
* @param {Object} call Information about the call
* @param {String} peerID Remote peer ID
* @param {String} signal Received signal
*/
receivedSignal: function(call, peerID, signal){
console.log("Received signal from " + peerID, signal);
var member = call.getMemberByCallID(peerID);
if(member == undefined)
return;
//Check we have got peer information
if(!call.streams.hasOwnProperty("peer-" + member.userID))
return;
call.streams["peer-" + member.userID].peer.signal(JSON.parse(signal));
},
/**
* This method is called when a remote stream becomes available
*
* @param {Object} call Information about remote call
* @param {String} member Information about target member
* @param {MediaStream} stream Remote stream available
*/
streamAvailable: function(call, member, stream){
call.setLoadingMessageVisibility(false);
call.streams["peer-" + member.userID].stream = stream;
call.streams["peer-" + member.userID].video = this.addVideoStream(call, false, stream);
},
/**
* Create and set a video object for a stream
*
* @param {Object} call Target call
* @param {Boolean} muted Specify whether audio should be muted
* or not
* @param {MediaStream} stream Target stream
* @return {HTMLVideoElement} Generated video element
*/
addVideoStream: function(call, muted, stream){
/**
* @type {HTMLVideoElement}
*/
var video = createElem2({
appendTo: call.window.videosTarget,
type: "video"
});
video.muted = muted;
//Set target video object and play it
video.srcObject = stream;
video.play();
return video;
},
/**
* Stop the ongoing call
*
* @param {Object} call Information about the call
*/
stop: function(call){
//Remove the call from the opened list
ComunicWeb.components.calls.currentList.removeCallFromList(call.info.id);
//Close the connection to the server
if(call.signalClient && call.signalClient.isConnected())
call.signalClient.close();
//Close all socket connections
for (var key in call.streams) {
if (call.streams.hasOwnProperty(key)) {
var element = call.streams[key];
element.removePeerConnection();
}
}
//Close local stream
if(call.localStream){
call.localStream.getTracks().forEach(function(track){
track.stop();
});
}
//Notify server
ComunicWeb.components.calls.interface.hangUp(call.info.id, function(){});
}
}

View File

@ -0,0 +1,293 @@
/**
* Calls controller
*
* @author Pierre HUBERT
*/
ComunicWeb.components.calls.controller = {
/**
* This variable contains the initialization state
* of the call component
*/
_is_init: false,
/**
* This variable contains whether the user is being
* notified of a call or not
*/
_is_processing_call: false,
/**
* Initialize calls component
*/
init: function(){
//We init this component just once
if(this._is_init)
return;
ComunicWeb.debug.logMessage("Initialize calls component");
//Initialize call container
var initializeCallContainer = function(){
//Signed out users can not make calls
if(!signed_in()){
ComunicWeb.components.calls.controller.userSignedOut();
return;
}
//Need a wrapper to continue
if(!byId("wrapper"))
return;
//Check if calls target already exists
if(byId("callsTarget"))
return;
//Call system must be available
if(!ComunicWeb.components.calls.controller.isAvailable())
return;
//Create call target
createElem2({
appendTo: byId("wrapper"),
type: "div",
id: "callsTarget"
});
//Now we have to reopen current calls
ComunicWeb.components.calls.controller.reopenCurrentCalls();
}
//We wait the user to be connected before trying to get
// call configuration
document.addEventListener("got_user_id", function(){
//Check if we have already the call configuration
if(ComunicWeb.components.calls.__config !== undefined)
return;
ComunicWeb.components.calls.interface.getConfig(function(config){
//Check if we could not get calls configuration
if(config.error)
return;
//Save calls configuration
ComunicWeb.components.calls.__config = config;
initializeCallContainer();
});
});
// Each time a page is opened, wec check if we have to create calls target
document.addEventListener("openPage", function(){
initializeCallContainer();
});
},
/**
* Access calls configuration
*
* @return Cached calls configuration
*/
getConfig: function() {
return ComunicWeb.components.calls.__config;
},
/**
* Check if the call feature is available or not
*/
isAvailable: function(){
//First, check if this browser support webrtc
if(!SimplePeer.WEBRTC_SUPPORT)
return false;
//If do not have any call configuration, it is not possible to
//make calls
if(this.getConfig() == null)
return false;
//Read configuration
return this.getConfig().enabled;
},
/**
* Initiate a call for a conversation
*
* @param {Number} conversationID The ID of the target conversation
*/
call: function(conversationID){
//Create / Get call information for the conversation
ComunicWeb.components.calls.interface.createForConversation(conversationID, function(call){
if(call.error)
return notify("Could not get a call for this conversation!", "danger");
ComunicWeb.components.calls.controller.open(call);
});
},
/**
* Call this method to initialize a call for a call we have information about
*
* @param {Object} call Information about the call
*/
open: function(call){
//Check if the call is already in the list of calls
if(ComunicWeb.components.calls.currentList.isCallInList(call.id))
return;
//Add the call to the list of opened calls
ComunicWeb.components.calls.currentList.addCallToList(call.id);
//Initialize call
ComunicWeb.components.calls.callWindow.initCall(call);
},
/**
* This method is called each time the notification service
* detect that the number of pending calls has increased. It
* must in fact be "thread-safe" to avoid to do twice things
* that should be one only once
*
* @param {number} number The number of pending calls
*/
newCallsAvailable: function(number){
//Check if user is already processing a call
if(this._is_processing_call)
return;
this._is_processing_call = true;
/**
* Switch processing call to false
*/
var undoIsProcessing = function(){
ComunicWeb.components.calls.controller._is_processing_call = false;
}
//Get information about the next pending call
ComunicWeb.components.calls.interface.getNextPendingCall(function(call){
//Check if there is no pending call
if(call.notice)
return undoIsProcessing();
ComunicWeb.components.conversations.utils.getNameForID(call.conversation_id, function(name){
//Check for errors
if(!name){
ComunicWeb.debug.logMessage("Could not get the name of the conversation for a call, cannot process it!");
undoIsProcessing();
return;
}
//Show ring screen
var prompting = true;
var ringScreenInfo = ComunicWeb.components.calls.ringScreen.show(name, 30, function(accept){
prompting = false;
undoIsProcessing();
ComunicWeb.components.calls.controller.applyReponseForCall(call, accept);
});
//Regulary check if the call is still valid
var interval = setInterval(function(){
if(!prompting)
return clearInterval(interval);
ComunicWeb.components.calls.interface.getInfo(call.id, function(info){
//Check for errors
if(info.error)
return;
//Refuse the call if everyone has left it
if(ComunicWeb.components.calls.utils.hasEveryoneLeft(info))
ringScreenInfo.respond(false);
//Close ring screen if user responded to the call on another Comunic client
if(ComunicWeb.components.calls.utils.getCurrentUserState(info) != "unknown")
ringScreenInfo.close();
});
}, 2000);
});
});
},
/**
* Apply a response for the call
*
* @param {Object} call Information about the target call
* @param {Boolean} accept TRUE to accept call / FALSE else
*/
applyReponseForCall: function(call, accept){
//Send response to server
ComunicWeb.components.calls.interface.respondToCall(call.id, accept, function(r){
//Check for error
if(r.error)
return notify("Could not send response to call to server!", "danger");
if(!accept)
return;
//We may start the call now
ComunicWeb.components.calls.controller.open(call);
});
},
/**
* Reopen all current calls
*/
reopenCurrentCalls: function(){
//Process each call to open it
ComunicWeb.components.calls.currentList.getCurrentCallsList().forEach(function(entry){
ComunicWeb.components.calls.interface.getInfo(entry, function(call){
if(call.error){
ComunicWeb.components.calls.currentList.removeCallFromList(entry);
return notify("Could not get information about a call!", "danger");
}
ComunicWeb.components.calls.controller.open(call);
});
});
},
/**
* Call this method only if the system is sure that
* nobody is signed in the current tab
*/
userSignedOut: function(){
//Remove all the current calls from the list
ComunicWeb.components.calls.currentList.removeAllCalls();
}
}

View File

@ -0,0 +1,84 @@
/**
* Currents calls list management
*
* @author Pierre HUBERT
*/
ComunicWeb.components.calls.currentList = {
/**
* This variable contains the name of the session storage
* variable that contains active calls
*/
_local_storage_list_calls_name: "current-calls",
/**
* Get the list of active calls
*
* @return {number[]} The list of calls
*/
getCurrentCallsList: function(){
var string = localStorage.getItem(this._local_storage_list_calls_name);
if(string === null || string == "")
return [];
else
return string.split(",");
},
/**
* Check if a call ID is in the list of opened calls
*
* @param {Number} id The ID of the call to check
*/
isCallInList: function(id){
return this.getCurrentCallsList().includes(""+id);
},
/**
* Save a new list of calls
*
* @param {number[]} list The new list of calls to save
*/
saveNewCallsList: function(list){
localStorage.setItem(this._local_storage_list_calls_name, list.join(","));
},
/**
* Add a call to the list of opened call
*
* @param {number} id The ID of the call to add
*/
addCallToList: function(id){
var list = this.getCurrentCallsList();
if(!list.includes(""+id))
list.push(id);
this.saveNewCallsList(list);
},
/**
* Remove a call from the list of calls
*
* @param {Number} id The ID of the call to remove
*/
removeCallFromList: function(id){
var list = this.getCurrentCallsList();
while(list.includes(""+id))
list.splice(list.indexOf(""+id), 1);
this.saveNewCallsList(list);
},
/**
* Remove all the calls from the list
*/
removeAllCalls: function(){
this.saveNewCallsList([]);
}
}

View File

@ -0,0 +1,103 @@
/**
* Calls interface
*
* @author Pierre HUBERT
*/
ComunicWeb.components.calls.interface = {
/**
* Get calls configuration
*
* @param {function} callback Function that will be called
* once the operation has completed
*/
getConfig: function(callback){
ComunicWeb.common.api.makeAPIrequest("calls/config", {}, true, callback);
},
/**
* Create a call for a conversation
*
* @param {Number} convID The ID of the target conversation
* @param {function} callback
*/
createForConversation : function(convID, callback){
ComunicWeb.common.api.makeAPIrequest(
"calls/createForConversation",
{
conversationID: convID
},
true,
callback
);
},
/**
* Get information about a single call
*
* @param {Number} callID The ID of the target call
* @param {function} callback Function called on request result
*/
getInfo: function (callID, callback){
ComunicWeb.common.api.makeAPIrequest(
"calls/getInfo",
{
call_id: callID
},
true,
callback
);
},
/**
* Get and return the next pending call for the
* user
*
* @param {function} callback
*/
getNextPendingCall: function(callback){
ComunicWeb.common.api.makeAPIrequest(
"calls/nextPending",
{},
true,
callback
);
},
/**
* Respond to call
*
* @param {number} call_id The ID of the target call
* @param {boolean} accept TRUE to accept call / FALSE els
* @param {function} callback Function to call once response has been set
*/
respondToCall: function(call_id, accept, callback){
ComunicWeb.common.api.makeAPIrequest(
"calls/respond",
{
call_id: call_id,
accept: accept
},
true,
callback
);
},
/**
* Hang up a call
*
* @param {Number} call_id The ID of the target call
* @param {function} callback Function to call on call callback
*/
hangUp: function(call_id, callback){
ComunicWeb.common.api.makeAPIrequest(
"calls/hangUp",
{
call_id: call_id,
},
true,
callback
);
}
}

View File

@ -0,0 +1,125 @@
/**
* Calls ring screen
*
* Display a popup to ask the user whether he wants
* to respond to a call or not
*
* @author Pierre HUBERT
*/
ComunicWeb.components.calls.ringScreen = {
/**
* Song object
*
* @type {SongPlayer}
*/
_song: undefined,
/**
* Notify user about an incoming call and offer him to respond it
*
* @param {String} title The title of the conversation
* @param {number} timeout Timeout after which the call is automatically
* considered as rejected
* @param {(accept : boolean) => any} callback Callback function
* @return {Object} Information about the window
*/
show: function(title, timeout, callback){
//Initialize song first
if(this._song == undefined)
this._song = new SongPlayer([
ComunicWeb.__config.assetsURL + "audio/phone_ring.mp3",
ComunicWeb.__config.assetsURL + "audio/phone_ring.ogg"
]);
this._song.playForever();
var callContainer = createElem2({
appendTo: document.body,
type: "div",
class: "ring-call-container"
});
var callMessageBox = createElem2({
appendTo: callContainer,
type: "div",
class: "call-message-box"
});
add_p(callMessageBox, "<i>" + title + "</i> is calling you");
//Add buttons to respond to call
var respondButtons = createElem2({
appendTo: callMessageBox,
type: "div",
class: "response-buttons"
});
var rejectButton = createElem2({
appendTo: respondButtons,
type: "button",
class: "btn btn-danger",
innerHTML: "Reject"
});
var acceptButton = createElem2({
appendTo: respondButtons,
type: "button",
class: "btn btn-success",
innerHTML: "Accept"
});
var close = function(){
ComunicWeb.components.calls.ringScreen._song.stop();
//Remove elem
emptyElem(callContainer);
callContainer.remove();
}
var hasResponded = false;
var respond = function(accept){
close();
if(hasResponded)
return;
hasResponded = true;
callback(accept);
}
rejectButton.addEventListener("click", function() {
respond(false);
});
acceptButton.addEventListener("click", function(){
respond(true);
});
//Automatically reject the call after a certain amount of time
setTimeout(function(){
respond(false);
}, timeout*1000);
return {
/**
* A function to close the current ringscreen, without
* calling callback
*/
close: close,
/**
* A function to programmatically respond to call
*/
respond: respond
};
}
}

View File

@ -0,0 +1,32 @@
/**
* User media getter
*
* @author Pierre HUBERT
*/
ComunicWeb.components.calls.userMedia = {
/**
* Get user media
*
* @return {Promise} A promise to get user media
*/
get: function(){
//Use latest API
return navigator.mediaDevices
//Request user media
.getUserMedia({
audio: true,
video: true
})
//Save stream
.then(function(stream){
ComunicWeb.components.calls.userMedia._currMedia = stream;
return stream;
});
}
}

View File

@ -0,0 +1,44 @@
/**
* Calls utilities
*
* @author Pierre HUBERT
*/
ComunicWeb.components.calls.utils = {
/**
* Check out whether all the members of a conversation stop to follow it,
* except the current user
*
* @param {Object} info Information about the conversation to analyze
*/
hasEveryoneLeft: function(info){
var allDisconnected = true;
info.members.forEach(function(member){
if(member.status != "rejected" && member.status != "hang_up" && member.userID != userID())
allDisconnected = false;
});
return allDisconnected;
},
/**
* Get the current user response to a call
*
* @param {Call} call Current call information
* @return The response of the current user to the call
*/
getCurrentUserState: function(call){
var userstate = undefined;
call.members.forEach(function(member){
if(member.userID == userID())
userstate = member.status
});
return userstate;
}
};

View File

@ -56,7 +56,7 @@ ComunicWeb.components.conversations.chatWindows = {
infosBox.conversationID = infos.conversationID;
//Change box root class name
infosBox.rootElem.className += " direct-chat direct-chat-primary";
infosBox.rootElem.className += " chat-window direct-chat direct-chat-primary";
//Adapt close button behaviour
infosBox.closeFunction = function(){
@ -84,18 +84,21 @@ ComunicWeb.components.conversations.chatWindows = {
});
//Add button to get conversation members
infosBox.membersButton = createElem("button");
infosBox.closeButton.parentNode.insertBefore(infosBox.membersButton, infosBox.closeButton);
infosBox.membersButton.type = "button";
infosBox.membersButton.className = "btn btn-box-tool";
infosBox.membersButton = createElem2({
type: "button",
insertBefore: infosBox.closeButton,
elemType: "button",
class: "btn btn-box-tool",
title: "Conversation members"
});
infosBox.membersButton.setAttribute("data-toggle", "tooltip");
infosBox.membersButton.setAttribute("data-widget", "chat-pane-toggle");
infosBox.membersButton.title = "Conversation members";
//Add button icon
var buttonIcon = createElem("i", infosBox.membersButton);
buttonIcon.className = "fa fa-users";
//Add conversation members pane
var membersPane = createElem("div", infosBox.boxBody);
membersPane.className = "direct-chat-contacts";
@ -319,6 +322,9 @@ ComunicWeb.components.conversations.chatWindows = {
return false;
};
//Add call button (if possible)
ComunicWeb.components.conversations.chatWindows.showCallButton(conversationInfos);
});
});
@ -563,16 +569,46 @@ ComunicWeb.components.conversations.chatWindows = {
return true;
},
/**
* Add a call button to the conversation, if possible
*
* @param {Object} conversation Information about the conversation
*/
showCallButton: function(conversation){
//Check if calls are disabled
if(!ComunicWeb.components.calls.controller.isAvailable())
return;
//Check if it is a conversation with an exceptable number of members
if(conversation.infos.members.length < 2
|| conversation.infos.members.length > ComunicWeb.components.calls.controller.getConfig().maximum_number_members)
return;
//Add the call button
var button = createElem2({
insertBefore: conversation.box.boxTools.firstChild,
type: "button",
class: "btn btn-box-tool",
innerHTML: "<i class='fa fa-phone'></i>"
});
conversation.box.callButton = button;
button.addEventListener("click", function(){
ComunicWeb.components.calls.controller.call(conversation.infos.ID);
});
},
/**
* Process submited update conversation form
*
* @param {Object} conversation Informations about the conversation
* @param {Object} conversation Information about the conversation
* @return {Boolean} True for a success
*/
submitUpdateForm: function(conversation){
//Then, get informations about the input
//Then, get information about the input
var newValues = {
conversationID: conversation.infos.ID,
following: conversation.settingsForm.followConversationInput.checked,
@ -621,7 +657,7 @@ ComunicWeb.components.conversations.chatWindows = {
/**
* Submit a new message form
*
* @param {Object} convInfos Informations about the conversation
* @param {Object} convInfos Information about the conversation
* @return {Boolean} True for a success
*/
submitMessageForm: function(convInfos){
@ -673,7 +709,7 @@ ComunicWeb.components.conversations.chatWindows = {
/**
* Reset a create a message form
*
* @param {Object} infos Informations about the conversation
* @param {Object} infos Information about the conversation
* @return {Boolean} True for a success
*/
resetCreateMessageForm: function(infos){
@ -910,6 +946,95 @@ ComunicWeb.components.conversations.chatWindows = {
element: textMessage,
});
//Add message dropdown menu
messageContainer.className += " dropdown";
var dropdownToggle = createElem2({
insertBefore: dateElem,
type: "i",
class: "hidden"
});
dropdownToggle.setAttribute("data-toggle", "dropdown");
var dropdownMenu = createElem2({
insertBefore: dateElem,
type: "ul",
class: "dropdown-menu"
});
dropdownMenu.setAttribute("role", "menu");
messageTargetElem.addEventListener("dblclick", function(){
$(dropdownToggle).dropdown("toggle");
});
//Add message options
if(userIsPoster){
//Update message content
var updateLi = createElem2({
type: "li",
appendTo: dropdownMenu
});
var updateLink = createElem2({
type: "a",
appendTo: updateLi,
innerHTML: "Edit"
});
updateLink.addEventListener("click", function(){
ComunicWeb.components.conversations.messageEditor.open(message, function(newContent){
//Apply and parse new message
textMessage.innerHTML = removeHtmlTags(newContent);
ComunicWeb.components.textParser.parse({
element: textMessage,
});
});
});
//Delete the message
var deleteLi = createElem2({
type: "li",
appendTo: dropdownMenu
});
var deleteLink = createElem2({
type: "a",
appendTo: deleteLi,
innerHTML: "Delete"
});
deleteLink.addEventListener("click", function(){
ComunicWeb.common.messages.confirm(
"Do you really want to delete this message? The operation can not be reverted!",
function(confirm){
if(!confirm) return;
//Hide the message
messageTargetElem.style.display = "none";
//Execute the request
ComunicWeb.components.conversations.interface.DeleteSingleMessage(
message.ID,
function(result){
if(!result){
messageTargetElem.style.display = "block";
notify("Could delete conversation message!", "danger");
}
}
);
}
)
});
}
//Return information about the message
return {
userID: message.ID_user,

View File

@ -363,6 +363,50 @@ ComunicWeb.components.conversations.interface = {
},
/**
* Intend to update the content of a single message
*
* @param {Number} messageID The ID of the message to update
* @param {String} content New content for the message
* @param {(success : Boolean) => any} callback Function called when
* the request is terminated
*/
UpdateSingleMessage: function(messageID, content, callback){
ComunicWeb.common.api.makeAPIrequest(
"conversations/updateMessage",
{
"messageID": messageID,
"content": content
},
true,
function(result){
callback(result.error ? false : true);
}
);
},
/**
* Intend to delete a single conversation message
*
* @param {Number} messageID The ID of the message to delete
* @param {(success: Boolean) => any} callback Function to call once the
* conversation message has been deleted
*/
DeleteSingleMessage: function(messageID, callback){
ComunicWeb.common.api.makeAPIrequest(
"conversations/deleteMessage",
{"messageID": messageID},
true,
function(result){
callback(result.error ? false : true);
}
);
},
/**
* Empty conversations cache
*

View File

@ -0,0 +1,49 @@
/**
* Conversation message editor
*
* @author Pierre HUBERT
*/
ComunicWeb.components.conversations.messageEditor = {
/**
* Open conversation message editor
*
* @param {Object} message Information about the message to open
* @param {(newcontent : String) => any} callback Callback function called only
* when the new message content has been applied
*/
open: function(message, callback){
ComunicWeb.common.messages.inputString(
"Update message content",
"Please specify the new content of the message:",
message.message,
function(content){
if(!content)
return;
//Intend to update message content
ComunicWeb.components.conversations.interface.UpdateSingleMessage(
message.ID,
content,
function(result){
if(!result)
return notify("Could not update conversation message content!", "danger");
message.message = content;
callback(content);
}
);
}
);
}
}

View File

@ -54,6 +54,27 @@ ComunicWeb.components.conversations.utils = {
return true;
},
/**
* Given a conversation ID, get its name
*
* @param {number} id The ID of the target conversation
* @param {function} onGotName Function called once we have got the name of the conversation
*/
getNameForID: function(id, onGotName){
ComunicWeb.components.conversations.interface.getInfosOne(id, function(info){
//Check if an error occurred
if(info.error)
return onGotName(false);
//Get and return the name of the conversation
ComunicWeb.components.conversations.utils.getName(info, onGotName);
});
},
/**
* Create and display a conversation creation / edition form
*

View File

@ -0,0 +1,66 @@
/**
* Dark theme component
*
* @author Pierre HUBERT
*/
ComunicWeb.components.darkTheme = {
/**
* Specify whether dark theme has to be enabled or not
*/
_local_storage_name: "dark_theme_mode",
/**
* CSS element that contains dark theme CSS rules
*/
_cssElem: null,
/**
* Check out whether dark theme is enabled or not
*
* @return {boolean} TRUE if enabled / FALSE else
*/
isEnabled: function(){
return localStorage.getItem(this._local_storage_name) == "true";
},
/**
* Specify whether dark theme should be enabled or not
*
* @param {boolean} enable TRUE to enable / FALSE else
*/
setEnabled: function(enable){
localStorage.setItem(this._local_storage_name, enable ? "true" : "false");
this.refresh();
},
/**
* Refresh dark theme state
*/
refresh: function(){
//Check if the theme has to be disabled
if(!this.isEnabled()){
if(this._cssElem != null)
this._cssElem.disabled = true;
return;
}
//Check if CSS element is already loaded
else if(this._cssElem != null)
this._cssElem.disabled = false;
//We need to load dark theme
else {
this._cssElem = createElem2({
type: "link",
href: ComunicWeb.__config.assetsURL + "css/dark_theme.css"
});
this._cssElem.setAttribute("rel", "stylesheet");
document.head.appendChild(this._cssElem);
}
}
}

View File

@ -23,6 +23,7 @@ ComunicWeb.components.emoji.list = {
//Objects
"(movie)": "&#x1F4FD;",
"(w)": " &#x2753;"
}
}

Some files were not shown because too many files have changed in this diff Show More