Greasy Fork is available in English.

wysiwyg.js

'wysiwyg.js' is a (uglified) 12k contenteditable-editor with no dependencies.

Script này sẽ không được không được cài đặt trực tiếp. Nó là một thư viện cho các script khác để bao gồm các chỉ thị meta // @require https://update.greatest.deepsurf.us/scripts/11012/62427/wysiwygjs.js

  1. /**
  2. * wysiwyg.js
  3. */
  4. (function(window, document, navigator, undefined){
  5. 'use strict';
  6.  
  7. // http://stackoverflow.com/questions/97962/debounce-clicks-when-submitting-a-web-form
  8. var debounce = function( callback, wait, cancelprevious )
  9. {
  10. var timeout;
  11. return function()
  12. {
  13. if( timeout )
  14. {
  15. if( ! cancelprevious )
  16. return ;
  17. clearTimeout( timeout );
  18. }
  19. var context = this,
  20. args = arguments;
  21. timeout = setTimeout(
  22. function()
  23. {
  24. timeout = null;
  25. callback.apply( context, args );
  26. }, wait );
  27. };
  28. };
  29.  
  30. // http://stackoverflow.com/questions/12949590/how-to-detach-event-in-ie-6-7-8-9-using-javascript
  31. var addEvent = function( element, type, handler, useCapture )
  32. {
  33. if( element.addEventListener ) {
  34. element.addEventListener( type, handler, useCapture ? true : false );
  35. }
  36. else if( element.attachEvent ) {
  37. element.attachEvent( 'on' + type, handler );
  38. }
  39. else if( element != window )
  40. element['on' + type] = handler;
  41. };
  42. var removeEvent = function( element, type, handler, useCapture )
  43. {
  44. if( element.removeEventListener ) {
  45. element.removeEventListener( type, handler, useCapture ? true : false );
  46. }
  47. else if( element.detachEvent) {
  48. element.detachEvent( 'on' + type, handler );
  49. }
  50. else if( element != window )
  51. element['on' + type] = null;
  52. };
  53. // http://www.cristinawithout.com/content/function-trigger-events-javascript
  54. var fireEvent = function( element, type, bubbles, cancelable )
  55. {
  56. if( document.createEvent ) {
  57. var event = document.createEvent('Event');
  58. event.initEvent( type, bubbles !== undefined ? bubbles : true, cancelable !== undefined ? cancelable : false );
  59. element.dispatchEvent(event);
  60. }
  61. else if( document.createEventObject ) { //IE
  62. var event = document.createEventObject();
  63. element.fireEvent( 'on' + type, event );
  64. }
  65. else if( typeof(element['on' + type]) == 'function' )
  66. element['on' + type]();
  67. };
  68. // prevent default
  69. var cancelEvent = function( e )
  70. {
  71. if( e.preventDefault )
  72. e.preventDefault();
  73. else
  74. e.returnValue = false;
  75. if( e.stopPropagation )
  76. e.stopPropagation();
  77. else
  78. e.cancelBubble = true;
  79. return false;
  80. };
  81.  
  82. // http://stackoverflow.com/questions/13377887/javascript-node-undefined-in-ie8-and-under
  83. var Node_ELEMENT_NODE = typeof(Node) != 'undefined' ? Node.ELEMENT_NODE : 1;
  84. var Node_TEXT_NODE = typeof(Node) != 'undefined' ? Node.TEXT_NODE : 3;
  85.  
  86. // http://stackoverflow.com/questions/2234979/how-to-check-in-javascript-if-one-element-is-a-child-of-another
  87. var isOrContainsNode = function( ancestor, descendant )
  88. {
  89. var node = descendant;
  90. while( node )
  91. {
  92. if( node === ancestor )
  93. return true;
  94. node = node.parentNode;
  95. }
  96. return false;
  97. };
  98.  
  99. // http://stackoverflow.com/questions/667951/how-to-get-nodes-lying-inside-a-range-with-javascript
  100. var nextNode = function( node, container )
  101. {
  102. if( node.firstChild )
  103. return node.firstChild;
  104. while( node )
  105. {
  106. if( node == container ) // do not walk out of the container
  107. return null;
  108. if( node.nextSibling )
  109. return node.nextSibling;
  110. node = node.parentNode;
  111. }
  112. return null;
  113. };
  114.  
  115. // save/restore selection
  116. // http://stackoverflow.com/questions/13949059/persisting-the-changes-of-range-objects-after-selection-in-html/13950376#13950376
  117. var saveSelection = function( containerNode )
  118. {
  119. if( window.getSelection )
  120. {
  121. var sel = window.getSelection();
  122. if( sel.rangeCount > 0 )
  123. return sel.getRangeAt(0);
  124. }
  125. else if( document.selection )
  126. {
  127. var sel = document.selection;
  128. return sel.createRange();
  129. }
  130. return null;
  131. };
  132. var restoreSelection = function( containerNode, savedSel )
  133. {
  134. if( ! savedSel )
  135. return;
  136. if( window.getSelection )
  137. {
  138. var sel = window.getSelection();
  139. sel.removeAllRanges();
  140. sel.addRange(savedSel);
  141. }
  142. else if( document.selection )
  143. {
  144. savedSel.select();
  145. }
  146. };
  147.  
  148. // http://stackoverflow.com/questions/12603397/calculate-width-height-of-the-selected-text-javascript
  149. // http://stackoverflow.com/questions/6846230/coordinates-of-selected-text-in-browser-page
  150. var getSelectionRect = function()
  151. {
  152. if( window.getSelection )
  153. {
  154. var sel = window.getSelection();
  155. if( ! sel.rangeCount )
  156. return false;
  157. var range = sel.getRangeAt(0).cloneRange();
  158. if( range.getBoundingClientRect ) // Missing for Firefox 3.5+3.6
  159. {
  160. var rect = range.getBoundingClientRect();
  161. // Safari 5.1 returns null, IE9 returns 0/0/0/0 if image selected
  162. if( ! rect || (rect.left == 0 && rect.top == 0 && rect.right == 0 && rect.bottom == 0) )
  163. return false;
  164. return {
  165. // Firefox returns floating-point numbers
  166. left: parseInt(rect.left),
  167. top: parseInt(rect.top),
  168. width: parseInt(rect.right - rect.left),
  169. height: parseInt(rect.bottom - rect.top)
  170. };
  171. }
  172. /*
  173. // Fall back to inserting a temporary element (only for Firefox 3.5 and 3.6)
  174. var span = document.createElement('span');
  175. if( span.getBoundingClientRect )
  176. {
  177. // Ensure span has dimensions and position by
  178. // adding a zero-width space character
  179. span.appendChild( document.createTextNode('\u200b') );
  180. range.insertNode( span );
  181. var rect = span.getBoundingClientRect();
  182. var spanParent = span.parentNode;
  183. spanParent.removeChild( span );
  184. // Glue any broken text nodes back together
  185. spanParent.normalize();
  186. return {
  187. left: parseInt(rect.left),
  188. top: parseInt(rect.top),
  189. width: parseInt(rect.right - rect.left),
  190. height: parseInt(rect.bottom - rect.top)
  191. };
  192. }
  193. */
  194. }
  195. else if( document.selection )
  196. {
  197. var sel = document.selection;
  198. if( sel.type != 'Control' )
  199. {
  200. var range = sel.createRange();
  201. // http://javascript.info/tutorial/coordinates
  202. // http://www.softcomplex.com/docs/get_window_size_and_scrollbar_position.html
  203. // http://www.howtocreate.co.uk/tutorials/javascript/browserwindow
  204. return {
  205. left: range.boundingLeft,
  206. top: range.boundingTop,
  207. width: range.boundingWidth,
  208. height: range.boundingHeight
  209. };
  210. }
  211. }
  212. return false;
  213. };
  214.  
  215. var getSelectionCollapsed = function( containerNode )
  216. {
  217. if( window.getSelection )
  218. {
  219. var sel = window.getSelection();
  220. if( sel.isCollapsed )
  221. return true;
  222. return false;
  223. }
  224. else if( document.selection )
  225. {
  226. var sel = document.selection;
  227. if( sel.type == 'Text' )
  228. {
  229. var range = document.selection.createRange();
  230. var textrange = document.body.createTextRange();
  231. textrange.moveToElementText(containerNode);
  232. textrange.setEndPoint('EndToStart', range);
  233. return range.htmlText.length == 0;
  234. }
  235. if( sel.type == 'Control' ) // e.g. an image selected
  236. return false;
  237. // sel.type == 'None' -> collapsed selection
  238. }
  239. return true;
  240. };
  241.  
  242. // http://stackoverflow.com/questions/7781963/js-get-array-of-all-selected-nodes-in-contenteditable-div
  243. var getSelectedNodes = function( containerNode )
  244. {
  245. if( window.getSelection )
  246. {
  247. var sel = window.getSelection();
  248. if( ! sel.rangeCount )
  249. return [];
  250. var nodes = [];
  251. for( var i=0; i < sel.rangeCount; ++i )
  252. {
  253. var range = sel.getRangeAt(i),
  254. node = range.startContainer,
  255. endNode = range.endContainer;
  256. while( node )
  257. {
  258. // add this node?
  259. if( node != containerNode )
  260. {
  261. var node_inside_selection = false;
  262. if( sel.containsNode )
  263. node_inside_selection = sel.containsNode( node, true );
  264. else // IE11
  265. {
  266. // http://stackoverflow.com/questions/5884210/how-to-find-if-a-htmlelement-is-enclosed-in-selected-text
  267. var noderange = document.createRange();
  268. noderange.selectNodeContents( node );
  269. for( var i=0; i < sel.rangeCount; ++i )
  270. {
  271. var range = sel.getRangeAt(i);
  272. // start after or end before -> skip node
  273. if( range.compareBoundaryPoints(range.END_TO_START,noderange) >= 0 &&
  274. range.compareBoundaryPoints(range.START_TO_END,noderange) <= 0 )
  275. {
  276. node_inside_selection = true;
  277. break;
  278. }
  279. }
  280. }
  281. if( node_inside_selection )
  282. nodes.push( node );
  283. }
  284. node = nextNode( node, node == endNode ? endNode : containerNode );
  285. }
  286. }
  287. // Fallback
  288. if( nodes.length == 0 && isOrContainsNode(containerNode,sel.focusNode) && sel.focusNode != containerNode )
  289. nodes.push( sel.focusNode );
  290. return nodes;
  291. }
  292. else if( document.selection )
  293. {
  294. var sel = document.selection;
  295. if( sel.type == 'Text' )
  296. {
  297. var nodes = [];
  298. var ranges = sel.createRangeCollection();
  299. for( var i=0; i < ranges.length; ++i )
  300. {
  301. var range = ranges[i],
  302. parentNode = range.parentElement(),
  303. node = parentNode;
  304. while( node )
  305. {
  306. // No clue how to detect whether a TextNode is within the selection...
  307. // ElementNode is easy: http://stackoverflow.com/questions/5884210/how-to-find-if-a-htmlelement-is-enclosed-in-selected-text
  308. var noderange = range.duplicate();
  309. noderange.moveToElementText( node.nodeType != Node_ELEMENT_NODE ? node.parentNode : node );
  310. // start after or end before -> skip node
  311. if( noderange.compareEndPoints('EndToStart',range) >= 0 &&
  312. noderange.compareEndPoints('StartToEnd',range) <= 0 )
  313. {
  314. // no "Array.indexOf()" in IE8
  315. var in_array = false;
  316. for( var j=0; j < nodes.length; ++j )
  317. {
  318. if( nodes[j] !== node )
  319. continue;
  320. in_array = true;
  321. break;
  322. }
  323. if( ! in_array )
  324. nodes.push( node );
  325. }
  326. node = nextNode( node, parentNode );
  327. }
  328. }
  329. // Fallback
  330. if( nodes.length == 0 && isOrContainsNode(containerNode,document.activeElement) && document.activeElement != containerNode )
  331. nodes.push( document.activeElement );
  332. return nodes;
  333. }
  334. if( sel.type == 'Control' ) // e.g. an image selected
  335. {
  336. var nodes = [];
  337. // http://msdn.microsoft.com/en-us/library/ie/hh826021%28v=vs.85%29.aspx
  338. var range = sel.createRange();
  339. for( var i=0; i < range.length; ++i )
  340. nodes.push( range(i) );
  341. return nodes;
  342. }
  343. }
  344. return [];
  345. };
  346.  
  347. // http://stackoverflow.com/questions/8513368/collapse-selection-to-start-of-selection-not-div
  348. var collapseSelectionEnd = function()
  349. {
  350. if( window.getSelection )
  351. {
  352. var sel = window.getSelection();
  353. if( ! sel.isCollapsed )
  354. {
  355. // Form-submits via Enter throw 'NS_ERROR_FAILURE' on Firefox 34
  356. try {
  357. sel.collapseToEnd();
  358. }
  359. catch( e ) {
  360. }
  361. }
  362. }
  363. else if( document.selection )
  364. {
  365. var sel = document.selection;
  366. if( sel.type != 'Control' )
  367. {
  368. var range = sel.createRange();
  369. range.collapse(false);
  370. range.select();
  371. }
  372. }
  373. };
  374.  
  375. // http://stackoverflow.com/questions/4652734/return-html-from-a-user-selected-text/4652824#4652824
  376. var getSelectionHtml = function( containerNode )
  377. {
  378. if( getSelectionCollapsed( containerNode ) )
  379. return null;
  380. if( window.getSelection )
  381. {
  382. var sel = window.getSelection();
  383. if( sel.rangeCount )
  384. {
  385. var container = document.createElement('div'),
  386. len = sel.rangeCount;
  387. for( var i=0; i < len; ++i )
  388. {
  389. var contents = sel.getRangeAt(i).cloneContents();
  390. container.appendChild(contents);
  391. }
  392. return container.innerHTML;
  393. }
  394. }
  395. else if( document.selection )
  396. {
  397. var sel = document.selection;
  398. if( sel.type == 'Text' )
  399. {
  400. var range = sel.createRange();
  401. return range.htmlText;
  402. }
  403. }
  404. return null;
  405. };
  406.  
  407. var selectionInside = function( containerNode, force )
  408. {
  409. // selection inside editor?
  410. if( window.getSelection )
  411. {
  412. var sel = window.getSelection();
  413. if( isOrContainsNode(containerNode,sel.anchorNode) && isOrContainsNode(containerNode,sel.focusNode) )
  414. return true;
  415. // selection at least partly outside editor
  416. if( ! force )
  417. return false;
  418. // force selection to editor
  419. var range = document.createRange();
  420. range.selectNodeContents( containerNode );
  421. range.collapse( false );
  422. sel.removeAllRanges();
  423. sel.addRange(range);
  424. }
  425. else if( document.selection )
  426. {
  427. var sel = document.selection;
  428. if( sel.type == 'Control' ) // e.g. an image selected
  429. {
  430. // http://msdn.microsoft.com/en-us/library/ie/hh826021%28v=vs.85%29.aspx
  431. var range = sel.createRange();
  432. if( range.length != 0 && isOrContainsNode(containerNode,range(0)) ) // test only the first element
  433. return true;
  434. }
  435. else //if( sel.type == 'Text' || sel.type == 'None' )
  436. {
  437. // Range of container
  438. // http://stackoverflow.com/questions/12243898/how-to-select-all-text-in-contenteditable-div
  439. var rangeContainer = document.body.createTextRange();
  440. rangeContainer.moveToElementText(containerNode);
  441. // Compare with selection range
  442. var range = sel.createRange();
  443. if( rangeContainer.inRange(range) )
  444. return true;
  445. }
  446. // selection at least partly outside editor
  447. if( ! force )
  448. return false;
  449. // force selection to editor
  450. // http://stackoverflow.com/questions/12243898/how-to-select-all-text-in-contenteditable-div
  451. var range = document.body.createTextRange();
  452. range.moveToElementText(containerNode);
  453. range.setEndPoint('StartToEnd',range); // collapse
  454. range.select();
  455. }
  456. return true;
  457. };
  458.  
  459. /*
  460. var clipSelectionTo = function( containerNode )
  461. {
  462. if( window.getSelection && containerNode.compareDocumentPosition )
  463. {
  464. var sel = window.getSelection();
  465. var left_node = sel.anchorNode,
  466. left_offset = sel.anchorOffset,
  467. right_node = sel.focusNode,
  468. right_offset = sel.focusOffset;
  469. // http://stackoverflow.com/questions/10710733/dom-determine-if-the-anchornode-or-focusnode-is-on-the-left-side
  470. if( (left_node == right_node && left_offset > right_offset) ||
  471. (left_node.compareDocumentPosition(right_node) & Node.DOCUMENT_POSITION_PRECEDING) )
  472. {
  473. // Right-to-left selection
  474. left_node = sel.focusNode;
  475. left_offset = sel.focusOffset;
  476. right_node = sel.anchorNode,
  477. right_offset = sel.anchorOffset;
  478. }
  479. // Speed up: selection inside editor
  480. var left_inside = isOrContainsNode(containerNode,left_node),
  481. right_inside = isOrContainsNode(containerNode,right_node);
  482. if( left_inside && right_inside )
  483. return true;
  484. // Selection before/after container?
  485. if( ! left_inside && containerNode.compareDocumentPosition(left_node) & Node.DOCUMENT_POSITION_FOLLOWING )
  486. return false; // selection after
  487. if( ! right_inside && containerNode.compareDocumentPosition(right_node) & Node.DOCUMENT_POSITION_PRECEDING )
  488. return false; // selection before
  489. // Selection partly before/after container
  490. // http://stackoverflow.com/questions/12243898/how-to-select-all-text-in-contenteditable-div
  491. var range = document.createRange();
  492. range.selectNodeContents( containerNode );
  493. if( left_inside )
  494. range.setStart( left_node, left_offset );
  495. if( right_inside )
  496. range.setEnd( right_node, right_offset );
  497. sel.removeAllRanges();
  498. sel.addRange(range);
  499. return true;
  500. }
  501. else if( document.selection )
  502. {
  503. var sel = document.selection;
  504. if( sel.type == 'Text' )
  505. {
  506. // Range of container
  507. // http://stackoverflow.com/questions/12243898/how-to-select-all-text-in-contenteditable-div
  508. var rangeContainer = document.body.createTextRange();
  509. rangeContainer.moveToElementText(containerNode);
  510. // Compare with selection range
  511. var range = sel.createRange();
  512. if( rangeContainer.inRange(range) )
  513. return true;
  514. // Selection before/after container?
  515. if( rangeContainer.compareEndPoints('StartToEnd',range) > 0 )
  516. return false;
  517. if( rangeContainer.compareEndPoints('EndToStart',range) < 0 )
  518. return false;
  519. // Selection partly before/after container
  520. if( rangeContainer.compareEndPoints('StartToStart',range) > 0 )
  521. range.setEndPoint('StartToStart',rangeContainer);
  522. if( rangeContainer.compareEndPoints('EndToEnd',range) < 0 )
  523. range.setEndPoint('EndToEnd',rangeContainer);
  524. // select range
  525. range.select();
  526. return true;
  527. }
  528. }
  529. return true;
  530. };
  531. */
  532.  
  533. // http://stackoverflow.com/questions/6690752/insert-html-at-caret-in-a-contenteditable-div/6691294#6691294
  534. // http://stackoverflow.com/questions/4823691/insert-an-html-element-in-a-contenteditable-element
  535. // http://stackoverflow.com/questions/6139107/programatically-select-text-in-a-contenteditable-html-element
  536. var pasteHtmlAtCaret = function( containerNode, html )
  537. {
  538. if( window.getSelection )
  539. {
  540. // IE9 and non-IE
  541. var sel = window.getSelection();
  542. if( sel.getRangeAt && sel.rangeCount )
  543. {
  544. var range = sel.getRangeAt(0);
  545. // Range.createContextualFragment() would be useful here but is
  546. // only relatively recently standardized and is not supported in
  547. // some browsers (IE9, for one)
  548. var el = document.createElement('div');
  549. el.innerHTML = html;
  550. var frag = document.createDocumentFragment(), node, lastNode;
  551. while ( (node = el.firstChild) ) {
  552. lastNode = frag.appendChild(node);
  553. }
  554. if( isOrContainsNode(containerNode, range.commonAncestorContainer) )
  555. {
  556. range.deleteContents();
  557. range.insertNode(frag);
  558. }
  559. else {
  560. containerNode.appendChild(frag);
  561. }
  562. // Preserve the selection
  563. if( lastNode )
  564. {
  565. range = range.cloneRange();
  566. range.setStartAfter(lastNode);
  567. range.collapse(true);
  568. sel.removeAllRanges();
  569. sel.addRange(range);
  570. }
  571. }
  572. }
  573. else if( document.selection )
  574. {
  575. // IE <= 8
  576. var sel = document.selection;
  577. if( sel.type != 'Control' )
  578. {
  579. var originalRange = sel.createRange();
  580. originalRange.collapse(true);
  581. var range = sel.createRange();
  582. if( isOrContainsNode(containerNode, range.parentElement()) )
  583. range.pasteHTML( html );
  584. else // simply append to Editor
  585. {
  586. var textRange = document.body.createTextRange();
  587. textRange.moveToElementText(containerNode);
  588. textRange.collapse(false);
  589. textRange.select();
  590. textRange.pasteHTML( html );
  591. }
  592. // Preserve the selection
  593. range = sel.createRange();
  594. range.setEndPoint('StartToEnd', originalRange);
  595. range.select();
  596. }
  597. }
  598. };
  599.  
  600. // Interface: Create wysiwyg
  601. window.wysiwyg = function( option )
  602. {
  603. // Options
  604. option = option || {};
  605. var option_element = option.element || null;
  606. if( typeof(option_element) == 'string' )
  607. option_element = document.getElementById( option_element );
  608. var option_onkeypress = option.onkeypress || null;
  609. var option_onselection = option.onselection || null;
  610. var option_onplaceholder = option.onplaceholder || null;
  611. var option_hijackcontextmenu = option.hijackcontextmenu || false;
  612.  
  613. // Keep textarea if browser can't handle content-editable
  614. var is_textarea = option_element.nodeName == 'TEXTAREA' || option_element.nodeName == 'INPUT';
  615. if( is_textarea )
  616. {
  617. // http://stackoverflow.com/questions/1882205/how-do-i-detect-support-for-contenteditable-via-javascript
  618. var canContentEditable = 'contentEditable' in document.body;
  619. if( canContentEditable )
  620. {
  621. // Sniffer useragent...
  622. var webkit = navigator.userAgent.match(/(?:iPad|iPhone|Android).* AppleWebKit\/([^ ]+)/);
  623. if( webkit && 420 <= parseInt(webkit[1]) && parseInt(webkit[1]) < 534 ) // iPhone 1 was Webkit/420
  624. canContentEditable = false;
  625. }
  626. if( ! canContentEditable )
  627. {
  628. // Keep textarea
  629. var node_textarea = option_element;
  630. // Add a 'newline' after each '<br>'
  631. var newlineAfterBR = function( html ) {
  632. return html.replace(/<br[ \/]*>\n?/gi,'<br>\n');
  633. };
  634. node_textarea.value = newlineAfterBR( node_textarea.value );
  635. // Command structure
  636. var dummy_this = function() {
  637. return this;
  638. };
  639. var dummy_null = function() {
  640. return null;
  641. };
  642. return {
  643. legacy: true,
  644. // properties
  645. getElement: function()
  646. {
  647. return node_textarea;
  648. },
  649. getHTML: function()
  650. {
  651. return node_textarea.value;
  652. },
  653. setHTML: function( html )
  654. {
  655. node_textarea.value = newlineAfterBR( html );
  656. return this;
  657. },
  658. getSelectedHTML: dummy_null,
  659. sync: dummy_this,
  660. // selection and popup
  661. collapseSelection: dummy_this,
  662. openPopup: dummy_null,
  663. closePopup: dummy_this,
  664. // exec commands
  665. removeFormat: dummy_this,
  666. bold: dummy_this,
  667. italic: dummy_this,
  668. underline: dummy_this,
  669. strikethrough: dummy_this,
  670. forecolor: dummy_this,
  671. highlight: dummy_this,
  672. fontName: dummy_this,
  673. fontSize: dummy_this,
  674. subscript: dummy_this,
  675. superscript: dummy_this,
  676. align: dummy_this,
  677. format: dummy_this,
  678. indent: dummy_this,
  679. insertLink: dummy_this,
  680. insertImage: dummy_this,
  681. insertHTML: dummy_this,
  682. insertList: dummy_this
  683. };
  684. }
  685. }
  686.  
  687. // create content-editable
  688. var node_textarea = null,
  689. node_wysiwyg = null;
  690. if( is_textarea )
  691. {
  692. // Textarea
  693. node_textarea = option_element;
  694. node_textarea.style.display = 'none';
  695.  
  696. // Contenteditable
  697. node_wysiwyg = document.createElement( 'DIV' );
  698. node_wysiwyg.innerHTML = node_textarea.value;
  699. var parent = node_textarea.parentNode,
  700. next = node_textarea.nextSibling;
  701. if( next )
  702. parent.insertBefore( node_wysiwyg, next );
  703. else
  704. parent.appendChild( node_wysiwyg );
  705. }
  706. else
  707. node_wysiwyg = option_element;
  708. node_wysiwyg.setAttribute( 'contentEditable', 'true' ); // IE7 is case sensitive
  709.  
  710. // IE8 uses 'document' instead of 'window'
  711. // http://tanalin.com/en/articles/ie-version-js/
  712. var window_ie8 = (document.all && !document.addEventListener) ? document : window;
  713.  
  714. // Sync Editor with Textarea
  715. var syncTextarea = null;
  716. if( is_textarea )
  717. {
  718. var previous_html = node_wysiwyg.innerHTML;
  719. syncTextarea = function()
  720. {
  721. var new_html = node_wysiwyg.innerHTML;
  722. if( new_html == previous_html )
  723. return ;
  724. // HTML changed
  725. node_textarea.value = new_html;
  726. previous_html = new_html;
  727. // Event Handler
  728. fireEvent( node_textarea, 'change', false );
  729. };
  730. }
  731.  
  732. // Show placeholder
  733. var showPlaceholder;
  734. if( option_onplaceholder )
  735. {
  736. var placeholder_visible = false;
  737. showPlaceholder = function()
  738. {
  739. // Test if wysiwyg has content
  740. var wysiwyg_empty = true;
  741. var node = node_wysiwyg;
  742. while( node )
  743. {
  744. node = nextNode( node, node_wysiwyg );
  745. // Test if node contains something visible
  746. if( ! node )
  747. ;
  748. else if( node.nodeType == Node_ELEMENT_NODE )
  749. {
  750. if( node.nodeName == 'IMG' )
  751. {
  752. wysiwyg_empty = false;
  753. break;
  754. }
  755. }
  756. else if( node.nodeType == Node_TEXT_NODE )
  757. {
  758. var text = node.nodeValue;
  759. if( text && text.search(/[^\s]/) != -1 )
  760. {
  761. wysiwyg_empty = false;
  762. break;
  763. }
  764. }
  765. }
  766. if( placeholder_visible != wysiwyg_empty )
  767. {
  768. option_onplaceholder( wysiwyg_empty );
  769. placeholder_visible = wysiwyg_empty;
  770. }
  771. };
  772. showPlaceholder();
  773. }
  774.  
  775. // Handle selection
  776. var popup_saved_selection = null, // preserve selection during popup
  777. handleSelection = null,
  778. debounced_handleSelection = null;
  779. if( option_onselection )
  780. {
  781. handleSelection = function( clientX, clientY, rightclick )
  782. {
  783. // Detect collapsed selection
  784. var collapsed = getSelectionCollapsed( node_wysiwyg );
  785. // List of all selected nodes
  786. var nodes = getSelectedNodes( node_wysiwyg );
  787. // Rectangle of the selection
  788. var rect = (clientX === null || clientY === null) ? null :
  789. {
  790. left: clientX,
  791. top: clientY,
  792. width: 0,
  793. height: 0
  794. };
  795. var selectionRect = getSelectionRect();
  796. if( selectionRect )
  797. rect = selectionRect;
  798. if( rect )
  799. {
  800. // So far 'rect' is relative to viewport
  801. if( node_wysiwyg.getBoundingClientRect )
  802. {
  803. // Make it relative to the editor via 'getBoundingClientRect()'
  804. var boundingrect = node_wysiwyg.getBoundingClientRect();
  805. rect.left -= parseInt(boundingrect.left);
  806. rect.top -= parseInt(boundingrect.top);
  807. }
  808. else
  809. {
  810. var node = node_wysiwyg,
  811. offsetLeft = 0,
  812. offsetTop = 0,
  813. fixed = false;
  814. do {
  815. offsetLeft += node.offsetLeft ? parseInt(node.offsetLeft) : 0;
  816. offsetTop += node.offsetTop ? parseInt(node.offsetTop) : 0;
  817. if( node.style.position == 'fixed' )
  818. fixed = true;
  819. }
  820. while( node = node.offsetParent );
  821. rect.left -= offsetLeft - (fixed ? 0 : window.pageXOffset);
  822. rect.top -= offsetTop - (fixed ? 0 : window.pageYOffset);
  823. }
  824. // Trim rectangle to the editor
  825. if( rect.left < 0 )
  826. rect.left = 0;
  827. if( rect.top < 0 )
  828. rect.top = 0;
  829. if( rect.width > node_wysiwyg.offsetWidth )
  830. rect.width = node_wysiwyg.offsetWidth;
  831. if( rect.height > node_wysiwyg.offsetHeight )
  832. rect.height = node_wysiwyg.offsetHeight;
  833. }
  834. else if( nodes.length )
  835. {
  836. // What else could we do? Offset of first element...
  837. for( var i=0; i < nodes.length; ++i )
  838. {
  839. var node = nodes[i];
  840. if( node.nodeType != Node_ELEMENT_NODE )
  841. continue;
  842. rect = {
  843. left: node.offsetLeft,
  844. top: node.offsetTop,
  845. width: node.offsetWidth,
  846. height: node.offsetHeight
  847. };
  848. break;
  849. }
  850. }
  851. // Callback
  852. option_onselection( collapsed, rect, nodes, rightclick );
  853. };
  854. debounced_handleSelection = debounce( handleSelection, 1 );
  855. }
  856.  
  857. // Open popup
  858. var node_popup = null;
  859. var popupClickClose = function( e )
  860. {
  861. // http://www.quirksmode.org/js/events_properties.html
  862. if( !e )
  863. var e = window.event;
  864. var target = e.target || e.srcElement;
  865. if( target.nodeType == Node_TEXT_NODE ) // defeat Safari bug
  866. target = target.parentNode;
  867. // Click within popup?
  868. if( isOrContainsNode(node_popup,target) )
  869. return ;
  870. // close popup
  871. popupClose();
  872. };
  873. var popupOpen = function()
  874. {
  875. // Already open?
  876. if( node_popup )
  877. return node_popup;
  878.  
  879. // Global click closes popup
  880. addEvent( window_ie8, 'mousedown', popupClickClose, true );
  881.  
  882. // Create popup element
  883. node_popup = document.createElement( 'DIV' );
  884. var parent = node_wysiwyg.parentNode,
  885. next = node_wysiwyg.nextSibling;
  886. if( next )
  887. parent.insertBefore( node_popup, next );
  888. else
  889. parent.appendChild( node_popup );
  890. return node_popup;
  891. };
  892. var popupClose = function()
  893. {
  894. if( ! node_popup )
  895. return ;
  896. node_popup.parentNode.removeChild( node_popup );
  897. node_popup = null;
  898. removeEvent( window_ie8, 'mousedown', popupClickClose, true );
  899. };
  900.  
  901. // Focus/Blur events
  902. addEvent( node_wysiwyg, 'focus', function()
  903. {
  904. // forward focus/blur to the textarea
  905. if( node_textarea )
  906. fireEvent( node_textarea, 'focus', false );
  907. });
  908. addEvent( node_wysiwyg, 'blur', function()
  909. {
  910. // sync textarea immediately
  911. if( syncTextarea )
  912. syncTextarea();
  913. // forward focus/blur to the textarea
  914. if( node_textarea )
  915. fireEvent( node_textarea, 'blur', false );
  916. });
  917.  
  918. // Change events
  919. var debounced_changeHandler = null;
  920. if( showPlaceholder || syncTextarea )
  921. {
  922. // debounce 'syncTextarea' a second time, because 'innerHTML' is quite burdensome
  923. var debounced_syncTextarea = syncTextarea ? debounce( syncTextarea, 250, true ) : null; // high timeout is save, because of "onblur" fires immediately
  924. var changeHandler = function( e )
  925. {
  926. if( showPlaceholder )
  927. showPlaceholder();
  928. if( debounced_syncTextarea )
  929. debounced_syncTextarea();
  930. };
  931. debounced_changeHandler = debounce( changeHandler, 1 );
  932.  
  933. // Catch change events
  934. // http://stackoverflow.com/questions/1391278/contenteditable-change-events/1411296#1411296
  935. // http://stackoverflow.com/questions/8694054/onchange-event-with-contenteditable/8694125#8694125
  936. // https://github.com/mindmup/bootstrap-wysiwyg/pull/50/files
  937. // http://codebits.glennjones.net/editing/events-contenteditable.htm
  938. addEvent( node_wysiwyg, 'input', debounced_changeHandler );
  939. addEvent( node_wysiwyg, 'DOMNodeInserted', debounced_changeHandler );
  940. addEvent( node_wysiwyg, 'DOMNodeRemoved', debounced_changeHandler );
  941. addEvent( node_wysiwyg, 'DOMSubtreeModified', debounced_changeHandler );
  942. addEvent( node_wysiwyg, 'DOMCharacterDataModified', debounced_changeHandler ); // polyfill input in IE 9-10
  943. addEvent( node_wysiwyg, 'propertychange', debounced_changeHandler );
  944. addEvent( node_wysiwyg, 'textInput', debounced_changeHandler );
  945. addEvent( node_wysiwyg, 'paste', debounced_changeHandler );
  946. addEvent( node_wysiwyg, 'cut', debounced_changeHandler );
  947. addEvent( node_wysiwyg, 'drop', debounced_changeHandler );
  948. }
  949.  
  950. // Key events
  951. // http://sandbox.thewikies.com/html5-experiments/key-events.html
  952. var keyHandler = function( e, phase )
  953. {
  954. // http://www.quirksmode.org/js/events_properties.html
  955. if( !e )
  956. var e = window.event;
  957. var code = 0;
  958. if( e.keyCode )
  959. code = e.keyCode;
  960. else if( e.which )
  961. code = e.which;
  962. // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent
  963. var character = e.charCode;
  964.  
  965. // Callback
  966. if( phase == 1 && option_onkeypress )
  967. {
  968. var rv = option_onkeypress( code, character?String(String):String.fromCharCode(code), e.shiftKey||false, e.altKey||false, e.ctrlKey||false, e.metaKey||false );
  969. if( rv === false ) // dismiss key
  970. return cancelEvent( e );
  971. }
  972. // Keys can change the selection
  973. if( phase == 2 || phase == 3 )
  974. {
  975. popup_saved_selection = null;
  976. if( debounced_handleSelection )
  977. debounced_handleSelection( null, null, false );
  978. }
  979. // Most keys can cause changes
  980. if( phase == 2 && debounced_changeHandler )
  981. {
  982. switch( code )
  983. {
  984. case 33: // pageUp
  985. case 34: // pageDown
  986. case 35: // end
  987. case 36: // home
  988. case 37: // left
  989. case 38: // up
  990. case 39: // right
  991. case 40: // down
  992. // cursors do not
  993. break;
  994. default:
  995. // call change handler
  996. debounced_changeHandler();
  997. break;
  998. }
  999. }
  1000. };
  1001. addEvent( node_wysiwyg, 'keydown', function( e )
  1002. {
  1003. return keyHandler( e, 1 );
  1004. });
  1005. addEvent( node_wysiwyg, 'keypress', function( e )
  1006. {
  1007. return keyHandler( e, 2 );
  1008. });
  1009. addEvent( node_wysiwyg, 'keyup', function( e )
  1010. {
  1011. return keyHandler( e, 3 );
  1012. });
  1013.  
  1014. // Mouse events
  1015. var mouseHandler = function( e, rightclick )
  1016. {
  1017. // http://www.quirksmode.org/js/events_properties.html
  1018. if( !e )
  1019. var e = window.event;
  1020. // mouse position
  1021. var clientX = null,
  1022. clientY = null;
  1023. if( e.clientX && e.clientY )
  1024. {
  1025. clientX = e.clientX;
  1026. clientY = e.clientY;
  1027. }
  1028. else if( e.pageX && e.pageY )
  1029. {
  1030. clientX = e.pageX - window.pageXOffset;
  1031. clientY = e.pageY - window.pageYOffset;
  1032. }
  1033. // mouse button
  1034. if( e.which && e.which == 3 )
  1035. rightclick = true;
  1036. else if( e.button && e.button == 2 )
  1037. rightclick = true;
  1038.  
  1039. // remove event handler
  1040. removeEvent( window_ie8, 'mouseup', mouseHandler );
  1041. // Callback selection
  1042. popup_saved_selection = null;
  1043. if( ! option_hijackcontextmenu && rightclick )
  1044. return ;
  1045. if( debounced_handleSelection )
  1046. debounced_handleSelection( clientX, clientY, rightclick );
  1047. };
  1048. addEvent( node_wysiwyg, 'mousedown', function( e )
  1049. {
  1050. // catch event if 'mouseup' outside 'node_wysiwyg'
  1051. removeEvent( window_ie8, 'mouseup', mouseHandler );
  1052. addEvent( window_ie8, 'mouseup', mouseHandler );
  1053. });
  1054. addEvent( node_wysiwyg, 'mouseup', function( e )
  1055. {
  1056. mouseHandler( e );
  1057. // Trigger change
  1058. if( debounced_changeHandler )
  1059. debounced_changeHandler();
  1060. });
  1061. addEvent( node_wysiwyg, 'dblclick', function( e )
  1062. {
  1063. mouseHandler( e );
  1064. });
  1065. addEvent( node_wysiwyg, 'selectionchange', function( e )
  1066. {
  1067. mouseHandler( e );
  1068. });
  1069. if( option_hijackcontextmenu )
  1070. {
  1071. addEvent( node_wysiwyg, 'contextmenu', function( e )
  1072. {
  1073. mouseHandler( e, true );
  1074. return cancelEvent( e );
  1075. });
  1076. }
  1077.  
  1078.  
  1079. // exec command
  1080. // https://developer.mozilla.org/en-US/docs/Web/API/document.execCommand
  1081. // http://www.quirksmode.org/dom/execCommand.html
  1082. var execCommand = function( command, param, force_selection )
  1083. {
  1084. // give selection to contenteditable element
  1085. restoreSelection( node_wysiwyg, popup_saved_selection );
  1086. if( ! selectionInside(node_wysiwyg, force_selection) ) // returns 'selection inside editor'
  1087. return false;
  1088. // for webkit, mozilla, opera
  1089. if( window.getSelection )
  1090. {
  1091. // Buggy, call within 'try/catch'
  1092. try {
  1093. if( document.queryCommandSupported && ! document.queryCommandSupported(command) )
  1094. return false;
  1095. return document.execCommand( command, false, param );
  1096. }
  1097. catch( e ) {
  1098. }
  1099. }
  1100. // for IE
  1101. else if( document.selection )
  1102. {
  1103. var sel = document.selection;
  1104. if( sel.type != 'None' )
  1105. {
  1106. var range = sel.createRange();
  1107. // Buggy, call within 'try/catch'
  1108. try {
  1109. if( ! range.queryCommandEnabled(command) )
  1110. return false;
  1111. return range.execCommand( command, false, param );
  1112. }
  1113. catch( e ) {
  1114. }
  1115. }
  1116. }
  1117. return false;
  1118. };
  1119.  
  1120. // Command structure
  1121. var trailingDiv = null;
  1122. var IEtrailingDIV = function()
  1123. {
  1124. // Detect IE - http://stackoverflow.com/questions/17907445/how-to-detect-ie11
  1125. if( document.all || !!window.MSInputMethodContext )
  1126. {
  1127. // Workaround IE11 - https://github.com/wysiwygjs/wysiwyg.js/issues/14
  1128. trailingDiv = document.createElement( 'DIV' );
  1129. node_wysiwyg.appendChild( trailingDiv );
  1130. }
  1131. };
  1132. var callUpdates = function( selection_destroyed )
  1133. {
  1134. // Remove IE11 workaround
  1135. if( trailingDiv )
  1136. {
  1137. node_wysiwyg.removeChild( trailingDiv );
  1138. trailingDiv = null;
  1139. }
  1140. // change-handler
  1141. if( debounced_changeHandler )
  1142. debounced_changeHandler();
  1143. // handle saved selection
  1144. if( selection_destroyed )
  1145. {
  1146. collapseSelectionEnd();
  1147. popup_saved_selection = null; // selection destroyed
  1148. }
  1149. else if( popup_saved_selection )
  1150. popup_saved_selection = saveSelection( node_wysiwyg );
  1151. };
  1152. return {
  1153. // properties
  1154. getElement: function()
  1155. {
  1156. return node_wysiwyg;
  1157. },
  1158. getHTML: function()
  1159. {
  1160. return node_wysiwyg.innerHTML;
  1161. },
  1162. setHTML: function( html )
  1163. {
  1164. node_wysiwyg.innerHTML = html;
  1165. callUpdates( true ); // selection destroyed
  1166. return this;
  1167. },
  1168. getSelectedHTML: function()
  1169. {
  1170. restoreSelection( node_wysiwyg, popup_saved_selection );
  1171. if( ! selectionInside(node_wysiwyg) )
  1172. return null;
  1173. return getSelectionHtml( node_wysiwyg );
  1174. },
  1175. sync: function()
  1176. {
  1177. if( syncTextarea )
  1178. syncTextarea();
  1179. return this;
  1180. },
  1181. // selection and popup
  1182. collapseSelection: function()
  1183. {
  1184. collapseSelectionEnd();
  1185. popup_saved_selection = null; // selection destroyed
  1186. return this;
  1187. },
  1188. openPopup: function()
  1189. {
  1190. if( ! popup_saved_selection )
  1191. popup_saved_selection = saveSelection( node_wysiwyg ); // save current selection
  1192. return popupOpen();
  1193. },
  1194. closePopup: function()
  1195. {
  1196. popupClose();
  1197. return this;
  1198. },
  1199. removeFormat: function()
  1200. {
  1201. execCommand( 'removeFormat' );
  1202. execCommand( 'unlink' );
  1203. callUpdates();
  1204. return this;
  1205. },
  1206. bold: function()
  1207. {
  1208. execCommand( 'bold' );
  1209. callUpdates();
  1210. return this;
  1211. },
  1212. italic: function()
  1213. {
  1214. execCommand( 'italic' );
  1215. callUpdates();
  1216. return this;
  1217. },
  1218. underline: function()
  1219. {
  1220. execCommand( 'underline' );
  1221. callUpdates();
  1222. return this;
  1223. },
  1224. strikethrough: function()
  1225. {
  1226. execCommand( 'strikeThrough' );
  1227. callUpdates();
  1228. return this;
  1229. },
  1230. forecolor: function( color )
  1231. {
  1232. execCommand( 'foreColor', color );
  1233. callUpdates();
  1234. return this;
  1235. },
  1236. highlight: function( color )
  1237. {
  1238. // http://stackoverflow.com/questions/2756931/highlight-the-text-of-the-dom-range-element
  1239. if( ! execCommand('hiliteColor',color) ) // some browsers apply 'backColor' to the whole block
  1240. execCommand( 'backColor', color );
  1241. callUpdates();
  1242. return this;
  1243. },
  1244. fontName: function( name )
  1245. {
  1246. execCommand( 'fontName', name );
  1247. callUpdates();
  1248. return this;
  1249. },
  1250. fontSize: function( size )
  1251. {
  1252. execCommand( 'fontSize', size );
  1253. callUpdates();
  1254. return this;
  1255. },
  1256. subscript: function()
  1257. {
  1258. execCommand( 'subscript' );
  1259. callUpdates();
  1260. return this;
  1261. },
  1262. superscript: function()
  1263. {
  1264. execCommand( 'superscript' );
  1265. callUpdates();
  1266. return this;
  1267. },
  1268. align: function( align )
  1269. {
  1270. IEtrailingDIV();
  1271. if( align == 'left' )
  1272. execCommand( 'justifyLeft' );
  1273. else if( align == 'center' )
  1274. execCommand( 'justifyCenter' );
  1275. else if( align == 'right' )
  1276. execCommand( 'justifyRight' );
  1277. else if( align == 'justify' )
  1278. execCommand( 'justifyFull' );
  1279. callUpdates();
  1280. return this;
  1281. },
  1282. format: function( tagname )
  1283. {
  1284. IEtrailingDIV();
  1285. execCommand( 'formatBlock', tagname );
  1286. callUpdates();
  1287. return this;
  1288. },
  1289. indent: function( outdent )
  1290. {
  1291. IEtrailingDIV();
  1292. execCommand( outdent ? 'outdent' : 'indent' );
  1293. callUpdates();
  1294. return this;
  1295. },
  1296. insertLink: function( url )
  1297. {
  1298. execCommand( 'createLink', url );
  1299. callUpdates( true ); // selection destroyed
  1300. return this;
  1301. },
  1302. insertImage: function( url )
  1303. {
  1304. execCommand( 'insertImage', url, true );
  1305. callUpdates( true ); // selection destroyed
  1306. return this;
  1307. },
  1308. insertHTML: function( html )
  1309. {
  1310. if( ! execCommand('insertHTML', html, true) )
  1311. {
  1312. // IE 11 still does not support 'insertHTML'
  1313. restoreSelection( node_wysiwyg, popup_saved_selection );
  1314. selectionInside( node_wysiwyg, true );
  1315. pasteHtmlAtCaret( node_wysiwyg, html );
  1316. }
  1317. callUpdates( true ); // selection destroyed
  1318. return this;
  1319. },
  1320. insertList: function( ordered )
  1321. {
  1322. IEtrailingDIV();
  1323. execCommand( ordered ? 'insertOrderedList' : 'insertUnorderedList' );
  1324. callUpdates();
  1325. return this;
  1326. }
  1327. };
  1328. };
  1329. })(window, document, navigator);
  1330.  
  1331.  
  1332. /**
  1333. * wysiwyg-editor.js
  1334. */
  1335. (function(window, document, $, undefined){
  1336. 'use strict';
  1337.  
  1338. // http://stackoverflow.com/questions/17242144/javascript-convert-hsb-hsv-color-to-rgb-accurately
  1339. var HSVtoRGB = function( h, s, v )
  1340. {
  1341. var r, g, b, i, f, p, q, t;
  1342. i = Math.floor(h * 6);
  1343. f = h * 6 - i;
  1344. p = v * (1 - s);
  1345. q = v * (1 - f * s);
  1346. t = v * (1 - (1 - f) * s);
  1347. switch (i % 6)
  1348. {
  1349. case 0: r = v, g = t, b = p; break;
  1350. case 1: r = q, g = v, b = p; break;
  1351. case 2: r = p, g = v, b = t; break;
  1352. case 3: r = p, g = q, b = v; break;
  1353. case 4: r = t, g = p, b = v; break;
  1354. case 5: r = v, g = p, b = q; break;
  1355. }
  1356. var hr = Math.floor(r * 255).toString(16);
  1357. var hg = Math.floor(g * 255).toString(16);
  1358. var hb = Math.floor(b * 255).toString(16);
  1359. return '#' + (hr.length < 2 ? '0' : '') + hr +
  1360. (hg.length < 2 ? '0' : '') + hg +
  1361. (hb.length < 2 ? '0' : '') + hb;
  1362. };
  1363.  
  1364. // Encode htmlentities() - http://stackoverflow.com/questions/5499078/fastest-method-to-escape-html-tags-as-html-entities
  1365. var html_encode = function( string )
  1366. {
  1367. return string.replace(/[&<>"]/g, function(tag)
  1368. {
  1369. var charsToReplace = {
  1370. '&': '&amp;',
  1371. '<': '&lt;',
  1372. '>': '&gt;',
  1373. '"': '&quot;'
  1374. };
  1375. return charsToReplace[tag] || tag;
  1376. });
  1377. };
  1378.  
  1379. // Create the Editor
  1380. var create_editor = function( $textarea, classes, placeholder, toolbar_position, toolbar_buttons, toolbar_submit, label_selectImage,
  1381. placeholder_url, placeholder_embed, max_imagesize, on_imageupload, force_imageupload, video_from_url, on_keypress )
  1382. {
  1383. // Content: Insert link
  1384. var wysiwygeditor_insertLink = function( wysiwygeditor, url )
  1385. {
  1386. if( ! url )
  1387. ;
  1388. else if( wysiwygeditor.getSelectedHTML() )
  1389. wysiwygeditor.insertLink( url );
  1390. else
  1391. wysiwygeditor.insertHTML( '<a href="' + html_encode(url) + '">' + html_encode(url) + '</a>' );
  1392. wysiwygeditor.closePopup().collapseSelection();
  1393. };
  1394. var content_insertlink = function(wysiwygeditor, $modify_link)
  1395. {
  1396. var $button = toolbar_button( toolbar_submit );
  1397. var $inputurl = $('<input type="text" value="' + ($modify_link ? $modify_link.attr('href') : '') + '" />').addClass('wysiwyg-input')
  1398. .keypress(function(event){
  1399. if( event.which != 10 && event.which != 13 )
  1400. return ;
  1401. if( $modify_link )
  1402. {
  1403. $modify_link.attr( 'href', $inputurl.val() );
  1404. wysiwygeditor.closePopup().collapseSelection();
  1405. }
  1406. else
  1407. wysiwygeditor_insertLink( wysiwygeditor,$inputurl.val() );
  1408. });
  1409. if( placeholder_url )
  1410. $inputurl.attr( 'placeholder', placeholder_url );
  1411. var $okaybutton = $button.click(function(event){
  1412. if( $modify_link )
  1413. {
  1414. $modify_link.attr( 'href', $inputurl.val() );
  1415. wysiwygeditor.closePopup().collapseSelection();
  1416. }
  1417. else
  1418. wysiwygeditor_insertLink( wysiwygeditor, $inputurl.val() );
  1419. event.stopPropagation();
  1420. event.preventDefault();
  1421. return false;
  1422. });
  1423. var $content = $('<div/>').addClass('wysiwyg-toolbar-form')
  1424. .attr('unselectable','on');
  1425. $content.append($inputurl).append($okaybutton);
  1426. return $content;
  1427. };
  1428.  
  1429. // Content: Insert image
  1430. var content_insertimage = function(wysiwygeditor)
  1431. {
  1432. // Add image to editor
  1433. var insert_image_wysiwyg = function( url, filename )
  1434. {
  1435. var html = '<img id="wysiwyg-insert-image" src="" alt=""' + (filename ? ' title="'+html_encode(filename)+'"' : '') + ' />';
  1436. wysiwygeditor.insertHTML( html ).closePopup().collapseSelection();
  1437. var $image = $('#wysiwyg-insert-image').removeAttr('id');
  1438. if( max_imagesize )
  1439. {
  1440. $image.css({maxWidth: max_imagesize[0]+'px',
  1441. maxHeight: max_imagesize[1]+'px'})
  1442. .load( function() {
  1443. $image.css({maxWidth: '',
  1444. maxHeight: ''});
  1445. // Resize $image to fit "clip-image"
  1446. var image_width = $image.width(),
  1447. image_height = $image.height();
  1448. if( image_width > max_imagesize[0] || image_height > max_imagesize[1] )
  1449. {
  1450. if( (image_width/image_height) > (max_imagesize[0]/max_imagesize[1]) )
  1451. {
  1452. image_height = parseInt(image_height / image_width * max_imagesize[0]);
  1453. image_width = max_imagesize[0];
  1454. }
  1455. else
  1456. {
  1457. image_width = parseInt(image_width / image_height * max_imagesize[1]);
  1458. image_height = max_imagesize[1];
  1459. }
  1460. $image.attr('width',image_width)
  1461. .attr('height',image_height);
  1462. }
  1463. });
  1464. }
  1465. $image.attr('src', url);
  1466. };
  1467. // Create popup
  1468. var $content = $('<div/>').addClass('wysiwyg-toolbar-form')
  1469. .attr('unselectable','on');
  1470. // Add image via 'Browse...'
  1471. var $fileuploader = null,
  1472. $fileuploader_input = $('<input type="file" />')
  1473. .css({position: 'absolute',
  1474. left: 0,
  1475. top: 0,
  1476. width: '100%',
  1477. height: '100%',
  1478. opacity: 0,
  1479. cursor: 'pointer'});
  1480. if( ! force_imageupload && window.File && window.FileReader && window.FileList )
  1481. {
  1482. // File-API
  1483. var loadImageFromFile = function( file )
  1484. {
  1485. // Only process image files
  1486. if( ! file.type.match('image.*') )
  1487. return;
  1488. var reader = new FileReader();
  1489. reader.onload = function(event) {
  1490. var dataurl = event.target.result;
  1491. insert_image_wysiwyg( dataurl, file.name );
  1492. };
  1493. // Read in the image file as a data URL
  1494. reader.readAsDataURL( file );
  1495. };
  1496. $fileuploader = $fileuploader_input
  1497. .attr('draggable','true')
  1498. .change(function(event){
  1499. var files = event.target.files; // FileList object
  1500. for(var i=0; i < files.length; ++i)
  1501. loadImageFromFile( files[i] );
  1502. })
  1503. .on('dragover',function(event){
  1504. event.originalEvent.dataTransfer.dropEffect = 'copy'; // Explicitly show this is a copy.
  1505. event.stopPropagation();
  1506. event.preventDefault();
  1507. return false;
  1508. })
  1509. .on('drop', function(event){
  1510. var files = event.originalEvent.dataTransfer.files; // FileList object.
  1511. for(var i=0; i < files.length; ++i)
  1512. loadImageFromFile( files[i] );
  1513. event.stopPropagation();
  1514. event.preventDefault();
  1515. return false;
  1516. });
  1517. }
  1518. else if( on_imageupload )
  1519. {
  1520. // Upload image to a server
  1521. var $input = $fileuploader_input
  1522. .change(function(event){
  1523. on_imageupload.call( this, insert_image_wysiwyg );
  1524. });
  1525. $fileuploader = $('<form/>').append($input);
  1526. }
  1527. if( $fileuploader )
  1528. $('<div/>').addClass( 'wysiwyg-browse' )
  1529. .html( label_selectImage )
  1530. .append( $fileuploader )
  1531. .appendTo( $content );
  1532. // Add image via 'URL'
  1533. var $button = toolbar_button( toolbar_submit );
  1534. var $inputurl = $('<input type="text" value="" />').addClass('wysiwyg-input')
  1535. .keypress(function(event){
  1536. if( event.which == 10 || event.which == 13 )
  1537. insert_image_wysiwyg( $inputurl.val() );
  1538. });
  1539. if( placeholder_url )
  1540. $inputurl.attr( 'placeholder', placeholder_url );
  1541. var $okaybutton = $button.click(function(event){
  1542. insert_image_wysiwyg( $inputurl.val() );
  1543. event.stopPropagation();
  1544. event.preventDefault();
  1545. return false;
  1546. });
  1547. $content.append( $('<div/>').append($inputurl).append($okaybutton) );
  1548. return $content;
  1549. };
  1550.  
  1551. // Content: Insert video
  1552. var content_insertvideo = function(wysiwygeditor)
  1553. {
  1554. // Add video to editor
  1555. var insert_video_wysiwyg = function( url, html )
  1556. {
  1557. url = $.trim(url||'');
  1558. html = $.trim(html||'');
  1559. var website_url = false;
  1560. if( url.length && ! html.length )
  1561. website_url = url;
  1562. else if( html.indexOf('<') == -1 && html.indexOf('>') == -1 &&
  1563. html.match(/^(?:https?:\/)?\/?(?:[^:\/\s]+)(?:(?:\/\w+)*\/)(?:[\w\-\.]+[^#?\s]+)(?:.*)?(?:#[\w\-]+)?$/) )
  1564. website_url = html;
  1565. if( website_url && video_from_url )
  1566. html = video_from_url( website_url ) || '';
  1567. if( ! html.length && website_url )
  1568. html = '<video src="' + html_encode(website_url) + '" />';
  1569. wysiwygeditor.insertHTML( html ).closePopup().collapseSelection();
  1570. };
  1571. // Create popup
  1572. var $content = $('<div/>').addClass('wysiwyg-toolbar-form')
  1573. .attr('unselectable','on');
  1574. // Add video via '<embed/>'
  1575. var $textareaembed = $('<textarea>').addClass('wysiwyg-input wysiwyg-inputtextarea');
  1576. if( placeholder_embed )
  1577. $textareaembed.attr( 'placeholder', placeholder_embed );
  1578. $('<div/>').addClass( 'wysiwyg-embedcode' )
  1579. .append( $textareaembed )
  1580. .appendTo( $content );
  1581. // Add video via 'URL'
  1582. var $button = toolbar_button( toolbar_submit );
  1583. var $inputurl = $('<input type="text" value="" />').addClass('wysiwyg-input')
  1584. .keypress(function(event){
  1585. if( event.which == 10 || event.which == 13 )
  1586. insert_video_wysiwyg( $inputurl.val() );
  1587. });
  1588. if( placeholder_url )
  1589. $inputurl.attr( 'placeholder', placeholder_url );
  1590. var $okaybutton = $button.click(function(event){
  1591. insert_video_wysiwyg( $inputurl.val(), $textareaembed.val() );
  1592. event.stopPropagation();
  1593. event.preventDefault();
  1594. return false;
  1595. });
  1596. $content.append( $('<div/>').append($inputurl).append($okaybutton) );
  1597. return $content;
  1598. };
  1599.  
  1600. // Content: Color palette
  1601. var content_colorpalette = function( wysiwygeditor, forecolor )
  1602. {
  1603. var $content = $('<table/>')
  1604. .attr('cellpadding','0')
  1605. .attr('cellspacing','0')
  1606. .attr('unselectable','on');
  1607. for( var row=1; row < 15; ++row ) // should be '16' - but last line looks so dark
  1608. {
  1609. var $rows = $('<tr/>');
  1610. for( var col=0; col < 25; ++col ) // last column is grayscale
  1611. {
  1612. var color;
  1613. if( col == 24 )
  1614. {
  1615. var gray = Math.floor(255 / 13 * (14 - row)).toString(16);
  1616. var hexg = (gray.length < 2 ? '0' : '') + gray;
  1617. color = '#' + hexg + hexg + hexg;
  1618. }
  1619. else
  1620. {
  1621. var hue = col / 24;
  1622. var saturation = row <= 8 ? row /8 : 1;
  1623. var value = row > 8 ? (16-row)/8 : 1;
  1624. color = HSVtoRGB( hue, saturation, value );
  1625. }
  1626. $('<td/>').addClass('wysiwyg-toolbar-color')
  1627. .attr('title', color)
  1628. .attr('unselectable','on')
  1629. .css({backgroundColor: color})
  1630. .click(function(){
  1631. var color = this.title;
  1632. if( forecolor )
  1633. wysiwygeditor.forecolor( color ).closePopup().collapseSelection();
  1634. else
  1635. wysiwygeditor.highlight( color ).closePopup().collapseSelection();
  1636. return false;
  1637. })
  1638. .appendTo( $rows );
  1639. }
  1640. $content.append( $rows );
  1641. }
  1642. return $content;
  1643. };
  1644.  
  1645. // Handlers
  1646. var get_toolbar_handler = function( name, popup_callback )
  1647. {
  1648. switch( name )
  1649. {
  1650. case 'insertimage':
  1651. if( ! popup_callback )
  1652. return null;
  1653. return function( target ) {
  1654. popup_callback( content_insertimage(wysiwygeditor), target );
  1655. };
  1656. case 'insertvideo':
  1657. if( ! popup_callback )
  1658. return null;
  1659. return function( target ) {
  1660. popup_callback( content_insertvideo(wysiwygeditor), target );
  1661. };
  1662. case 'insertlink':
  1663. if( ! popup_callback )
  1664. return null;
  1665. return function( target ) {
  1666. popup_callback( content_insertlink(wysiwygeditor), target );
  1667. };
  1668. case 'bold':
  1669. return function() {
  1670. wysiwygeditor.bold(); // .closePopup().collapseSelection()
  1671. };
  1672. case 'italic':
  1673. return function() {
  1674. wysiwygeditor.italic(); // .closePopup().collapseSelection()
  1675. };
  1676. case 'underline':
  1677. return function() {
  1678. wysiwygeditor.underline(); // .closePopup().collapseSelection()
  1679. };
  1680. case 'strikethrough':
  1681. return function() {
  1682. wysiwygeditor.strikethrough(); // .closePopup().collapseSelection()
  1683. };
  1684. case 'forecolor':
  1685. if( ! popup_callback )
  1686. return null;
  1687. return function( target ) {
  1688. popup_callback( content_colorpalette(wysiwygeditor,true), target );
  1689. };
  1690. case 'highlight':
  1691. if( ! popup_callback )
  1692. return null;
  1693. return function( target ) {
  1694. popup_callback( content_colorpalette(wysiwygeditor,false), target );
  1695. };
  1696. case 'alignleft':
  1697. return function() {
  1698. wysiwygeditor.align('left'); // .closePopup().collapseSelection()
  1699. };
  1700. case 'aligncenter':
  1701. return function() {
  1702. wysiwygeditor.align('center'); // .closePopup().collapseSelection()
  1703. };
  1704. case 'alignright':
  1705. return function() {
  1706. wysiwygeditor.align('right'); // .closePopup().collapseSelection()
  1707. };
  1708. case 'alignjustify':
  1709. return function() {
  1710. wysiwygeditor.align('justify'); // .closePopup().collapseSelection()
  1711. };
  1712. case 'subscript':
  1713. return function() {
  1714. wysiwygeditor.subscript(); // .closePopup().collapseSelection()
  1715. };
  1716. case 'superscript':
  1717. return function() {
  1718. wysiwygeditor.superscript(); // .closePopup().collapseSelection()
  1719. };
  1720. case 'indent':
  1721. return function() {
  1722. wysiwygeditor.indent(); // .closePopup().collapseSelection()
  1723. };
  1724. case 'outdent':
  1725. return function() {
  1726. wysiwygeditor.indent(true); // .closePopup().collapseSelection()
  1727. };
  1728. case 'orderedList':
  1729. return function() {
  1730. wysiwygeditor.insertList(true); // .closePopup().collapseSelection()
  1731. };
  1732. case 'unorderedList':
  1733. return function() {
  1734. wysiwygeditor.insertList(); // .closePopup().collapseSelection()
  1735. };
  1736. case 'removeformat':
  1737. return function() {
  1738. wysiwygeditor.removeFormat().closePopup().collapseSelection();
  1739. };
  1740. }
  1741. return null;
  1742. }
  1743.  
  1744. // Create the toolbar
  1745. var toolbar_button = function( button ) {
  1746. return $('<a/>').addClass( 'wysiwyg-toolbar-icon' )
  1747. .attr('href','#')
  1748. .attr('title', button.title)
  1749. .attr('unselectable','on')
  1750. .append(button.image);
  1751. };
  1752. var add_buttons_to_toolbar = function( $toolbar, selection, popup_open_callback, popup_position_callback )
  1753. {
  1754. $.each( toolbar_buttons, function(key, value) {
  1755. if( ! value )
  1756. return ;
  1757. // Skip buttons on the toolbar
  1758. if( selection === false && 'showstatic' in value && ! value.showstatic )
  1759. return ;
  1760. // Skip buttons on selection
  1761. if( selection === true && 'showselection' in value && ! value.showselection )
  1762. return ;
  1763. // Click handler
  1764. var toolbar_handler;
  1765. if( 'click' in value )
  1766. toolbar_handler = function( target ) {
  1767. value.click( $(target) );
  1768. };
  1769. else if( 'popup' in value )
  1770. toolbar_handler = function( target ) {
  1771. var $popup = popup_open_callback();
  1772. var overwrite_offset = value.popup( $popup, $(target) );
  1773. popup_position_callback( $popup, target, overwrite_offset );
  1774. };
  1775. else
  1776. toolbar_handler = get_toolbar_handler( key, function( $content, target ) {
  1777. var $popup = popup_open_callback();
  1778. $popup.append( $content );
  1779. popup_position_callback( $popup, target );
  1780. $popup.find('input[type=text]:first').focus();
  1781. });
  1782. // Create the toolbar button
  1783. var $button;
  1784. if( toolbar_handler )
  1785. $button = toolbar_button( value ).click( function(event) {
  1786. toolbar_handler( event.currentTarget );
  1787. // Give the focus back to the editor. Technically not necessary
  1788. if( get_toolbar_handler(key) ) // only if not a popup-handler
  1789. wysiwygeditor.getElement().focus();
  1790. event.stopPropagation();
  1791. event.preventDefault();
  1792. return false;
  1793. });
  1794. else if( value.html )
  1795. $button = $(value.html);
  1796. if( $button )
  1797. $toolbar.append( $button );
  1798. });
  1799. };
  1800. var popup_position = function( $popup, $container, left, top ) // left+top relative to $container
  1801. {
  1802. // Test parents
  1803. var offsetparent = $container.get(0).offsetParent,
  1804. offsetparent_offset = { left: 0, top: 0 }, //$.offset() does not work with Safari 3 and 'position:fixed'
  1805. offsetparent_fixed = false,
  1806. offsetparent_overflow = false,
  1807. popup_width = $popup.width(),
  1808. node = offsetparent;
  1809. while( node )
  1810. {
  1811. offsetparent_offset.left += node.offsetLeft;
  1812. offsetparent_offset.top += node.offsetTop;
  1813. var $node = $(node);
  1814. if( $node.css('position') == 'fixed' )
  1815. offsetparent_fixed = true;
  1816. if( $node.css('overflow') != 'visible' )
  1817. offsetparent_overflow = true;
  1818. node = node.offsetParent;
  1819. }
  1820. // Move $popup as high as possible in the DOM tree: offsetParent of $container
  1821. var $offsetparent = $(offsetparent || document.body);
  1822. $offsetparent.append( $popup );
  1823. var offset = $container.position();
  1824. left += offset.left;
  1825. top += offset.top;
  1826. // Trim to offset-parent
  1827. if( offsetparent_fixed || offsetparent_overflow )
  1828. {
  1829. if( left + popup_width > $offsetparent.width() - 1 )
  1830. left = $offsetparent.width() - popup_width - 1;
  1831. if( left < 1 )
  1832. left = 1;
  1833. }
  1834. // Trim to viewport
  1835. var viewport_width = $(window).width();
  1836. if( offsetparent_offset.left + left + popup_width > viewport_width - 1 )
  1837. left = viewport_width - offsetparent_offset.left - popup_width - 1;
  1838. var scroll_left = offsetparent_fixed ? 0 : $(window).scrollLeft();
  1839. if( offsetparent_offset.left + left < scroll_left + 1 )
  1840. left = scroll_left - offsetparent_offset.left + 1;
  1841. // Set offset
  1842. $popup.css({ left: parseInt(left) + 'px',
  1843. top: parseInt(top) + 'px' });
  1844. };
  1845.  
  1846.  
  1847. // Transform the textarea to contenteditable
  1848. var hotkeys = {};
  1849. var create_wysiwyg = function( $textarea, $container, placeholder )
  1850. {
  1851. var option = {
  1852. element: $textarea.get(0),
  1853. onkeypress: function( code, character, shiftKey, altKey, ctrlKey, metaKey )
  1854. {
  1855. // Ask master
  1856. if( on_keypress && on_keypress(code, character, shiftKey, altKey, ctrlKey, metaKey) === false )
  1857. return false; // swallow key
  1858. // Exec hotkey
  1859. if( character && !shiftKey && !altKey && ctrlKey && !metaKey )
  1860. {
  1861. var hotkey = character.toLowerCase();
  1862. if( ! hotkeys[hotkey] )
  1863. return ;
  1864. hotkeys[hotkey]();
  1865. return false; // prevent default
  1866. }
  1867. },
  1868. onselection: function( collapsed, rect, nodes, rightclick )
  1869. {
  1870. var show_popup = true,
  1871. $special_popup = null;
  1872. // Click on a link opens the link-popup
  1873. if( collapsed )
  1874. $.each( nodes, function(index, node) {
  1875. if( $(node).parents('a').length != 0 ) { // only clicks on text-nodes
  1876. $special_popup = content_insertlink( wysiwygeditor, $(node).parents('a:first') )
  1877. return false; // break
  1878. }
  1879. });
  1880. // Fix type error - https://github.com/wysiwygjs/wysiwyg.js/issues/4
  1881. if( ! rect )
  1882. show_popup = false;
  1883. // Force a special popup?
  1884. else if( $special_popup )
  1885. ;
  1886. // A right-click always opens the popup
  1887. else if( rightclick )
  1888. ;
  1889. // No selection-popup wanted?
  1890. else if( toolbar_position != 'selection' && toolbar_position != 'top-selection' && toolbar_position != 'bottom-selection' )
  1891. show_popup = false;
  1892. // Selected popup wanted, but nothing selected (=selection collapsed)
  1893. else if( collapsed )
  1894. show_popup = false;
  1895. // Only one image? Better: Display a special image-popup
  1896. else if( nodes.length == 1 && nodes[0].nodeName == 'IMG' ) // nodes is not a sparse array
  1897. show_popup = false;
  1898. if( ! show_popup )
  1899. {
  1900. wysiwygeditor.closePopup();
  1901. return ;
  1902. }
  1903. // Popup position
  1904. var $popup;
  1905. var apply_popup_position = function()
  1906. {
  1907. var popup_width = $popup.outerWidth();
  1908. // Point is the center of the selection - relative to $container not the element
  1909. var container_offset = $container.offset(),
  1910. editor_offset = $(wysiwygeditor.getElement()).offset();
  1911. var left = rect.left + parseInt(rect.width / 2) - parseInt(popup_width / 2) + editor_offset.left - container_offset.left;
  1912. var top = rect.top + rect.height + editor_offset.top - container_offset.top;
  1913. popup_position( $popup, $container, left, top );
  1914. };
  1915. // Open popup
  1916. $popup = $(wysiwygeditor.openPopup());
  1917. // if wrong popup -> create a new one
  1918. if( $popup.hasClass('wysiwyg-popup') && ! $popup.hasClass('wysiwyg-popuphover') || $popup.data('special') != (!!$special_popup) )
  1919. $popup = $(wysiwygeditor.closePopup().openPopup());
  1920. if( ! $popup.hasClass('wysiwyg-popup') )
  1921. {
  1922. // add classes + buttons
  1923. $popup.addClass( 'wysiwyg-popup wysiwyg-popuphover' );
  1924. if( $special_popup )
  1925. $popup.empty().append( $special_popup ).data('special',true);
  1926. else
  1927. add_buttons_to_toolbar( $popup, true,
  1928. function() {
  1929. return $popup.empty();
  1930. },
  1931. apply_popup_position );
  1932. }
  1933. // Apply position
  1934. apply_popup_position();
  1935. },
  1936. hijackcontextmenu: (toolbar_position == 'selection')
  1937. };
  1938. if( placeholder )
  1939. {
  1940. var $placeholder = $('<div/>').addClass( 'wysiwyg-placeholder' )
  1941. .html( placeholder )
  1942. .hide();
  1943. $container.prepend( $placeholder );
  1944. option.onplaceholder = function( visible ) {
  1945. if( visible )
  1946. $placeholder.show();
  1947. else
  1948. $placeholder.hide();
  1949. };
  1950. }
  1951.  
  1952. var wysiwygeditor = wysiwyg( option );
  1953. return wysiwygeditor;
  1954. }
  1955.  
  1956.  
  1957. // Create a container
  1958. var $container = $('<div/>').addClass('wysiwyg-container');
  1959. if( classes )
  1960. $container.addClass( classes );
  1961. $textarea.wrap( $container );
  1962. $container = $textarea.parent( '.wysiwyg-container' );
  1963.  
  1964. // Create the editor-wrapper if placeholder
  1965. var $wrapper = false;
  1966. if( placeholder )
  1967. {
  1968. $wrapper = $('<div/>').addClass('wysiwyg-wrapper')
  1969. .click(function(){ // Clicking the placeholder focus editor - fixes IE6-IE8
  1970. wysiwygeditor.getElement().focus();
  1971. });
  1972. $textarea.wrap( $wrapper );
  1973. $wrapper = $textarea.parent( '.wysiwyg-wrapper' );
  1974. }
  1975.  
  1976. // Create the WYSIWYG Editor
  1977. var wysiwygeditor = create_wysiwyg( $textarea, placeholder ? $wrapper : $container, placeholder );
  1978. if( wysiwygeditor.legacy )
  1979. {
  1980. var $textarea = $(wysiwygeditor.getElement());
  1981. $textarea.addClass( 'wysiwyg-textarea' );
  1982. if( $textarea.is(':visible') ) // inside the DOM
  1983. $textarea.width( $container.width() - ($textarea.outerWidth() - $textarea.width()) );
  1984. }
  1985. else
  1986. $(wysiwygeditor.getElement()).addClass( 'wysiwyg-editor' );
  1987.  
  1988. // Hotkey+Commands-List
  1989. var commands = {};
  1990. $.each( toolbar_buttons, function(key, value) {
  1991. if( ! value || ! value.hotkey )
  1992. return ;
  1993. var toolbar_handler = get_toolbar_handler( key );
  1994. if( ! toolbar_handler )
  1995. return ;
  1996. hotkeys[value.hotkey.toLowerCase()] = toolbar_handler;
  1997. commands[key] = toolbar_handler;
  1998. });
  1999.  
  2000. // Toolbar on top or bottom
  2001. if( toolbar_position != 'selection' )
  2002. {
  2003. var toolbar_top = toolbar_position == 'top' || toolbar_position == 'top-selection';
  2004. var $toolbar = $('<div/>').addClass( 'wysiwyg-toolbar' ).addClass( toolbar_top ? 'wysiwyg-toolbar-top' : 'wysiwyg-toolbar-bottom' );
  2005. add_buttons_to_toolbar( $toolbar, false,
  2006. function() {
  2007. // Open a popup from the toolbar
  2008. var $popup = $(wysiwygeditor.openPopup());
  2009. // if wrong popup -> create a new one
  2010. if( $popup.hasClass('wysiwyg-popup') && $popup.hasClass('wysiwyg-popuphover') )
  2011. $popup = $(wysiwygeditor.closePopup().openPopup());
  2012. if( ! $popup.hasClass('wysiwyg-popup') )
  2013. // add classes + content
  2014. $popup.addClass( 'wysiwyg-popup' );
  2015. return $popup;
  2016. },
  2017. function( $popup, target, overwrite_offset ) {
  2018. // Popup position
  2019. var $button = $(target);
  2020. var popup_width = $popup.outerWidth();
  2021. // Point is the top/bottom-center of the button
  2022. var left = $button.offset().left - $container.offset().left + parseInt($button.width() / 2) - parseInt(popup_width / 2);
  2023. var top = $button.offset().top - $container.offset().top;
  2024. if( toolbar_top )
  2025. top += $button.outerHeight();
  2026. else
  2027. top -= $popup.outerHeight();
  2028. if( overwrite_offset )
  2029. {
  2030. left = overwrite_offset.left;
  2031. top = overwrite_offset.top;
  2032. }
  2033. popup_position( $popup, $container, left, top );
  2034. });
  2035. if( toolbar_top )
  2036. $container.prepend( $toolbar );
  2037. else
  2038. $container.append( $toolbar );
  2039. }
  2040.  
  2041. // Export userdata
  2042. return {
  2043. wysiwygeditor: wysiwygeditor,
  2044. $container: $container
  2045. };
  2046. };
  2047.  
  2048. // jQuery Interface
  2049. $.fn.wysiwyg = function( option, param )
  2050. {
  2051. if( ! option || typeof(option) === 'object' )
  2052. {
  2053. option = $.extend( {}, option );
  2054. return this.each(function() {
  2055. var $that = $(this);
  2056. // Already an editor
  2057. if( $that.data( 'wysiwyg') )
  2058. return ;
  2059.  
  2060. // Two modes: toolbar on top and on bottom
  2061. var classes = option.classes,
  2062. placeholder = option.placeholder || $that.attr('placeholder'),
  2063. 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',
  2064. toolbar_buttons = option.buttons,
  2065. toolbar_submit = option.submit,
  2066. label_selectImage = option.selectImage,
  2067. placeholder_url = option.placeholderUrl || null,
  2068. placeholder_embed = option.placeholderEmbed || null,
  2069. max_imagesize = option.maxImageSize || null,
  2070. on_imageupload = option.onImageUpload || null,
  2071. force_imageupload = option.forceImageUpload && on_imageupload,
  2072. video_from_url = option.videoFromUrl || null,
  2073. on_keypress = option.onKeyPress;
  2074.  
  2075. // Create the WYSIWYG Editor
  2076. var data = create_editor( $that, classes, placeholder, toolbar_position, toolbar_buttons, toolbar_submit, label_selectImage,
  2077. placeholder_url, placeholder_embed, max_imagesize, on_imageupload, force_imageupload, video_from_url, on_keypress );
  2078. $that.data( 'wysiwyg', data );
  2079. });
  2080. }
  2081. else if( this.length == 1 )
  2082. {
  2083. var data = this.data('wysiwyg');
  2084. if( ! data )
  2085. return this;
  2086. if( option == 'container' )
  2087. return data.$container;
  2088. if( option == 'shell' )
  2089. return data.wysiwygeditor;
  2090. }
  2091. return this;
  2092. };
  2093. })(window, document, jQuery);