Greasy Fork is available in English.

wysiwyg.js

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

אין להתקין סקריפט זה ישירות. זוהי ספריה עבור סקריפטים אחרים // @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);