אין להתקין סקריפט זה ישירות. זוהי ספריה עבור סקריפטים אחרים // @require https://update.greatest.deepsurf.us/scripts/11012/62427/wysiwygjs.js
- /**
- * wysiwyg.js
- */
- (function(window, document, navigator, undefined){
- 'use strict';
-
- // http://stackoverflow.com/questions/97962/debounce-clicks-when-submitting-a-web-form
- var debounce = function( callback, wait, cancelprevious )
- {
- var timeout;
- return function()
- {
- if( timeout )
- {
- if( ! cancelprevious )
- return ;
- clearTimeout( timeout );
- }
- var context = this,
- args = arguments;
- timeout = setTimeout(
- function()
- {
- timeout = null;
- callback.apply( context, args );
- }, wait );
- };
- };
-
- // http://stackoverflow.com/questions/12949590/how-to-detach-event-in-ie-6-7-8-9-using-javascript
- var addEvent = function( element, type, handler, useCapture )
- {
- if( element.addEventListener ) {
- element.addEventListener( type, handler, useCapture ? true : false );
- }
- else if( element.attachEvent ) {
- element.attachEvent( 'on' + type, handler );
- }
- else if( element != window )
- element['on' + type] = handler;
- };
- var removeEvent = function( element, type, handler, useCapture )
- {
- if( element.removeEventListener ) {
- element.removeEventListener( type, handler, useCapture ? true : false );
- }
- else if( element.detachEvent) {
- element.detachEvent( 'on' + type, handler );
- }
- else if( element != window )
- element['on' + type] = null;
- };
- // http://www.cristinawithout.com/content/function-trigger-events-javascript
- var fireEvent = function( element, type, bubbles, cancelable )
- {
- if( document.createEvent ) {
- var event = document.createEvent('Event');
- event.initEvent( type, bubbles !== undefined ? bubbles : true, cancelable !== undefined ? cancelable : false );
- element.dispatchEvent(event);
- }
- else if( document.createEventObject ) { //IE
- var event = document.createEventObject();
- element.fireEvent( 'on' + type, event );
- }
- else if( typeof(element['on' + type]) == 'function' )
- element['on' + type]();
- };
- // prevent default
- var cancelEvent = function( e )
- {
- if( e.preventDefault )
- e.preventDefault();
- else
- e.returnValue = false;
- if( e.stopPropagation )
- e.stopPropagation();
- else
- e.cancelBubble = true;
- return false;
- };
-
- // http://stackoverflow.com/questions/13377887/javascript-node-undefined-in-ie8-and-under
- var Node_ELEMENT_NODE = typeof(Node) != 'undefined' ? Node.ELEMENT_NODE : 1;
- var Node_TEXT_NODE = typeof(Node) != 'undefined' ? Node.TEXT_NODE : 3;
-
- // http://stackoverflow.com/questions/2234979/how-to-check-in-javascript-if-one-element-is-a-child-of-another
- var isOrContainsNode = function( ancestor, descendant )
- {
- var node = descendant;
- while( node )
- {
- if( node === ancestor )
- return true;
- node = node.parentNode;
- }
- return false;
- };
-
- // http://stackoverflow.com/questions/667951/how-to-get-nodes-lying-inside-a-range-with-javascript
- var nextNode = function( node, container )
- {
- if( node.firstChild )
- return node.firstChild;
- while( node )
- {
- if( node == container ) // do not walk out of the container
- return null;
- if( node.nextSibling )
- return node.nextSibling;
- node = node.parentNode;
- }
- return null;
- };
-
- // save/restore selection
- // http://stackoverflow.com/questions/13949059/persisting-the-changes-of-range-objects-after-selection-in-html/13950376#13950376
- var saveSelection = function( containerNode )
- {
- if( window.getSelection )
- {
- var sel = window.getSelection();
- if( sel.rangeCount > 0 )
- return sel.getRangeAt(0);
- }
- else if( document.selection )
- {
- var sel = document.selection;
- return sel.createRange();
- }
- return null;
- };
- var restoreSelection = function( containerNode, savedSel )
- {
- if( ! savedSel )
- return;
- if( window.getSelection )
- {
- var sel = window.getSelection();
- sel.removeAllRanges();
- sel.addRange(savedSel);
- }
- else if( document.selection )
- {
- savedSel.select();
- }
- };
-
- // http://stackoverflow.com/questions/12603397/calculate-width-height-of-the-selected-text-javascript
- // http://stackoverflow.com/questions/6846230/coordinates-of-selected-text-in-browser-page
- var getSelectionRect = function()
- {
- if( window.getSelection )
- {
- var sel = window.getSelection();
- if( ! sel.rangeCount )
- return false;
- var range = sel.getRangeAt(0).cloneRange();
- if( range.getBoundingClientRect ) // Missing for Firefox 3.5+3.6
- {
- var rect = range.getBoundingClientRect();
- // Safari 5.1 returns null, IE9 returns 0/0/0/0 if image selected
- if( ! rect || (rect.left == 0 && rect.top == 0 && rect.right == 0 && rect.bottom == 0) )
- return false;
- return {
- // Firefox returns floating-point numbers
- left: parseInt(rect.left),
- top: parseInt(rect.top),
- width: parseInt(rect.right - rect.left),
- height: parseInt(rect.bottom - rect.top)
- };
- }
- /*
- // Fall back to inserting a temporary element (only for Firefox 3.5 and 3.6)
- var span = document.createElement('span');
- if( span.getBoundingClientRect )
- {
- // Ensure span has dimensions and position by
- // adding a zero-width space character
- span.appendChild( document.createTextNode('\u200b') );
- range.insertNode( span );
- var rect = span.getBoundingClientRect();
- var spanParent = span.parentNode;
- spanParent.removeChild( span );
- // Glue any broken text nodes back together
- spanParent.normalize();
- return {
- left: parseInt(rect.left),
- top: parseInt(rect.top),
- width: parseInt(rect.right - rect.left),
- height: parseInt(rect.bottom - rect.top)
- };
- }
- */
- }
- else if( document.selection )
- {
- var sel = document.selection;
- if( sel.type != 'Control' )
- {
- var range = sel.createRange();
- // http://javascript.info/tutorial/coordinates
- // http://www.softcomplex.com/docs/get_window_size_and_scrollbar_position.html
- // http://www.howtocreate.co.uk/tutorials/javascript/browserwindow
- return {
- left: range.boundingLeft,
- top: range.boundingTop,
- width: range.boundingWidth,
- height: range.boundingHeight
- };
- }
- }
- return false;
- };
-
- var getSelectionCollapsed = function( containerNode )
- {
- if( window.getSelection )
- {
- var sel = window.getSelection();
- if( sel.isCollapsed )
- return true;
- return false;
- }
- else if( document.selection )
- {
- var sel = document.selection;
- if( sel.type == 'Text' )
- {
- var range = document.selection.createRange();
- var textrange = document.body.createTextRange();
- textrange.moveToElementText(containerNode);
- textrange.setEndPoint('EndToStart', range);
- return range.htmlText.length == 0;
- }
- if( sel.type == 'Control' ) // e.g. an image selected
- return false;
- // sel.type == 'None' -> collapsed selection
- }
- return true;
- };
-
- // http://stackoverflow.com/questions/7781963/js-get-array-of-all-selected-nodes-in-contenteditable-div
- var getSelectedNodes = function( containerNode )
- {
- if( window.getSelection )
- {
- var sel = window.getSelection();
- if( ! sel.rangeCount )
- return [];
- var nodes = [];
- for( var i=0; i < sel.rangeCount; ++i )
- {
- var range = sel.getRangeAt(i),
- node = range.startContainer,
- endNode = range.endContainer;
- while( node )
- {
- // add this node?
- if( node != containerNode )
- {
- var node_inside_selection = false;
- if( sel.containsNode )
- node_inside_selection = sel.containsNode( node, true );
- else // IE11
- {
- // http://stackoverflow.com/questions/5884210/how-to-find-if-a-htmlelement-is-enclosed-in-selected-text
- var noderange = document.createRange();
- noderange.selectNodeContents( node );
- for( var i=0; i < sel.rangeCount; ++i )
- {
- var range = sel.getRangeAt(i);
- // start after or end before -> skip node
- if( range.compareBoundaryPoints(range.END_TO_START,noderange) >= 0 &&
- range.compareBoundaryPoints(range.START_TO_END,noderange) <= 0 )
- {
- node_inside_selection = true;
- break;
- }
- }
- }
- if( node_inside_selection )
- nodes.push( node );
- }
- node = nextNode( node, node == endNode ? endNode : containerNode );
- }
- }
- // Fallback
- if( nodes.length == 0 && isOrContainsNode(containerNode,sel.focusNode) && sel.focusNode != containerNode )
- nodes.push( sel.focusNode );
- return nodes;
- }
- else if( document.selection )
- {
- var sel = document.selection;
- if( sel.type == 'Text' )
- {
- var nodes = [];
- var ranges = sel.createRangeCollection();
- for( var i=0; i < ranges.length; ++i )
- {
- var range = ranges[i],
- parentNode = range.parentElement(),
- node = parentNode;
- while( node )
- {
- // No clue how to detect whether a TextNode is within the selection...
- // ElementNode is easy: http://stackoverflow.com/questions/5884210/how-to-find-if-a-htmlelement-is-enclosed-in-selected-text
- var noderange = range.duplicate();
- noderange.moveToElementText( node.nodeType != Node_ELEMENT_NODE ? node.parentNode : node );
- // start after or end before -> skip node
- if( noderange.compareEndPoints('EndToStart',range) >= 0 &&
- noderange.compareEndPoints('StartToEnd',range) <= 0 )
- {
- // no "Array.indexOf()" in IE8
- var in_array = false;
- for( var j=0; j < nodes.length; ++j )
- {
- if( nodes[j] !== node )
- continue;
- in_array = true;
- break;
- }
- if( ! in_array )
- nodes.push( node );
- }
- node = nextNode( node, parentNode );
- }
- }
- // Fallback
- if( nodes.length == 0 && isOrContainsNode(containerNode,document.activeElement) && document.activeElement != containerNode )
- nodes.push( document.activeElement );
- return nodes;
- }
- if( sel.type == 'Control' ) // e.g. an image selected
- {
- var nodes = [];
- // http://msdn.microsoft.com/en-us/library/ie/hh826021%28v=vs.85%29.aspx
- var range = sel.createRange();
- for( var i=0; i < range.length; ++i )
- nodes.push( range(i) );
- return nodes;
- }
- }
- return [];
- };
-
- // http://stackoverflow.com/questions/8513368/collapse-selection-to-start-of-selection-not-div
- var collapseSelectionEnd = function()
- {
- if( window.getSelection )
- {
- var sel = window.getSelection();
- if( ! sel.isCollapsed )
- {
- // Form-submits via Enter throw 'NS_ERROR_FAILURE' on Firefox 34
- try {
- sel.collapseToEnd();
- }
- catch( e ) {
- }
- }
- }
- else if( document.selection )
- {
- var sel = document.selection;
- if( sel.type != 'Control' )
- {
- var range = sel.createRange();
- range.collapse(false);
- range.select();
- }
- }
- };
-
- // http://stackoverflow.com/questions/4652734/return-html-from-a-user-selected-text/4652824#4652824
- var getSelectionHtml = function( containerNode )
- {
- if( getSelectionCollapsed( containerNode ) )
- return null;
- if( window.getSelection )
- {
- var sel = window.getSelection();
- if( sel.rangeCount )
- {
- var container = document.createElement('div'),
- len = sel.rangeCount;
- for( var i=0; i < len; ++i )
- {
- var contents = sel.getRangeAt(i).cloneContents();
- container.appendChild(contents);
- }
- return container.innerHTML;
- }
- }
- else if( document.selection )
- {
- var sel = document.selection;
- if( sel.type == 'Text' )
- {
- var range = sel.createRange();
- return range.htmlText;
- }
- }
- return null;
- };
-
- var selectionInside = function( containerNode, force )
- {
- // selection inside editor?
- if( window.getSelection )
- {
- var sel = window.getSelection();
- if( isOrContainsNode(containerNode,sel.anchorNode) && isOrContainsNode(containerNode,sel.focusNode) )
- return true;
- // selection at least partly outside editor
- if( ! force )
- return false;
- // force selection to editor
- var range = document.createRange();
- range.selectNodeContents( containerNode );
- range.collapse( false );
- sel.removeAllRanges();
- sel.addRange(range);
- }
- else if( document.selection )
- {
- var sel = document.selection;
- if( sel.type == 'Control' ) // e.g. an image selected
- {
- // http://msdn.microsoft.com/en-us/library/ie/hh826021%28v=vs.85%29.aspx
- var range = sel.createRange();
- if( range.length != 0 && isOrContainsNode(containerNode,range(0)) ) // test only the first element
- return true;
- }
- else //if( sel.type == 'Text' || sel.type == 'None' )
- {
- // Range of container
- // http://stackoverflow.com/questions/12243898/how-to-select-all-text-in-contenteditable-div
- var rangeContainer = document.body.createTextRange();
- rangeContainer.moveToElementText(containerNode);
- // Compare with selection range
- var range = sel.createRange();
- if( rangeContainer.inRange(range) )
- return true;
- }
- // selection at least partly outside editor
- if( ! force )
- return false;
- // force selection to editor
- // http://stackoverflow.com/questions/12243898/how-to-select-all-text-in-contenteditable-div
- var range = document.body.createTextRange();
- range.moveToElementText(containerNode);
- range.setEndPoint('StartToEnd',range); // collapse
- range.select();
- }
- return true;
- };
-
- /*
- var clipSelectionTo = function( containerNode )
- {
- if( window.getSelection && containerNode.compareDocumentPosition )
- {
- var sel = window.getSelection();
- var left_node = sel.anchorNode,
- left_offset = sel.anchorOffset,
- right_node = sel.focusNode,
- right_offset = sel.focusOffset;
- // http://stackoverflow.com/questions/10710733/dom-determine-if-the-anchornode-or-focusnode-is-on-the-left-side
- if( (left_node == right_node && left_offset > right_offset) ||
- (left_node.compareDocumentPosition(right_node) & Node.DOCUMENT_POSITION_PRECEDING) )
- {
- // Right-to-left selection
- left_node = sel.focusNode;
- left_offset = sel.focusOffset;
- right_node = sel.anchorNode,
- right_offset = sel.anchorOffset;
- }
- // Speed up: selection inside editor
- var left_inside = isOrContainsNode(containerNode,left_node),
- right_inside = isOrContainsNode(containerNode,right_node);
- if( left_inside && right_inside )
- return true;
- // Selection before/after container?
- if( ! left_inside && containerNode.compareDocumentPosition(left_node) & Node.DOCUMENT_POSITION_FOLLOWING )
- return false; // selection after
- if( ! right_inside && containerNode.compareDocumentPosition(right_node) & Node.DOCUMENT_POSITION_PRECEDING )
- return false; // selection before
- // Selection partly before/after container
- // http://stackoverflow.com/questions/12243898/how-to-select-all-text-in-contenteditable-div
- var range = document.createRange();
- range.selectNodeContents( containerNode );
- if( left_inside )
- range.setStart( left_node, left_offset );
- if( right_inside )
- range.setEnd( right_node, right_offset );
- sel.removeAllRanges();
- sel.addRange(range);
- return true;
- }
- else if( document.selection )
- {
- var sel = document.selection;
- if( sel.type == 'Text' )
- {
- // Range of container
- // http://stackoverflow.com/questions/12243898/how-to-select-all-text-in-contenteditable-div
- var rangeContainer = document.body.createTextRange();
- rangeContainer.moveToElementText(containerNode);
- // Compare with selection range
- var range = sel.createRange();
- if( rangeContainer.inRange(range) )
- return true;
- // Selection before/after container?
- if( rangeContainer.compareEndPoints('StartToEnd',range) > 0 )
- return false;
- if( rangeContainer.compareEndPoints('EndToStart',range) < 0 )
- return false;
- // Selection partly before/after container
- if( rangeContainer.compareEndPoints('StartToStart',range) > 0 )
- range.setEndPoint('StartToStart',rangeContainer);
- if( rangeContainer.compareEndPoints('EndToEnd',range) < 0 )
- range.setEndPoint('EndToEnd',rangeContainer);
- // select range
- range.select();
- return true;
- }
- }
- return true;
- };
- */
-
- // http://stackoverflow.com/questions/6690752/insert-html-at-caret-in-a-contenteditable-div/6691294#6691294
- // http://stackoverflow.com/questions/4823691/insert-an-html-element-in-a-contenteditable-element
- // http://stackoverflow.com/questions/6139107/programatically-select-text-in-a-contenteditable-html-element
- var pasteHtmlAtCaret = function( containerNode, html )
- {
- if( window.getSelection )
- {
- // IE9 and non-IE
- var sel = window.getSelection();
- if( sel.getRangeAt && sel.rangeCount )
- {
- var range = sel.getRangeAt(0);
- // Range.createContextualFragment() would be useful here but is
- // only relatively recently standardized and is not supported in
- // some browsers (IE9, for one)
- var el = document.createElement('div');
- el.innerHTML = html;
- var frag = document.createDocumentFragment(), node, lastNode;
- while ( (node = el.firstChild) ) {
- lastNode = frag.appendChild(node);
- }
- if( isOrContainsNode(containerNode, range.commonAncestorContainer) )
- {
- range.deleteContents();
- range.insertNode(frag);
- }
- else {
- containerNode.appendChild(frag);
- }
- // Preserve the selection
- if( lastNode )
- {
- range = range.cloneRange();
- range.setStartAfter(lastNode);
- range.collapse(true);
- sel.removeAllRanges();
- sel.addRange(range);
- }
- }
- }
- else if( document.selection )
- {
- // IE <= 8
- var sel = document.selection;
- if( sel.type != 'Control' )
- {
- var originalRange = sel.createRange();
- originalRange.collapse(true);
- var range = sel.createRange();
- if( isOrContainsNode(containerNode, range.parentElement()) )
- range.pasteHTML( html );
- else // simply append to Editor
- {
- var textRange = document.body.createTextRange();
- textRange.moveToElementText(containerNode);
- textRange.collapse(false);
- textRange.select();
- textRange.pasteHTML( html );
- }
- // Preserve the selection
- range = sel.createRange();
- range.setEndPoint('StartToEnd', originalRange);
- range.select();
- }
- }
- };
-
- // Interface: Create wysiwyg
- window.wysiwyg = function( option )
- {
- // Options
- option = option || {};
- var option_element = option.element || null;
- if( typeof(option_element) == 'string' )
- option_element = document.getElementById( option_element );
- var option_onkeypress = option.onkeypress || null;
- var option_onselection = option.onselection || null;
- var option_onplaceholder = option.onplaceholder || null;
- var option_hijackcontextmenu = option.hijackcontextmenu || false;
-
- // Keep textarea if browser can't handle content-editable
- var is_textarea = option_element.nodeName == 'TEXTAREA' || option_element.nodeName == 'INPUT';
- if( is_textarea )
- {
- // http://stackoverflow.com/questions/1882205/how-do-i-detect-support-for-contenteditable-via-javascript
- var canContentEditable = 'contentEditable' in document.body;
- if( canContentEditable )
- {
- // Sniffer useragent...
- var webkit = navigator.userAgent.match(/(?:iPad|iPhone|Android).* AppleWebKit\/([^ ]+)/);
- if( webkit && 420 <= parseInt(webkit[1]) && parseInt(webkit[1]) < 534 ) // iPhone 1 was Webkit/420
- canContentEditable = false;
- }
- if( ! canContentEditable )
- {
- // Keep textarea
- var node_textarea = option_element;
- // Add a 'newline' after each '<br>'
- var newlineAfterBR = function( html ) {
- return html.replace(/<br[ \/]*>\n?/gi,'<br>\n');
- };
- node_textarea.value = newlineAfterBR( node_textarea.value );
- // Command structure
- var dummy_this = function() {
- return this;
- };
- var dummy_null = function() {
- return null;
- };
- return {
- legacy: true,
- // properties
- getElement: function()
- {
- return node_textarea;
- },
- getHTML: function()
- {
- return node_textarea.value;
- },
- setHTML: function( html )
- {
- node_textarea.value = newlineAfterBR( html );
- return this;
- },
- getSelectedHTML: dummy_null,
- sync: dummy_this,
- // selection and popup
- collapseSelection: dummy_this,
- openPopup: dummy_null,
- closePopup: dummy_this,
- // exec commands
- removeFormat: dummy_this,
- bold: dummy_this,
- italic: dummy_this,
- underline: dummy_this,
- strikethrough: dummy_this,
- forecolor: dummy_this,
- highlight: dummy_this,
- fontName: dummy_this,
- fontSize: dummy_this,
- subscript: dummy_this,
- superscript: dummy_this,
- align: dummy_this,
- format: dummy_this,
- indent: dummy_this,
- insertLink: dummy_this,
- insertImage: dummy_this,
- insertHTML: dummy_this,
- insertList: dummy_this
- };
- }
- }
-
- // create content-editable
- var node_textarea = null,
- node_wysiwyg = null;
- if( is_textarea )
- {
- // Textarea
- node_textarea = option_element;
- node_textarea.style.display = 'none';
-
- // Contenteditable
- node_wysiwyg = document.createElement( 'DIV' );
- node_wysiwyg.innerHTML = node_textarea.value;
- var parent = node_textarea.parentNode,
- next = node_textarea.nextSibling;
- if( next )
- parent.insertBefore( node_wysiwyg, next );
- else
- parent.appendChild( node_wysiwyg );
- }
- else
- node_wysiwyg = option_element;
- node_wysiwyg.setAttribute( 'contentEditable', 'true' ); // IE7 is case sensitive
-
- // IE8 uses 'document' instead of 'window'
- // http://tanalin.com/en/articles/ie-version-js/
- var window_ie8 = (document.all && !document.addEventListener) ? document : window;
-
- // Sync Editor with Textarea
- var syncTextarea = null;
- if( is_textarea )
- {
- var previous_html = node_wysiwyg.innerHTML;
- syncTextarea = function()
- {
- var new_html = node_wysiwyg.innerHTML;
- if( new_html == previous_html )
- return ;
- // HTML changed
- node_textarea.value = new_html;
- previous_html = new_html;
- // Event Handler
- fireEvent( node_textarea, 'change', false );
- };
- }
-
- // Show placeholder
- var showPlaceholder;
- if( option_onplaceholder )
- {
- var placeholder_visible = false;
- showPlaceholder = function()
- {
- // Test if wysiwyg has content
- var wysiwyg_empty = true;
- var node = node_wysiwyg;
- while( node )
- {
- node = nextNode( node, node_wysiwyg );
- // Test if node contains something visible
- if( ! node )
- ;
- else if( node.nodeType == Node_ELEMENT_NODE )
- {
- if( node.nodeName == 'IMG' )
- {
- wysiwyg_empty = false;
- break;
- }
- }
- else if( node.nodeType == Node_TEXT_NODE )
- {
- var text = node.nodeValue;
- if( text && text.search(/[^\s]/) != -1 )
- {
- wysiwyg_empty = false;
- break;
- }
- }
- }
- if( placeholder_visible != wysiwyg_empty )
- {
- option_onplaceholder( wysiwyg_empty );
- placeholder_visible = wysiwyg_empty;
- }
- };
- showPlaceholder();
- }
-
- // Handle selection
- var popup_saved_selection = null, // preserve selection during popup
- handleSelection = null,
- debounced_handleSelection = null;
- if( option_onselection )
- {
- handleSelection = function( clientX, clientY, rightclick )
- {
- // Detect collapsed selection
- var collapsed = getSelectionCollapsed( node_wysiwyg );
- // List of all selected nodes
- var nodes = getSelectedNodes( node_wysiwyg );
- // Rectangle of the selection
- var rect = (clientX === null || clientY === null) ? null :
- {
- left: clientX,
- top: clientY,
- width: 0,
- height: 0
- };
- var selectionRect = getSelectionRect();
- if( selectionRect )
- rect = selectionRect;
- if( rect )
- {
- // So far 'rect' is relative to viewport
- if( node_wysiwyg.getBoundingClientRect )
- {
- // Make it relative to the editor via 'getBoundingClientRect()'
- var boundingrect = node_wysiwyg.getBoundingClientRect();
- rect.left -= parseInt(boundingrect.left);
- rect.top -= parseInt(boundingrect.top);
- }
- else
- {
- var node = node_wysiwyg,
- offsetLeft = 0,
- offsetTop = 0,
- fixed = false;
- do {
- offsetLeft += node.offsetLeft ? parseInt(node.offsetLeft) : 0;
- offsetTop += node.offsetTop ? parseInt(node.offsetTop) : 0;
- if( node.style.position == 'fixed' )
- fixed = true;
- }
- while( node = node.offsetParent );
- rect.left -= offsetLeft - (fixed ? 0 : window.pageXOffset);
- rect.top -= offsetTop - (fixed ? 0 : window.pageYOffset);
- }
- // Trim rectangle to the editor
- if( rect.left < 0 )
- rect.left = 0;
- if( rect.top < 0 )
- rect.top = 0;
- if( rect.width > node_wysiwyg.offsetWidth )
- rect.width = node_wysiwyg.offsetWidth;
- if( rect.height > node_wysiwyg.offsetHeight )
- rect.height = node_wysiwyg.offsetHeight;
- }
- else if( nodes.length )
- {
- // What else could we do? Offset of first element...
- for( var i=0; i < nodes.length; ++i )
- {
- var node = nodes[i];
- if( node.nodeType != Node_ELEMENT_NODE )
- continue;
- rect = {
- left: node.offsetLeft,
- top: node.offsetTop,
- width: node.offsetWidth,
- height: node.offsetHeight
- };
- break;
- }
- }
- // Callback
- option_onselection( collapsed, rect, nodes, rightclick );
- };
- debounced_handleSelection = debounce( handleSelection, 1 );
- }
-
- // Open popup
- var node_popup = null;
- var popupClickClose = function( e )
- {
- // http://www.quirksmode.org/js/events_properties.html
- if( !e )
- var e = window.event;
- var target = e.target || e.srcElement;
- if( target.nodeType == Node_TEXT_NODE ) // defeat Safari bug
- target = target.parentNode;
- // Click within popup?
- if( isOrContainsNode(node_popup,target) )
- return ;
- // close popup
- popupClose();
- };
- var popupOpen = function()
- {
- // Already open?
- if( node_popup )
- return node_popup;
-
- // Global click closes popup
- addEvent( window_ie8, 'mousedown', popupClickClose, true );
-
- // Create popup element
- node_popup = document.createElement( 'DIV' );
- var parent = node_wysiwyg.parentNode,
- next = node_wysiwyg.nextSibling;
- if( next )
- parent.insertBefore( node_popup, next );
- else
- parent.appendChild( node_popup );
- return node_popup;
- };
- var popupClose = function()
- {
- if( ! node_popup )
- return ;
- node_popup.parentNode.removeChild( node_popup );
- node_popup = null;
- removeEvent( window_ie8, 'mousedown', popupClickClose, true );
- };
-
- // Focus/Blur events
- addEvent( node_wysiwyg, 'focus', function()
- {
- // forward focus/blur to the textarea
- if( node_textarea )
- fireEvent( node_textarea, 'focus', false );
- });
- addEvent( node_wysiwyg, 'blur', function()
- {
- // sync textarea immediately
- if( syncTextarea )
- syncTextarea();
- // forward focus/blur to the textarea
- if( node_textarea )
- fireEvent( node_textarea, 'blur', false );
- });
-
- // Change events
- var debounced_changeHandler = null;
- if( showPlaceholder || syncTextarea )
- {
- // debounce 'syncTextarea' a second time, because 'innerHTML' is quite burdensome
- var debounced_syncTextarea = syncTextarea ? debounce( syncTextarea, 250, true ) : null; // high timeout is save, because of "onblur" fires immediately
- var changeHandler = function( e )
- {
- if( showPlaceholder )
- showPlaceholder();
- if( debounced_syncTextarea )
- debounced_syncTextarea();
- };
- debounced_changeHandler = debounce( changeHandler, 1 );
-
- // Catch change events
- // http://stackoverflow.com/questions/1391278/contenteditable-change-events/1411296#1411296
- // http://stackoverflow.com/questions/8694054/onchange-event-with-contenteditable/8694125#8694125
- // https://github.com/mindmup/bootstrap-wysiwyg/pull/50/files
- // http://codebits.glennjones.net/editing/events-contenteditable.htm
- addEvent( node_wysiwyg, 'input', debounced_changeHandler );
- addEvent( node_wysiwyg, 'DOMNodeInserted', debounced_changeHandler );
- addEvent( node_wysiwyg, 'DOMNodeRemoved', debounced_changeHandler );
- addEvent( node_wysiwyg, 'DOMSubtreeModified', debounced_changeHandler );
- addEvent( node_wysiwyg, 'DOMCharacterDataModified', debounced_changeHandler ); // polyfill input in IE 9-10
- addEvent( node_wysiwyg, 'propertychange', debounced_changeHandler );
- addEvent( node_wysiwyg, 'textInput', debounced_changeHandler );
- addEvent( node_wysiwyg, 'paste', debounced_changeHandler );
- addEvent( node_wysiwyg, 'cut', debounced_changeHandler );
- addEvent( node_wysiwyg, 'drop', debounced_changeHandler );
- }
-
- // Key events
- // http://sandbox.thewikies.com/html5-experiments/key-events.html
- var keyHandler = function( e, phase )
- {
- // http://www.quirksmode.org/js/events_properties.html
- if( !e )
- var e = window.event;
- var code = 0;
- if( e.keyCode )
- code = e.keyCode;
- else if( e.which )
- code = e.which;
- // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent
- var character = e.charCode;
-
- // Callback
- if( phase == 1 && option_onkeypress )
- {
- var rv = option_onkeypress( code, character?String(String):String.fromCharCode(code), e.shiftKey||false, e.altKey||false, e.ctrlKey||false, e.metaKey||false );
- if( rv === false ) // dismiss key
- return cancelEvent( e );
- }
- // Keys can change the selection
- if( phase == 2 || phase == 3 )
- {
- popup_saved_selection = null;
- if( debounced_handleSelection )
- debounced_handleSelection( null, null, false );
- }
- // Most keys can cause changes
- if( phase == 2 && debounced_changeHandler )
- {
- switch( code )
- {
- case 33: // pageUp
- case 34: // pageDown
- case 35: // end
- case 36: // home
- case 37: // left
- case 38: // up
- case 39: // right
- case 40: // down
- // cursors do not
- break;
- default:
- // call change handler
- debounced_changeHandler();
- break;
- }
- }
- };
- addEvent( node_wysiwyg, 'keydown', function( e )
- {
- return keyHandler( e, 1 );
- });
- addEvent( node_wysiwyg, 'keypress', function( e )
- {
- return keyHandler( e, 2 );
- });
- addEvent( node_wysiwyg, 'keyup', function( e )
- {
- return keyHandler( e, 3 );
- });
-
- // Mouse events
- var mouseHandler = function( e, rightclick )
- {
- // http://www.quirksmode.org/js/events_properties.html
- if( !e )
- var e = window.event;
- // mouse position
- var clientX = null,
- clientY = null;
- if( e.clientX && e.clientY )
- {
- clientX = e.clientX;
- clientY = e.clientY;
- }
- else if( e.pageX && e.pageY )
- {
- clientX = e.pageX - window.pageXOffset;
- clientY = e.pageY - window.pageYOffset;
- }
- // mouse button
- if( e.which && e.which == 3 )
- rightclick = true;
- else if( e.button && e.button == 2 )
- rightclick = true;
-
- // remove event handler
- removeEvent( window_ie8, 'mouseup', mouseHandler );
- // Callback selection
- popup_saved_selection = null;
- if( ! option_hijackcontextmenu && rightclick )
- return ;
- if( debounced_handleSelection )
- debounced_handleSelection( clientX, clientY, rightclick );
- };
- addEvent( node_wysiwyg, 'mousedown', function( e )
- {
- // catch event if 'mouseup' outside 'node_wysiwyg'
- removeEvent( window_ie8, 'mouseup', mouseHandler );
- addEvent( window_ie8, 'mouseup', mouseHandler );
- });
- addEvent( node_wysiwyg, 'mouseup', function( e )
- {
- mouseHandler( e );
- // Trigger change
- if( debounced_changeHandler )
- debounced_changeHandler();
- });
- addEvent( node_wysiwyg, 'dblclick', function( e )
- {
- mouseHandler( e );
- });
- addEvent( node_wysiwyg, 'selectionchange', function( e )
- {
- mouseHandler( e );
- });
- if( option_hijackcontextmenu )
- {
- addEvent( node_wysiwyg, 'contextmenu', function( e )
- {
- mouseHandler( e, true );
- return cancelEvent( e );
- });
- }
-
-
- // exec command
- // https://developer.mozilla.org/en-US/docs/Web/API/document.execCommand
- // http://www.quirksmode.org/dom/execCommand.html
- var execCommand = function( command, param, force_selection )
- {
- // give selection to contenteditable element
- restoreSelection( node_wysiwyg, popup_saved_selection );
- if( ! selectionInside(node_wysiwyg, force_selection) ) // returns 'selection inside editor'
- return false;
- // for webkit, mozilla, opera
- if( window.getSelection )
- {
- // Buggy, call within 'try/catch'
- try {
- if( document.queryCommandSupported && ! document.queryCommandSupported(command) )
- return false;
- return document.execCommand( command, false, param );
- }
- catch( e ) {
- }
- }
- // for IE
- else if( document.selection )
- {
- var sel = document.selection;
- if( sel.type != 'None' )
- {
- var range = sel.createRange();
- // Buggy, call within 'try/catch'
- try {
- if( ! range.queryCommandEnabled(command) )
- return false;
- return range.execCommand( command, false, param );
- }
- catch( e ) {
- }
- }
- }
- return false;
- };
-
- // Command structure
- var trailingDiv = null;
- var IEtrailingDIV = function()
- {
- // Detect IE - http://stackoverflow.com/questions/17907445/how-to-detect-ie11
- if( document.all || !!window.MSInputMethodContext )
- {
- // Workaround IE11 - https://github.com/wysiwygjs/wysiwyg.js/issues/14
- trailingDiv = document.createElement( 'DIV' );
- node_wysiwyg.appendChild( trailingDiv );
- }
- };
- var callUpdates = function( selection_destroyed )
- {
- // Remove IE11 workaround
- if( trailingDiv )
- {
- node_wysiwyg.removeChild( trailingDiv );
- trailingDiv = null;
- }
- // change-handler
- if( debounced_changeHandler )
- debounced_changeHandler();
- // handle saved selection
- if( selection_destroyed )
- {
- collapseSelectionEnd();
- popup_saved_selection = null; // selection destroyed
- }
- else if( popup_saved_selection )
- popup_saved_selection = saveSelection( node_wysiwyg );
- };
- return {
- // properties
- getElement: function()
- {
- return node_wysiwyg;
- },
- getHTML: function()
- {
- return node_wysiwyg.innerHTML;
- },
- setHTML: function( html )
- {
- node_wysiwyg.innerHTML = html;
- callUpdates( true ); // selection destroyed
- return this;
- },
- getSelectedHTML: function()
- {
- restoreSelection( node_wysiwyg, popup_saved_selection );
- if( ! selectionInside(node_wysiwyg) )
- return null;
- return getSelectionHtml( node_wysiwyg );
- },
- sync: function()
- {
- if( syncTextarea )
- syncTextarea();
- return this;
- },
- // selection and popup
- collapseSelection: function()
- {
- collapseSelectionEnd();
- popup_saved_selection = null; // selection destroyed
- return this;
- },
- openPopup: function()
- {
- if( ! popup_saved_selection )
- popup_saved_selection = saveSelection( node_wysiwyg ); // save current selection
- return popupOpen();
- },
- closePopup: function()
- {
- popupClose();
- return this;
- },
- removeFormat: function()
- {
- execCommand( 'removeFormat' );
- execCommand( 'unlink' );
- callUpdates();
- return this;
- },
- bold: function()
- {
- execCommand( 'bold' );
- callUpdates();
- return this;
- },
- italic: function()
- {
- execCommand( 'italic' );
- callUpdates();
- return this;
- },
- underline: function()
- {
- execCommand( 'underline' );
- callUpdates();
- return this;
- },
- strikethrough: function()
- {
- execCommand( 'strikeThrough' );
- callUpdates();
- return this;
- },
- forecolor: function( color )
- {
- execCommand( 'foreColor', color );
- callUpdates();
- return this;
- },
- highlight: function( color )
- {
- // http://stackoverflow.com/questions/2756931/highlight-the-text-of-the-dom-range-element
- if( ! execCommand('hiliteColor',color) ) // some browsers apply 'backColor' to the whole block
- execCommand( 'backColor', color );
- callUpdates();
- return this;
- },
- fontName: function( name )
- {
- execCommand( 'fontName', name );
- callUpdates();
- return this;
- },
- fontSize: function( size )
- {
- execCommand( 'fontSize', size );
- callUpdates();
- return this;
- },
- subscript: function()
- {
- execCommand( 'subscript' );
- callUpdates();
- return this;
- },
- superscript: function()
- {
- execCommand( 'superscript' );
- callUpdates();
- return this;
- },
- align: function( align )
- {
- IEtrailingDIV();
- if( align == 'left' )
- execCommand( 'justifyLeft' );
- else if( align == 'center' )
- execCommand( 'justifyCenter' );
- else if( align == 'right' )
- execCommand( 'justifyRight' );
- else if( align == 'justify' )
- execCommand( 'justifyFull' );
- callUpdates();
- return this;
- },
- format: function( tagname )
- {
- IEtrailingDIV();
- execCommand( 'formatBlock', tagname );
- callUpdates();
- return this;
- },
- indent: function( outdent )
- {
- IEtrailingDIV();
- execCommand( outdent ? 'outdent' : 'indent' );
- callUpdates();
- return this;
- },
- insertLink: function( url )
- {
- execCommand( 'createLink', url );
- callUpdates( true ); // selection destroyed
- return this;
- },
- insertImage: function( url )
- {
- execCommand( 'insertImage', url, true );
- callUpdates( true ); // selection destroyed
- return this;
- },
- insertHTML: function( html )
- {
- if( ! execCommand('insertHTML', html, true) )
- {
- // IE 11 still does not support 'insertHTML'
- restoreSelection( node_wysiwyg, popup_saved_selection );
- selectionInside( node_wysiwyg, true );
- pasteHtmlAtCaret( node_wysiwyg, html );
- }
- callUpdates( true ); // selection destroyed
- return this;
- },
- insertList: function( ordered )
- {
- IEtrailingDIV();
- execCommand( ordered ? 'insertOrderedList' : 'insertUnorderedList' );
- callUpdates();
- return this;
- }
- };
- };
- })(window, document, navigator);
-
-
- /**
- * wysiwyg-editor.js
- */
- (function(window, document, $, undefined){
- 'use strict';
-
- // http://stackoverflow.com/questions/17242144/javascript-convert-hsb-hsv-color-to-rgb-accurately
- var HSVtoRGB = function( h, s, v )
- {
- var r, g, b, i, f, p, q, t;
- i = Math.floor(h * 6);
- f = h * 6 - i;
- p = v * (1 - s);
- q = v * (1 - f * s);
- t = v * (1 - (1 - f) * s);
- switch (i % 6)
- {
- case 0: r = v, g = t, b = p; break;
- case 1: r = q, g = v, b = p; break;
- case 2: r = p, g = v, b = t; break;
- case 3: r = p, g = q, b = v; break;
- case 4: r = t, g = p, b = v; break;
- case 5: r = v, g = p, b = q; break;
- }
- var hr = Math.floor(r * 255).toString(16);
- var hg = Math.floor(g * 255).toString(16);
- var hb = Math.floor(b * 255).toString(16);
- return '#' + (hr.length < 2 ? '0' : '') + hr +
- (hg.length < 2 ? '0' : '') + hg +
- (hb.length < 2 ? '0' : '') + hb;
- };
-
- // Encode htmlentities() - http://stackoverflow.com/questions/5499078/fastest-method-to-escape-html-tags-as-html-entities
- var html_encode = function( string )
- {
- return string.replace(/[&<>"]/g, function(tag)
- {
- var charsToReplace = {
- '&': '&',
- '<': '<',
- '>': '>',
- '"': '"'
- };
- return charsToReplace[tag] || tag;
- });
- };
-
- // Create the Editor
- var create_editor = function( $textarea, classes, placeholder, toolbar_position, toolbar_buttons, toolbar_submit, label_selectImage,
- placeholder_url, placeholder_embed, max_imagesize, on_imageupload, force_imageupload, video_from_url, on_keypress )
- {
- // Content: Insert link
- var wysiwygeditor_insertLink = function( wysiwygeditor, url )
- {
- if( ! url )
- ;
- else if( wysiwygeditor.getSelectedHTML() )
- wysiwygeditor.insertLink( url );
- else
- wysiwygeditor.insertHTML( '<a href="' + html_encode(url) + '">' + html_encode(url) + '</a>' );
- wysiwygeditor.closePopup().collapseSelection();
- };
- var content_insertlink = function(wysiwygeditor, $modify_link)
- {
- var $button = toolbar_button( toolbar_submit );
- var $inputurl = $('<input type="text" value="' + ($modify_link ? $modify_link.attr('href') : '') + '" />').addClass('wysiwyg-input')
- .keypress(function(event){
- if( event.which != 10 && event.which != 13 )
- return ;
- if( $modify_link )
- {
- $modify_link.attr( 'href', $inputurl.val() );
- wysiwygeditor.closePopup().collapseSelection();
- }
- else
- wysiwygeditor_insertLink( wysiwygeditor,$inputurl.val() );
- });
- if( placeholder_url )
- $inputurl.attr( 'placeholder', placeholder_url );
- var $okaybutton = $button.click(function(event){
- if( $modify_link )
- {
- $modify_link.attr( 'href', $inputurl.val() );
- wysiwygeditor.closePopup().collapseSelection();
- }
- else
- wysiwygeditor_insertLink( wysiwygeditor, $inputurl.val() );
- event.stopPropagation();
- event.preventDefault();
- return false;
- });
- var $content = $('<div/>').addClass('wysiwyg-toolbar-form')
- .attr('unselectable','on');
- $content.append($inputurl).append($okaybutton);
- return $content;
- };
-
- // Content: Insert image
- var content_insertimage = function(wysiwygeditor)
- {
- // Add image to editor
- var insert_image_wysiwyg = function( url, filename )
- {
- var html = '<img id="wysiwyg-insert-image" src="" alt=""' + (filename ? ' title="'+html_encode(filename)+'"' : '') + ' />';
- wysiwygeditor.insertHTML( html ).closePopup().collapseSelection();
- var $image = $('#wysiwyg-insert-image').removeAttr('id');
- if( max_imagesize )
- {
- $image.css({maxWidth: max_imagesize[0]+'px',
- maxHeight: max_imagesize[1]+'px'})
- .load( function() {
- $image.css({maxWidth: '',
- maxHeight: ''});
- // Resize $image to fit "clip-image"
- var image_width = $image.width(),
- image_height = $image.height();
- if( image_width > max_imagesize[0] || image_height > max_imagesize[1] )
- {
- if( (image_width/image_height) > (max_imagesize[0]/max_imagesize[1]) )
- {
- image_height = parseInt(image_height / image_width * max_imagesize[0]);
- image_width = max_imagesize[0];
- }
- else
- {
- image_width = parseInt(image_width / image_height * max_imagesize[1]);
- image_height = max_imagesize[1];
- }
- $image.attr('width',image_width)
- .attr('height',image_height);
- }
- });
- }
- $image.attr('src', url);
- };
- // Create popup
- var $content = $('<div/>').addClass('wysiwyg-toolbar-form')
- .attr('unselectable','on');
- // Add image via 'Browse...'
- var $fileuploader = null,
- $fileuploader_input = $('<input type="file" />')
- .css({position: 'absolute',
- left: 0,
- top: 0,
- width: '100%',
- height: '100%',
- opacity: 0,
- cursor: 'pointer'});
- if( ! force_imageupload && window.File && window.FileReader && window.FileList )
- {
- // File-API
- var loadImageFromFile = function( file )
- {
- // Only process image files
- if( ! file.type.match('image.*') )
- return;
- var reader = new FileReader();
- reader.onload = function(event) {
- var dataurl = event.target.result;
- insert_image_wysiwyg( dataurl, file.name );
- };
- // Read in the image file as a data URL
- reader.readAsDataURL( file );
- };
- $fileuploader = $fileuploader_input
- .attr('draggable','true')
- .change(function(event){
- var files = event.target.files; // FileList object
- for(var i=0; i < files.length; ++i)
- loadImageFromFile( files[i] );
- })
- .on('dragover',function(event){
- event.originalEvent.dataTransfer.dropEffect = 'copy'; // Explicitly show this is a copy.
- event.stopPropagation();
- event.preventDefault();
- return false;
- })
- .on('drop', function(event){
- var files = event.originalEvent.dataTransfer.files; // FileList object.
- for(var i=0; i < files.length; ++i)
- loadImageFromFile( files[i] );
- event.stopPropagation();
- event.preventDefault();
- return false;
- });
- }
- else if( on_imageupload )
- {
- // Upload image to a server
- var $input = $fileuploader_input
- .change(function(event){
- on_imageupload.call( this, insert_image_wysiwyg );
- });
- $fileuploader = $('<form/>').append($input);
- }
- if( $fileuploader )
- $('<div/>').addClass( 'wysiwyg-browse' )
- .html( label_selectImage )
- .append( $fileuploader )
- .appendTo( $content );
- // Add image via 'URL'
- var $button = toolbar_button( toolbar_submit );
- var $inputurl = $('<input type="text" value="" />').addClass('wysiwyg-input')
- .keypress(function(event){
- if( event.which == 10 || event.which == 13 )
- insert_image_wysiwyg( $inputurl.val() );
- });
- if( placeholder_url )
- $inputurl.attr( 'placeholder', placeholder_url );
- var $okaybutton = $button.click(function(event){
- insert_image_wysiwyg( $inputurl.val() );
- event.stopPropagation();
- event.preventDefault();
- return false;
- });
- $content.append( $('<div/>').append($inputurl).append($okaybutton) );
- return $content;
- };
-
- // Content: Insert video
- var content_insertvideo = function(wysiwygeditor)
- {
- // Add video to editor
- var insert_video_wysiwyg = function( url, html )
- {
- url = $.trim(url||'');
- html = $.trim(html||'');
- var website_url = false;
- if( url.length && ! html.length )
- website_url = url;
- else if( html.indexOf('<') == -1 && html.indexOf('>') == -1 &&
- html.match(/^(?:https?:\/)?\/?(?:[^:\/\s]+)(?:(?:\/\w+)*\/)(?:[\w\-\.]+[^#?\s]+)(?:.*)?(?:#[\w\-]+)?$/) )
- website_url = html;
- if( website_url && video_from_url )
- html = video_from_url( website_url ) || '';
- if( ! html.length && website_url )
- html = '<video src="' + html_encode(website_url) + '" />';
- wysiwygeditor.insertHTML( html ).closePopup().collapseSelection();
- };
- // Create popup
- var $content = $('<div/>').addClass('wysiwyg-toolbar-form')
- .attr('unselectable','on');
- // Add video via '<embed/>'
- var $textareaembed = $('<textarea>').addClass('wysiwyg-input wysiwyg-inputtextarea');
- if( placeholder_embed )
- $textareaembed.attr( 'placeholder', placeholder_embed );
- $('<div/>').addClass( 'wysiwyg-embedcode' )
- .append( $textareaembed )
- .appendTo( $content );
- // Add video via 'URL'
- var $button = toolbar_button( toolbar_submit );
- var $inputurl = $('<input type="text" value="" />').addClass('wysiwyg-input')
- .keypress(function(event){
- if( event.which == 10 || event.which == 13 )
- insert_video_wysiwyg( $inputurl.val() );
- });
- if( placeholder_url )
- $inputurl.attr( 'placeholder', placeholder_url );
- var $okaybutton = $button.click(function(event){
- insert_video_wysiwyg( $inputurl.val(), $textareaembed.val() );
- event.stopPropagation();
- event.preventDefault();
- return false;
- });
- $content.append( $('<div/>').append($inputurl).append($okaybutton) );
- return $content;
- };
-
- // Content: Color palette
- var content_colorpalette = function( wysiwygeditor, forecolor )
- {
- var $content = $('<table/>')
- .attr('cellpadding','0')
- .attr('cellspacing','0')
- .attr('unselectable','on');
- for( var row=1; row < 15; ++row ) // should be '16' - but last line looks so dark
- {
- var $rows = $('<tr/>');
- for( var col=0; col < 25; ++col ) // last column is grayscale
- {
- var color;
- if( col == 24 )
- {
- var gray = Math.floor(255 / 13 * (14 - row)).toString(16);
- var hexg = (gray.length < 2 ? '0' : '') + gray;
- color = '#' + hexg + hexg + hexg;
- }
- else
- {
- var hue = col / 24;
- var saturation = row <= 8 ? row /8 : 1;
- var value = row > 8 ? (16-row)/8 : 1;
- color = HSVtoRGB( hue, saturation, value );
- }
- $('<td/>').addClass('wysiwyg-toolbar-color')
- .attr('title', color)
- .attr('unselectable','on')
- .css({backgroundColor: color})
- .click(function(){
- var color = this.title;
- if( forecolor )
- wysiwygeditor.forecolor( color ).closePopup().collapseSelection();
- else
- wysiwygeditor.highlight( color ).closePopup().collapseSelection();
- return false;
- })
- .appendTo( $rows );
- }
- $content.append( $rows );
- }
- return $content;
- };
-
- // Handlers
- var get_toolbar_handler = function( name, popup_callback )
- {
- switch( name )
- {
- case 'insertimage':
- if( ! popup_callback )
- return null;
- return function( target ) {
- popup_callback( content_insertimage(wysiwygeditor), target );
- };
- case 'insertvideo':
- if( ! popup_callback )
- return null;
- return function( target ) {
- popup_callback( content_insertvideo(wysiwygeditor), target );
- };
- case 'insertlink':
- if( ! popup_callback )
- return null;
- return function( target ) {
- popup_callback( content_insertlink(wysiwygeditor), target );
- };
- case 'bold':
- return function() {
- wysiwygeditor.bold(); // .closePopup().collapseSelection()
- };
- case 'italic':
- return function() {
- wysiwygeditor.italic(); // .closePopup().collapseSelection()
- };
- case 'underline':
- return function() {
- wysiwygeditor.underline(); // .closePopup().collapseSelection()
- };
- case 'strikethrough':
- return function() {
- wysiwygeditor.strikethrough(); // .closePopup().collapseSelection()
- };
- case 'forecolor':
- if( ! popup_callback )
- return null;
- return function( target ) {
- popup_callback( content_colorpalette(wysiwygeditor,true), target );
- };
- case 'highlight':
- if( ! popup_callback )
- return null;
- return function( target ) {
- popup_callback( content_colorpalette(wysiwygeditor,false), target );
- };
- case 'alignleft':
- return function() {
- wysiwygeditor.align('left'); // .closePopup().collapseSelection()
- };
- case 'aligncenter':
- return function() {
- wysiwygeditor.align('center'); // .closePopup().collapseSelection()
- };
- case 'alignright':
- return function() {
- wysiwygeditor.align('right'); // .closePopup().collapseSelection()
- };
- case 'alignjustify':
- return function() {
- wysiwygeditor.align('justify'); // .closePopup().collapseSelection()
- };
- case 'subscript':
- return function() {
- wysiwygeditor.subscript(); // .closePopup().collapseSelection()
- };
- case 'superscript':
- return function() {
- wysiwygeditor.superscript(); // .closePopup().collapseSelection()
- };
- case 'indent':
- return function() {
- wysiwygeditor.indent(); // .closePopup().collapseSelection()
- };
- case 'outdent':
- return function() {
- wysiwygeditor.indent(true); // .closePopup().collapseSelection()
- };
- case 'orderedList':
- return function() {
- wysiwygeditor.insertList(true); // .closePopup().collapseSelection()
- };
- case 'unorderedList':
- return function() {
- wysiwygeditor.insertList(); // .closePopup().collapseSelection()
- };
- case 'removeformat':
- return function() {
- wysiwygeditor.removeFormat().closePopup().collapseSelection();
- };
- }
- return null;
- }
-
- // Create the toolbar
- var toolbar_button = function( button ) {
- return $('<a/>').addClass( 'wysiwyg-toolbar-icon' )
- .attr('href','#')
- .attr('title', button.title)
- .attr('unselectable','on')
- .append(button.image);
- };
- var add_buttons_to_toolbar = function( $toolbar, selection, popup_open_callback, popup_position_callback )
- {
- $.each( toolbar_buttons, function(key, value) {
- if( ! value )
- return ;
- // Skip buttons on the toolbar
- if( selection === false && 'showstatic' in value && ! value.showstatic )
- return ;
- // Skip buttons on selection
- if( selection === true && 'showselection' in value && ! value.showselection )
- return ;
- // Click handler
- var toolbar_handler;
- if( 'click' in value )
- toolbar_handler = function( target ) {
- value.click( $(target) );
- };
- else if( 'popup' in value )
- toolbar_handler = function( target ) {
- var $popup = popup_open_callback();
- var overwrite_offset = value.popup( $popup, $(target) );
- popup_position_callback( $popup, target, overwrite_offset );
- };
- else
- toolbar_handler = get_toolbar_handler( key, function( $content, target ) {
- var $popup = popup_open_callback();
- $popup.append( $content );
- popup_position_callback( $popup, target );
- $popup.find('input[type=text]:first').focus();
- });
- // Create the toolbar button
- var $button;
- if( toolbar_handler )
- $button = toolbar_button( value ).click( function(event) {
- toolbar_handler( event.currentTarget );
- // Give the focus back to the editor. Technically not necessary
- if( get_toolbar_handler(key) ) // only if not a popup-handler
- wysiwygeditor.getElement().focus();
- event.stopPropagation();
- event.preventDefault();
- return false;
- });
- else if( value.html )
- $button = $(value.html);
- if( $button )
- $toolbar.append( $button );
- });
- };
- var popup_position = function( $popup, $container, left, top ) // left+top relative to $container
- {
- // Test parents
- var offsetparent = $container.get(0).offsetParent,
- offsetparent_offset = { left: 0, top: 0 }, //$.offset() does not work with Safari 3 and 'position:fixed'
- offsetparent_fixed = false,
- offsetparent_overflow = false,
- popup_width = $popup.width(),
- node = offsetparent;
- while( node )
- {
- offsetparent_offset.left += node.offsetLeft;
- offsetparent_offset.top += node.offsetTop;
- var $node = $(node);
- if( $node.css('position') == 'fixed' )
- offsetparent_fixed = true;
- if( $node.css('overflow') != 'visible' )
- offsetparent_overflow = true;
- node = node.offsetParent;
- }
- // Move $popup as high as possible in the DOM tree: offsetParent of $container
- var $offsetparent = $(offsetparent || document.body);
- $offsetparent.append( $popup );
- var offset = $container.position();
- left += offset.left;
- top += offset.top;
- // Trim to offset-parent
- if( offsetparent_fixed || offsetparent_overflow )
- {
- if( left + popup_width > $offsetparent.width() - 1 )
- left = $offsetparent.width() - popup_width - 1;
- if( left < 1 )
- left = 1;
- }
- // Trim to viewport
- var viewport_width = $(window).width();
- if( offsetparent_offset.left + left + popup_width > viewport_width - 1 )
- left = viewport_width - offsetparent_offset.left - popup_width - 1;
- var scroll_left = offsetparent_fixed ? 0 : $(window).scrollLeft();
- if( offsetparent_offset.left + left < scroll_left + 1 )
- left = scroll_left - offsetparent_offset.left + 1;
- // Set offset
- $popup.css({ left: parseInt(left) + 'px',
- top: parseInt(top) + 'px' });
- };
-
-
- // Transform the textarea to contenteditable
- var hotkeys = {};
- var create_wysiwyg = function( $textarea, $container, placeholder )
- {
- var option = {
- element: $textarea.get(0),
- onkeypress: function( code, character, shiftKey, altKey, ctrlKey, metaKey )
- {
- // Ask master
- if( on_keypress && on_keypress(code, character, shiftKey, altKey, ctrlKey, metaKey) === false )
- return false; // swallow key
- // Exec hotkey
- if( character && !shiftKey && !altKey && ctrlKey && !metaKey )
- {
- var hotkey = character.toLowerCase();
- if( ! hotkeys[hotkey] )
- return ;
- hotkeys[hotkey]();
- return false; // prevent default
- }
- },
- onselection: function( collapsed, rect, nodes, rightclick )
- {
- var show_popup = true,
- $special_popup = null;
- // Click on a link opens the link-popup
- if( collapsed )
- $.each( nodes, function(index, node) {
- if( $(node).parents('a').length != 0 ) { // only clicks on text-nodes
- $special_popup = content_insertlink( wysiwygeditor, $(node).parents('a:first') )
- return false; // break
- }
- });
- // Fix type error - https://github.com/wysiwygjs/wysiwyg.js/issues/4
- if( ! rect )
- show_popup = false;
- // Force a special popup?
- else if( $special_popup )
- ;
- // A right-click always opens the popup
- else if( rightclick )
- ;
- // No selection-popup wanted?
- else if( toolbar_position != 'selection' && toolbar_position != 'top-selection' && toolbar_position != 'bottom-selection' )
- show_popup = false;
- // Selected popup wanted, but nothing selected (=selection collapsed)
- else if( collapsed )
- show_popup = false;
- // Only one image? Better: Display a special image-popup
- else if( nodes.length == 1 && nodes[0].nodeName == 'IMG' ) // nodes is not a sparse array
- show_popup = false;
- if( ! show_popup )
- {
- wysiwygeditor.closePopup();
- return ;
- }
- // Popup position
- var $popup;
- var apply_popup_position = function()
- {
- var popup_width = $popup.outerWidth();
- // Point is the center of the selection - relative to $container not the element
- var container_offset = $container.offset(),
- editor_offset = $(wysiwygeditor.getElement()).offset();
- var left = rect.left + parseInt(rect.width / 2) - parseInt(popup_width / 2) + editor_offset.left - container_offset.left;
- var top = rect.top + rect.height + editor_offset.top - container_offset.top;
- popup_position( $popup, $container, left, top );
- };
- // Open popup
- $popup = $(wysiwygeditor.openPopup());
- // if wrong popup -> create a new one
- if( $popup.hasClass('wysiwyg-popup') && ! $popup.hasClass('wysiwyg-popuphover') || $popup.data('special') != (!!$special_popup) )
- $popup = $(wysiwygeditor.closePopup().openPopup());
- if( ! $popup.hasClass('wysiwyg-popup') )
- {
- // add classes + buttons
- $popup.addClass( 'wysiwyg-popup wysiwyg-popuphover' );
- if( $special_popup )
- $popup.empty().append( $special_popup ).data('special',true);
- else
- add_buttons_to_toolbar( $popup, true,
- function() {
- return $popup.empty();
- },
- apply_popup_position );
- }
- // Apply position
- apply_popup_position();
- },
- hijackcontextmenu: (toolbar_position == 'selection')
- };
- if( placeholder )
- {
- var $placeholder = $('<div/>').addClass( 'wysiwyg-placeholder' )
- .html( placeholder )
- .hide();
- $container.prepend( $placeholder );
- option.onplaceholder = function( visible ) {
- if( visible )
- $placeholder.show();
- else
- $placeholder.hide();
- };
- }
-
- var wysiwygeditor = wysiwyg( option );
- return wysiwygeditor;
- }
-
-
- // Create a container
- var $container = $('<div/>').addClass('wysiwyg-container');
- if( classes )
- $container.addClass( classes );
- $textarea.wrap( $container );
- $container = $textarea.parent( '.wysiwyg-container' );
-
- // Create the editor-wrapper if placeholder
- var $wrapper = false;
- if( placeholder )
- {
- $wrapper = $('<div/>').addClass('wysiwyg-wrapper')
- .click(function(){ // Clicking the placeholder focus editor - fixes IE6-IE8
- wysiwygeditor.getElement().focus();
- });
- $textarea.wrap( $wrapper );
- $wrapper = $textarea.parent( '.wysiwyg-wrapper' );
- }
-
- // Create the WYSIWYG Editor
- var wysiwygeditor = create_wysiwyg( $textarea, placeholder ? $wrapper : $container, placeholder );
- if( wysiwygeditor.legacy )
- {
- var $textarea = $(wysiwygeditor.getElement());
- $textarea.addClass( 'wysiwyg-textarea' );
- if( $textarea.is(':visible') ) // inside the DOM
- $textarea.width( $container.width() - ($textarea.outerWidth() - $textarea.width()) );
- }
- else
- $(wysiwygeditor.getElement()).addClass( 'wysiwyg-editor' );
-
- // Hotkey+Commands-List
- var commands = {};
- $.each( toolbar_buttons, function(key, value) {
- if( ! value || ! value.hotkey )
- return ;
- var toolbar_handler = get_toolbar_handler( key );
- if( ! toolbar_handler )
- return ;
- hotkeys[value.hotkey.toLowerCase()] = toolbar_handler;
- commands[key] = toolbar_handler;
- });
-
- // Toolbar on top or bottom
- if( toolbar_position != 'selection' )
- {
- var toolbar_top = toolbar_position == 'top' || toolbar_position == 'top-selection';
- var $toolbar = $('<div/>').addClass( 'wysiwyg-toolbar' ).addClass( toolbar_top ? 'wysiwyg-toolbar-top' : 'wysiwyg-toolbar-bottom' );
- add_buttons_to_toolbar( $toolbar, false,
- function() {
- // Open a popup from the toolbar
- var $popup = $(wysiwygeditor.openPopup());
- // if wrong popup -> create a new one
- if( $popup.hasClass('wysiwyg-popup') && $popup.hasClass('wysiwyg-popuphover') )
- $popup = $(wysiwygeditor.closePopup().openPopup());
- if( ! $popup.hasClass('wysiwyg-popup') )
- // add classes + content
- $popup.addClass( 'wysiwyg-popup' );
- return $popup;
- },
- function( $popup, target, overwrite_offset ) {
- // Popup position
- var $button = $(target);
- var popup_width = $popup.outerWidth();
- // Point is the top/bottom-center of the button
- var left = $button.offset().left - $container.offset().left + parseInt($button.width() / 2) - parseInt(popup_width / 2);
- var top = $button.offset().top - $container.offset().top;
- if( toolbar_top )
- top += $button.outerHeight();
- else
- top -= $popup.outerHeight();
- if( overwrite_offset )
- {
- left = overwrite_offset.left;
- top = overwrite_offset.top;
- }
- popup_position( $popup, $container, left, top );
- });
- if( toolbar_top )
- $container.prepend( $toolbar );
- else
- $container.append( $toolbar );
- }
-
- // Export userdata
- return {
- wysiwygeditor: wysiwygeditor,
- $container: $container
- };
- };
-
- // jQuery Interface
- $.fn.wysiwyg = function( option, param )
- {
- if( ! option || typeof(option) === 'object' )
- {
- option = $.extend( {}, option );
- return this.each(function() {
- var $that = $(this);
- // Already an editor
- if( $that.data( 'wysiwyg') )
- return ;
-
- // Two modes: toolbar on top and on bottom
- var classes = option.classes,
- placeholder = option.placeholder || $that.attr('placeholder'),
- toolbar_position = (option.toolbar && (option.toolbar == 'top' || option.toolbar == 'top-selection' || option.toolbar == 'bottom' || option.toolbar == 'bottom-selection' || option.toolbar == 'selection')) ? option.toolbar : 'top-selection',
- toolbar_buttons = option.buttons,
- toolbar_submit = option.submit,
- label_selectImage = option.selectImage,
- placeholder_url = option.placeholderUrl || null,
- placeholder_embed = option.placeholderEmbed || null,
- max_imagesize = option.maxImageSize || null,
- on_imageupload = option.onImageUpload || null,
- force_imageupload = option.forceImageUpload && on_imageupload,
- video_from_url = option.videoFromUrl || null,
- on_keypress = option.onKeyPress;
-
- // Create the WYSIWYG Editor
- var data = create_editor( $that, classes, placeholder, toolbar_position, toolbar_buttons, toolbar_submit, label_selectImage,
- placeholder_url, placeholder_embed, max_imagesize, on_imageupload, force_imageupload, video_from_url, on_keypress );
- $that.data( 'wysiwyg', data );
- });
- }
- else if( this.length == 1 )
- {
- var data = this.data('wysiwyg');
- if( ! data )
- return this;
- if( option == 'container' )
- return data.$container;
- if( option == 'shell' )
- return data.wysiwygeditor;
- }
- return this;
- };
- })(window, document, jQuery);