diff --git a/assets/3rdparty/bootstrap-wysiwyg.js b/assets/3rdparty/bootstrap-wysiwyg.js new file mode 100644 index 00000000..75c52faa --- /dev/null +++ b/assets/3rdparty/bootstrap-wysiwyg.js @@ -0,0 +1,333 @@ +/* jshint browser: true */ + +( function( window, $ ) +{ + "use strict"; + + /* + * Represenets an editor + * @constructor + * @param {DOMNode} element - The TEXTAREA element to add the Wysiwyg to. + * @param {object} userOptions - The default options selected by the user. + */ + function Wysiwyg( element, userOptions ) { + + // This calls the $ function, with the element as a parameter and + // returns the jQuery object wrapper for element. It also assigns the + // jQuery object wrapper to the property $editor on `this`. + this.selectedRange = null; + this.editor = $( element ); + var editor = $( element ); + var defaults = { + hotKeys: { + "Ctrl+b meta+b": "bold", + "Ctrl+i meta+i": "italic", + "Ctrl+u meta+u": "underline", + "Ctrl+z": "undo", + "Ctrl+y meta+y meta+shift+z": "redo", + "Ctrl+l meta+l": "justifyleft", + "Ctrl+r meta+r": "justifyright", + "Ctrl+e meta+e": "justifycenter", + "Ctrl+j meta+j": "justifyfull", + "Shift+tab": "outdent", + "tab": "indent" + }, + toolbarSelector: "[data-role=editor-toolbar]", + commandRole: "edit", + activeToolbarClass: "btn-info", + selectionMarker: "edit-focus-marker", + selectionColor: "darkgrey", + dragAndDropImages: true, + keypressTimeout: 200, + fileUploadError: function( reason, detail ) { console.log( "File upload error", reason, detail ); } + }; + + var options = $.extend( true, {}, defaults, userOptions ); + var toolbarBtnSelector = "a[data-" + options.commandRole + "],button[data-" + options.commandRole + "],input[type=button][data-" + options.commandRole + "]"; + this.bindHotkeys( editor, options, toolbarBtnSelector ); + + if ( options.dragAndDropImages ) { + this.initFileDrops( editor, options, toolbarBtnSelector ); + } + + this.bindToolbar( editor, $( options.toolbarSelector ), options, toolbarBtnSelector ); + + editor.attr( "contenteditable", true ) + .on( "mouseup keyup mouseout", function() { + this.saveSelection(); + this.updateToolbar( editor, toolbarBtnSelector, options ); + }.bind( this ) ); + + $( window ).bind( "touchend", function( e ) { + var isInside = ( editor.is( e.target ) || editor.has( e.target ).length > 0 ), + currentRange = this.getCurrentRange(), + clear = currentRange && ( currentRange.startContainer === currentRange.endContainer && currentRange.startOffset === currentRange.endOffset ); + + if ( !clear || isInside ) { + this.saveSelection(); + this.updateToolbar( editor, toolbarBtnSelector, options ); + } + } ); + } + + Wysiwyg.prototype.readFileIntoDataUrl = function( fileInfo ) { + var loader = $.Deferred(), + fReader = new FileReader(); + + fReader.onload = function( e ) { + loader.resolve( e.target.result ); + }; + + fReader.onerror = loader.reject; + fReader.onprogress = loader.notify; + fReader.readAsDataURL( fileInfo ); + return loader.promise(); + }; + + Wysiwyg.prototype.cleanHtml = function( o ) { + var self = this; + if ( $( self ).data( "wysiwyg-html-mode" ) === true ) { + $( self ).html( $( self ).text() ); + $( self ).attr( "contenteditable", true ); + $( self ).data( "wysiwyg-html-mode", false ); + } + + // Strip the images with src="data:image/.." out; + if ( o === true && $( self ).parent().is( "form" ) ) { + var gGal = $( self ).html; + if ( $( gGal ).has( "img" ).length ) { + var gImages = $( "img", $( gGal ) ); + var gResults = []; + var gEditor = $( self ).parent(); + $.each( gImages, function( i, v ) { + if ( $( v ).attr( "src" ).match( /^data:image\/.*$/ ) ) { + gResults.push( gImages[ i ] ); + $( gEditor ).prepend( "" ); + $( v ).attr( "src", "postedimage/" + i ); + } + } ); + } + } + + var html = $( self ).html(); + return html && html.replace( /(
|\s|

<\/div>| )*$/, "" ); + }; + + Wysiwyg.prototype.updateToolbar = function( editor, toolbarBtnSelector, options ) { + if ( options.activeToolbarClass ) { + $( options.toolbarSelector ).find( toolbarBtnSelector ).each( function() { + var self = $( this ); + var commandArr = self.data( options.commandRole ).split( " " ); + var command = commandArr[ 0 ]; + + // If the command has an argument and its value matches this button. == used for string/number comparison + if ( commandArr.length > 1 && document.queryCommandEnabled( command ) && document.queryCommandValue( command ) === commandArr[ 1 ] ) { + self.addClass( options.activeToolbarClass ); + } + + // Else if the command has no arguments and it is active + else if ( commandArr.length === 1 && document.queryCommandEnabled( command ) && document.queryCommandState( command ) ) { + self.addClass( options.activeToolbarClass ); + } + + // Else the command is not active + else { + self.removeClass( options.activeToolbarClass ); + } + } ); + } + }; + + Wysiwyg.prototype.execCommand = function( commandWithArgs, valueArg, editor, options, toolbarBtnSelector ) { + var commandArr = commandWithArgs.split( " " ), + command = commandArr.shift(), + args = commandArr.join( " " ) + ( valueArg || "" ); + + var parts = commandWithArgs.split( "-" ); + + if ( parts.length === 1 ) { + document.execCommand( command, false, args ); + } else if ( parts[ 0 ] === "format" && parts.length === 2 ) { + document.execCommand( "formatBlock", false, parts[ 1 ] ); + } + + ( editor ).trigger( "change" ); + this.updateToolbar( editor, toolbarBtnSelector, options ); + }; + + Wysiwyg.prototype.bindHotkeys = function( editor, options, toolbarBtnSelector ) { + var self = this; + $.each( options.hotKeys, function( hotkey, command ) { + if(!command) return; + + $( editor ).keydown( hotkey, function( e ) { + if ( editor.attr( "contenteditable" ) && $( editor ).is( ":visible" ) ) { + e.preventDefault(); + e.stopPropagation(); + self.execCommand( command, null, editor, options, toolbarBtnSelector ); + } + } ).keyup( hotkey, function( e ) { + if ( editor.attr( "contenteditable" ) && $( editor ).is( ":visible" ) ) { + e.preventDefault(); + e.stopPropagation(); + } + } ); + } ); + + editor.keyup( function() { editor.trigger( "change" ); } ); + }; + + Wysiwyg.prototype.getCurrentRange = function() { + var sel, range; + if ( window.getSelection ) { + sel = window.getSelection(); + if ( sel.getRangeAt && sel.rangeCount ) { + range = sel.getRangeAt( 0 ); + } + } else if ( document.selection ) { + range = document.selection.createRange(); + } + + return range; + }; + + Wysiwyg.prototype.saveSelection = function() { + this.selectedRange = this.getCurrentRange(); + }; + + Wysiwyg.prototype.restoreSelection = function() { + var selection; + if ( window.getSelection || document.createRange ) { + selection = window.getSelection(); + if ( this.selectedRange ) { + try { + selection.removeAllRanges(); + } + catch ( ex ) { + document.body.createTextRange().select(); + document.selection.empty(); + } + selection.addRange( this.selectedRange ); + } + } else if ( document.selection && this.selectedRange ) { + this.selectedRange.select(); + } + }; + + // Adding Toggle HTML based on the work by @jd0000, but cleaned up a little to work in this context. + Wysiwyg.prototype.toggleHtmlEdit = function( editor ) { + if ( editor.data( "wysiwyg-html-mode" ) !== true ) { + var oContent = editor.html(); + var editorPre = $( "
" );
+            $( editorPre ).append( document.createTextNode( oContent ) );
+            $( editorPre ).attr( "contenteditable", true );
+            $( editor ).html( " " );
+            $( editor ).append( $( editorPre ) );
+            $( editor ).attr( "contenteditable", false );
+            $( editor ).data( "wysiwyg-html-mode", true );
+            $( editorPre ).focus();
+        } else {
+            $( editor ).html( $( editor ).text() );
+            $( editor ).attr( "contenteditable", true );
+            $( editor ).data( "wysiwyg-html-mode", false );
+            $( editor ).focus();
+        }
+     };
+
+     Wysiwyg.prototype.insertFiles = function( files, options, editor, toolbarBtnSelector ) {
+        var self = this;
+        editor.focus();
+        $.each( files, function( idx, fileInfo ) {
+            if ( /^image\//.test( fileInfo.type ) ) {
+                $.when( self.readFileIntoDataUrl( fileInfo ) ).done( function( dataUrl ) {
+                    self.execCommand( "insertimage", dataUrl, editor, options, toolbarBtnSelector );
+                    editor.trigger( "image-inserted" );
+                } ).fail( function( e ) {
+                    options.fileUploadError( "file-reader", e );
+                } );
+            } else {
+                options.fileUploadError( "unsupported-file-type", fileInfo.type );
+            }
+        } );
+     };
+
+     Wysiwyg.prototype.markSelection = function( input, color, options ) {
+        this.restoreSelection(  );
+        if ( document.queryCommandSupported( "hiliteColor" ) ) {
+            document.execCommand( "hiliteColor", false, color || "transparent" );
+        }
+        this.saveSelection(  );
+        input.data( options.selectionMarker, color );
+     };
+
+     Wysiwyg.prototype.bindToolbar = function( editor, toolbar, options, toolbarBtnSelector ) {
+        var self = this;
+        toolbar.find( toolbarBtnSelector ).click( function() {
+            self.restoreSelection(  );
+            editor.focus();
+
+            if ( editor.data( options.commandRole ) === "html" ) {
+                self.toggleHtmlEdit( editor );
+            } else {
+                self.execCommand( $( this ).data( options.commandRole ), null, editor, options, toolbarBtnSelector );
+            }
+
+            self.saveSelection(  );
+        } );
+
+        toolbar.find( "[data-toggle=dropdown]" ).click( this.restoreSelection(  ) );
+
+        toolbar.find( "input[type=text][data-" + options.commandRole + "]" ).on( "webkitspeechchange change", function() {
+            var newValue = this.value;  // Ugly but prevents fake double-calls due to selection restoration
+            this.value = "";
+            self.restoreSelection(  );
+            if ( newValue ) {
+                editor.focus();
+                self.execCommand( $( this ).data( options.commandRole ), newValue, editor, options, toolbarBtnSelector );
+            }
+            self.saveSelection(  );
+        } ).on( "focus", function() {
+            var input = $( this );
+            if ( !input.data( options.selectionMarker ) ) {
+                self.markSelection( input, options.selectionColor, options );
+                input.focus();
+            }
+        } ).on( "blur", function() {
+            var input = $( this );
+            if ( input.data( options.selectionMarker ) ) {
+                self.markSelection( input, false, options );
+            }
+        } );
+        toolbar.find( "input[type=file][data-" + options.commandRole + "]" ).change( function() {
+            self.restoreSelection(  );
+            if ( this.type === "file" && this.files && this.files.length > 0 ) {
+                self.insertFiles( this.files, options, editor, toolbarBtnSelector );
+            }
+            self.saveSelection(  );
+            this.value = "";
+        } );
+     };
+
+     Wysiwyg.prototype.initFileDrops = function( editor, options, toolbarBtnSelector ) {
+         var self = this;
+        editor.on( "dragenter dragover", false ).on( "drop", function( e ) {
+            var dataTransfer = e.originalEvent.dataTransfer;
+            e.stopPropagation();
+            e.preventDefault();
+            if ( dataTransfer && dataTransfer.files && dataTransfer.files.length > 0 ) {
+                self.insertFiles( dataTransfer.files, options, editor, toolbarBtnSelector );
+            }
+        } );
+     };
+
+     /*
+      *  Represenets an editor
+      *  @constructor
+      *  @param {object} userOptions - The default options selected by the user.
+      */
+
+     $.fn.wysiwyg = function( userOptions ) {
+        var wysiwyg = new Wysiwyg( this, userOptions );
+     };
+
+} )( window, window.jQuery );
diff --git a/assets/3rdparty/jquery.hotkeys.js b/assets/3rdparty/jquery.hotkeys.js
new file mode 100644
index 00000000..e7701f39
--- /dev/null
+++ b/assets/3rdparty/jquery.hotkeys.js
@@ -0,0 +1,204 @@
+/*jslint browser: true*/
+/*jslint jquery: true*/
+
+/*
+ * jQuery Hotkeys Plugin
+ * Copyright 2010, John Resig
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ *
+ * Based upon the plugin by Tzury Bar Yochay:
+ * https://github.com/tzuryby/jquery.hotkeys
+ *
+ * Original idea by:
+ * Binny V A, http://www.openjs.com/scripts/events/keyboard_shortcuts/
+ */
+
+/*
+ * One small change is: now keys are passed by object { keys: '...' }
+ * Might be useful, when you want to pass some other data to your handler
+ */
+
+(function(jQuery) {
+
+  jQuery.hotkeys = {
+    version: "0.2.0",
+
+    specialKeys: {
+      8: "backspace",
+      9: "tab",
+      10: "return",
+      13: "return",
+      16: "shift",
+      17: "ctrl",
+      18: "alt",
+      19: "pause",
+      20: "capslock",
+      27: "esc",
+      32: "space",
+      33: "pageup",
+      34: "pagedown",
+      35: "end",
+      36: "home",
+      37: "left",
+      38: "up",
+      39: "right",
+      40: "down",
+      45: "insert",
+      46: "del",
+      59: ";",
+      61: "=",
+      96: "0",
+      97: "1",
+      98: "2",
+      99: "3",
+      100: "4",
+      101: "5",
+      102: "6",
+      103: "7",
+      104: "8",
+      105: "9",
+      106: "*",
+      107: "+",
+      109: "-",
+      110: ".",
+      111: "/",
+      112: "f1",
+      113: "f2",
+      114: "f3",
+      115: "f4",
+      116: "f5",
+      117: "f6",
+      118: "f7",
+      119: "f8",
+      120: "f9",
+      121: "f10",
+      122: "f11",
+      123: "f12",
+      144: "numlock",
+      145: "scroll",
+      173: "-",
+      186: ";",
+      187: "=",
+      188: ",",
+      189: "-",
+      190: ".",
+      191: "/",
+      192: "`",
+      219: "[",
+      220: "\\",
+      221: "]",
+      222: "'"
+    },
+
+    shiftNums: {
+      "`": "~",
+      "1": "!",
+      "2": "@",
+      "3": "#",
+      "4": "$",
+      "5": "%",
+      "6": "^",
+      "7": "&",
+      "8": "*",
+      "9": "(",
+      "0": ")",
+      "-": "_",
+      "=": "+",
+      ";": ": ",
+      "'": "\"",
+      ",": "<",
+      ".": ">",
+      "/": "?",
+      "\\": "|"
+    },
+
+    // excludes: button, checkbox, file, hidden, image, password, radio, reset, search, submit, url
+    textAcceptingInputTypes: [
+      "text", "password", "number", "email", "url", "range", "date", "month", "week", "time", "datetime",
+      "datetime-local", "search", "color", "tel"],
+
+    // default input types not to bind to unless bound directly
+    textInputTypes: /textarea|input|select/i,
+
+    options: {
+      filterInputAcceptingElements: true,
+      filterTextInputs: true,
+      filterContentEditable: true
+    }
+  };
+
+  function keyHandler(handleObj) {
+    if (typeof handleObj.data === "string") {
+      handleObj.data = {
+        keys: handleObj.data
+      };
+    }
+
+    // Only care when a possible input has been specified
+    if (!handleObj.data || !handleObj.data.keys || typeof handleObj.data.keys !== "string") {
+      return;
+    }
+
+    var origHandler = handleObj.handler,
+      keys = handleObj.data.keys.toLowerCase().split(" ");
+
+    handleObj.handler = function(event) {
+      //      Don't fire in text-accepting inputs that we didn't directly bind to
+      if (this !== event.target &&
+        (jQuery.hotkeys.options.filterInputAcceptingElements &&
+          jQuery.hotkeys.textInputTypes.test(event.target.nodeName) ||
+          (jQuery.hotkeys.options.filterContentEditable && jQuery(event.target).attr('contenteditable')) ||
+          (jQuery.hotkeys.options.filterTextInputs &&
+            jQuery.inArray(event.target.type, jQuery.hotkeys.textAcceptingInputTypes) > -1))) {
+        return;
+      }
+
+      var special = event.type !== "keypress" && jQuery.hotkeys.specialKeys[event.which],
+        character = String.fromCharCode(event.which).toLowerCase(),
+        modif = "",
+        possible = {};
+
+      jQuery.each(["alt", "ctrl", "shift"], function(index, specialKey) {
+
+        if (event[specialKey + 'Key'] && special !== specialKey) {
+          modif += specialKey + '+';
+        }
+      });
+
+      // metaKey is triggered off ctrlKey erronously
+      if (event.metaKey && !event.ctrlKey && special !== "meta") {
+        modif += "meta+";
+      }
+
+      if (event.metaKey && special !== "meta" && modif.indexOf("alt+ctrl+shift+") > -1) {
+        modif = modif.replace("alt+ctrl+shift+", "hyper+");
+      }
+
+      if (special) {
+        possible[modif + special] = true;
+      }
+      else {
+        possible[modif + character] = true;
+        possible[modif + jQuery.hotkeys.shiftNums[character]] = true;
+
+        // "$" can be triggered as "Shift+4" or "Shift+$" or just "$"
+        if (modif === "shift+") {
+          possible[jQuery.hotkeys.shiftNums[character]] = true;
+        }
+      }
+
+      for (var i = 0, l = keys.length; i < l; i++) {
+        if (possible[keys[i]]) {
+          return origHandler.apply(this, arguments);
+        }
+      }
+    };
+  }
+
+  jQuery.each(["keydown", "keyup", "keypress"], function() {
+    jQuery.event.special[this] = {
+      add: keyHandler
+    };
+  });
+
+})(jQuery || this.jQuery || window.jQuery);
diff --git a/assets/css/components/posts/form.css b/assets/css/components/posts/form.css
new file mode 100644
index 00000000..73efd881
--- /dev/null
+++ b/assets/css/components/posts/form.css
@@ -0,0 +1,15 @@
+/**
+ * Posts creation form
+ *
+ * @author Pierre HUBERT
+ */
+
+.post-form .new-message {
+	width: 100%;
+	min-height: 100px;
+	font-size: 14px;
+	line-height: 18px;
+	border: 1px solid #dddddd;
+	padding: 10px;
+	margin-bottom: 10px;
+}
\ No newline at end of file
diff --git a/assets/js/common/functionsSchema.js b/assets/js/common/functionsSchema.js
index a551062d..f22030c1 100644
--- a/assets/js/common/functionsSchema.js
+++ b/assets/js/common/functionsSchema.js
@@ -655,9 +655,16 @@ var ComunicWeb = {
 			 * Posts UI
 			 */
 			ui: {
-
+				//TODO : implement
 			},
 
+			/**
+			 * Posts creation form
+			 */
+			form: {
+				//TODO : implement
+			}
+
 		},
 
 		/**
diff --git a/assets/js/components/posts/form.js b/assets/js/components/posts/form.js
new file mode 100644
index 00000000..12021012
--- /dev/null
+++ b/assets/js/components/posts/form.js
@@ -0,0 +1,55 @@
+/**
+ * Posts creation form
+ * 
+ * @author Pierre HUBERT
+ */
+
+ComunicWeb.components.posts.form = {
+
+	/**
+	 * Display post creation form
+	 * 
+	 * @param {string} kind The kind of page
+	 * @param {int} id The ID of the page
+	 * @param {HTMLElement} target The target of the form
+	 */
+	display: function(kind, id, target){
+
+		//Log action
+		ComunicWeb.debug.logMessage("Display post creation form");
+
+		//Create form creation box
+		var boxRoot = createElem2({
+			appendTo: target,
+			type: "div",
+			class: "box box-primary post-form"
+		});
+
+		//Create box body
+		var boxBody = createElem2({
+			appendTo: boxRoot,
+			type: "div",
+			class: "box-body"
+		});
+
+		//Create post message textarea
+		var inputMessageDiv = createElem2({
+			appendTo: boxBody,
+			type: "div",
+			class: "new-message",
+		});
+
+		//Enable bootstrap-wysiwyg
+		$(inputMessageDiv).wysiwyg();
+
+
+		//Add send button
+		var sendButton = createElem2({
+			appendTo: boxBody,
+			type: "button",
+			class: "btn btn-primary pull-right",
+			innerHTML: "Send"
+		});
+	}
+
+}
\ No newline at end of file
diff --git a/assets/js/pages/userPage/main.js b/assets/js/pages/userPage/main.js
index 54e04557..21781e97 100644
--- a/assets/js/pages/userPage/main.js
+++ b/assets/js/pages/userPage/main.js
@@ -154,6 +154,10 @@ ComunicWeb.pages.userPage.main = {
 			class: "col-md-6"
 		});
 
+		//Display post creation form if the user is allowed to do so
+		if(infos.can_post_texts == true)
+			ComunicWeb.components.posts.form.display("user", infos.userID, rightColumn);
+
 		//Display posts
 		ComunicWeb.pages.userPage.posts.display(infos, params, rightColumn);
 	}
diff --git a/system/config/dev.config.php b/system/config/dev.config.php
index c4ea6993..923cf790 100644
--- a/system/config/dev.config.php
+++ b/system/config/dev.config.php
@@ -83,6 +83,12 @@ class Dev {
 		//ChartJS
 		"3rdparty/adminLTE/plugins/chartjs/Chart.min.js",
 
+		//Jquery hotkeys
+		"3rdparty/jquery.hotkeys.js",
+
+		//Bootstrap-WYSIWYG
+		"3rdparty/bootstrap-wysiwyg.js",
+
 		//VideoJS
 		//"3rdparty/videojs/6.4.0/video.min.js"
 	);
@@ -119,6 +125,7 @@ class Dev {
 
 			//Posts component
 			"css/components/posts/ui.css",
+			"css/components/posts/form.css",
 
 		//Pages stylesheets
 			//User Page
@@ -200,6 +207,7 @@ class Dev {
 			//Posts component
 			"js/components/posts/interface.js",
 			"js/components/posts/ui.js",
+			"js/components/posts/form.js",
 
 			//Modern textarea handler
 			"js/components/textarea.js",