PageRenderTime 42ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/forum/site_media/js/wym/wymeditor/jquery.wymeditor.js

http://djforum.googlecode.com/
JavaScript | 4606 lines | 3655 code | 347 blank | 604 comment | 245 complexity | df0c1374d0b1ce0c425d4d0e2eb4f41e MD5 | raw file
Possible License(s): GPL-2.0, GPL-3.0
  1. /*
  2. * WYMeditor : what you see is What You Mean web-based editor
  3. * Copyright (c) 2008 Jean-Francois Hovinne, http://www.wymeditor.org/
  4. * Dual licensed under the MIT (MIT-license.txt)
  5. * and GPL (GPL-license.txt) licenses.
  6. *
  7. * For further information visit:
  8. * http://www.wymeditor.org/
  9. *
  10. * File: jquery.wymeditor.js
  11. *
  12. * Main JS file with core classes and functions.
  13. * See the documentation for more info.
  14. *
  15. * About: authors
  16. *
  17. * Jean-Francois Hovinne (jf.hovinne a-t wymeditor dotorg)
  18. * Volker Mische (vmx a-t gmx dotde)
  19. * Scott Lewis (lewiscot a-t gmail dotcom)
  20. * Bermi Ferrer (wymeditor a-t bermi dotorg)
  21. * Daniel Reszka (d.reszka a-t wymeditor dotorg)
  22. * Jonatan Lundin (jonatan.lundin _at_ gmail.com)
  23. */
  24. /*
  25. Namespace: WYMeditor
  26. Global WYMeditor namespace.
  27. */
  28. if(!WYMeditor) var WYMeditor = {};
  29. jQuery.extend(WYMeditor, {
  30. /*
  31. Constants: Global WYMeditor constants.
  32. VERSION - Defines WYMeditor version.
  33. INSTANCES - An array of loaded WYMeditor.editor instances.
  34. STRINGS - An array of loaded WYMeditor language pairs/values.
  35. SKINS - An array of loaded WYMeditor skins.
  36. NAME - The "name" attribute.
  37. INDEX - A string replaced by the instance index.
  38. WYM_INDEX - A string used to get/set the instance index.
  39. BASE_PATH - A string replaced by WYMeditor's base path.
  40. SKIN_PATH - A string replaced by WYMeditor's skin path.
  41. WYM_PATH - A string replaced by WYMeditor's main JS file path.
  42. SKINS_DEFAULT_PATH - The skins default base path.
  43. SKINS_DEFAULT_CSS - The skins default CSS file.
  44. LANG_DEFAULT_PATH - The language files default path.
  45. IFRAME_BASE_PATH - A string replaced by the designmode iframe's base path.
  46. IFRAME_DEFAULT - The iframe's default base path.
  47. JQUERY_PATH - A string replaced by the computed jQuery path.
  48. DIRECTION - A string replaced by the text direction (rtl or ltr).
  49. LOGO - A string replaced by WYMeditor logo.
  50. TOOLS - A string replaced by the toolbar's HTML.
  51. TOOLS_ITEMS - A string replaced by the toolbar items.
  52. TOOL_NAME - A string replaced by a toolbar item's name.
  53. TOOL_TITLE - A string replaced by a toolbar item's title.
  54. TOOL_CLASS - A string replaced by a toolbar item's class.
  55. CLASSES - A string replaced by the classes panel's HTML.
  56. CLASSES_ITEMS - A string replaced by the classes items.
  57. CLASS_NAME - A string replaced by a class item's name.
  58. CLASS_TITLE - A string replaced by a class item's title.
  59. CONTAINERS - A string replaced by the containers panel's HTML.
  60. CONTAINERS_ITEMS - A string replaced by the containers items.
  61. CONTAINER_NAME - A string replaced by a container item's name.
  62. CONTAINER_TITLE - A string replaced by a container item's title.
  63. CONTAINER_CLASS - A string replaced by a container item's class.
  64. HTML - A string replaced by the HTML view panel's HTML.
  65. IFRAME - A string replaced by the designmode iframe.
  66. STATUS - A string replaced by the status panel's HTML.
  67. DIALOG_TITLE - A string replaced by a dialog's title.
  68. DIALOG_BODY - A string replaced by a dialog's HTML body.
  69. BODY - The BODY element.
  70. STRING - The "string" type.
  71. BODY,DIV,P,
  72. H1,H2,H3,H4,H5,H6,
  73. PRE,BLOCKQUOTE,
  74. A,BR,IMG,
  75. TABLE,TD,TH,
  76. UL,OL,LI - HTML elements string representation.
  77. CLASS,HREF,SRC,
  78. TITLE,ALT - HTML attributes string representation.
  79. DIALOG_LINK - A link dialog type.
  80. DIALOG_IMAGE - An image dialog type.
  81. DIALOG_TABLE - A table dialog type.
  82. DIALOG_PASTE - A 'Paste from Word' dialog type.
  83. BOLD - Command: (un)set selection to <strong>.
  84. ITALIC - Command: (un)set selection to <em>.
  85. CREATE_LINK - Command: open the link dialog or (un)set link.
  86. INSERT_IMAGE - Command: open the image dialog or insert an image.
  87. INSERT_TABLE - Command: open the table dialog.
  88. PASTE - Command: open the paste dialog.
  89. INDENT - Command: nest a list item.
  90. OUTDENT - Command: unnest a list item.
  91. TOGGLE_HTML - Command: display/hide the HTML view.
  92. FORMAT_BLOCK - Command: set a block element to another type.
  93. PREVIEW - Command: open the preview dialog.
  94. UNLINK - Command: unset a link.
  95. INSERT_UNORDEREDLIST- Command: insert an unordered list.
  96. INSERT_ORDEREDLIST - Command: insert an ordered list.
  97. MAIN_CONTAINERS - An array of the main HTML containers used in WYMeditor.
  98. BLOCKS - An array of the HTML block elements.
  99. KEY - Standard key codes.
  100. NODE - Node types.
  101. */
  102. VERSION : "0.5-a2",
  103. INSTANCES : [],
  104. STRINGS : [],
  105. SKINS : [],
  106. NAME : "name",
  107. INDEX : "{Wym_Index}",
  108. WYM_INDEX : "wym_index",
  109. BASE_PATH : "{Wym_Base_Path}",
  110. CSS_PATH : "{Wym_Css_Path}",
  111. WYM_PATH : "{Wym_Wym_Path}",
  112. SKINS_DEFAULT_PATH : "skins/",
  113. SKINS_DEFAULT_CSS : "skin.css",
  114. SKINS_DEFAULT_JS : "skin.js",
  115. LANG_DEFAULT_PATH : "lang/",
  116. IFRAME_BASE_PATH : "{Wym_Iframe_Base_Path}",
  117. IFRAME_DEFAULT : "iframe/default/",
  118. JQUERY_PATH : "{Wym_Jquery_Path}",
  119. DIRECTION : "{Wym_Direction}",
  120. LOGO : "{Wym_Logo}",
  121. TOOLS : "{Wym_Tools}",
  122. TOOLS_ITEMS : "{Wym_Tools_Items}",
  123. TOOL_NAME : "{Wym_Tool_Name}",
  124. TOOL_TITLE : "{Wym_Tool_Title}",
  125. TOOL_CLASS : "{Wym_Tool_Class}",
  126. CLASSES : "{Wym_Classes}",
  127. CLASSES_ITEMS : "{Wym_Classes_Items}",
  128. CLASS_NAME : "{Wym_Class_Name}",
  129. CLASS_TITLE : "{Wym_Class_Title}",
  130. CONTAINERS : "{Wym_Containers}",
  131. CONTAINERS_ITEMS : "{Wym_Containers_Items}",
  132. CONTAINER_NAME : "{Wym_Container_Name}",
  133. CONTAINER_TITLE : "{Wym_Containers_Title}",
  134. CONTAINER_CLASS : "{Wym_Container_Class}",
  135. HTML : "{Wym_Html}",
  136. IFRAME : "{Wym_Iframe}",
  137. STATUS : "{Wym_Status}",
  138. DIALOG_TITLE : "{Wym_Dialog_Title}",
  139. DIALOG_BODY : "{Wym_Dialog_Body}",
  140. STRING : "string",
  141. BODY : "body",
  142. DIV : "div",
  143. P : "p",
  144. H1 : "h1",
  145. H2 : "h2",
  146. H3 : "h3",
  147. H4 : "h4",
  148. H5 : "h5",
  149. H6 : "h6",
  150. PRE : "pre",
  151. BLOCKQUOTE : "blockquote",
  152. A : "a",
  153. BR : "br",
  154. IMG : "img",
  155. TABLE : "table",
  156. TD : "td",
  157. TH : "th",
  158. UL : "ul",
  159. OL : "ol",
  160. LI : "li",
  161. CLASS : "class",
  162. HREF : "href",
  163. SRC : "src",
  164. TITLE : "title",
  165. ALT : "alt",
  166. DIALOG_LINK : "Link",
  167. DIALOG_IMAGE : "Image",
  168. DIALOG_TABLE : "Table",
  169. DIALOG_PASTE : "Paste_From_Word",
  170. BOLD : "Bold",
  171. ITALIC : "Italic",
  172. CREATE_LINK : "CreateLink",
  173. INSERT_IMAGE : "InsertImage",
  174. INSERT_TABLE : "InsertTable",
  175. INSERT_HTML : "InsertHTML",
  176. PASTE : "Paste",
  177. INDENT : "Indent",
  178. OUTDENT : "Outdent",
  179. TOGGLE_HTML : "ToggleHtml",
  180. FORMAT_BLOCK : "FormatBlock",
  181. PREVIEW : "Preview",
  182. UNLINK : "Unlink",
  183. INSERT_UNORDEREDLIST : "InsertUnorderedList",
  184. INSERT_ORDEREDLIST : "InsertOrderedList",
  185. MAIN_CONTAINERS : new Array("p","h1","h2","h3","h4","h5","h6","pre","blockquote"),
  186. BLOCKS : new Array("address", "blockquote", "div", "dl",
  187. "fieldset", "form", "h1", "h2", "h3", "h4", "h5", "h6", "hr",
  188. "noscript", "ol", "p", "pre", "table", "ul", "dd", "dt",
  189. "li", "tbody", "td", "tfoot", "th", "thead", "tr"),
  190. KEY : {
  191. BACKSPACE: 8,
  192. ENTER: 13,
  193. END: 35,
  194. HOME: 36,
  195. LEFT: 37,
  196. UP: 38,
  197. RIGHT: 39,
  198. DOWN: 40,
  199. CURSOR: new Array(37, 38, 39, 40),
  200. DELETE: 46
  201. },
  202. NODE : {
  203. ELEMENT: 1,
  204. ATTRIBUTE: 2,
  205. TEXT: 3
  206. },
  207. /*
  208. Class: WYMeditor.editor
  209. WYMeditor editor main class, instanciated for each editor occurrence.
  210. */
  211. editor : function(elem, options) {
  212. /*
  213. Constructor: WYMeditor.editor
  214. Initializes main values (index, elements, paths, ...)
  215. and call WYMeditor.editor.init which initializes the editor.
  216. Parameters:
  217. elem - The HTML element to be replaced by the editor.
  218. options - The hash of options.
  219. Returns:
  220. Nothing.
  221. See Also:
  222. <WYMeditor.editor.init>
  223. */
  224. //store the instance in the INSTANCES array and store the index
  225. this._index = WYMeditor.INSTANCES.push(this) - 1;
  226. //store the element replaced by the editor
  227. this._element = elem;
  228. //store the options
  229. this._options = options;
  230. //store the element's inner value
  231. this._html = jQuery(elem).val();
  232. //store the HTML option, if any
  233. if(this._options.html) this._html = this._options.html;
  234. //get or compute the base path (where the main JS file is located)
  235. this._options.basePath = this._options.basePath
  236. || this.computeBasePath();
  237. //get or set the skin path (where the skin files are located)
  238. this._options.skinPath = this._options.skinPath
  239. || this._options.basePath + WYMeditor.SKINS_DEFAULT_PATH
  240. + this._options.skin + '/';
  241. //get or compute the main JS file location
  242. this._options.wymPath = this._options.wymPath
  243. || this.computeWymPath();
  244. //get or set the language files path
  245. this._options.langPath = this._options.langPath
  246. || this._options.basePath + WYMeditor.LANG_DEFAULT_PATH;
  247. //get or set the designmode iframe's base path
  248. this._options.iframeBasePath = this._options.iframeBasePath
  249. || this._options.basePath + WYMeditor.IFRAME_DEFAULT;
  250. //get or compute the jQuery JS file location
  251. this._options.jQueryPath = this._options.jQueryPath
  252. || this.computeJqueryPath();
  253. //initialize the editor instance
  254. this.init();
  255. }
  256. });
  257. /********** JQUERY **********/
  258. /**
  259. * Replace an HTML element by WYMeditor
  260. *
  261. * @example jQuery(".wymeditor").wymeditor(
  262. * {
  263. *
  264. * }
  265. * );
  266. * @desc Example description here
  267. *
  268. * @name WYMeditor
  269. * @description WYMeditor is a web-based WYSIWYM XHTML editor
  270. * @param Hash hash A hash of parameters
  271. * @option Integer iExample Description here
  272. * @option String sExample Description here
  273. *
  274. * @type jQuery
  275. * @cat Plugins/WYMeditor
  276. * @author Jean-Francois Hovinne
  277. */
  278. jQuery.fn.wymeditor = function(options) {
  279. options = jQuery.extend({
  280. html: "",
  281. basePath: false,
  282. skinPath: false,
  283. wymPath: false,
  284. iframeBasePath: false,
  285. jQueryPath: false,
  286. styles: false,
  287. stylesheet: false,
  288. skin: "default",
  289. initSkin: true,
  290. loadSkin: true,
  291. lang: "en",
  292. direction: "ltr",
  293. boxHtml: "<div class='wym_box'>"
  294. + "<div class='wym_area_top'>"
  295. + WYMeditor.TOOLS
  296. + "</div>"
  297. + "<div class='wym_area_left'></div>"
  298. + "<div class='wym_area_right'>"
  299. + WYMeditor.CONTAINERS
  300. + WYMeditor.CLASSES
  301. + "</div>"
  302. + "<div class='wym_area_main'>"
  303. + WYMeditor.HTML
  304. + WYMeditor.IFRAME
  305. + WYMeditor.STATUS
  306. + "</div>"
  307. + "<div class='wym_area_bottom'>"
  308. + WYMeditor.LOGO
  309. + "</div>"
  310. + "</div>",
  311. logoHtml: "<a class='wym_wymeditor_link' "
  312. + "href='http://www.wymeditor.org/'>WYMeditor</a>",
  313. iframeHtml:"<div class='wym_iframe wym_section'>"
  314. + "<iframe "
  315. + "src='"
  316. + WYMeditor.IFRAME_BASE_PATH
  317. + "wymiframe.html' "
  318. + "onload='this.contentWindow.parent.WYMeditor.INSTANCES["
  319. + WYMeditor.INDEX + "].initIframe(this)'"
  320. + "></iframe>"
  321. + "</div>",
  322. editorStyles: [],
  323. toolsHtml: "<div class='wym_tools wym_section'>"
  324. + "<h2>{Tools}</h2>"
  325. + "<ul>"
  326. + WYMeditor.TOOLS_ITEMS
  327. + "</ul>"
  328. + "</div>",
  329. toolsItemHtml: "<li class='"
  330. + WYMeditor.TOOL_CLASS
  331. + "'><a href='#' name='"
  332. + WYMeditor.TOOL_NAME
  333. + "' title='"
  334. + WYMeditor.TOOL_TITLE
  335. + "'>"
  336. + WYMeditor.TOOL_TITLE
  337. + "</a></li>",
  338. toolsItems: [
  339. {'name': 'Bold', 'title': 'Strong', 'css': 'wym_tools_strong'},
  340. {'name': 'Italic', 'title': 'Emphasis', 'css': 'wym_tools_emphasis'},
  341. {'name': 'Superscript', 'title': 'Superscript',
  342. 'css': 'wym_tools_superscript'},
  343. {'name': 'Subscript', 'title': 'Subscript',
  344. 'css': 'wym_tools_subscript'},
  345. {'name': 'InsertOrderedList', 'title': 'Ordered_List',
  346. 'css': 'wym_tools_ordered_list'},
  347. {'name': 'InsertUnorderedList', 'title': 'Unordered_List',
  348. 'css': 'wym_tools_unordered_list'},
  349. {'name': 'Indent', 'title': 'Indent', 'css': 'wym_tools_indent'},
  350. {'name': 'Outdent', 'title': 'Outdent', 'css': 'wym_tools_outdent'},
  351. {'name': 'Undo', 'title': 'Undo', 'css': 'wym_tools_undo'},
  352. {'name': 'Redo', 'title': 'Redo', 'css': 'wym_tools_redo'},
  353. {'name': 'CreateLink', 'title': 'Link', 'css': 'wym_tools_link'},
  354. {'name': 'Unlink', 'title': 'Unlink', 'css': 'wym_tools_unlink'},
  355. {'name': 'InsertImage', 'title': 'Image', 'css': 'wym_tools_image'},
  356. {'name': 'InsertTable', 'title': 'Table', 'css': 'wym_tools_table'},
  357. {'name': 'Paste', 'title': 'Paste_From_Word',
  358. 'css': 'wym_tools_paste'},
  359. {'name': 'ToggleHtml', 'title': 'HTML', 'css': 'wym_tools_html'},
  360. {'name': 'Preview', 'title': 'Preview', 'css': 'wym_tools_preview'}
  361. ],
  362. containersHtml: "<div class='wym_containers wym_section'>"
  363. + "<h2>{Containers}</h2>"
  364. + "<ul>"
  365. + WYMeditor.CONTAINERS_ITEMS
  366. + "</ul>"
  367. + "</div>",
  368. containersItemHtml:"<li class='"
  369. + WYMeditor.CONTAINER_CLASS
  370. + "'>"
  371. + "<a href='#' name='"
  372. + WYMeditor.CONTAINER_NAME
  373. + "'>"
  374. + WYMeditor.CONTAINER_TITLE
  375. + "</a></li>",
  376. containersItems: [
  377. {'name': 'P', 'title': 'Paragraph', 'css': 'wym_containers_p'},
  378. {'name': 'H1', 'title': 'Heading_1', 'css': 'wym_containers_h1'},
  379. {'name': 'H2', 'title': 'Heading_2', 'css': 'wym_containers_h2'},
  380. {'name': 'H3', 'title': 'Heading_3', 'css': 'wym_containers_h3'},
  381. {'name': 'H4', 'title': 'Heading_4', 'css': 'wym_containers_h4'},
  382. {'name': 'H5', 'title': 'Heading_5', 'css': 'wym_containers_h5'},
  383. {'name': 'H6', 'title': 'Heading_6', 'css': 'wym_containers_h6'},
  384. {'name': 'PRE', 'title': 'Preformatted', 'css': 'wym_containers_pre'},
  385. {'name': 'BLOCKQUOTE', 'title': 'Blockquote',
  386. 'css': 'wym_containers_blockquote'},
  387. {'name': 'TH', 'title': 'Table_Header', 'css': 'wym_containers_th'}
  388. ],
  389. classesHtml: "<div class='wym_classes wym_section'>"
  390. + "<h2>{Classes}</h2><ul>"
  391. + WYMeditor.CLASSES_ITEMS
  392. + "</ul></div>",
  393. classesItemHtml: "<li><a href='#' name='"
  394. + WYMeditor.CLASS_NAME
  395. + "'>"
  396. + WYMeditor.CLASS_TITLE
  397. + "</a></li>",
  398. classesItems: [],
  399. statusHtml: "<div class='wym_status wym_section'>"
  400. + "<h2>{Status}</h2>"
  401. + "</div>",
  402. htmlHtml: "<div class='wym_html wym_section'>"
  403. + "<h2>{Source_Code}</h2>"
  404. + "<textarea class='wym_html_val'></textarea>"
  405. + "</div>",
  406. boxSelector: ".wym_box",
  407. toolsSelector: ".wym_tools",
  408. toolsListSelector: " ul",
  409. containersSelector:".wym_containers",
  410. classesSelector: ".wym_classes",
  411. htmlSelector: ".wym_html",
  412. iframeSelector: ".wym_iframe iframe",
  413. iframeBodySelector:".wym_iframe",
  414. statusSelector: ".wym_status",
  415. toolSelector: ".wym_tools a",
  416. containerSelector: ".wym_containers a",
  417. classSelector: ".wym_classes a",
  418. htmlValSelector: ".wym_html_val",
  419. hrefSelector: ".wym_href",
  420. srcSelector: ".wym_src",
  421. titleSelector: ".wym_title",
  422. altSelector: ".wym_alt",
  423. textSelector: ".wym_text",
  424. rowsSelector: ".wym_rows",
  425. colsSelector: ".wym_cols",
  426. captionSelector: ".wym_caption",
  427. summarySelector: ".wym_summary",
  428. submitSelector: ".wym_submit",
  429. cancelSelector: ".wym_cancel",
  430. previewSelector: "",
  431. dialogTypeSelector: ".wym_dialog_type",
  432. dialogLinkSelector: ".wym_dialog_link",
  433. dialogImageSelector: ".wym_dialog_image",
  434. dialogTableSelector: ".wym_dialog_table",
  435. dialogPasteSelector: ".wym_dialog_paste",
  436. dialogPreviewSelector: ".wym_dialog_preview",
  437. updateSelector: ".wymupdate",
  438. updateEvent: "click",
  439. dialogFeatures: "menubar=no,titlebar=no,toolbar=no,resizable=no"
  440. + ",width=560,height=300,top=0,left=0",
  441. dialogHtml: "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Strict//EN'"
  442. + " 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'>"
  443. + "<html dir='"
  444. + WYMeditor.DIRECTION
  445. + "'><head>"
  446. + "<link rel='stylesheet' type='text/css' media='screen'"
  447. + " href='"
  448. + WYMeditor.CSS_PATH
  449. + "' />"
  450. + "<title>"
  451. + WYMeditor.DIALOG_TITLE
  452. + "</title>"
  453. + "<script type='text/javascript'"
  454. + " src='"
  455. + WYMeditor.JQUERY_PATH
  456. + "'></script>"
  457. + "<script type='text/javascript'"
  458. + " src='"
  459. + WYMeditor.WYM_PATH
  460. + "'></script>"
  461. + "</head>"
  462. + WYMeditor.DIALOG_BODY
  463. + "</html>",
  464. dialogLinkHtml: "<body class='wym_dialog wym_dialog_link'"
  465. + " onload='WYMeditor.INIT_DIALOG(" + WYMeditor.INDEX + ")'"
  466. + ">"
  467. + "<form>"
  468. + "<fieldset>"
  469. + "<input type='hidden' class='wym_dialog_type' value='"
  470. + WYMeditor.DIALOG_LINK
  471. + "' />"
  472. + "<legend>{Link}</legend>"
  473. + "<div class='row'>"
  474. + "<label>{URL}</label>"
  475. + "<input type='text' class='wym_href' value='' size='40' />"
  476. + "</div>"
  477. + "<div class='row'>"
  478. + "<label>{Title}</label>"
  479. + "<input type='text' class='wym_title' value='' size='40' />"
  480. + "</div>"
  481. + "<div class='row row-indent'>"
  482. + "<input class='wym_submit' type='button'"
  483. + " value='{Submit}' />"
  484. + "<input class='wym_cancel' type='button'"
  485. + "value='{Cancel}' />"
  486. + "</div>"
  487. + "</fieldset>"
  488. + "</form>"
  489. + "</body>",
  490. dialogImageHtml: "<body class='wym_dialog wym_dialog_image'"
  491. + " onload='WYMeditor.INIT_DIALOG(" + WYMeditor.INDEX + ")'"
  492. + ">"
  493. + "<form>"
  494. + "<fieldset>"
  495. + "<input type='hidden' class='wym_dialog_type' value='"
  496. + WYMeditor.DIALOG_IMAGE
  497. + "' />"
  498. + "<legend>{Image}</legend>"
  499. + "<div class='row'>"
  500. + "<label>{URL}</label>"
  501. + "<input type='text' class='wym_src' value='' size='40' />"
  502. + "</div>"
  503. + "<div class='row'>"
  504. + "<label>{Alternative_Text}</label>"
  505. + "<input type='text' class='wym_alt' value='' size='40' />"
  506. + "</div>"
  507. + "<div class='row'>"
  508. + "<label>{Title}</label>"
  509. + "<input type='text' class='wym_title' value='' size='40' />"
  510. + "</div>"
  511. + "<div class='row row-indent'>"
  512. + "<input class='wym_submit' type='button'"
  513. + " value='{Submit}' />"
  514. + "<input class='wym_cancel' type='button'"
  515. + "value='{Cancel}' />"
  516. + "</div>"
  517. + "</fieldset>"
  518. + "</form>"
  519. + "</body>",
  520. dialogTableHtml: "<body class='wym_dialog wym_dialog_table'"
  521. + " onload='WYMeditor.INIT_DIALOG(" + WYMeditor.INDEX + ")'"
  522. + ">"
  523. + "<form>"
  524. + "<fieldset>"
  525. + "<input type='hidden' class='wym_dialog_type' value='"
  526. + WYMeditor.DIALOG_TABLE
  527. + "' />"
  528. + "<legend>{Table}</legend>"
  529. + "<div class='row'>"
  530. + "<label>{Caption}</label>"
  531. + "<input type='text' class='wym_caption' value='' size='40' />"
  532. + "</div>"
  533. + "<div class='row'>"
  534. + "<label>{Summary}</label>"
  535. + "<input type='text' class='wym_summary' value='' size='40' />"
  536. + "</div>"
  537. + "<div class='row'>"
  538. + "<label>{Number_Of_Rows}</label>"
  539. + "<input type='text' class='wym_rows' value='3' size='3' />"
  540. + "</div>"
  541. + "<div class='row'>"
  542. + "<label>{Number_Of_Cols}</label>"
  543. + "<input type='text' class='wym_cols' value='2' size='3' />"
  544. + "</div>"
  545. + "<div class='row row-indent'>"
  546. + "<input class='wym_submit' type='button'"
  547. + " value='{Submit}' />"
  548. + "<input class='wym_cancel' type='button'"
  549. + "value='{Cancel}' />"
  550. + "</div>"
  551. + "</fieldset>"
  552. + "</form>"
  553. + "</body>",
  554. dialogPasteHtml: "<body class='wym_dialog wym_dialog_paste'"
  555. + " onload='WYMeditor.INIT_DIALOG(" + WYMeditor.INDEX + ")'"
  556. + ">"
  557. + "<form>"
  558. + "<input type='hidden' class='wym_dialog_type' value='"
  559. + WYMeditor.DIALOG_PASTE
  560. + "' />"
  561. + "<fieldset>"
  562. + "<legend>{Paste_From_Word}</legend>"
  563. + "<div class='row'>"
  564. + "<textarea class='wym_text' rows='10' cols='50'></textarea>"
  565. + "</div>"
  566. + "<div class='row'>"
  567. + "<input class='wym_submit' type='button'"
  568. + " value='{Submit}' />"
  569. + "<input class='wym_cancel' type='button'"
  570. + "value='{Cancel}' />"
  571. + "</div>"
  572. + "</fieldset>"
  573. + "</form>"
  574. + "</body>",
  575. dialogPreviewHtml: "<body class='wym_dialog wym_dialog_preview'"
  576. + " onload='WYMeditor.INIT_DIALOG(" + WYMeditor.INDEX + ")'"
  577. + "></body>",
  578. dialogStyles: [],
  579. stringDelimiterLeft: "{",
  580. stringDelimiterRight:"}",
  581. preInit: null,
  582. preBind: null,
  583. postInit: null,
  584. preInitDialog: null,
  585. postInitDialog: null
  586. }, options);
  587. return this.each(function() {
  588. new WYMeditor.editor(jQuery(this),options);
  589. });
  590. };
  591. /* @name extend
  592. * @description Returns the WYMeditor instance based on its index
  593. */
  594. jQuery.extend({
  595. wymeditors: function(i) {
  596. return (WYMeditor.INSTANCES[i]);
  597. }
  598. });
  599. /********** WYMeditor **********/
  600. /* @name Wymeditor
  601. * @description WYMeditor class
  602. */
  603. /* @name init
  604. * @description Initializes a WYMeditor instance
  605. */
  606. WYMeditor.editor.prototype.init = function() {
  607. //load subclass - browser specific
  608. //unsupported browsers: do nothing
  609. if (jQuery.browser.msie) {
  610. var WymClass = new WYMeditor.WymClassExplorer(this);
  611. }
  612. else if (jQuery.browser.mozilla) {
  613. var WymClass = new WYMeditor.WymClassMozilla(this);
  614. }
  615. else if (jQuery.browser.opera) {
  616. var WymClass = new WYMeditor.WymClassOpera(this);
  617. }
  618. else if (jQuery.browser.safari) {
  619. var WymClass = new WYMeditor.WymClassSafari(this);
  620. }
  621. if(WymClass) {
  622. if(jQuery.isFunction(this._options.preInit)) this._options.preInit(this);
  623. var SaxListener = new WYMeditor.XhtmlSaxListener();
  624. jQuery.extend(SaxListener, WymClass);
  625. this.parser = new WYMeditor.XhtmlParser(SaxListener);
  626. if(this._options.styles || this._options.stylesheet){
  627. this.configureEditorUsingRawCss();
  628. }
  629. this.helper = new WYMeditor.XmlHelper();
  630. //extend the Wymeditor object
  631. //don't use jQuery.extend since 1.1.4
  632. //jQuery.extend(this, WymClass);
  633. for (var prop in WymClass) { this[prop] = WymClass[prop]; }
  634. //load wymbox
  635. this._box = jQuery(this._element).hide().after(this._options.boxHtml).next();
  636. //store the instance index in the wymbox element
  637. jQuery(this._box).data(WYMeditor.WYM_INDEX, this._index);
  638. var h = WYMeditor.Helper;
  639. //construct the iframe
  640. var iframeHtml = this._options.iframeHtml;
  641. iframeHtml = h.replaceAll(iframeHtml, WYMeditor.INDEX, this._index);
  642. iframeHtml = h.replaceAll(iframeHtml, WYMeditor.IFRAME_BASE_PATH, this._options.iframeBasePath);
  643. //construct wymbox
  644. var boxHtml = jQuery(this._box).html();
  645. boxHtml = h.replaceAll(boxHtml, WYMeditor.LOGO, this._options.logoHtml);
  646. boxHtml = h.replaceAll(boxHtml, WYMeditor.TOOLS, this._options.toolsHtml);
  647. boxHtml = h.replaceAll(boxHtml, WYMeditor.CONTAINERS,this._options.containersHtml);
  648. boxHtml = h.replaceAll(boxHtml, WYMeditor.CLASSES, this._options.classesHtml);
  649. boxHtml = h.replaceAll(boxHtml, WYMeditor.HTML, this._options.htmlHtml);
  650. boxHtml = h.replaceAll(boxHtml, WYMeditor.IFRAME, iframeHtml);
  651. boxHtml = h.replaceAll(boxHtml, WYMeditor.STATUS, this._options.statusHtml);
  652. //construct tools list
  653. var aTools = eval(this._options.toolsItems);
  654. var sTools = "";
  655. for(var i = 0; i < aTools.length; i++) {
  656. var oTool = aTools[i];
  657. if(oTool.name && oTool.title)
  658. var sTool = this._options.toolsItemHtml;
  659. var sTool = h.replaceAll(sTool, WYMeditor.TOOL_NAME, oTool.name);
  660. sTool = h.replaceAll(sTool, WYMeditor.TOOL_TITLE, this._options.stringDelimiterLeft
  661. + oTool.title
  662. + this._options.stringDelimiterRight);
  663. sTool = h.replaceAll(sTool, WYMeditor.TOOL_CLASS, oTool.css);
  664. sTools += sTool;
  665. }
  666. boxHtml = h.replaceAll(boxHtml, WYMeditor.TOOLS_ITEMS, sTools);
  667. //construct classes list
  668. var aClasses = eval(this._options.classesItems);
  669. var sClasses = "";
  670. for(var i = 0; i < aClasses.length; i++) {
  671. var oClass = aClasses[i];
  672. if(oClass.name && oClass.title)
  673. var sClass = this._options.classesItemHtml;
  674. sClass = h.replaceAll(sClass, WYMeditor.CLASS_NAME, oClass.name);
  675. sClass = h.replaceAll(sClass, WYMeditor.CLASS_TITLE, oClass.title);
  676. sClasses += sClass;
  677. }
  678. boxHtml = h.replaceAll(boxHtml, WYMeditor.CLASSES_ITEMS, sClasses);
  679. //construct containers list
  680. var aContainers = eval(this._options.containersItems);
  681. var sContainers = "";
  682. for(var i = 0; i < aContainers.length; i++) {
  683. var oContainer = aContainers[i];
  684. if(oContainer.name && oContainer.title)
  685. var sContainer = this._options.containersItemHtml;
  686. sContainer = h.replaceAll(sContainer, WYMeditor.CONTAINER_NAME, oContainer.name);
  687. sContainer = h.replaceAll(sContainer, WYMeditor.CONTAINER_TITLE,
  688. this._options.stringDelimiterLeft
  689. + oContainer.title
  690. + this._options.stringDelimiterRight);
  691. sContainer = h.replaceAll(sContainer, WYMeditor.CONTAINER_CLASS, oContainer.css);
  692. sContainers += sContainer;
  693. }
  694. boxHtml = h.replaceAll(boxHtml, WYMeditor.CONTAINERS_ITEMS, sContainers);
  695. //l10n
  696. boxHtml = this.replaceStrings(boxHtml);
  697. //load html in wymbox
  698. jQuery(this._box).html(boxHtml);
  699. //hide the html value
  700. jQuery(this._box).find(this._options.htmlSelector).hide();
  701. //enable the skin
  702. this.loadSkin();
  703. }
  704. };
  705. WYMeditor.editor.prototype.bindEvents = function() {
  706. //copy the instance
  707. var wym = this;
  708. //handle click event on tools buttons
  709. jQuery(this._box).find(this._options.toolSelector).click(function() {
  710. wym.exec(jQuery(this).attr(WYMeditor.NAME));
  711. return(false);
  712. });
  713. //handle click event on containers buttons
  714. jQuery(this._box).find(this._options.containerSelector).click(function() {
  715. wym.container(jQuery(this).attr(WYMeditor.NAME));
  716. return(false);
  717. });
  718. //handle keyup event on html value: set the editor value
  719. jQuery(this._box).find(this._options.htmlValSelector).keyup(function() {
  720. jQuery(wym._doc.body).html(jQuery(this).val());
  721. });
  722. //handle click event on classes buttons
  723. jQuery(this._box).find(this._options.classSelector).click(function() {
  724. var aClasses = eval(wym._options.classesItems);
  725. var sName = jQuery(this).attr(WYMeditor.NAME);
  726. var oClass = WYMeditor.Helper.findByName(aClasses, sName);
  727. if(oClass) {
  728. var jqexpr = oClass.expr;
  729. wym.toggleClass(sName, jqexpr);
  730. }
  731. return(false);
  732. });
  733. //handle event on update element
  734. jQuery(this._options.updateSelector)
  735. .bind(this._options.updateEvent, function() {
  736. wym.update();
  737. });
  738. };
  739. WYMeditor.editor.prototype.ready = function() {
  740. return(this._doc != null);
  741. };
  742. /********** METHODS **********/
  743. /* @name box
  744. * @description Returns the WYMeditor container
  745. */
  746. WYMeditor.editor.prototype.box = function() {
  747. return(this._box);
  748. };
  749. /* @name html
  750. * @description Get/Set the html value
  751. */
  752. WYMeditor.editor.prototype.html = function(html) {
  753. if(typeof html === 'string') jQuery(this._doc.body).html(html);
  754. else return(jQuery(this._doc.body).html());
  755. };
  756. /* @name xhtml
  757. * @description Cleans up the HTML
  758. */
  759. WYMeditor.editor.prototype.xhtml = function() {
  760. return this.parser.parse(this.html());
  761. };
  762. /* @name exec
  763. * @description Executes a button command
  764. */
  765. WYMeditor.editor.prototype.exec = function(cmd) {
  766. //base function for execCommand
  767. //open a dialog or exec
  768. switch(cmd) {
  769. case WYMeditor.CREATE_LINK:
  770. var container = this.container();
  771. if(container || this._selected_image) this.dialog(WYMeditor.DIALOG_LINK);
  772. break;
  773. case WYMeditor.INSERT_IMAGE:
  774. this.dialog(WYMeditor.DIALOG_IMAGE);
  775. break;
  776. case WYMeditor.INSERT_TABLE:
  777. this.dialog(WYMeditor.DIALOG_TABLE);
  778. break;
  779. case WYMeditor.PASTE:
  780. this.dialog(WYMeditor.DIALOG_PASTE);
  781. break;
  782. case WYMeditor.TOGGLE_HTML:
  783. this.update();
  784. this.toggleHtml();
  785. break;
  786. case WYMeditor.PREVIEW:
  787. this.dialog(WYMeditor.PREVIEW);
  788. break;
  789. default:
  790. this._exec(cmd);
  791. break;
  792. }
  793. };
  794. /* @name container
  795. * @description Get/Set the selected container
  796. */
  797. WYMeditor.editor.prototype.container = function(sType) {
  798. if(sType) {
  799. var container = null;
  800. if(sType.toLowerCase() == WYMeditor.TH) {
  801. container = this.container();
  802. //find the TD or TH container
  803. switch(container.tagName.toLowerCase()) {
  804. case WYMeditor.TD: case WYMeditor.TH:
  805. break;
  806. default:
  807. var aTypes = new Array(WYMeditor.TD,WYMeditor.TH);
  808. container = this.findUp(this.container(), aTypes);
  809. break;
  810. }
  811. //if it exists, switch
  812. if(container!=null) {
  813. sType = (container.tagName.toLowerCase() == WYMeditor.TD)? WYMeditor.TH: WYMeditor.TD;
  814. this.switchTo(container,sType);
  815. this.update();
  816. }
  817. } else {
  818. //set the container type
  819. var aTypes=new Array(WYMeditor.P,WYMeditor.H1,WYMeditor.H2,WYMeditor.H3,WYMeditor.H4,WYMeditor.H5,
  820. WYMeditor.H6,WYMeditor.PRE,WYMeditor.BLOCKQUOTE);
  821. container = this.findUp(this.container(), aTypes);
  822. if(container) {
  823. var newNode = null;
  824. //blockquotes must contain a block level element
  825. if(sType.toLowerCase() == WYMeditor.BLOCKQUOTE) {
  826. var blockquote = this.findUp(this.container(), WYMeditor.BLOCKQUOTE);
  827. if(blockquote == null) {
  828. newNode = this._doc.createElement(sType);
  829. container.parentNode.insertBefore(newNode,container);
  830. newNode.appendChild(container);
  831. this.setFocusToNode(newNode.firstChild);
  832. } else {
  833. var nodes = blockquote.childNodes;
  834. var lgt = nodes.length;
  835. var firstNode = null;
  836. if(lgt > 0) firstNode = nodes.item(0);
  837. for(var x=0; x<lgt; x++) {
  838. blockquote.parentNode.insertBefore(nodes.item(0),blockquote);
  839. }
  840. blockquote.parentNode.removeChild(blockquote);
  841. if(firstNode) this.setFocusToNode(firstNode);
  842. }
  843. }
  844. else this.switchTo(container,sType);
  845. this.update();
  846. }
  847. }
  848. }
  849. else return(this.selected());
  850. };
  851. /* @name toggleClass
  852. * @description Toggles class on selected element, or one of its parents
  853. */
  854. WYMeditor.editor.prototype.toggleClass = function(sClass, jqexpr) {
  855. var container = (this._selected_image
  856. ? this._selected_image
  857. : jQuery(this.selected()));
  858. container = jQuery(container).parentsOrSelf(jqexpr);
  859. jQuery(container).toggleClass(sClass);
  860. if(!jQuery(container).attr(WYMeditor.CLASS)) jQuery(container).removeAttr(this._class);
  861. };
  862. /* @name findUp
  863. * @description Returns the first parent or self container, based on its type
  864. */
  865. WYMeditor.editor.prototype.findUp = function(node, filter) {
  866. //filter is a string or an array of strings
  867. if(node) {
  868. var tagname = node.tagName.toLowerCase();
  869. if(typeof(filter) == WYMeditor.STRING) {
  870. while(tagname != filter && tagname != WYMeditor.BODY) {
  871. node = node.parentNode;
  872. tagname = node.tagName.toLowerCase();
  873. }
  874. } else {
  875. var bFound = false;
  876. while(!bFound && tagname != WYMeditor.BODY) {
  877. for(var i = 0; i < filter.length; i++) {
  878. if(tagname == filter[i]) {
  879. bFound = true;
  880. break;
  881. }
  882. }
  883. if(!bFound) {
  884. node = node.parentNode;
  885. tagname = node.tagName.toLowerCase();
  886. }
  887. }
  888. }
  889. if(tagname != WYMeditor.BODY) return(node);
  890. else return(null);
  891. } else return(null);
  892. };
  893. /* @name switchTo
  894. * @description Switch the node's type
  895. */
  896. WYMeditor.editor.prototype.switchTo = function(node,sType) {
  897. var newNode = this._doc.createElement(sType);
  898. var html = jQuery(node).html();
  899. node.parentNode.replaceChild(newNode,node);
  900. jQuery(newNode).html(html);
  901. this.setFocusToNode(newNode);
  902. };
  903. WYMeditor.editor.prototype.replaceStrings = function(sVal) {
  904. //check if the language file has already been loaded
  905. //if not, get it via a synchronous ajax call
  906. if(!WYMeditor.STRINGS[this._options.lang])
  907. eval(jQuery.ajax({url:this._options.langPath
  908. + this._options.lang + '.js', async:false}).responseText);
  909. //replace all the strings in sVal and return it
  910. for (var key in WYMeditor.STRINGS[this._options.lang]) {
  911. sVal = WYMeditor.Helper.replaceAll(sVal, this._options.stringDelimiterLeft + key
  912. + this._options.stringDelimiterRight,
  913. WYMeditor.STRINGS[this._options.lang][key]);
  914. };
  915. return(sVal);
  916. };
  917. WYMeditor.editor.prototype.encloseString = function(sVal) {
  918. return(this._options.stringDelimiterLeft
  919. + sVal
  920. + this._options.stringDelimiterRight);
  921. };
  922. /* @name status
  923. * @description Prints a status message
  924. */
  925. WYMeditor.editor.prototype.status = function(sMessage) {
  926. //print status message
  927. jQuery(this._box).find(this._options.statusSelector).html(sMessage);
  928. };
  929. /* @name update
  930. * @description Updates the element and textarea values
  931. */
  932. WYMeditor.editor.prototype.update = function() {
  933. var html = this.xhtml();
  934. jQuery(this._element).val(html);
  935. jQuery(this._box).find(this._options.htmlValSelector).val(html);
  936. };
  937. /* @name dialog
  938. * @description Opens a dialog box
  939. */
  940. WYMeditor.editor.prototype.dialog = function(sType) {
  941. var wDialog = window.open(
  942. '',
  943. 'dialog',
  944. this._wym._options.dialogFeatures);
  945. if(wDialog) {
  946. var sBodyHtml = "";
  947. switch(sType) {
  948. case(WYMeditor.DIALOG_LINK):
  949. sBodyHtml = this._options.dialogLinkHtml;
  950. break;
  951. case(WYMeditor.DIALOG_IMAGE):
  952. sBodyHtml = this._options.dialogImageHtml;
  953. break;
  954. case(WYMeditor.DIALOG_TABLE):
  955. sBodyHtml = this._options.dialogTableHtml;
  956. break;
  957. case(WYMeditor.DIALOG_PASTE):
  958. sBodyHtml = this._options.dialogPasteHtml;
  959. break;
  960. case(WYMeditor.PREVIEW):
  961. sBodyHtml = this._options.dialogPreviewHtml;
  962. break;
  963. }
  964. var h = WYMeditor.Helper;
  965. //construct the dialog
  966. var dialogHtml = this._options.dialogHtml;
  967. dialogHtml = h.replaceAll(dialogHtml, WYMeditor.BASE_PATH, this._options.basePath);
  968. dialogHtml = h.replaceAll(dialogHtml, WYMeditor.DIRECTION, this._options.direction);
  969. dialogHtml = h.replaceAll(dialogHtml, WYMeditor.CSS_PATH, this._options.skinPath + WYMeditor.SKINS_DEFAULT_CSS);
  970. dialogHtml = h.replaceAll(dialogHtml, WYMeditor.WYM_PATH, this._options.wymPath);
  971. dialogHtml = h.replaceAll(dialogHtml, WYMeditor.JQUERY_PATH, this._options.jQueryPath);
  972. dialogHtml = h.replaceAll(dialogHtml, WYMeditor.DIALOG_TITLE, this.encloseString(sType));
  973. dialogHtml = h.replaceAll(dialogHtml, WYMeditor.DIALOG_BODY, sBodyHtml);
  974. dialogHtml = h.replaceAll(dialogHtml, WYMeditor.INDEX, this._index);
  975. dialogHtml = this.replaceStrings(dialogHtml);
  976. var doc = wDialog.document;
  977. doc.write(dialogHtml);
  978. doc.close();
  979. }
  980. };
  981. /* @name toggleHtml
  982. * @description Shows/Hides the HTML
  983. */
  984. WYMeditor.editor.prototype.toggleHtml = function() {
  985. jQuery(this._box).find(this._options.htmlSelector).toggle();
  986. };
  987. WYMeditor.editor.prototype.uniqueStamp = function() {
  988. var now = new Date();
  989. return("wym-" + now.getTime());
  990. };
  991. WYMeditor.editor.prototype.paste = function(sData) {
  992. var sTmp;
  993. var container = this.selected();
  994. //split the data, using double newlines as the separator
  995. var aP = sData.split(this._newLine + this._newLine);
  996. var rExp = new RegExp(this._newLine, "g");
  997. //add a P for each item
  998. if(container && container.tagName.toLowerCase() != WYMeditor.BODY) {
  999. for(x = aP.length - 1; x >= 0; x--) {
  1000. sTmp = aP[x];
  1001. //simple newlines are replaced by a break
  1002. sTmp = sTmp.replace(rExp, "<br />");
  1003. jQuery(container).after("<p>" + sTmp + "</p>");
  1004. }
  1005. } else {
  1006. for(x = 0; x < aP.length; x++) {
  1007. sTmp = aP[x];
  1008. //simple newlines are replaced by a break
  1009. sTmp = sTmp.replace(rExp, "<br />");
  1010. jQuery(this._doc.body).append("<p>" + sTmp + "</p>");
  1011. }
  1012. }
  1013. };
  1014. WYMeditor.editor.prototype.insert = function(html) {
  1015. // Do we have a selection?
  1016. if (this._iframe.contentWindow.getSelection().focusNode != null) {
  1017. // Overwrite selection with provided html
  1018. this._exec( WYMeditor.INSERT_HTML, html);
  1019. } else {
  1020. // Fall back to the internal paste function if there's no selection
  1021. this.paste(html)
  1022. }
  1023. };
  1024. WYMeditor.editor.prototype.addCssRules = function(doc, aCss) {
  1025. var styles = doc.styleSheets[0];
  1026. if(styles) {
  1027. for(var i = 0; i < aCss.length; i++) {
  1028. var oCss = aCss[i];
  1029. if(oCss.name && oCss.css) this.addCssRule(styles, oCss);
  1030. }
  1031. }
  1032. };
  1033. /********** CONFIGURATION **********/
  1034. WYMeditor.editor.prototype.computeBasePath = function() {
  1035. return jQuery(jQuery.grep(jQuery('script'), function(s){
  1036. return (s.src && s.src.match(/jquery\.wymeditor(\.pack){0,1}\.js(\?.*)?$/ ))
  1037. })).attr('src').replace(/jquery\.wymeditor(\.pack){0,1}\.js(\?.*)?$/, '');
  1038. };
  1039. WYMeditor.editor.prototype.computeWymPath = function() {
  1040. return jQuery(jQuery.grep(jQuery('script'), function(s){
  1041. return (s.src && s.src.match(/jquery\.wymeditor(\.pack){0,1}\.js(\?.*)?$/ ))
  1042. })).attr('src');
  1043. };
  1044. WYMeditor.editor.prototype.computeJqueryPath = function() {
  1045. return jQuery(jQuery.grep(jQuery('script'), function(s){
  1046. return (s.src && s.src.match(/jquery(-(.*)){0,1}(\.pack){0,1}\.js(\?.*)?$/ ))
  1047. })).attr('src');
  1048. };
  1049. WYMeditor.editor.prototype.computeCssPath = function() {
  1050. return jQuery(jQuery.grep(jQuery('link'), function(s){
  1051. return (s.href && s.href.match(/wymeditor\/skins\/(.*)screen\.css(\?.*)?$/ ))
  1052. })).attr('href');
  1053. };
  1054. WYMeditor.editor.prototype.configureEditorUsingRawCss = function() {
  1055. var CssParser = new WYMeditor.WymCssParser();
  1056. if(this._options.stylesheet){
  1057. CssParser.parse(jQuery.ajax({url: this._options.stylesheet,async:false}).responseText);
  1058. }else{
  1059. CssParser.parse(this._options.styles, false);
  1060. }
  1061. if(this._options.classesItems.length == 0) {
  1062. this._options.classesItems = CssParser.css_settings.classesItems;
  1063. }
  1064. if(this._options.editorStyles.length == 0) {
  1065. this._options.editorStyles = CssParser.css_settings.editorStyles;
  1066. }
  1067. if(this._options.dialogStyles.length == 0) {
  1068. this._options.dialogStyles = CssParser.css_settings.dialogStyles;
  1069. }
  1070. };
  1071. /********** EVENTS **********/
  1072. WYMeditor.editor.prototype.listen = function() {
  1073. //don't use jQuery.find() on the iframe body
  1074. //because of MSIE + jQuery + expando issue (#JQ1143)
  1075. //jQuery(this._doc.body).find("*").bind("mouseup", this.mouseup);
  1076. jQuery(this._doc.body).bind("mousedown", this.mousedown);
  1077. var images = this._doc.body.getElementsByTagName("img");
  1078. for(var i=0; i < images.length; i++) {
  1079. jQuery(images[i]).bind("mousedown", this.mousedown);
  1080. }
  1081. };
  1082. WYMeditor.editor.prototype.mousedown = function(evt) {
  1083. var wym = WYMeditor.INSTANCES[this.ownerDocument.title];
  1084. wym._selected_image = (this.tagName.toLowerCase() == WYMeditor.IMG) ? this : null;
  1085. evt.stopPropagation();
  1086. };
  1087. /********** SKINS **********/
  1088. /*
  1089. * Function: WYMeditor.loadCss
  1090. * Loads a stylesheet in the document.
  1091. *
  1092. * Parameters:
  1093. * href - The CSS path.
  1094. */
  1095. WYMeditor.loadCss = function(href) {
  1096. var link = document.createElement('link');
  1097. link.rel = 'stylesheet';
  1098. link.href = href;
  1099. var head = jQuery('head').get(0);
  1100. head.appendChild(link);
  1101. };
  1102. /*
  1103. * Function: WYMeditor.editor.loadSkin
  1104. * Loads the skin CSS and initialization script (if needed).
  1105. */
  1106. WYMeditor.editor.prototype.loadSkin = function() {
  1107. //does the user want to automatically load the CSS (default: yes)?
  1108. //we also test if it hasn't been already loaded by another instance
  1109. //see below for a better (second) test
  1110. if(this._options.loadSkin && !WYMeditor.SKINS[this._options.skin]) {
  1111. //check if it hasn't been already loaded
  1112. //so we don't load it more than once
  1113. //(we check the existing <link> elements)
  1114. var found = false;
  1115. var rExp = new RegExp(this._options.skin
  1116. + '\/' + WYMeditor.SKINS_DEFAULT_CSS + '$');
  1117. jQuery('link').each( function() {
  1118. if(this.href.match(rExp)) found = true;
  1119. });
  1120. //load it, using the skin path
  1121. if(!found) WYMeditor.loadCss( this._options.skinPath
  1122. + WYMeditor.SKINS_DEFAULT_CSS );
  1123. }
  1124. //put the classname (ex. wym_skin_default) on wym_box
  1125. jQuery(this._box).addClass( "wym_skin_" + this._options.skin );
  1126. //does the user want to use some JS to initialize the skin (default: yes)?
  1127. //also check if it hasn't already been loaded by another instance
  1128. if(this._options.initSkin && !WYMeditor.SKINS[this._options.skin]) {
  1129. eval(jQuery.ajax({url:this._options.skinPath
  1130. + WYMeditor.SKINS_DEFAULT_JS, async:false}).responseText);
  1131. }
  1132. //init the skin, if needed
  1133. if(WYMeditor.SKINS[this._options.skin]
  1134. && WYMeditor.SKINS[this._options.skin].init)
  1135. WYMeditor.SKINS[this._options.skin].init(this);
  1136. };
  1137. /********** DIALOGS **********/
  1138. WYMeditor.INIT_DIALOG = function(index) {
  1139. var wym = window.opener.WYMeditor.INSTANCES[index];
  1140. var doc = window.document;
  1141. var selected = wym.selected();
  1142. var dialogType = jQuery(wym._options.dialogTypeSelector).val();
  1143. var sStamp = wym.uniqueStamp();
  1144. switch(dialogType) {
  1145. case WYMeditor.DIALOG_LINK:
  1146. //ensure that we select the link to populate the fields
  1147. if(selected && selected.tagName && selected.tagName.toLowerCase != WYMeditor.A)
  1148. selected = jQuery(selected).parentsOrSelf(WYMeditor.A);
  1149. //fix MSIE selection if link image has been clicked
  1150. if(!selected && wym._selected_image)
  1151. selected = jQuery(wym._selected_image).parentsOrSelf(WYMeditor.A);
  1152. break;
  1153. }
  1154. //pre-init functions
  1155. if(jQuery.isFunction(wym._options.preInitDialog))
  1156. wym._options.preInitDialog(wym,window);
  1157. //add css rules from options
  1158. var styles = doc.styleSheets[0];
  1159. var aCss = eval(wym._options.dialogStyles);
  1160. wym.addCssRules(doc, aCss);
  1161. //auto populate fields if selected container (e.g. A)
  1162. if(selected) {
  1163. jQuery(wym._options.hrefSelector).val(jQuery(selected).attr(WYMeditor.HREF));
  1164. jQuery(wym._options.srcSelector).val(jQuery(selected).attr(WYMeditor.SRC));
  1165. jQuery(wym._options.titleSelector).val(jQuery(selected).attr(WYMeditor.TITLE));
  1166. jQuery(wym._options.altSelector).val(jQuery(selected).attr(WYMeditor.ALT));
  1167. }
  1168. //auto populate image fields if selected image
  1169. if(wym._selected_image) {
  1170. jQuery(wym._options.dialogImageSelector + " " + wym._options.srcSelector)
  1171. .val(jQuery(wym._selected_image).attr(WYMeditor.SRC));
  1172. jQuery(wym._options.dialogImageSelector + " " + wym._options.titleSelector)
  1173. .val(jQuery(wym._selected_image).attr(WYMeditor.TITLE));
  1174. jQuery(wym._options.dialogImageSelector + " " + wym._options.altSelector)
  1175. .val(jQuery(wym._selected_image).attr(WYMeditor.ALT));
  1176. }
  1177. jQuery(wym._options.dialogLinkSelector + " "
  1178. + wym._options.submitSelector).click(function() {
  1179. var sUrl = jQuery(wym._options.hrefSelector).val();
  1180. if(sUrl.length > 0) {
  1181. wym._exec(WYMeditor.CREATE_LINK, sStamp);
  1182. jQuery("a[@href=" + sStamp + "]", wym._doc.body)
  1183. .attr(WYMeditor.HREF, sUrl)
  1184. .attr(WYMeditor.TITLE, jQuery(wym._options.titleSelector).val());
  1185. }
  1186. window.close();
  1187. });
  1188. jQuery(wym._options.dialogImageSelector + " "
  1189. + wym._options.submitSelector).click(function() {
  1190. var sUrl = jQuery(wym._options.srcSelector).val();
  1191. if(sUrl.length > 0) {
  1192. wym._exec(WYMeditor.INSERT_IMAGE, sStamp);
  1193. jQuery("img[@src=" + sStamp + "]", wym._doc.body)
  1194. .attr(WYMeditor.SRC, sUrl)
  1195. .attr(WYMeditor.TITLE, jQuery(wym._options.titleSelector).val())
  1196. .attr(WYMeditor.ALT, jQuery(wym._options.altSelector).val());
  1197. }
  1198. window.close();
  1199. });
  1200. jQuery(wym._options.dialogTableSelector + " "
  1201. + wym._options.submitSelector).click(function() {
  1202. var iRows = jQuery(wym._options.rowsSelector).val();
  1203. var iCols = jQuery(wym._options.colsSelector).val();
  1204. if(iRows > 0 && iCols > 0) {
  1205. var table = wym._doc.createElement(WYMeditor.TABLE);
  1206. var newRow = null;
  1207. var newCol = null;
  1208. var sCaption = jQuery(wym._options.captionSelector).val();
  1209. //we create the caption
  1210. var newCaption = table.createCaption();
  1211. newCaption.innerHTML = sCaption;
  1212. //we create the rows and cells
  1213. for(x=0; x<iRows; x++) {
  1214. newRow = table.insertRow(x);
  1215. for(y=0; y<iCols; y++) {newRow.insertCell(y);}
  1216. }
  1217. //set the summary attr
  1218. jQuery(table).attr('summary',
  1219. jQuery(wym._options.summarySelector).val());
  1220. //append the table after the selected container
  1221. var node = jQuery(wym.findUp(wym.container(),
  1222. WYMeditor.MAIN_CONTAINERS)).get(0);
  1223. if(!node || !node.parentNode) jQuery(wym._doc.body).append(table);
  1224. else jQuery(node).after(table);
  1225. }
  1226. window.close();
  1227. });
  1228. jQuery(wym._options.dialogPasteSelector + " "
  1229. + wym._options.submitSelector).click(function() {
  1230. var sText = jQuery(wym._options.textSelector).val();
  1231. wym.paste(sText);
  1232. window.close();
  1233. });
  1234. jQuery(wym._options.dialogPreviewSelector + " "
  1235. + wym._options.previewSelector)
  1236. .html(wym.xhtml());
  1237. //cancel button
  1238. jQuery(wym._options.cancelSelector).mousedown(function() {
  1239. window.close();
  1240. });
  1241. //pre-init functions
  1242. if(jQuery.isFunction(wym._options.postInitDialog))
  1243. wym._options.postInitDialog(wym,window);
  1244. };
  1245. /********** XHTML LEXER/PARSER **********/
  1246. /*
  1247. * @name xml
  1248. * @description Use these methods to generate XML and XHTML compliant tags and
  1249. * escape tag attributes correctly
  1250. * @author Bermi Ferrer - http://bermi.org
  1251. * @author David Heinemeier Hansson http://loudthinking.com
  1252. */
  1253. WYMeditor.XmlHelper = function()
  1254. {
  1255. this._entitiesDiv = document.createElement('div');
  1256. return this;
  1257. };
  1258. /*
  1259. * @name tag
  1260. * @description
  1261. * Returns an empty HTML tag of type *name* which by default is XHTML
  1262. * compliant. Setting *open* to true will create an open tag compatible
  1263. * with HTML 4.0 and below. Add HTML attributes by passing an attributes
  1264. * array to *options*. For attributes with no value like (disabled and
  1265. * readonly), give it a value of true in the *options* array.
  1266. *
  1267. * Examples:
  1268. *
  1269. * this.tag('br')
  1270. * # => <br />
  1271. * this.tag ('br', false, true)
  1272. * # => <br>
  1273. * this.tag ('input', jQuery({type:'text',disabled:true }) )
  1274. * # => <input type="text" disabled="disabled" />
  1275. */
  1276. WYMeditor.XmlHelper.prototype.tag = function(name, options, open)
  1277. {
  1278. options = options || false;
  1279. open = open || false;
  1280. return '<'+name+(options ? this.tagOptions(options) : '')+(open ? '>' : ' />');
  1281. };
  1282. /*
  1283. * @name contentTag
  1284. * @description
  1285. * Returns a XML block tag of type *name* surrounding the *content*. Add
  1286. * XML attributes by passing an attributes array to *options*. For attributes
  1287. * with no value like (disabled and readonly), give it a value of true in
  1288. * the *options* array. You can use symbols or strings for the attribute names.
  1289. *
  1290. * this.contentTag ('p', 'Hello world!' )
  1291. * # => <p>Hello world!</p>
  1292. * this.contentTag('div', this.contentTag('p', "Hello world!"), jQuery({class : "strong"}))
  1293. * # => <div class="strong"><p>Hello world!</p></div>
  1294. * this.contentTag("select", options, jQuery({multiple : true}))
  1295. * # => <select multiple="multiple">...options...</select>
  1296. */
  1297. WYMeditor.XmlHelper.prototype.contentTag = function(name, content, options)
  1298. {
  1299. options = options || false;
  1300. return '<'+name+(options ? this.tagOptions(options) : '')+'>'+content+'</'+name+'>';
  1301. };
  1302. /*
  1303. * @name cdataSection
  1304. * @description
  1305. * Returns a CDATA section for the given +content+. CDATA sections
  1306. * are used to escape blocks of text containing characters which would
  1307. * otherwise be recognized as markup. CDATA sections begin with the string
  1308. * <tt>&lt;![CDATA[</tt> and } with (and may not contain) the string
  1309. * <tt>]]></tt>.
  1310. */
  1311. WYMeditor.XmlHelper.prototype.cdataSection = function(content)
  1312. {
  1313. return '<![CDATA['+content+']]>';
  1314. };
  1315. /*
  1316. * @name escapeOnce
  1317. * @description
  1318. * Returns the escaped +xml+ without affecting existing escaped entities.
  1319. *
  1320. * this.escapeOnce( "1 > 2 &amp; 3")
  1321. * # => "1 &gt; 2 &amp; 3"
  1322. */
  1323. WYMeditor.XmlHelper.prototype.escapeOnce = function(xml)
  1324. {
  1325. return this._fixDoubleEscape(this.escapeEntities(xml));
  1326. };
  1327. /*
  1328. * @name _fixDoubleEscape
  1329. * @description
  1330. * Fix double-escaped entities, such as &amp;amp;, &amp;#123;, etc.
  1331. */
  1332. WYMeditor.XmlHelper.prototype._fixDoubleEscape = function(escaped)
  1333. {
  1334. return escaped.replace(/&amp;([a-z]+|(#\d+));/ig, "&$1;");
  1335. };
  1336. /*
  1337. * @name tagOptions
  1338. * @description
  1339. * Takes an array like the one generated by Tag.parseAttributes
  1340. * [["src", "http://www.editam.com/?a=b&c=d&amp;f=g"], ["title", "Editam, <Simplified> CMS"]]
  1341. * or an object like {src:"http://www.editam.com/?a=b&c=d&amp;f=g", title:"Editam, <Simplified> CMS"}
  1342. * and returns a string properly escaped like
  1343. * ' src = "http://www.editam.com/?a=b&amp;c=d&amp;f=g" title = "Editam, &lt;Simplified&gt; CMS"'
  1344. * which is valid for strict XHTML
  1345. */
  1346. WYMeditor.XmlHelper.prototype.tagOptions = function(options)
  1347. {
  1348. var xml = this;
  1349. xml._formated_options = '';
  1350. for (var key in options) {
  1351. var formated_options = '';
  1352. var value = options[key];
  1353. if(typeof value != 'function' && value.length > 0) {
  1354. if(parseInt(key) == key && typeof value == 'object'){
  1355. key = value.shift();
  1356. value = value.pop();
  1357. }
  1358. if(key != '' && value != ''){
  1359. xml._formated_options += ' '+key+'="'+xml.escapeOnce(value)+'"';
  1360. }
  1361. }
  1362. }
  1363. return xml._formated_options;
  1364. };
  1365. /*
  1366. * @name escapeEntities
  1367. * @description
  1368. * Escapes XML/HTML entities <, >, & and ". If seccond parameter is set to false it
  1369. * will not escape ". If set to true it will also escape '
  1370. */
  1371. WYMeditor.XmlHelper.prototype.escapeEntities = function(string, escape_quotes)
  1372. {
  1373. this._entitiesDiv.innerHTML = string;
  1374. this._entitiesDiv.textContent = string;
  1375. var result = this._entitiesDiv.innerHTML;
  1376. if(typeof escape_quotes == 'undefined'){
  1377. if(escape_quotes != false) result = result.replace('"', '&quot;');
  1378. if(escape_quotes == true) result = result.replace('"', '&#039;');
  1379. }
  1380. return result;
  1381. };
  1382. /*
  1383. * Parses a string conatining tag attributes and values an returns an array formated like
  1384. * [["src", "http://www.editam.com"], ["title", "Editam, Simplified CMS"]]
  1385. */
  1386. WYMeditor.XmlHelper.prototype.parseAttributes = function(tag_attributes)
  1387. {
  1388. // Use a compounded regex to match single quoted, double quoted and unquoted attribute pairs
  1389. var result = [];
  1390. var matches = tag_attributes.split(/((=\s*")(")("))|((=\s*\')(\')(\'))|((=\s*[^>\s]*))/g);
  1391. if(matches.toString() != tag_attributes){
  1392. for (var k in matches) {
  1393. var v = matches[k];
  1394. if(typeof v != 'function' && v.length != 0){
  1395. var re = new RegExp('(\\w+)\\s*'+v);
  1396. if(match = tag_attributes.match(re) ){
  1397. var value = v.replace(/^[\s=]+/, "");
  1398. var delimiter = value.charAt(0);
  1399. delimiter = delimiter == '"' ? '"' : (delimiter=="'"?"'":'');
  1400. if(delimiter != ''){
  1401. value = delimiter == '"' ? value.replace(/^"|"+$/g, '') : value.replace(/^'|'+$/g, '');
  1402. }
  1403. tag_attributes = tag_attributes.replace(match[0],'');
  1404. result.push([match[1] , value]);
  1405. }
  1406. }
  1407. }
  1408. }
  1409. return result;
  1410. };
  1411. /**
  1412. * XhtmlValidator for validating tag attributes
  1413. *
  1414. * @author Bermi Ferrer - http://bermi.org
  1415. */
  1416. WYMeditor.XhtmlValidator = {
  1417. "_attributes":
  1418. {
  1419. "core":
  1420. {
  1421. "except":[
  1422. "base",
  1423. "head",
  1424. "html",
  1425. "meta",
  1426. "param",
  1427. "script",
  1428. "style",
  1429. "title"
  1430. ],
  1431. "attributes":[
  1432. "class",
  1433. "id",
  1434. "style",
  1435. "title",
  1436. "accesskey",
  1437. "tabindex"
  1438. ]
  1439. },
  1440. "language":
  1441. {
  1442. "except":[
  1443. "base",
  1444. "br",
  1445. "hr",
  1446. "iframe",
  1447. "param",
  1448. "script"
  1449. ],
  1450. "attributes":
  1451. {
  1452. "dir":[
  1453. "ltr",
  1454. "rtl"
  1455. ],
  1456. "0":"lang",
  1457. "1":"xml:lang"
  1458. }
  1459. },
  1460. "keyboard":
  1461. {
  1462. "attributes":
  1463. {
  1464. "accesskey":/^(\w){1}$/,
  1465. "tabindex":/^(\d)+$/
  1466. }
  1467. }
  1468. },
  1469. "_events":
  1470. {
  1471. "window":
  1472. {
  1473. "only":[
  1474. "body"
  1475. ],
  1476. "attributes":[
  1477. "onload",
  1478. "onunload"
  1479. ]
  1480. },
  1481. "form":
  1482. {
  1483. "only":[
  1484. "form",
  1485. "input",
  1486. "textarea",
  1487. "select",
  1488. "a",
  1489. "label",
  1490. "button"
  1491. ],
  1492. "attributes":[
  1493. "onchange",
  1494. "onsubmit",
  1495. "onreset",
  1496. "onselect",
  1497. "onblur",
  1498. "onfocus"
  1499. ]
  1500. },
  1501. "keyboard":
  1502. {
  1503. "except":[
  1504. "base",
  1505. "bdo",
  1506. "br",
  1507. "frame",
  1508. "frameset",
  1509. "head",
  1510. "html",
  1511. "iframe",
  1512. "meta",
  1513. "param",
  1514. "script",
  1515. "style",
  1516. "title"
  1517. ],
  1518. "attributes":[
  1519. "onkeydown",
  1520. "onkeypress",
  1521. "onkeyup"
  1522. ]
  1523. },
  1524. "mouse":
  1525. {
  1526. "except":[
  1527. "base",
  1528. "bdo",
  1529. "br",
  1530. "head",
  1531. "html",
  1532. "meta",
  1533. "param",
  1534. "script",
  1535. "style",
  1536. "title"
  1537. ],
  1538. "attributes":[
  1539. "onclick",
  1540. "ondblclick",
  1541. "onmousedown",
  1542. "onmousemove",
  1543. "onmouseover",
  1544. "onmouseout",
  1545. "onmouseup"
  1546. ]
  1547. }
  1548. },
  1549. "_tags":
  1550. {
  1551. "a":
  1552. {
  1553. "attributes":
  1554. {
  1555. "0":"charset",
  1556. "1":"coords",
  1557. "2":"href",
  1558. "3":"hreflang",
  1559. "4":"name",
  1560. "rel":/^(alternate|designates|stylesheet|start|next|prev|contents|index|glossary|copyright|chapter|section|subsection|appendix|help|bookmark| |shortcut|icon)+$/,
  1561. "rev":/^(alternate|designates|stylesheet|start|next|prev|contents|index|glossary|copyright|chapter|section|subsection|appendix|help|bookmark| |shortcut|icon)+$/,
  1562. "shape":/^(rect|rectangle|circ|circle|poly|polygon)$/,
  1563. "5":"type"
  1564. }
  1565. },
  1566. "0":"abbr",
  1567. "1":"acronym",
  1568. "2":"address",
  1569. "area":
  1570. {
  1571. "attributes":
  1572. {
  1573. "0":"alt",
  1574. "1":"coords",
  1575. "2":"href",
  1576. "nohref":/^(true|false)$/,
  1577. "shape":/^(rect|rectangle|circ|circle|poly|polygon)$/
  1578. },
  1579. "required":[
  1580. "alt"
  1581. ]
  1582. },
  1583. "3":"b",
  1584. "base":
  1585. {
  1586. "attributes":[
  1587. "href"
  1588. ],
  1589. "required":[
  1590. "href"
  1591. ]
  1592. },
  1593. "bdo":
  1594. {
  1595. "attributes":
  1596. {
  1597. "dir":/^(ltr|rtl)$/
  1598. },
  1599. "required":[
  1600. "dir"
  1601. ]
  1602. },
  1603. "4":"big",
  1604. "blockquote":
  1605. {
  1606. "attributes":[
  1607. "cite"
  1608. ]
  1609. },
  1610. "5":"body",
  1611. "6":"br",
  1612. "button":
  1613. {
  1614. "attributes":
  1615. {
  1616. "disabled":/^(disabled)$/,
  1617. "type":/^(button|reset|submit)$/,
  1618. "0":"value"
  1619. },
  1620. "inside":"form"
  1621. },
  1622. "7":"caption",
  1623. "8":"cite",
  1624. "9":"code",
  1625. "col":
  1626. {
  1627. "attributes":
  1628. {
  1629. "align":/^(right|left|center|justify)$/,
  1630. "0":"char",
  1631. "1":"charoff",
  1632. "span":/^(\d)+$/,
  1633. "valign":/^(top|middle|bottom|baseline)$/,
  1634. "2":"width"
  1635. },
  1636. "inside":"colgroup"
  1637. },
  1638. "colgroup":
  1639. {
  1640. "attributes":
  1641. {
  1642. "align":/^(right|left|center|justify)$/,
  1643. "0":"char",
  1644. "1":"charoff",
  1645. "span":/^(\d)+$/,
  1646. "valign":/^(top|middle|bottom|baseline)$/,
  1647. "2":"width"
  1648. }
  1649. },
  1650. "10":"dd",
  1651. "del":
  1652. {
  1653. "attributes":
  1654. {
  1655. "0":"cite",
  1656. "datetime":/^([0-9]){8}/
  1657. }
  1658. },
  1659. "11":"div",
  1660. "12":"dfn",
  1661. "13":"dl",
  1662. "14":"dt",
  1663. "15":"em",
  1664. "fieldset":
  1665. {
  1666. "inside":"form"
  1667. },
  1668. "form":
  1669. {
  1670. "attributes":
  1671. {
  1672. "0":"action",
  1673. "1":"accept",
  1674. "2":"accept-charset",
  1675. "3":"enctype",
  1676. "method":/^(get|post)$/
  1677. },
  1678. "required":[
  1679. "action"
  1680. ]
  1681. },
  1682. "head":
  1683. {
  1684. "attributes":[
  1685. "profile"
  1686. ]
  1687. },
  1688. "16":"h1",
  1689. "17":"h2",
  1690. "18":"h3",
  1691. "19":"h4",
  1692. "20":"h5",
  1693. "21":"h6",
  1694. "22":"hr",
  1695. "html":
  1696. {
  1697. "attributes":[
  1698. "xmlns"
  1699. ]
  1700. },
  1701. "23":"i",
  1702. "img":
  1703. {
  1704. "attributes":[
  1705. "alt",
  1706. "src",
  1707. "height",
  1708. "ismap",
  1709. "longdesc",
  1710. "usemap",
  1711. "width"
  1712. ],
  1713. "required":[
  1714. "alt",
  1715. "src"
  1716. ]
  1717. },
  1718. "input":
  1719. {
  1720. "attributes":
  1721. {
  1722. "0":"accept",
  1723. "1":"alt",
  1724. "checked":/^(checked)$/,
  1725. "disabled":/^(disabled)$/,
  1726. "maxlength":/^(\d)+$/,
  1727. "2":"name",
  1728. "readonly":/^(readonly)$/,
  1729. "size":/^(\d)+$/,
  1730. "3":"src",
  1731. "type":/^(button|checkbox|file|hidden|image|password|radio|reset|submit|text)$/,
  1732. "4":"value"
  1733. },
  1734. "inside":"form"
  1735. },
  1736. "ins":
  1737. {
  1738. "attributes":
  1739. {
  1740. "0":"cite",
  1741. "datetime":/^([0-9]){8}/
  1742. }
  1743. },
  1744. "24":"kbd",
  1745. "label":
  1746. {
  1747. "attributes":[
  1748. "for"
  1749. ],
  1750. "inside":"form"
  1751. },
  1752. "25":"legend",
  1753. "26":"li",
  1754. "link":
  1755. {
  1756. "attributes":
  1757. {
  1758. "0":"charset",
  1759. "1":"href",
  1760. "2":"hreflang",
  1761. "media":/^(all|braille|print|projection|screen|speech|,|;| )+$/i,
  1762. //next comment line required by Opera!
  1763. /*"rel":/^(alternate|appendix|bookmark|chapter|contents|copyright|glossary|help|home|index|next|prev|section|start|stylesheet|subsection| |shortcut|icon)+$/i,*/
  1764. "rel":/^(alternate|appendix|bookmark|chapter|contents|copyright|glossary|help|home|index|next|prev|section|start|stylesheet|subsection| |shortcut|icon)+$/i,
  1765. "rev":/^(alternate|appendix|bookmark|chapter|contents|copyright|glossary|help|home|index|next|prev|section|start|stylesheet|subsection| |shortcut|icon)+$/i,
  1766. "3":"type"
  1767. },
  1768. "inside":"head"
  1769. },
  1770. "map":
  1771. {
  1772. "attributes":[
  1773. "id",
  1774. "name"
  1775. ],
  1776. "required":[
  1777. "id"
  1778. ]
  1779. },
  1780. "meta":
  1781. {
  1782. "attributes":
  1783. {
  1784. "0":"content",
  1785. "http-equiv":/^(content\-type|expires|refresh|set\-cookie)$/i,
  1786. "1":"name",
  1787. "2":"scheme"
  1788. },
  1789. "required":[
  1790. "content"
  1791. ]
  1792. },
  1793. "27":"noscript",
  1794. "object":
  1795. {
  1796. "attributes":[
  1797. "archive",
  1798. "classid",
  1799. "codebase",
  1800. "codetype",
  1801. "data",
  1802. "declare",
  1803. "height",
  1804. "name",
  1805. "standby",
  1806. "type",
  1807. "usemap",
  1808. "width"
  1809. ]
  1810. },
  1811. "28":"ol",
  1812. "optgroup":
  1813. {
  1814. "attributes":
  1815. {
  1816. "0":"label",
  1817. "disabled": /^(disabled)$/
  1818. },
  1819. "required":[
  1820. "label"
  1821. ]
  1822. },
  1823. "option":
  1824. {
  1825. "attributes":
  1826. {
  1827. "0":"label",
  1828. "disabled":/^(disabled)$/,
  1829. "selected":/^(selected)$/,
  1830. "1":"value"
  1831. },
  1832. "inside":"select"
  1833. },
  1834. "29":"p",
  1835. "param":
  1836. {
  1837. "attributes":
  1838. {
  1839. "0":"type",
  1840. "valuetype":/^(data|ref|object)$/,
  1841. "1":"valuetype",
  1842. "2":"value"
  1843. },
  1844. "required":[
  1845. "name"
  1846. ]
  1847. },
  1848. "30":"pre",
  1849. "q":
  1850. {
  1851. "attributes":[
  1852. "cite"
  1853. ]
  1854. },
  1855. "31":"samp",
  1856. "script":
  1857. {
  1858. "attributes":
  1859. {
  1860. "type":/^(text\/ecmascript|text\/javascript|text\/jscript|text\/vbscript|text\/vbs|text\/xml)$/,
  1861. "0":"charset",
  1862. "defer":/^(defer)$/,
  1863. "1":"src"
  1864. },
  1865. "required":[
  1866. "type"
  1867. ]
  1868. },
  1869. "select":
  1870. {
  1871. "attributes":
  1872. {
  1873. "disabled":/^(disabled)$/,
  1874. "multiple":/^(multiple)$/,
  1875. "0":"name",
  1876. "1":"size"
  1877. },
  1878. "inside":"form"
  1879. },
  1880. "32":"small",
  1881. "33":"span",
  1882. "34":"strong",
  1883. "style":
  1884. {
  1885. "attributes":
  1886. {
  1887. "0":"type",
  1888. "media":/^(screen|tty|tv|projection|handheld|print|braille|aural|all)$/
  1889. },
  1890. "required":[
  1891. "type"
  1892. ]
  1893. },
  1894. "35":"sub",
  1895. "36":"sup",
  1896. "table":
  1897. {
  1898. "attributes":
  1899. {
  1900. "0":"border",
  1901. "1":"cellpadding",
  1902. "2":"cellspacing",
  1903. "frame":/^(void|above|below|hsides|lhs|rhs|vsides|box|border)$/,
  1904. "rules":/^(none|groups|rows|cols|all)$/,
  1905. "3":"summary",
  1906. "4":"width"
  1907. }
  1908. },
  1909. "tbody":
  1910. {
  1911. "attributes":
  1912. {
  1913. "align":/^(right|left|center|justify)$/,
  1914. "0":"char",
  1915. "1":"charoff",
  1916. "valign":/^(top|middle|bottom|baseline)$/
  1917. }
  1918. },
  1919. "td":
  1920. {
  1921. "attributes":
  1922. {
  1923. "0":"abbr",
  1924. "align":/^(left|right|center|justify|char)$/,
  1925. "1":"axis",
  1926. "2":"char",
  1927. "3":"charoff",
  1928. "colspan":/^(\d)+$/,
  1929. "4":"headers",
  1930. "rowspan":/^(\d)+$/,
  1931. "scope":/^(col|colgroup|row|rowgroup)$/,
  1932. "valign":/^(top|middle|bottom|baseline)$/
  1933. }
  1934. },
  1935. "textarea":
  1936. {
  1937. "attributes":[
  1938. "cols",
  1939. "rows",
  1940. "disabled",
  1941. "name",
  1942. "readonly"
  1943. ],
  1944. "required":[
  1945. "cols",
  1946. "rows"
  1947. ],
  1948. "inside":"form"
  1949. },
  1950. "tfoot":
  1951. {
  1952. "attributes":
  1953. {
  1954. "align":/^(right|left|center|justify)$/,
  1955. "0":"char",
  1956. "1":"charoff",
  1957. "valign":/^(top|middle|bottom)$/,
  1958. "2":"baseline"
  1959. }
  1960. },
  1961. "th":
  1962. {
  1963. "attributes":
  1964. {
  1965. "0":"abbr",
  1966. "align":/^(left|right|center|justify|char)$/,
  1967. "1":"axis",
  1968. "2":"char",
  1969. "3":"charoff",
  1970. "colspan":/^(\d)+$/,
  1971. "4":"headers",
  1972. "rowspan":/^(\d)+$/,
  1973. "scope":/^(col|colgroup|row|rowgroup)$/,
  1974. "valign":/^(top|middle|bottom|baseline)$/
  1975. }
  1976. },
  1977. "thead":
  1978. {
  1979. "attributes":
  1980. {
  1981. "align":/^(right|left|center|justify)$/,
  1982. "0":"char",
  1983. "1":"charoff",
  1984. "valign":/^(top|middle|bottom|baseline)$/
  1985. }
  1986. },
  1987. "37":"title",
  1988. "tr":
  1989. {
  1990. "attributes":
  1991. {
  1992. "align":/^(right|left|center|justify|char)$/,
  1993. "0":"char",
  1994. "1":"charoff",
  1995. "valign":/^(top|middle|bottom|baseline)$/
  1996. }
  1997. },
  1998. "38":"tt",
  1999. "39":"ul",
  2000. "40":"var"
  2001. },
  2002. // Temporary skiped attributes
  2003. skiped_attributes : [],
  2004. skiped_attribute_values : [],
  2005. getValidTagAttributes: function(tag, attributes)
  2006. {
  2007. var valid_attributes = {};
  2008. var possible_attributes = this.getPossibleTagAttributes(tag);
  2009. for(var attribute in attributes) {
  2010. var value = attributes[attribute];
  2011. var h = WYMeditor.Helper;
  2012. if(!h.contains(this.skiped_attributes, attribute) && !h.contains(this.skiped_attribute_values, value)){
  2013. if (typeof value != 'function' && h.contains(possible_attributes, attribute)) {
  2014. if (this.doesAttributeNeedsValidation(tag, attribute)) {
  2015. if(this.validateAttribute(tag, attribute, value)){
  2016. valid_attributes[attribute] = value;
  2017. }
  2018. }else{
  2019. valid_attributes[attribute] = value;
  2020. }
  2021. }
  2022. }
  2023. }
  2024. return valid_attributes;
  2025. },
  2026. getUniqueAttributesAndEventsForTag : function(tag)
  2027. {
  2028. var result = [];
  2029. if (this._tags[tag] && this._tags[tag]['attributes']) {
  2030. for (k in this._tags[tag]['attributes']) {
  2031. result.push(parseInt(k) == k ? this._tags[tag]['attributes'][k] : k);
  2032. }
  2033. }
  2034. return result;
  2035. },
  2036. getDefaultAttributesAndEventsForTags : function()
  2037. {
  2038. var result = [];
  2039. for (var key in this._events){
  2040. result.push(this._events[key]);
  2041. }
  2042. for (var key in this._attributes){
  2043. result.push(this._attributes[key]);
  2044. }
  2045. return result;
  2046. },
  2047. isValidTag : function(tag)
  2048. {
  2049. if(this._tags[tag]){
  2050. return true;
  2051. }
  2052. for(var key in this._tags){
  2053. if(this._tags[key] == tag){
  2054. return true;
  2055. }
  2056. }
  2057. return false;
  2058. },
  2059. getDefaultAttributesAndEventsForTag : function(tag)
  2060. {
  2061. var default_attributes = [];
  2062. if (this.isValidTag(tag)) {
  2063. var default_attributes_and_events = this.getDefaultAttributesAndEventsForTags();
  2064. for(var key in default_attributes_and_events) {
  2065. var defaults = default_attributes_and_events[key];
  2066. if(typeof defaults == 'object'){
  2067. var h = WYMeditor.Helper;
  2068. if ((defaults['except'] && h.contains(defaults['except'], tag)) || (defaults['only'] && !h.contains(defaults['only'], tag))) {
  2069. continue;
  2070. }
  2071. var tag_defaults = defaults['attributes'] ? defaults['attributes'] : defaults['events'];
  2072. for(var k in tag_defaults) {
  2073. default_attributes.push(typeof tag_defaults[k] != 'string' ? k : tag_defaults[k]);
  2074. }
  2075. }
  2076. }
  2077. }
  2078. return default_attributes;
  2079. },
  2080. doesAttributeNeedsValidation: function(tag, attribute)
  2081. {
  2082. return this._tags[tag] && ((this._tags[tag]['attributes'] && this._tags[tag]['attributes'][attribute]) || (this._tags[tag]['required'] &&
  2083. WYMeditor.Helper.contains(this._tags[tag]['required'], attribute)));
  2084. },
  2085. validateAttribute : function(tag, attribute, value)
  2086. {
  2087. if ( this._tags[tag] &&
  2088. (this._tags[tag]['attributes'] && this._tags[tag]['attributes'][attribute] && value.length > 0 && !value.match(this._tags[tag]['attributes'][attribute])) || // invalid format
  2089. (this._tags[tag] && this._tags[tag]['required'] && WYMeditor.Helper.contains(this._tags[tag]['required'], attribute) && value.length == 0) // required attribute
  2090. ) {
  2091. return false;
  2092. }
  2093. return typeof this._tags[tag] != 'undefined';
  2094. },
  2095. getPossibleTagAttributes : function(tag)
  2096. {
  2097. if (!this._possible_tag_attributes) {
  2098. this._possible_tag_attributes = {};
  2099. }
  2100. if (!this._possible_tag_attributes[tag]) {
  2101. this._possible_tag_attributes[tag] = this.getUniqueAttributesAndEventsForTag(tag).concat(this.getDefaultAttributesAndEventsForTag(tag));
  2102. }
  2103. return this._possible_tag_attributes[tag];
  2104. }
  2105. };
  2106. /**
  2107. * Compounded regular expression. Any of
  2108. * the contained patterns could match and
  2109. * when one does, it's label is returned.
  2110. *
  2111. * Constructor. Starts with no patterns.
  2112. * @param boolean case True for case sensitive, false
  2113. * for insensitive.
  2114. * @access public
  2115. * @author Marcus Baker (http://lastcraft.com)
  2116. * @author Bermi Ferrer (http://bermi.org)
  2117. */
  2118. WYMeditor.ParallelRegex = function(case_sensitive)
  2119. {
  2120. this._case = case_sensitive;
  2121. this._patterns = [];
  2122. this._labels = [];
  2123. this._regex = null;
  2124. return this;
  2125. };
  2126. /**
  2127. * Adds a pattern with an optional label.
  2128. * @param string pattern Perl style regex, but ( and )
  2129. * lose the usual meaning.
  2130. * @param string label Label of regex to be returned
  2131. * on a match.
  2132. * @access public
  2133. */
  2134. WYMeditor.ParallelRegex.prototype.addPattern = function(pattern, label)
  2135. {
  2136. label = label || true;
  2137. var count = this._patterns.length;
  2138. this._patterns[count] = pattern;
  2139. this._labels[count] = label;
  2140. this._regex = null;
  2141. };
  2142. /**
  2143. * Attempts to match all patterns at once against
  2144. * a string.
  2145. * @param string subject String to match against.
  2146. *
  2147. * @return boolean True on success.
  2148. * @return string match First matched portion of
  2149. * subject.
  2150. * @access public
  2151. */
  2152. WYMeditor.ParallelRegex.prototype.match = function(subject)
  2153. {
  2154. if (this._patterns.length == 0) {
  2155. return [false, ''];
  2156. }
  2157. var matches = subject.match(this._getCompoundedRegex());
  2158. if(!matches){
  2159. return [false, ''];
  2160. }
  2161. var match = matches[0];
  2162. for (var i = 1; i < matches.length; i++) {
  2163. if (matches[i]) {
  2164. return [this._labels[i-1], match];
  2165. }
  2166. }
  2167. return [true, matches[0]];
  2168. };
  2169. /**
  2170. * Compounds the patterns into a single
  2171. * regular expression separated with the
  2172. * "or" operator. Caches the regex.
  2173. * Will automatically escape (, ) and / tokens.
  2174. * @param array patterns List of patterns in order.
  2175. * @access private
  2176. */
  2177. WYMeditor.ParallelRegex.prototype._getCompoundedRegex = function()
  2178. {
  2179. if (this._regex == null) {
  2180. for (var i = 0, count = this._patterns.length; i < count; i++) {
  2181. this._patterns[i] = '(' + this._untokenizeRegex(this._tokenizeRegex(this._patterns[i]).replace(/([\/\(\)])/g,'\\$1')) + ')';
  2182. }
  2183. this._regex = new RegExp(this._patterns.join("|") ,this._getPerlMatchingFlags());
  2184. }
  2185. return this._regex;
  2186. };
  2187. /**
  2188. * Escape lookahead/lookbehind blocks
  2189. */
  2190. WYMeditor.ParallelRegex.prototype._tokenizeRegex = function(regex)
  2191. {
  2192. return regex.
  2193. replace(/\(\?(i|m|s|x|U)\)/, '~~~~~~Tk1\$1~~~~~~').
  2194. replace(/\(\?(\-[i|m|s|x|U])\)/, '~~~~~~Tk2\$1~~~~~~').
  2195. replace(/\(\?\=(.*)\)/, '~~~~~~Tk3\$1~~~~~~').
  2196. replace(/\(\?\!(.*)\)/, '~~~~~~Tk4\$1~~~~~~').
  2197. replace(/\(\?\<\=(.*)\)/, '~~~~~~Tk5\$1~~~~~~').
  2198. replace(/\(\?\<\!(.*)\)/, '~~~~~~Tk6\$1~~~~~~').
  2199. replace(/\(\?\:(.*)\)/, '~~~~~~Tk7\$1~~~~~~');
  2200. };
  2201. /**
  2202. * Unscape lookahead/lookbehind blocks
  2203. */
  2204. WYMeditor.ParallelRegex.prototype._untokenizeRegex = function(regex)
  2205. {
  2206. return regex.
  2207. replace(/~~~~~~Tk1(.{1})~~~~~~/, "(?\$1)").
  2208. replace(/~~~~~~Tk2(.{2})~~~~~~/, "(?\$1)").
  2209. replace(/~~~~~~Tk3(.*)~~~~~~/, "(?=\$1)").
  2210. replace(/~~~~~~Tk4(.*)~~~~~~/, "(?!\$1)").
  2211. replace(/~~~~~~Tk5(.*)~~~~~~/, "(?<=\$1)").
  2212. replace(/~~~~~~Tk6(.*)~~~~~~/, "(?<!\$1)").
  2213. replace(/~~~~~~Tk7(.*)~~~~~~/, "(?:\$1)");
  2214. };
  2215. /**
  2216. * Accessor for perl regex mode flags to use.
  2217. * @return string Perl regex flags.
  2218. * @access private
  2219. */
  2220. WYMeditor.ParallelRegex.prototype._getPerlMatchingFlags = function()
  2221. {
  2222. return (this._case ? "m" : "mi");
  2223. };
  2224. /**
  2225. * States for a stack machine.
  2226. *
  2227. * Constructor. Starts in named state.
  2228. * @param string start Starting state name.
  2229. * @access public
  2230. * @author Marcus Baker (http://lastcraft.com)
  2231. * @author Bermi Ferrer (http://bermi.org)
  2232. */
  2233. WYMeditor.StateStack = function(start)
  2234. {
  2235. this._stack = [start];
  2236. return this;
  2237. };
  2238. /**
  2239. * Accessor for current state.
  2240. * @return string State.
  2241. * @access public
  2242. */
  2243. WYMeditor.StateStack.prototype.getCurrent = function()
  2244. {
  2245. return this._stack[this._stack.length - 1];
  2246. };
  2247. /**
  2248. * Adds a state to the stack and sets it
  2249. * to be the current state.
  2250. * @param string state New state.
  2251. * @access public
  2252. */
  2253. WYMeditor.StateStack.prototype.enter = function(state)
  2254. {
  2255. this._stack.push(state);
  2256. };
  2257. /**
  2258. * Leaves the current state and reverts
  2259. * to the previous one.
  2260. * @return boolean False if we drop off
  2261. * the bottom of the list.
  2262. * @access public
  2263. */
  2264. WYMeditor.StateStack.prototype.leave = function()
  2265. {
  2266. if (this._stack.length == 1) {
  2267. return false;
  2268. }
  2269. this._stack.pop();
  2270. return true;
  2271. };
  2272. // GLOBALS
  2273. WYMeditor.LEXER_ENTER = 1;
  2274. WYMeditor.LEXER_MATCHED = 2;
  2275. WYMeditor.LEXER_UNMATCHED = 3;
  2276. WYMeditor.LEXER_EXIT = 4;
  2277. WYMeditor.LEXER_SPECIAL = 5;
  2278. /**
  2279. * Accepts text and breaks it into tokens.
  2280. * Some optimisation to make the sure the
  2281. * content is only scanned by the PHP regex
  2282. * parser once. Lexer modes must not start
  2283. * with leading underscores.
  2284. *
  2285. * Sets up the lexer in case insensitive matching
  2286. * by default.
  2287. * @param Parser parser Handling strategy by reference.
  2288. * @param string start Starting handler.
  2289. * @param boolean case True for case sensitive.
  2290. * @access public
  2291. * @author Marcus Baker (http://lastcraft.com)
  2292. * @author Bermi Ferrer (http://bermi.org)
  2293. */
  2294. WYMeditor.Lexer = function(parser, start, case_sensitive)
  2295. {
  2296. start = start || 'accept';
  2297. this._case = case_sensitive || false;
  2298. this._regexes = {};
  2299. this._parser = parser;
  2300. this._mode = new WYMeditor.StateStack(start);
  2301. this._mode_handlers = {};
  2302. this._mode_handlers[start] = start;
  2303. return this;
  2304. };
  2305. /**
  2306. * Adds a token search pattern for a particular
  2307. * parsing mode. The pattern does not change the
  2308. * current mode.
  2309. * @param string pattern Perl style regex, but ( and )
  2310. * lose the usual meaning.
  2311. * @param string mode Should only apply this
  2312. * pattern when dealing with
  2313. * this type of input.
  2314. * @access public
  2315. */
  2316. WYMeditor.Lexer.prototype.addPattern = function(pattern, mode)
  2317. {
  2318. var mode = mode || "accept";
  2319. if (typeof this._regexes[mode] == 'undefined') {
  2320. this._regexes[mode] = new WYMeditor.ParallelRegex(this._case);
  2321. }
  2322. this._regexes[mode].addPattern(pattern);
  2323. if (typeof this._mode_handlers[mode] == 'undefined') {
  2324. this._mode_handlers[mode] = mode;
  2325. }
  2326. };
  2327. /**
  2328. * Adds a pattern that will enter a new parsing
  2329. * mode. Useful for entering parenthesis, strings,
  2330. * tags, etc.
  2331. * @param string pattern Perl style regex, but ( and )
  2332. * lose the usual meaning.
  2333. * @param string mode Should only apply this
  2334. * pattern when dealing with
  2335. * this type of input.
  2336. * @param string new_mode Change parsing to this new
  2337. * nested mode.
  2338. * @access public
  2339. */
  2340. WYMeditor.Lexer.prototype.addEntryPattern = function(pattern, mode, new_mode)
  2341. {
  2342. if (typeof this._regexes[mode] == 'undefined') {
  2343. this._regexes[mode] = new WYMeditor.ParallelRegex(this._case);
  2344. }
  2345. this._regexes[mode].addPattern(pattern, new_mode);
  2346. if (typeof this._mode_handlers[new_mode] == 'undefined') {
  2347. this._mode_handlers[new_mode] = new_mode;
  2348. }
  2349. };
  2350. /**
  2351. * Adds a pattern that will exit the current mode
  2352. * and re-enter the previous one.
  2353. * @param string pattern Perl style regex, but ( and )
  2354. * lose the usual meaning.
  2355. * @param string mode Mode to leave.
  2356. * @access public
  2357. */
  2358. WYMeditor.Lexer.prototype.addExitPattern = function(pattern, mode)
  2359. {
  2360. if (typeof this._regexes[mode] == 'undefined') {
  2361. this._regexes[mode] = new WYMeditor.ParallelRegex(this._case);
  2362. }
  2363. this._regexes[mode].addPattern(pattern, "__exit");
  2364. if (typeof this._mode_handlers[mode] == 'undefined') {
  2365. this._mode_handlers[mode] = mode;
  2366. }
  2367. };
  2368. /**
  2369. * Adds a pattern that has a special mode. Acts as an entry
  2370. * and exit pattern in one go, effectively calling a special
  2371. * parser handler for this token only.
  2372. * @param string pattern Perl style regex, but ( and )
  2373. * lose the usual meaning.
  2374. * @param string mode Should only apply this
  2375. * pattern when dealing with
  2376. * this type of input.
  2377. * @param string special Use this mode for this one token.
  2378. * @access public
  2379. */
  2380. WYMeditor.Lexer.prototype.addSpecialPattern = function(pattern, mode, special)
  2381. {
  2382. if (typeof this._regexes[mode] == 'undefined') {
  2383. this._regexes[mode] = new WYMeditor.ParallelRegex(this._case);
  2384. }
  2385. this._regexes[mode].addPattern(pattern, '_'+special);
  2386. if (typeof this._mode_handlers[special] == 'undefined') {
  2387. this._mode_handlers[special] = special;
  2388. }
  2389. };
  2390. /**
  2391. * Adds a mapping from a mode to another handler.
  2392. * @param string mode Mode to be remapped.
  2393. * @param string handler New target handler.
  2394. * @access public
  2395. */
  2396. WYMeditor.Lexer.prototype.mapHandler = function(mode, handler)
  2397. {
  2398. this._mode_handlers[mode] = handler;
  2399. };
  2400. /**
  2401. * Splits the page text into tokens. Will fail
  2402. * if the handlers report an error or if no
  2403. * content is consumed. If successful then each
  2404. * unparsed and parsed token invokes a call to the
  2405. * held listener.
  2406. * @param string raw Raw HTML text.
  2407. * @return boolean True on success, else false.
  2408. * @access public
  2409. */
  2410. WYMeditor.Lexer.prototype.parse = function(raw)
  2411. {
  2412. if (typeof this._parser == 'undefined') {
  2413. return false;
  2414. }
  2415. var length = raw.length;
  2416. var parsed;
  2417. while (typeof (parsed = this._reduce(raw)) == 'object') {
  2418. var raw = parsed[0];
  2419. var unmatched = parsed[1];
  2420. var matched = parsed[2];
  2421. var mode = parsed[3];
  2422. if (! this._dispatchTokens(unmatched, matched, mode)) {
  2423. return false;
  2424. }
  2425. if (raw == '') {
  2426. return true;
  2427. }
  2428. if (raw.length == length) {
  2429. return false;
  2430. }
  2431. length = raw.length;
  2432. }
  2433. if (! parsed ) {
  2434. return false;
  2435. }
  2436. return this._invokeParser(raw, WYMeditor.LEXER_UNMATCHED);
  2437. };
  2438. /**
  2439. * Sends the matched token and any leading unmatched
  2440. * text to the parser changing the lexer to a new
  2441. * mode if one is listed.
  2442. * @param string unmatched Unmatched leading portion.
  2443. * @param string matched Actual token match.
  2444. * @param string mode Mode after match. A boolean
  2445. * false mode causes no change.
  2446. * @return boolean False if there was any error
  2447. * from the parser.
  2448. * @access private
  2449. */
  2450. WYMeditor.Lexer.prototype._dispatchTokens = function(unmatched, matched, mode)
  2451. {
  2452. mode = mode || false;
  2453. if (! this._invokeParser(unmatched, WYMeditor.LEXER_UNMATCHED)) {
  2454. return false;
  2455. }
  2456. if (typeof mode == 'boolean') {
  2457. return this._invokeParser(matched, WYMeditor.LEXER_MATCHED);
  2458. }
  2459. if (this._isModeEnd(mode)) {
  2460. if (! this._invokeParser(matched, WYMeditor.LEXER_EXIT)) {
  2461. return false;
  2462. }
  2463. return this._mode.leave();
  2464. }
  2465. if (this._isSpecialMode(mode)) {
  2466. this._mode.enter(this._decodeSpecial(mode));
  2467. if (! this._invokeParser(matched, WYMeditor.LEXER_SPECIAL)) {
  2468. return false;
  2469. }
  2470. return this._mode.leave();
  2471. }
  2472. this._mode.enter(mode);
  2473. return this._invokeParser(matched, WYMeditor.LEXER_ENTER);
  2474. };
  2475. /**
  2476. * Tests to see if the new mode is actually to leave
  2477. * the current mode and pop an item from the matching
  2478. * mode stack.
  2479. * @param string mode Mode to test.
  2480. * @return boolean True if this is the exit mode.
  2481. * @access private
  2482. */
  2483. WYMeditor.Lexer.prototype._isModeEnd = function(mode)
  2484. {
  2485. return (mode === "__exit");
  2486. };
  2487. /**
  2488. * Test to see if the mode is one where this mode
  2489. * is entered for this token only and automatically
  2490. * leaves immediately afterwoods.
  2491. * @param string mode Mode to test.
  2492. * @return boolean True if this is the exit mode.
  2493. * @access private
  2494. */
  2495. WYMeditor.Lexer.prototype._isSpecialMode = function(mode)
  2496. {
  2497. return (mode.substring(0,1) == "_");
  2498. };
  2499. /**
  2500. * Strips the magic underscore marking single token
  2501. * modes.
  2502. * @param string mode Mode to decode.
  2503. * @return string Underlying mode name.
  2504. * @access private
  2505. */
  2506. WYMeditor.Lexer.prototype._decodeSpecial = function(mode)
  2507. {
  2508. return mode.substring(1);
  2509. };
  2510. /**
  2511. * Calls the parser method named after the current
  2512. * mode. Empty content will be ignored. The lexer
  2513. * has a parser handler for each mode in the lexer.
  2514. * @param string content Text parsed.
  2515. * @param boolean is_match Token is recognised rather
  2516. * than unparsed data.
  2517. * @access private
  2518. */
  2519. WYMeditor.Lexer.prototype._invokeParser = function(content, is_match)
  2520. {
  2521. if (!/ +/.test(content) && ((content === '') || (content == false))) {
  2522. return true;
  2523. }
  2524. var current = this._mode.getCurrent();
  2525. var handler = this._mode_handlers[current];
  2526. var result;
  2527. eval('result = this._parser.' + handler + '(content, is_match);');
  2528. return result;
  2529. };
  2530. /**
  2531. * Tries to match a chunk of text and if successful
  2532. * removes the recognised chunk and any leading
  2533. * unparsed data. Empty strings will not be matched.
  2534. * @param string raw The subject to parse. This is the
  2535. * content that will be eaten.
  2536. * @return array/boolean Three item list of unparsed
  2537. * content followed by the
  2538. * recognised token and finally the
  2539. * action the parser is to take.
  2540. * True if no match, false if there
  2541. * is a parsing error.
  2542. * @access private
  2543. */
  2544. WYMeditor.Lexer.prototype._reduce = function(raw)
  2545. {
  2546. var matched = this._regexes[this._mode.getCurrent()].match(raw);
  2547. var match = matched[1];
  2548. var action = matched[0];
  2549. if (action) {
  2550. var unparsed_character_count = raw.indexOf(match);
  2551. var unparsed = raw.substr(0, unparsed_character_count);
  2552. raw = raw.substring(unparsed_character_count + match.length);
  2553. return [raw, unparsed, match, action];
  2554. }
  2555. return true;
  2556. };
  2557. /**
  2558. * This are the rules for breaking the XHTML code into events
  2559. * handled by the provided parser.
  2560. *
  2561. * @author Marcus Baker (http://lastcraft.com)
  2562. * @author Bermi Ferrer (http://bermi.org)
  2563. */
  2564. WYMeditor.XhtmlLexer = function(parser)
  2565. {
  2566. jQuery.extend(this, new WYMeditor.Lexer(parser, 'Text'));
  2567. this.mapHandler('Text', 'Text');
  2568. this.addTokens();
  2569. this.init();
  2570. return this;
  2571. };
  2572. WYMeditor.XhtmlLexer.prototype.init = function()
  2573. {
  2574. };
  2575. WYMeditor.XhtmlLexer.prototype.addTokens = function()
  2576. {
  2577. this.addCommentTokens('Text');
  2578. this.addScriptTokens('Text');
  2579. this.addCssTokens('Text');
  2580. this.addTagTokens('Text');
  2581. };
  2582. WYMeditor.XhtmlLexer.prototype.addCommentTokens = function(scope)
  2583. {
  2584. this.addEntryPattern("<!--", scope, 'Comment');
  2585. this.addExitPattern("-->", 'Comment');
  2586. };
  2587. WYMeditor.XhtmlLexer.prototype.addScriptTokens = function(scope)
  2588. {
  2589. this.addEntryPattern("<script", scope, 'Script');
  2590. this.addExitPattern("</script>", 'Script');
  2591. };
  2592. WYMeditor.XhtmlLexer.prototype.addCssTokens = function(scope)
  2593. {
  2594. this.addEntryPattern("<style", scope, 'Css');
  2595. this.addExitPattern("</style>", 'Css');
  2596. };
  2597. WYMeditor.XhtmlLexer.prototype.addTagTokens = function(scope)
  2598. {
  2599. this.addSpecialPattern("<\\s*[a-z0-9:\-]+\\s*>", scope, 'OpeningTag');
  2600. this.addEntryPattern("<[a-z0-9:\-]+"+'[\\\/ \\\>]+', scope, 'OpeningTag');
  2601. this.addInTagDeclarationTokens('OpeningTag');
  2602. this.addSpecialPattern("</\\s*[a-z0-9:\-]+\\s*>", scope, 'ClosingTag');
  2603. };
  2604. WYMeditor.XhtmlLexer.prototype.addInTagDeclarationTokens = function(scope)
  2605. {
  2606. this.addSpecialPattern('\\s+', scope, 'Ignore');
  2607. this.addAttributeTokens(scope);
  2608. this.addExitPattern('/>', scope);
  2609. this.addExitPattern('>', scope);
  2610. };
  2611. WYMeditor.XhtmlLexer.prototype.addAttributeTokens = function(scope)
  2612. {
  2613. this.addSpecialPattern("\\s*[a-z-_0-9]*:?[a-z-_0-9]+\\s*(?=\=)\\s*", scope, 'TagAttributes');
  2614. this.addEntryPattern('=\\s*"', scope, 'DoubleQuotedAttribute');
  2615. this.addPattern("\\\\\"", 'DoubleQuotedAttribute');
  2616. this.addExitPattern('"', 'DoubleQuotedAttribute');
  2617. this.addEntryPattern("=\\s*'", scope, 'SingleQuotedAttribute');
  2618. this.addPattern("\\\\'", 'SingleQuotedAttribute');
  2619. this.addExitPattern("'", 'SingleQuotedAttribute');
  2620. this.addSpecialPattern('=\\s*[^>\\s]*', scope, 'UnquotedAttribute');
  2621. };
  2622. /**
  2623. * XHTML Parser.
  2624. *
  2625. * This XHTML parser will trigger the events available on on
  2626. * current SaxListener
  2627. *
  2628. * @author Bermi Ferrer (http://bermi.org)
  2629. */
  2630. WYMeditor.XhtmlParser = function(Listener, mode)
  2631. {
  2632. var mode = mode || 'Text';
  2633. this._Lexer = new WYMeditor.XhtmlLexer(this);
  2634. this._Listener = Listener;
  2635. this._mode = mode;
  2636. this._matches = [];
  2637. this._last_match = '';
  2638. this._current_match = '';
  2639. return this;
  2640. };
  2641. WYMeditor.XhtmlParser.prototype.parse = function(raw)
  2642. {
  2643. this._Lexer.parse(this.beforeParsing(raw));
  2644. return this.afterParsing(this._Listener.getResult());
  2645. };
  2646. WYMeditor.XhtmlParser.prototype.beforeParsing = function(raw)
  2647. {
  2648. if(raw.match(/class="MsoNormal"/) || raw.match(/ns = "urn:schemas-microsoft-com/)){
  2649. // Usefull for cleaning up content pasted from other sources (MSWord)
  2650. this._Listener.avoidStylingTagsAndAttributes();
  2651. }
  2652. return this._Listener.beforeParsing(raw);
  2653. };
  2654. WYMeditor.XhtmlParser.prototype.afterParsing = function(parsed)
  2655. {
  2656. if(this._Listener._avoiding_tags_implicitly){
  2657. this._Listener.allowStylingTagsAndAttributes();
  2658. }
  2659. return this._Listener.afterParsing(parsed);
  2660. };
  2661. WYMeditor.XhtmlParser.prototype.Ignore = function(match, state)
  2662. {
  2663. return true;
  2664. };
  2665. WYMeditor.XhtmlParser.prototype.Text = function(text)
  2666. {
  2667. this._Listener.addContent(text);
  2668. return true;
  2669. };
  2670. WYMeditor.XhtmlParser.prototype.Comment = function(match, status)
  2671. {
  2672. return this._addNonTagBlock(match, status, 'addComment');
  2673. };
  2674. WYMeditor.XhtmlParser.prototype.Script = function(match, status)
  2675. {
  2676. return this._addNonTagBlock(match, status, 'addScript');
  2677. };
  2678. WYMeditor.XhtmlParser.prototype.Css = function(match, status)
  2679. {
  2680. return this._addNonTagBlock(match, status, 'addCss');
  2681. };
  2682. WYMeditor.XhtmlParser.prototype._addNonTagBlock = function(match, state, type)
  2683. {
  2684. switch (state){
  2685. case WYMeditor.LEXER_ENTER:
  2686. this._non_tag = match;
  2687. break;
  2688. case WYMeditor.LEXER_UNMATCHED:
  2689. this._non_tag += match;
  2690. break;
  2691. case WYMeditor.LEXER_EXIT:
  2692. switch(type) {
  2693. case 'addComment':
  2694. this._Listener.addComment(this._non_tag+match);
  2695. break;
  2696. case 'addScript':
  2697. this._Listener.addScript(this._non_tag+match);
  2698. break;
  2699. case 'addCss':
  2700. this._Listener.addCss(this._non_tag+match);
  2701. break;
  2702. }
  2703. }
  2704. return true;
  2705. };
  2706. WYMeditor.XhtmlParser.prototype.OpeningTag = function(match, state)
  2707. {
  2708. switch (state){
  2709. case WYMeditor.LEXER_ENTER:
  2710. this._tag = this.normalizeTag(match);
  2711. this._tag_attributes = {};
  2712. break;
  2713. case WYMeditor.LEXER_SPECIAL:
  2714. this._callOpenTagListener(this.normalizeTag(match));
  2715. break;
  2716. case WYMeditor.LEXER_EXIT:
  2717. this._callOpenTagListener(this._tag, this._tag_attributes);
  2718. }
  2719. return true;
  2720. };
  2721. WYMeditor.XhtmlParser.prototype.ClosingTag = function(match, state)
  2722. {
  2723. this._callCloseTagListener(this.normalizeTag(match));
  2724. return true;
  2725. };
  2726. WYMeditor.XhtmlParser.prototype._callOpenTagListener = function(tag, attributes)
  2727. {
  2728. var attributes = attributes || {};
  2729. this.autoCloseUnclosedBeforeNewOpening(tag);
  2730. if(this._Listener.isBlockTag(tag)){
  2731. this._Listener._tag_stack.push(tag);
  2732. this._Listener.fixNestingBeforeOpeningBlockTag(tag, attributes);
  2733. this._Listener.openBlockTag(tag, attributes);
  2734. this._increaseOpenTagCounter(tag);
  2735. }else if(this._Listener.isInlineTag(tag)){
  2736. this._Listener.inlineTag(tag, attributes);
  2737. }else{
  2738. this._Listener.openUnknownTag(tag, attributes);
  2739. this._increaseOpenTagCounter(tag);
  2740. }
  2741. this._Listener.last_tag = tag;
  2742. this._Listener.last_tag_opened = true;
  2743. this._Listener.last_tag_attributes = attributes;
  2744. };
  2745. WYMeditor.XhtmlParser.prototype._callCloseTagListener = function(tag)
  2746. {
  2747. if(this._decreaseOpenTagCounter(tag)){
  2748. this.autoCloseUnclosedBeforeTagClosing(tag);
  2749. if(this._Listener.isBlockTag(tag)){
  2750. var expected_tag = this._Listener._tag_stack.pop();
  2751. if(expected_tag == false){
  2752. return;
  2753. }else if(expected_tag != tag){
  2754. tag = expected_tag;
  2755. }
  2756. this._Listener.closeBlockTag(tag);
  2757. }else{
  2758. this._Listener.closeUnknownTag(tag);
  2759. }
  2760. }else{
  2761. this._Listener.closeUnopenedTag(tag);
  2762. }
  2763. this._Listener.last_tag = tag;
  2764. this._Listener.last_tag_opened = false;
  2765. };
  2766. WYMeditor.XhtmlParser.prototype._increaseOpenTagCounter = function(tag)
  2767. {
  2768. this._Listener._open_tags[tag] = this._Listener._open_tags[tag] || 0;
  2769. this._Listener._open_tags[tag]++;
  2770. };
  2771. WYMeditor.XhtmlParser.prototype._decreaseOpenTagCounter = function(tag)
  2772. {
  2773. if(this._Listener._open_tags[tag]){
  2774. this._Listener._open_tags[tag]--;
  2775. if(this._Listener._open_tags[tag] == 0){
  2776. this._Listener._open_tags[tag] = undefined;
  2777. }
  2778. return true;
  2779. }
  2780. return false;
  2781. };
  2782. WYMeditor.XhtmlParser.prototype.autoCloseUnclosedBeforeNewOpening = function(new_tag)
  2783. {
  2784. this._autoCloseUnclosed(new_tag, false);
  2785. };
  2786. WYMeditor.XhtmlParser.prototype.autoCloseUnclosedBeforeTagClosing = function(tag)
  2787. {
  2788. this._autoCloseUnclosed(tag, true);
  2789. };
  2790. WYMeditor.XhtmlParser.prototype._autoCloseUnclosed = function(new_tag, closing)
  2791. {
  2792. var closing = closing || false;
  2793. if(this._Listener._open_tags){
  2794. for (var tag in this._Listener._open_tags) {
  2795. var counter = this._Listener._open_tags[tag];
  2796. if(counter > 0 && this._Listener.shouldCloseTagAutomatically(tag, new_tag, closing)){
  2797. this._callCloseTagListener(tag, true);
  2798. }
  2799. }
  2800. }
  2801. };
  2802. WYMeditor.XhtmlParser.prototype.getTagReplacements = function()
  2803. {
  2804. return this._Listener.getTagReplacements();
  2805. };
  2806. WYMeditor.XhtmlParser.prototype.normalizeTag = function(tag)
  2807. {
  2808. tag = tag.replace(/^([\s<\/>]*)|([\s<\/>]*)$/gm,'').toLowerCase();
  2809. var tags = this._Listener.getTagReplacements();
  2810. if(tags[tag]){
  2811. return tags[tag];
  2812. }
  2813. return tag;
  2814. };
  2815. WYMeditor.XhtmlParser.prototype.TagAttributes = function(match, state)
  2816. {
  2817. if(WYMeditor.LEXER_SPECIAL == state){
  2818. this._current_attribute = match;
  2819. }
  2820. return true;
  2821. };
  2822. WYMeditor.XhtmlParser.prototype.DoubleQuotedAttribute = function(match, state)
  2823. {
  2824. if(WYMeditor.LEXER_UNMATCHED == state){
  2825. this._tag_attributes[this._current_attribute] = match;
  2826. }
  2827. return true;
  2828. };
  2829. WYMeditor.XhtmlParser.prototype.SingleQuotedAttribute = function(match, state)
  2830. {
  2831. if(WYMeditor.LEXER_UNMATCHED == state){
  2832. this._tag_attributes[this._current_attribute] = match;
  2833. }
  2834. return true;
  2835. };
  2836. WYMeditor.XhtmlParser.prototype.UnquotedAttribute = function(match, state)
  2837. {
  2838. this._tag_attributes[this._current_attribute] = match.replace(/^=/,'');
  2839. return true;
  2840. };
  2841. /**
  2842. * XHTML Sax parser.
  2843. *
  2844. * @author Bermi Ferrer (http://bermi.org)
  2845. */
  2846. WYMeditor.XhtmlSaxListener = function()
  2847. {
  2848. this.output = '';
  2849. this.helper = new WYMeditor.XmlHelper();
  2850. this._open_tags = {};
  2851. this.validator = WYMeditor.XhtmlValidator;
  2852. this._tag_stack = [];
  2853. this.avoided_tags = [];
  2854. this.entities = {
  2855. '&nbsp;':'&#160;','&iexcl;':'&#161;','&cent;':'&#162;',
  2856. '&pound;':'&#163;','&curren;':'&#164;','&yen;':'&#165;',
  2857. '&brvbar;':'&#166;','&sect;':'&#167;','&uml;':'&#168;',
  2858. '&copy;':'&#169;','&ordf;':'&#170;','&laquo;':'&#171;',
  2859. '&not;':'&#172;','&shy;':'&#173;','&reg;':'&#174;',
  2860. '&macr;':'&#175;','&deg;':'&#176;','&plusmn;':'&#177;',
  2861. '&sup2;':'&#178;','&sup3;':'&#179;','&acute;':'&#180;',
  2862. '&micro;':'&#181;','&para;':'&#182;','&middot;':'&#183;',
  2863. '&cedil;':'&#184;','&sup1;':'&#185;','&ordm;':'&#186;',
  2864. '&raquo;':'&#187;','&frac14;':'&#188;','&frac12;':'&#189;',
  2865. '&frac34;':'&#190;','&iquest;':'&#191;','&Agrave;':'&#192;',
  2866. '&Aacute;':'&#193;','&Acirc;':'&#194;','&Atilde;':'&#195;',
  2867. '&Auml;':'&#196;','&Aring;':'&#197;','&AElig;':'&#198;',
  2868. '&Ccedil;':'&#199;','&Egrave;':'&#200;','&Eacute;':'&#201;',
  2869. '&Ecirc;':'&#202;','&Euml;':'&#203;','&Igrave;':'&#204;',
  2870. '&Iacute;':'&#205;','&Icirc;':'&#206;','&Iuml;':'&#207;',
  2871. '&ETH;':'&#208;','&Ntilde;':'&#209;','&Ograve;':'&#210;',
  2872. '&Oacute;':'&#211;','&Ocirc;':'&#212;','&Otilde;':'&#213;',
  2873. '&Ouml;':'&#214;','&times;':'&#215;','&Oslash;':'&#216;',
  2874. '&Ugrave;':'&#217;','&Uacute;':'&#218;','&Ucirc;':'&#219;',
  2875. '&Uuml;':'&#220;','&Yacute;':'&#221;','&THORN;':'&#222;',
  2876. '&szlig;':'&#223;','&agrave;':'&#224;','&aacute;':'&#225;',
  2877. '&acirc;':'&#226;','&atilde;':'&#227;','&auml;':'&#228;',
  2878. '&aring;':'&#229;','&aelig;':'&#230;','&ccedil;':'&#231;',
  2879. '&egrave;':'&#232;','&eacute;':'&#233;','&ecirc;':'&#234;',
  2880. '&euml;':'&#235;','&igrave;':'&#236;','&iacute;':'&#237;',
  2881. '&icirc;':'&#238;','&iuml;':'&#239;','&eth;':'&#240;',
  2882. '&ntilde;':'&#241;','&ograve;':'&#242;','&oacute;':'&#243;',
  2883. '&ocirc;':'&#244;','&otilde;':'&#245;','&ouml;':'&#246;',
  2884. '&divide;':'&#247;','&oslash;':'&#248;','&ugrave;':'&#249;',
  2885. '&uacute;':'&#250;','&ucirc;':'&#251;','&uuml;':'&#252;',
  2886. '&yacute;':'&#253;','&thorn;':'&#254;','&yuml;':'&#255;',
  2887. '&OElig;':'&#338;','&oelig;':'&#339;','&Scaron;':'&#352;',
  2888. '&scaron;':'&#353;','&Yuml;':'&#376;','&fnof;':'&#402;',
  2889. '&circ;':'&#710;','&tilde;':'&#732;','&Alpha;':'&#913;',
  2890. '&Beta;':'&#914;','&Gamma;':'&#915;','&Delta;':'&#916;',
  2891. '&Epsilon;':'&#917;','&Zeta;':'&#918;','&Eta;':'&#919;',
  2892. '&Theta;':'&#920;','&Iota;':'&#921;','&Kappa;':'&#922;',
  2893. '&Lambda;':'&#923;','&Mu;':'&#924;','&Nu;':'&#925;',
  2894. '&Xi;':'&#926;','&Omicron;':'&#927;','&Pi;':'&#928;',
  2895. '&Rho;':'&#929;','&Sigma;':'&#931;','&Tau;':'&#932;',
  2896. '&Upsilon;':'&#933;','&Phi;':'&#934;','&Chi;':'&#935;',
  2897. '&Psi;':'&#936;','&Omega;':'&#937;','&alpha;':'&#945;',
  2898. '&beta;':'&#946;','&gamma;':'&#947;','&delta;':'&#948;',
  2899. '&epsilon;':'&#949;','&zeta;':'&#950;','&eta;':'&#951;',
  2900. '&theta;':'&#952;','&iota;':'&#953;','&kappa;':'&#954;',
  2901. '&lambda;':'&#955;','&mu;':'&#956;','&nu;':'&#957;',
  2902. '&xi;':'&#958;','&omicron;':'&#959;','&pi;':'&#960;',
  2903. '&rho;':'&#961;','&sigmaf;':'&#962;','&sigma;':'&#963;',
  2904. '&tau;':'&#964;','&upsilon;':'&#965;','&phi;':'&#966;',
  2905. '&chi;':'&#967;','&psi;':'&#968;','&omega;':'&#969;',
  2906. '&thetasym;':'&#977;','&upsih;':'&#978;','&piv;':'&#982;',
  2907. '&ensp;':'&#8194;','&emsp;':'&#8195;','&thinsp;':'&#8201;',
  2908. '&zwnj;':'&#8204;','&zwj;':'&#8205;','&lrm;':'&#8206;',
  2909. '&rlm;':'&#8207;','&ndash;':'&#8211;','&mdash;':'&#8212;',
  2910. '&lsquo;':'&#8216;','&rsquo;':'&#8217;','&sbquo;':'&#8218;',
  2911. '&ldquo;':'&#8220;','&rdquo;':'&#8221;','&bdquo;':'&#8222;',
  2912. '&dagger;':'&#8224;','&Dagger;':'&#8225;','&bull;':'&#8226;',
  2913. '&hellip;':'&#8230;','&permil;':'&#8240;','&prime;':'&#8242;',
  2914. '&Prime;':'&#8243;','&lsaquo;':'&#8249;','&rsaquo;':'&#8250;',
  2915. '&oline;':'&#8254;','&frasl;':'&#8260;','&euro;':'&#8364;',
  2916. '&image;':'&#8465;','&weierp;':'&#8472;','&real;':'&#8476;',
  2917. '&trade;':'&#8482;','&alefsym;':'&#8501;','&larr;':'&#8592;',
  2918. '&uarr;':'&#8593;','&rarr;':'&#8594;','&darr;':'&#8595;',
  2919. '&harr;':'&#8596;','&crarr;':'&#8629;','&lArr;':'&#8656;',
  2920. '&uArr;':'&#8657;','&rArr;':'&#8658;','&dArr;':'&#8659;',
  2921. '&hArr;':'&#8660;','&forall;':'&#8704;','&part;':'&#8706;',
  2922. '&exist;':'&#8707;','&empty;':'&#8709;','&nabla;':'&#8711;',
  2923. '&isin;':'&#8712;','&notin;':'&#8713;','&ni;':'&#8715;',
  2924. '&prod;':'&#8719;','&sum;':'&#8721;','&minus;':'&#8722;',
  2925. '&lowast;':'&#8727;','&radic;':'&#8730;','&prop;':'&#8733;',
  2926. '&infin;':'&#8734;','&ang;':'&#8736;','&and;':'&#8743;',
  2927. '&or;':'&#8744;','&cap;':'&#8745;','&cup;':'&#8746;',
  2928. '&int;':'&#8747;','&there4;':'&#8756;','&sim;':'&#8764;',
  2929. '&cong;':'&#8773;','&asymp;':'&#8776;','&ne;':'&#8800;',
  2930. '&equiv;':'&#8801;','&le;':'&#8804;','&ge;':'&#8805;',
  2931. '&sub;':'&#8834;','&sup;':'&#8835;','&nsub;':'&#8836;',
  2932. '&sube;':'&#8838;','&supe;':'&#8839;','&oplus;':'&#8853;',
  2933. '&otimes;':'&#8855;','&perp;':'&#8869;','&sdot;':'&#8901;',
  2934. '&lceil;':'&#8968;','&rceil;':'&#8969;','&lfloor;':'&#8970;',
  2935. '&rfloor;':'&#8971;','&lang;':'&#9001;','&rang;':'&#9002;',
  2936. '&loz;':'&#9674;','&spades;':'&#9824;','&clubs;':'&#9827;',
  2937. '&hearts;':'&#9829;','&diams;':'&#9830;'};
  2938. this.block_tags = ["a", "abbr", "acronym", "address", "area", "b",
  2939. "base", "bdo", "big", "blockquote", "body", "button",
  2940. "caption", "cite", "code", "col", "colgroup", "dd", "del", "div",
  2941. "dfn", "dl", "dt", "em", "fieldset", "form", "head", "h1", "h2",
  2942. "h3", "h4", "h5", "h6", "html", "i", "ins",
  2943. "kbd", "label", "legend", "li", "map", "noscript",
  2944. "object", "ol", "optgroup", "option", "p", "param", "pre", "q",
  2945. "samp", "script", "select", "small", "span", "strong", "style",
  2946. "sub", "sup", "table", "tbody", "td", "textarea", "tfoot", "th",
  2947. "thead", "title", "tr", "tt", "ul", "var", "extends"];
  2948. this.inline_tags = ["br", "hr", "img", "input"];
  2949. return this;
  2950. };
  2951. WYMeditor.XhtmlSaxListener.prototype.shouldCloseTagAutomatically = function(tag, now_on_tag, closing)
  2952. {
  2953. var closing = closing || false;
  2954. if(tag == 'td'){
  2955. if((closing && now_on_tag == 'tr') || (!closing && now_on_tag == 'td')){
  2956. return true;
  2957. }
  2958. }
  2959. if(tag == 'option'){
  2960. if((closing && now_on_tag == 'select') || (!closing && now_on_tag == 'option')){
  2961. return true;
  2962. }
  2963. }
  2964. return false;
  2965. };
  2966. WYMeditor.XhtmlSaxListener.prototype.beforeParsing = function(raw)
  2967. {
  2968. this.output = '';
  2969. return raw;
  2970. };
  2971. WYMeditor.XhtmlSaxListener.prototype.afterParsing = function(xhtml)
  2972. {
  2973. xhtml = this.replaceNamedEntities(xhtml);
  2974. xhtml = this.joinRepeatedEntities(xhtml);
  2975. xhtml = this.removeEmptyTags(xhtml);
  2976. return xhtml;
  2977. };
  2978. WYMeditor.XhtmlSaxListener.prototype.replaceNamedEntities = function(xhtml)
  2979. {
  2980. for (var entity in this.entities) {
  2981. xhtml = xhtml.replace(entity, this.entities[entity]);
  2982. }
  2983. return xhtml;
  2984. };
  2985. WYMeditor.XhtmlSaxListener.prototype.joinRepeatedEntities = function(xhtml)
  2986. {
  2987. var tags = 'em|strong|sub|sup|acronym|pre|del|blockquote|address';
  2988. return xhtml.replace(new RegExp('<\/('+tags+')><\\1>' ,''),'').
  2989. replace(new RegExp('(\s*<('+tags+')>\s*){2}(.*)(\s*<\/\\2>\s*){2}' ,''),'<\$2>\$3<\$2>');
  2990. };
  2991. WYMeditor.XhtmlSaxListener.prototype.removeEmptyTags = function(xhtml)
  2992. {
  2993. return xhtml.replace(new RegExp('<('+this.block_tags.join("|")+')>(<br \/>|&#160;|&nbsp;|\\s)*<\/\\1>' ,'g'),'');
  2994. };
  2995. WYMeditor.XhtmlSaxListener.prototype.getResult = function()
  2996. {
  2997. return this.output;
  2998. };
  2999. WYMeditor.XhtmlSaxListener.prototype.getTagReplacements = function()
  3000. {
  3001. return {'b':'strong', 'i':'em'};
  3002. };
  3003. WYMeditor.XhtmlSaxListener.prototype.addContent = function(text)
  3004. {
  3005. this.output += text;
  3006. };
  3007. WYMeditor.XhtmlSaxListener.prototype.addComment = function(text)
  3008. {
  3009. if(this.remove_comments){
  3010. this.output += text;
  3011. }
  3012. };
  3013. WYMeditor.XhtmlSaxListener.prototype.addScript = function(text)
  3014. {
  3015. if(!this.remove_scripts){
  3016. this.output += text;
  3017. }
  3018. };
  3019. WYMeditor.XhtmlSaxListener.prototype.addCss = function(text)
  3020. {
  3021. if(!this.remove_embeded_styles){
  3022. this.output += text;
  3023. }
  3024. };
  3025. WYMeditor.XhtmlSaxListener.prototype.openBlockTag = function(tag, attributes)
  3026. {
  3027. this.output += this.helper.tag(tag, this.validator.getValidTagAttributes(tag, attributes), true);
  3028. };
  3029. WYMeditor.XhtmlSaxListener.prototype.inlineTag = function(tag, attributes)
  3030. {
  3031. this.output += this.helper.tag(tag, this.validator.getValidTagAttributes(tag, attributes));
  3032. };
  3033. WYMeditor.XhtmlSaxListener.prototype.openUnknownTag = function(tag, attributes)
  3034. {
  3035. //this.output += this.helper.tag(tag, attributes, true);
  3036. };
  3037. WYMeditor.XhtmlSaxListener.prototype.closeBlockTag = function(tag)
  3038. {
  3039. this.output = this.output.replace(/<br \/>$/, '')+this._getClosingTagContent('before', tag)+"</"+tag+">"+this._getClosingTagContent('after', tag);
  3040. };
  3041. WYMeditor.XhtmlSaxListener.prototype.closeUnknownTag = function(tag)
  3042. {
  3043. //this.output += "</"+tag+">";
  3044. };
  3045. WYMeditor.XhtmlSaxListener.prototype.closeUnopenedTag = function(tag)
  3046. {
  3047. this.output += "</"+tag+">";
  3048. };
  3049. WYMeditor.XhtmlSaxListener.prototype.avoidStylingTagsAndAttributes = function()
  3050. {
  3051. this.avoided_tags = ['div','span'];
  3052. this.validator.skiped_attributes = ['style'];
  3053. this.validator.skiped_attribute_values = ['MsoNormal','main1']; // MS Word attributes for class
  3054. this._avoiding_tags_implicitly = true;
  3055. };
  3056. WYMeditor.XhtmlSaxListener.prototype.allowStylingTagsAndAttributes = function()
  3057. {
  3058. this.avoided_tags = [];
  3059. this.validator.skiped_attributes = [];
  3060. this.validator.skiped_attribute_values = [];
  3061. this._avoiding_tags_implicitly = false;
  3062. };
  3063. WYMeditor.XhtmlSaxListener.prototype.isBlockTag = function(tag)
  3064. {
  3065. return !WYMeditor.Helper.contains(this.avoided_tags, tag) && WYMeditor.Helper.contains(this.block_tags, tag);
  3066. };
  3067. WYMeditor.XhtmlSaxListener.prototype.isInlineTag = function(tag)
  3068. {
  3069. return !WYMeditor.Helper.contains(this.avoided_tags, tag) && WYMeditor.Helper.contains(this.inline_tags, tag);
  3070. };
  3071. WYMeditor.XhtmlSaxListener.prototype.insertContentAfterClosingTag = function(tag, content)
  3072. {
  3073. this._insertContentWhenClosingTag('after', tag, content);
  3074. };
  3075. WYMeditor.XhtmlSaxListener.prototype.insertContentBeforeClosingTag = function(tag, content)
  3076. {
  3077. this._insertContentWhenClosingTag('before', tag, content);
  3078. };
  3079. WYMeditor.XhtmlSaxListener.prototype.fixNestingBeforeOpeningBlockTag = function(tag, attributes)
  3080. {
  3081. if(tag != 'li' && (tag == 'ul' || tag == 'ol') && this.last_tag && !this.last_tag_opened && this.last_tag == 'li'){
  3082. this.output = this.output.replace(/<\/li>$/, '');
  3083. this.insertContentAfterClosingTag(tag, '</li>');
  3084. }
  3085. };
  3086. WYMeditor.XhtmlSaxListener.prototype._insertContentWhenClosingTag = function(position, tag, content)
  3087. {
  3088. if(!this['_insert_'+position+'_closing']){
  3089. this['_insert_'+position+'_closing'] = [];
  3090. }
  3091. if(!this['_insert_'+position+'_closing'][tag]){
  3092. this['_insert_'+position+'_closing'][tag] = [];
  3093. }
  3094. this['_insert_'+position+'_closing'][tag].push(content);
  3095. };
  3096. WYMeditor.XhtmlSaxListener.prototype._getClosingTagContent = function(position, tag)
  3097. {
  3098. if( this['_insert_'+position+'_closing'] &&
  3099. this['_insert_'+position+'_closing'][tag] &&
  3100. this['_insert_'+position+'_closing'][tag].length > 0){
  3101. return this['_insert_'+position+'_closing'][tag].pop();
  3102. }
  3103. return '';
  3104. };
  3105. /********** CSS PARSER **********/
  3106. WYMeditor.WymCssLexer = function(parser, only_wym_blocks)
  3107. {
  3108. var only_wym_blocks = (typeof only_wym_blocks == 'undefined' ? true : only_wym_blocks);
  3109. jQuery.extend(this, new WYMeditor.Lexer(parser, (only_wym_blocks?'Ignore':'WymCss')));
  3110. this.mapHandler('WymCss', 'Ignore');
  3111. if(only_wym_blocks == true){
  3112. this.addEntryPattern("/\\\x2a[<\\s]*WYMeditor[>\\s]*\\\x2a/", 'Ignore', 'WymCss');
  3113. this.addExitPattern("/\\\x2a[<\/\\s]*WYMeditor[>\\s]*\\\x2a/", 'WymCss');
  3114. }
  3115. this.addSpecialPattern("\\\x2e[a-z-_0-9]+[\\sa-z]*", 'WymCss', 'WymCssStyleDeclaration');
  3116. this.addEntryPattern("/\\\x2a", 'WymCss', 'WymCssComment');
  3117. this.addExitPattern("\\\x2a/", 'WymCssComment');
  3118. this.addEntryPattern("\x7b", 'WymCss', 'WymCssStyle');
  3119. this.addExitPattern("\x7d", 'WymCssStyle');
  3120. this.addEntryPattern("/\\\x2a", 'WymCssStyle', 'WymCssFeddbackStyle');
  3121. this.addExitPattern("\\\x2a/", 'WymCssFeddbackStyle');
  3122. return this;
  3123. };
  3124. WYMeditor.WymCssParser = function()
  3125. {
  3126. this._in_style = false;
  3127. this._has_title = false;
  3128. this.only_wym_blocks = true;
  3129. this.css_settings = {'classesItems':[], 'editorStyles':[], 'dialogStyles':[]};
  3130. return this;
  3131. };
  3132. WYMeditor.WymCssParser.prototype.parse = function(raw, only_wym_blocks)
  3133. {
  3134. var only_wym_blocks = (typeof only_wym_blocks == 'undefined' ? this.only_wym_blocks : only_wym_blocks);
  3135. this._Lexer = new WYMeditor.WymCssLexer(this, only_wym_blocks);
  3136. this._Lexer.parse(raw);
  3137. };
  3138. WYMeditor.WymCssParser.prototype.Ignore = function(match, state)
  3139. {
  3140. return true;
  3141. };
  3142. WYMeditor.WymCssParser.prototype.WymCssComment = function(text, status)
  3143. {
  3144. if(text.match(/end[a-z0-9\s]*wym[a-z0-9\s]*/mi)){
  3145. return false;
  3146. }
  3147. if(status == WYMeditor.LEXER_UNMATCHED){
  3148. if(!this._in_style){
  3149. this._has_title = true;
  3150. this._current_item = {'title':WYMeditor.Helper.trim(text)};
  3151. }else{
  3152. if(this._current_item[this._current_element]){
  3153. if(!this._current_item[this._current_element].expressions){
  3154. this._current_item[this._current_element].expressions = [text];
  3155. }else{
  3156. this._current_item[this._current_element].expressions.push(text);
  3157. }
  3158. }
  3159. }
  3160. this._in_style = true;
  3161. }
  3162. return true;
  3163. };
  3164. WYMeditor.WymCssParser.prototype.WymCssStyle = function(match, status)
  3165. {
  3166. if(status == WYMeditor.LEXER_UNMATCHED){
  3167. match = WYMeditor.Helper.trim(match);
  3168. if(match != ''){
  3169. this._current_item[this._current_element].style = match;
  3170. }
  3171. }else if (status == WYMeditor.LEXER_EXIT){
  3172. this._in_style = false;
  3173. this._has_title = false;
  3174. this.addStyleSetting(this._current_item);
  3175. }
  3176. return true;
  3177. };
  3178. WYMeditor.WymCssParser.prototype.WymCssFeddbackStyle = function(match, status)
  3179. {
  3180. if(status == WYMeditor.LEXER_UNMATCHED){
  3181. this._current_item[this._current_element].feedback_style = match.replace(/^([\s\/\*]*)|([\s\/\*]*)$/gm,'');
  3182. }
  3183. return true;
  3184. };
  3185. WYMeditor.WymCssParser.prototype.WymCssStyleDeclaration = function(match)
  3186. {
  3187. match = match.replace(/^([\s\.]*)|([\s\.*]*)$/gm, '');
  3188. var tag = '';
  3189. if(match.indexOf(' ') > 0){
  3190. var parts = match.split(' ');
  3191. this._current_element = parts[0];
  3192. var tag = parts[1];
  3193. }else{
  3194. this._current_element = match;
  3195. }
  3196. if(!this._has_title){
  3197. this._current_item = {'title':(!tag?'':tag.toUpperCase()+': ')+this._current_element};
  3198. this._has_title = true;
  3199. }
  3200. if(!this._current_item[this._current_element]){
  3201. this._current_item[this._current_element] = {'name':this._current_element};
  3202. }
  3203. if(tag){
  3204. if(!this._current_item[this._current_element].tags){
  3205. this._current_item[this._current_element].tags = [tag];
  3206. }else{
  3207. this._current_item[this._current_element].tags.push(tag);
  3208. }
  3209. }
  3210. return true;
  3211. };
  3212. WYMeditor.WymCssParser.prototype.addStyleSetting = function(style_details)
  3213. {
  3214. for (var name in style_details){
  3215. var details = style_details[name];
  3216. if(typeof details == 'object' && name != 'title'){
  3217. this.css_settings.classesItems.push({
  3218. 'name': WYMeditor.Helper.trim(details.name),
  3219. 'title': style_details.title,
  3220. 'expr' : WYMeditor.Helper.trim((details.expressions||details.tags).join(', '))
  3221. });
  3222. if(details.feedback_style){
  3223. this.css_settings.editorStyles.push({
  3224. 'name': '.'+ WYMeditor.Helper.trim(details.name),
  3225. 'css': details.feedback_style
  3226. });
  3227. }
  3228. if(details.style){
  3229. this.css_settings.dialogStyles.push({
  3230. 'name': '.'+ WYMeditor.Helper.trim(details.name),
  3231. 'css': details.style
  3232. });
  3233. }
  3234. }
  3235. }
  3236. };
  3237. /********** HELPERS **********/
  3238. // Returns true if it is a text node with whitespaces only
  3239. jQuery.fn.isPhantomNode = function() {
  3240. if (this[0].nodeType == 3)
  3241. return !(/[^\t\n\r ]/.test(this[0].data));
  3242. return false;
  3243. };
  3244. WYMeditor.isPhantomNode = function(n) {
  3245. if (n.nodeType == 3)
  3246. return !(/[^\t\n\r ]/.test(n.data));
  3247. return false;
  3248. };
  3249. WYMeditor.isPhantomString = function(str) {
  3250. return !(/[^\t\n\r ]/.test(str));
  3251. };
  3252. // Returns the Parents or the node itself
  3253. // jqexpr = a jQuery expression
  3254. jQuery.fn.parentsOrSelf = function(jqexpr) {
  3255. var n = this;
  3256. if (n[0].nodeType == 3)
  3257. n = n.parents().slice(0,1);
  3258. // if (n.is(jqexpr)) // XXX should work, but doesn't (probably a jQuery bug)
  3259. if (n.filter(jqexpr).size() == 1)
  3260. return n;
  3261. else
  3262. return n.parents(jqexpr).slice(0,1);
  3263. };
  3264. // String & array helpers
  3265. WYMeditor.Helper = {
  3266. //replace all instances of 'old' by 'rep' in 'str' string
  3267. replaceAll: function(str, old, rep) {
  3268. var rExp = new RegExp(old, "g");
  3269. return(str.replace(rExp, rep));
  3270. },
  3271. //insert 'inserted' at position 'pos' in 'str' string
  3272. insertAt: function(str, inserted, pos) {
  3273. return(str.substr(0,pos) + inserted + str.substring(pos));
  3274. },
  3275. //trim 'str' string
  3276. trim: function(str) {
  3277. return str.replace(/^(\s*)|(\s*)$/gm,'');
  3278. },
  3279. //return true if 'arr' array contains 'elem', or false
  3280. contains: function(arr, elem) {
  3281. for (var i = 0; i < arr.length; i++) {
  3282. if (arr[i] === elem) return true;
  3283. }
  3284. return false;
  3285. },
  3286. //return 'item' position in 'arr' array, or -1
  3287. indexOf: function(arr, item) {
  3288. var ret=-1;
  3289. for(var i = 0; i < arr.length; i++) {
  3290. if (arr[i] == item) {
  3291. ret = i;
  3292. break;
  3293. }
  3294. }
  3295. return(ret);
  3296. },
  3297. //return 'item' object in 'arr' array, checking its 'name' property, or null
  3298. findByName: function(arr, name) {
  3299. for(var i = 0; i < arr.length; i++) {
  3300. var item = arr[i];
  3301. if(item.name == name) return(item);
  3302. }
  3303. return(null);
  3304. }
  3305. };
  3306. /*
  3307. * WYMeditor : what you see is What You Mean web-based editor
  3308. * Copyright (c) 2008 Jean-Francois Hovinne, http://www.wymeditor.org/
  3309. * Dual licensed under the MIT (MIT-license.txt)
  3310. * and GPL (GPL-license.txt) licenses.
  3311. *
  3312. * For further information visit:
  3313. * http://www.wymeditor.org/
  3314. *
  3315. * File Name:
  3316. * jquery.wymeditor.explorer.js
  3317. * MSIE specific class and functions.
  3318. * See the documentation for more info.
  3319. *
  3320. * File Authors:
  3321. * Jean-Francois Hovinne (jf.hovinne a-t wymeditor dotorg)
  3322. * Bermi Ferrer (wymeditor a-t bermi dotorg)
  3323. * Frédéric Palluel-Lafleur (fpalluel a-t gmail dotcom)
  3324. * Jonatan Lundin (jonatan.lundin _at_ gmail.com)
  3325. */
  3326. WYMeditor.WymClassExplorer = function(wym) {
  3327. this._wym = wym;
  3328. this._class = "className";
  3329. this._newLine = "\r\n";
  3330. };
  3331. WYMeditor.WymClassExplorer.prototype.initIframe = function(iframe) {
  3332. //This function is executed twice, though it is called once!
  3333. //But MSIE needs that, otherwise designMode won't work.
  3334. //Weird.
  3335. this._iframe = iframe;
  3336. this._doc = iframe.contentWindow.document;
  3337. //add css rules from options
  3338. var styles = this._doc.styleSheets[0];
  3339. var aCss = eval(this._options.editorStyles);
  3340. this.addCssRules(this._doc, aCss);
  3341. this._doc.title = this._wym._index;
  3342. //set the text direction
  3343. jQuery('html', this._doc).attr('dir', this._options.direction);
  3344. //init html value
  3345. jQuery(this._doc.body).html(this._wym._html);
  3346. //handle events
  3347. var wym = this;
  3348. this._doc.body.onfocus = function()
  3349. {wym._doc.designMode = "on"; wym._doc = iframe.contentWindow.document;};
  3350. this._doc.onbeforedeactivate = function() {wym.saveCaret();};
  3351. this._doc.onkeyup = function() {
  3352. wym.saveCaret();
  3353. wym.keyup();
  3354. };
  3355. this._doc.onclick = function() {wym.saveCaret();};
  3356. this._doc.body.onbeforepaste = function() {
  3357. wym._iframe.contentWindow.event.returnValue = false;
  3358. };
  3359. this._doc.body.onpaste = function() {
  3360. wym._iframe.contentWindow.event.returnValue = false;
  3361. wym.paste(window.clipboardData.getData("Text"));
  3362. };
  3363. //callback can't be executed twice, so we check
  3364. if(this._initialized) {
  3365. //pre-bind functions
  3366. if(jQuery.isFunction(this._options.preBind)) this._options.preBind(this);
  3367. //bind external events
  3368. this._wym.bindEvents();
  3369. //post-init functions
  3370. if(jQuery.isFunction(this._options.postInit)) this._options.postInit(this);
  3371. //add event listeners to doc elements, e.g. images
  3372. this.listen();
  3373. }
  3374. this._initialized = true;
  3375. //init designMode
  3376. this._doc.designMode="on";
  3377. try{
  3378. // (bermi's note) noticed when running unit tests on IE6
  3379. // Is this really needed, it trigger an unexisting property on IE6
  3380. this._doc = iframe.contentWindow.document;
  3381. }catch(e){}
  3382. };
  3383. WYMeditor.WymClassExplorer.prototype._exec = function(cmd,param) {
  3384. switch(cmd) {
  3385. case WYMeditor.INDENT: case WYMeditor.OUTDENT:
  3386. var container = this.findUp(this.container(), WYMeditor.LI);
  3387. if(container)
  3388. this._doc.execCommand(cmd);
  3389. break;
  3390. default:
  3391. if(param) this._doc.execCommand(cmd,false,param);
  3392. else this._doc.execCommand(cmd);
  3393. break;
  3394. }
  3395. this.listen();
  3396. };
  3397. WYMeditor.WymClassExplorer.prototype.selected = function() {
  3398. var caretPos = this._iframe.contentWindow.document.caretPos;
  3399. if(caretPos!=null) {
  3400. if(caretPos.parentElement!=undefined)
  3401. return(caretPos.parentElement());
  3402. }
  3403. };
  3404. WYMeditor.WymClassExplorer.prototype.saveCaret = function() {
  3405. this._doc.caretPos = this._doc.selection.createRange();
  3406. };
  3407. WYMeditor.WymClassExplorer.prototype.addCssRule = function(styles, oCss) {
  3408. styles.addRule(oCss.name, oCss.css);
  3409. };
  3410. WYMeditor.WymClassExplorer.prototype.insert = function(html) {
  3411. // Get the current selection
  3412. var range = this._doc.selection.createRange();
  3413. // Check if the current selection is inside the editor
  3414. if ( jQuery(range.parentElement()).parents( this._options.iframeBodySelector ).is('*') ) {
  3415. try {
  3416. // Overwrite selection with provided html
  3417. range.pasteHTML(html);
  3418. } catch (e) { }
  3419. } else {
  3420. // Fall back to the internal paste function if there's no selection
  3421. this.paste(html);
  3422. }
  3423. };
  3424. //keyup handler
  3425. WYMeditor.WymClassExplorer.prototype.keyup = function() {
  3426. this._selected_image = null;
  3427. };
  3428. WYMeditor.WymClassExplorer.prototype.setFocusToNode = function(node) {
  3429. var range = this._doc.selection.createRange();
  3430. range.moveToElementText(node);
  3431. range.collapse(false);
  3432. range.move('character',-1);
  3433. range.select();
  3434. node.focus();
  3435. };
  3436. /*
  3437. * WYMeditor : what you see is What You Mean web-based editor
  3438. * Copyright (c) 2008 Jean-Francois Hovinne, http://www.wymeditor.org/
  3439. * Dual licensed under the MIT (MIT-license.txt)
  3440. * and GPL (GPL-license.txt) licenses.
  3441. *
  3442. * For further information visit:
  3443. * http://www.wymeditor.org/
  3444. *
  3445. * File Name:
  3446. * jquery.wymeditor.mozilla.js
  3447. * Gecko specific class and functions.
  3448. * See the documentation for more info.
  3449. *
  3450. * File Authors:
  3451. * Jean-Francois Hovinne (jf.hovinne a-t wymeditor dotorg)
  3452. * Volker Mische (vmx a-t gmx dotde)
  3453. * Bermi Ferrer (wymeditor a-t bermi dotorg)
  3454. * Frédéric Palluel-Lafleur (fpalluel a-t gmail dotcom)
  3455. */
  3456. WYMeditor.WymClassMozilla = function(wym) {
  3457. this._wym = wym;
  3458. this._class = "class";
  3459. this._newLine = "\n";
  3460. };
  3461. WYMeditor.WymClassMozilla.prototype.initIframe = function(iframe) {
  3462. this._iframe = iframe;
  3463. this._doc = iframe.contentDocument;
  3464. //add css rules from options
  3465. var styles = this._doc.styleSheets[0];
  3466. var aCss = eval(this._options.editorStyles);
  3467. this.addCssRules(this._doc, aCss);
  3468. this._doc.title = this._wym._index;
  3469. //set the text direction
  3470. jQuery('html', this._doc).attr('dir', this._options.direction);
  3471. //init html value
  3472. this.html(this._wym._html);
  3473. //init designMode
  3474. this.enableDesignMode();
  3475. //pre-bind functions
  3476. if(jQuery.isFunction(this._options.preBind)) this._options.preBind(this);
  3477. //bind external events
  3478. this._wym.bindEvents();
  3479. //bind editor keydown events
  3480. jQuery(this._doc).bind("keydown", this.keydown);
  3481. //bind editor keyup events
  3482. jQuery(this._doc).bind("keyup", this.keyup);
  3483. //bind editor focus events (used to reset designmode - Gecko bug)
  3484. jQuery(this._doc).bind("focus", this.enableDesignMode);
  3485. //post-init functions
  3486. if(jQuery.isFunction(this._options.postInit)) this._options.postInit(this);
  3487. //add event listeners to doc elements, e.g. images
  3488. this.listen();
  3489. };
  3490. /* @name html
  3491. * @description Get/Set the html value
  3492. */
  3493. WYMeditor.WymClassMozilla.prototype.html = function(html) {
  3494. if(typeof html === 'string') {
  3495. //disable designMode
  3496. try { this._doc.designMode = "off"; } catch(e) { };
  3497. //replace em by i and strong by bold
  3498. //(designMode issue)
  3499. html = html.replace(/<em([^>]*)>/gi, "<i$1>")
  3500. .replace(/<\/em>/gi, "</i>")
  3501. .replace(/<strong([^>]*)>/gi, "<b$1>")
  3502. .replace(/<\/strong>/gi, "</b>");
  3503. //update the html body
  3504. jQuery(this._doc.body).html(html);
  3505. //re-init designMode
  3506. this.enableDesignMode();
  3507. }
  3508. else return(jQuery(this._doc.body).html());
  3509. };
  3510. WYMeditor.WymClassMozilla.prototype._exec = function(cmd,param) {
  3511. if(!this.selected()) return(false);
  3512. switch(cmd) {
  3513. case WYMeditor.INDENT: case WYMeditor.OUTDENT:
  3514. var focusNode = this.selected();
  3515. var sel = this._iframe.contentWindow.getSelection();
  3516. var anchorNode = sel.anchorNode;
  3517. if(anchorNode.nodeName == "#text") anchorNode = anchorNode.parentNode;
  3518. focusNode = this.findUp(focusNode, WYMeditor.BLOCKS);
  3519. anchorNode = this.findUp(anchorNode, WYMeditor.BLOCKS);
  3520. if(focusNode && focusNode == anchorNode
  3521. && focusNode.tagName.toLowerCase() == WYMeditor.LI) {
  3522. var ancestor = focusNode.parentNode.parentNode;
  3523. if(focusNode.parentNode.childNodes.length>1
  3524. || ancestor.tagName.toLowerCase() == WYMeditor.OL
  3525. || ancestor.tagName.toLowerCase() == WYMeditor.UL)
  3526. this._doc.execCommand(cmd,'',null);
  3527. }
  3528. break;
  3529. default:
  3530. if(param) this._doc.execCommand(cmd,'',param);
  3531. else this._doc.execCommand(cmd,'',null);
  3532. }
  3533. //set to P if parent = BODY
  3534. var container = this.selected();
  3535. if(container.tagName.toLowerCase() == WYMeditor.BODY)
  3536. this._exec(WYMeditor.FORMAT_BLOCK, WYMeditor.P);
  3537. //add event handlers on doc elements
  3538. this.listen();
  3539. };
  3540. /* @name selected
  3541. * @description Returns the selected container
  3542. */
  3543. WYMeditor.WymClassMozilla.prototype.selected = function() {
  3544. var sel = this._iframe.contentWindow.getSelection();
  3545. var node = sel.focusNode;
  3546. if(node) {
  3547. if(node.nodeName == "#text") return(node.parentNode);
  3548. else return(node);
  3549. } else return(null);
  3550. };
  3551. WYMeditor.WymClassMozilla.prototype.addCssRule = function(styles, oCss) {
  3552. styles.insertRule(oCss.name + " {" + oCss.css + "}",
  3553. styles.cssRules.length);
  3554. };
  3555. //keydown handler, mainly used for keyboard shortcuts
  3556. WYMeditor.WymClassMozilla.prototype.keydown = function(evt) {
  3557. //'this' is the doc
  3558. var wym = WYMeditor.INSTANCES[this.title];
  3559. if(evt.ctrlKey){
  3560. if(evt.keyCode == 66){
  3561. //CTRL+b => STRONG
  3562. wym._exec(WYMeditor.BOLD);
  3563. return false;
  3564. }
  3565. if(evt.keyCode == 73){
  3566. //CTRL+i => EMPHASIS
  3567. wym._exec(WYMeditor.ITALIC);
  3568. return false;
  3569. }
  3570. }
  3571. };
  3572. //keyup handler, mainly used for cleanups
  3573. WYMeditor.WymClassMozilla.prototype.keyup = function(evt) {
  3574. //'this' is the doc
  3575. var wym = WYMeditor.INSTANCES[this.title];
  3576. wym._selected_image = null;
  3577. var container = null;
  3578. if(evt.keyCode == 13 && !evt.shiftKey) {
  3579. //RETURN key
  3580. //cleanup <br><br> between paragraphs
  3581. jQuery(wym._doc.body).children(WYMeditor.BR).remove();
  3582. //fix PRE bug #73
  3583. container = wym.selected();
  3584. if(container && container.tagName.toLowerCase() == WYMeditor.PRE)
  3585. wym._exec(WYMeditor.FORMAT_BLOCK, WYMeditor.P); //create P after PRE
  3586. }
  3587. else if(evt.keyCode != 8
  3588. && evt.keyCode != 17
  3589. && evt.keyCode != 46
  3590. && evt.keyCode != 224
  3591. && !evt.metaKey
  3592. && !evt.ctrlKey) {
  3593. //NOT BACKSPACE, NOT DELETE, NOT CTRL, NOT COMMAND
  3594. //text nodes replaced by P
  3595. container = wym.selected();
  3596. var name = container.tagName.toLowerCase();
  3597. //fix forbidden main containers
  3598. if(
  3599. name == "strong" ||
  3600. name == "b" ||
  3601. name == "em" ||
  3602. name == "i" ||
  3603. name == "sub" ||
  3604. name == "sup" ||
  3605. name == "a"
  3606. ) name = container.parentNode.tagName.toLowerCase();
  3607. if(name == WYMeditor.BODY) wym._exec(WYMeditor.FORMAT_BLOCK, WYMeditor.P);
  3608. }
  3609. };
  3610. WYMeditor.WymClassMozilla.prototype.enableDesignMode = function() {
  3611. if(this.designMode == "off") {
  3612. try {
  3613. this.designMode = "on";
  3614. this.execCommand("styleWithCSS", '', false);
  3615. } catch(e) { }
  3616. }
  3617. };
  3618. WYMeditor.WymClassMozilla.prototype.setFocusToNode = function(node) {
  3619. var range = document.createRange();
  3620. range.selectNode(node);
  3621. var selected = this._iframe.contentWindow.getSelection();
  3622. selected.addRange(range);
  3623. selected.collapse(node, node.childNodes.length);
  3624. this._iframe.contentWindow.focus();
  3625. };
  3626. WYMeditor.WymClassMozilla.prototype.openBlockTag = function(tag, attributes)
  3627. {
  3628. var attributes = this.validator.getValidTagAttributes(tag, attributes);
  3629. // Handle Mozilla styled spans
  3630. if(tag == 'span' && attributes.style){
  3631. var new_tag = this.getTagForStyle(attributes.style);
  3632. if(new_tag){
  3633. this._tag_stack.pop();
  3634. var tag = new_tag;
  3635. this._tag_stack.push(new_tag);
  3636. attributes.style = '';
  3637. }else{
  3638. return;
  3639. }
  3640. }
  3641. this.output += this.helper.tag(tag, attributes, true);
  3642. };
  3643. WYMeditor.WymClassMozilla.prototype.getTagForStyle = function(style) {
  3644. if(/bold/.test(style)) return 'strong';
  3645. if(/italic/.test(style)) return 'em';
  3646. if(/sub/.test(style)) return 'sub';
  3647. if(/sub/.test(style)) return 'super';
  3648. return false;
  3649. };
  3650. /*
  3651. * WYMeditor : what you see is What You Mean web-based editor
  3652. * Copyright (c) 2008 Jean-Francois Hovinne, http://www.wymeditor.org/
  3653. * Dual licensed under the MIT (MIT-license.txt)
  3654. * and GPL (GPL-license.txt) licenses.
  3655. *
  3656. * For further information visit:
  3657. * http://www.wymeditor.org/
  3658. *
  3659. * File Name:
  3660. * jquery.wymeditor.opera.js
  3661. * Opera specific class and functions.
  3662. * See the documentation for more info.
  3663. *
  3664. * File Authors:
  3665. * Jean-Francois Hovinne (jf.hovinne a-t wymeditor dotorg)
  3666. */
  3667. WYMeditor.WymClassOpera = function(wym) {
  3668. this._wym = wym;
  3669. this._class = "class";
  3670. this._newLine = "\r\n";
  3671. };
  3672. WYMeditor.WymClassOpera.prototype.initIframe = function(iframe) {
  3673. this._iframe = iframe;
  3674. this._doc = iframe.contentWindow.document;
  3675. //add css rules from options
  3676. var styles = this._doc.styleSheets[0];
  3677. var aCss = eval(this._options.editorStyles);
  3678. this.addCssRules(this._doc, aCss);
  3679. this._doc.title = this._wym._index;
  3680. //set the text direction
  3681. jQuery('html', this._doc).attr('dir', this._options.direction);
  3682. //init designMode
  3683. this._doc.designMode = "on";
  3684. //init html value
  3685. this.html(this._wym._html);
  3686. //pre-bind functions
  3687. if(jQuery.isFunction(this._options.preBind)) this._options.preBind(this);
  3688. //bind external events
  3689. this._wym.bindEvents();
  3690. //bind editor keydown events
  3691. jQuery(this._doc).bind("keydown", this.keydown);
  3692. //bind editor events
  3693. jQuery(this._doc).bind("keyup", this.keyup);
  3694. //post-init functions
  3695. if(jQuery.isFunction(this._options.postInit)) this._options.postInit(this);
  3696. //add event listeners to doc elements, e.g. images
  3697. this.listen();
  3698. };
  3699. WYMeditor.WymClassOpera.prototype._exec = function(cmd,param) {
  3700. if(param) this._doc.execCommand(cmd,false,param);
  3701. else this._doc.execCommand(cmd);
  3702. this.listen();
  3703. };
  3704. WYMeditor.WymClassOpera.prototype.selected = function() {
  3705. var sel=this._iframe.contentWindow.getSelection();
  3706. var node=sel.focusNode;
  3707. if(node) {
  3708. if(node.nodeName=="#text")return(node.parentNode);
  3709. else return(node);
  3710. } else return(null);
  3711. };
  3712. WYMeditor.WymClassOpera.prototype.addCssRule = function(styles, oCss) {
  3713. styles.insertRule(oCss.name + " {" + oCss.css + "}",
  3714. styles.cssRules.length);
  3715. };
  3716. //keydown handler
  3717. WYMeditor.WymClassOpera.prototype.keydown = function(evt) {
  3718. //'this' is the doc
  3719. var wym = WYMeditor.INSTANCES[this.title];
  3720. var sel = wym._iframe.contentWindow.getSelection();
  3721. startNode = sel.getRangeAt(0).startContainer;
  3722. //Get a P instead of no container
  3723. if(!jQuery(startNode).parentsOrSelf(
  3724. WYMeditor.MAIN_CONTAINERS.join(","))[0]
  3725. && !jQuery(startNode).parentsOrSelf('li')
  3726. && evt.keyCode != WYMeditor.KEY.ENTER
  3727. && evt.keyCode != WYMeditor.KEY.LEFT
  3728. && evt.keyCode != WYMeditor.KEY.UP
  3729. && evt.keyCode != WYMeditor.KEY.RIGHT
  3730. && evt.keyCode != WYMeditor.KEY.DOWN
  3731. && evt.keyCode != WYMeditor.KEY.BACKSPACE
  3732. && evt.keyCode != WYMeditor.KEY.DELETE)
  3733. wym._exec(WYMeditor.FORMAT_BLOCK, WYMeditor.P);
  3734. };
  3735. //keyup handler
  3736. WYMeditor.WymClassOpera.prototype.keyup = function(evt) {
  3737. //'this' is the doc
  3738. var wym = WYMeditor.INSTANCES[this.title];
  3739. wym._selected_image = null;
  3740. };
  3741. // TODO: implement me
  3742. WYMeditor.WymClassOpera.prototype.setFocusToNode = function(node) {
  3743. };
  3744. /*
  3745. * WYMeditor : what you see is What You Mean web-based editor
  3746. * Copyright (c) 2008 Jean-Francois Hovinne, http://www.wymeditor.org/
  3747. * Dual licensed under the MIT (MIT-license.txt)
  3748. * and GPL (GPL-license.txt) licenses.
  3749. *
  3750. * For further information visit:
  3751. * http://www.wymeditor.org/
  3752. *
  3753. * File Name:
  3754. * jquery.wymeditor.safari.js
  3755. * Safari specific class and functions.
  3756. * See the documentation for more info.
  3757. *
  3758. * File Authors:
  3759. * Jean-Francois Hovinne (jf.hovinne a-t wymeditor dotorg)
  3760. * Scott Lewis (lewiscot a-t gmail dotcom)
  3761. */
  3762. WYMeditor.WymClassSafari = function(wym) {
  3763. this._wym = wym;
  3764. this._class = "class";
  3765. this._newLine = "\n";
  3766. };
  3767. WYMeditor.WymClassSafari.prototype.initIframe = function(iframe) {
  3768. this._iframe = iframe;
  3769. this._doc = iframe.contentDocument;
  3770. //add css rules from options
  3771. var styles = this._doc.styleSheets[0];
  3772. var aCss = eval(this._options.editorStyles);
  3773. this.addCssRules(this._doc, aCss);
  3774. this._doc.title = this._wym._index;
  3775. //set the text direction
  3776. jQuery('html', this._doc).attr('dir', this._options.direction);
  3777. //init designMode
  3778. this._doc.designMode = "on";
  3779. //init html value
  3780. this.html(this._wym._html);
  3781. //pre-bind functions
  3782. if(jQuery.isFunction(this._options.preBind)) this._options.preBind(this);
  3783. //bind external events
  3784. this._wym.bindEvents();
  3785. //bind editor keydown events
  3786. jQuery(this._doc).bind("keydown", this.keydown);
  3787. //bind editor keyup events
  3788. jQuery(this._doc).bind("keyup", this.keyup);
  3789. //post-init functions
  3790. if(jQuery.isFunction(this._options.postInit)) this._options.postInit(this);
  3791. //add event listeners to doc elements, e.g. images
  3792. this.listen();
  3793. };
  3794. WYMeditor.WymClassSafari.prototype._exec = function(cmd,param) {
  3795. if(!this.selected()) return(false);
  3796. switch(cmd) {
  3797. case WYMeditor.INDENT: case WYMeditor.OUTDENT:
  3798. var focusNode = this.selected();
  3799. var sel = this._iframe.contentWindow.getSelection();
  3800. var anchorNode = sel.anchorNode;
  3801. if(anchorNode.nodeName == "#text") anchorNode = anchorNode.parentNode;
  3802. focusNode = this.findUp(focusNode, WYMeditor.BLOCKS);
  3803. anchorNode = this.findUp(anchorNode, WYMeditor.BLOCKS);
  3804. if(focusNode && focusNode == anchorNode
  3805. && focusNode.tagName.toLowerCase() == WYMeditor.LI) {
  3806. var ancestor = focusNode.parentNode.parentNode;
  3807. if(focusNode.parentNode.childNodes.length>1
  3808. || ancestor.tagName.toLowerCase() == WYMeditor.OL
  3809. || ancestor.tagName.toLowerCase() == WYMeditor.UL)
  3810. this._doc.execCommand(cmd,'',null);
  3811. }
  3812. break;
  3813. case WYMeditor.INSERT_ORDEREDLIST: case WYMeditor.INSERT_UNORDEREDLIST:
  3814. this._doc.execCommand(cmd,'',null);
  3815. //Safari creates lists in e.g. paragraphs.
  3816. //Find the container, and remove it.
  3817. var focusNode = this.selected();
  3818. var container = this.findUp(focusNode, WYMeditor.MAIN_CONTAINERS);
  3819. if(container) jQuery(container).replaceWith(jQuery(container).html());
  3820. break;
  3821. default:
  3822. if(param) this._doc.execCommand(cmd,'',param);
  3823. else this._doc.execCommand(cmd,'',null);
  3824. }
  3825. //set to P if parent = BODY
  3826. var container = this.selected();
  3827. if(container && container.tagName.toLowerCase() == WYMeditor.BODY)
  3828. this._exec(WYMeditor.FORMAT_BLOCK, WYMeditor.P);
  3829. //add event handlers on doc elements
  3830. this.listen();
  3831. };
  3832. /* @name selected
  3833. * @description Returns the selected container
  3834. */
  3835. WYMeditor.WymClassSafari.prototype.selected = function() {
  3836. var sel = this._iframe.contentWindow.getSelection();
  3837. var node = sel.focusNode;
  3838. if(node) {
  3839. if(node.nodeName == "#text") return(node.parentNode);
  3840. else return(node);
  3841. } else return(null);
  3842. };
  3843. WYMeditor.WymClassSafari.prototype.addCssRule = function(styles, oCss) {
  3844. styles.insertRule(oCss.name + " {" + oCss.css + "}",
  3845. styles.cssRules.length);
  3846. };
  3847. //keydown handler, mainly used for keyboard shortcuts
  3848. WYMeditor.WymClassSafari.prototype.keydown = function(evt) {
  3849. //'this' is the doc
  3850. var wym = WYMeditor.INSTANCES[this.title];
  3851. if(evt.ctrlKey){
  3852. if(evt.keyCode == 66){
  3853. //CTRL+b => STRONG
  3854. wym._exec(WYMeditor.BOLD);
  3855. return false;
  3856. }
  3857. if(evt.keyCode == 73){
  3858. //CTRL+i => EMPHASIS
  3859. wym._exec(WYMeditor.ITALIC);
  3860. return false;
  3861. }
  3862. }
  3863. };
  3864. //keyup handler, mainly used for cleanups
  3865. WYMeditor.WymClassSafari.prototype.keyup = function(evt) {
  3866. //'this' is the doc
  3867. var wym = WYMeditor.INSTANCES[this.title];
  3868. wym._selected_image = null;
  3869. var container = null;
  3870. if(evt.keyCode == 13 && !evt.shiftKey) {
  3871. //RETURN key
  3872. //cleanup <br><br> between paragraphs
  3873. jQuery(wym._doc.body).children(WYMeditor.BR).remove();
  3874. //fix PRE bug #73
  3875. container = wym.selected();
  3876. if(container && container.tagName.toLowerCase() == WYMeditor.PRE)
  3877. wym._exec(WYMeditor.FORMAT_BLOCK, WYMeditor.P); //create P after PRE
  3878. }
  3879. //fix #112
  3880. if(evt.keyCode == 13 && evt.shiftKey) {
  3881. wym._exec('InsertLineBreak');
  3882. }
  3883. if(evt.keyCode != 8
  3884. && evt.keyCode != 17
  3885. && evt.keyCode != 46
  3886. && evt.keyCode != 224
  3887. && !evt.metaKey
  3888. && !evt.ctrlKey) {
  3889. //NOT BACKSPACE, NOT DELETE, NOT CTRL, NOT COMMAND
  3890. //text nodes replaced by P
  3891. container = wym.selected();
  3892. var name = container.tagName.toLowerCase();
  3893. //fix forbidden main containers
  3894. if(
  3895. name == "strong" ||
  3896. name == "b" ||
  3897. name == "em" ||
  3898. name == "i" ||
  3899. name == "sub" ||
  3900. name == "sup" ||
  3901. name == "a" ||
  3902. name == "span" //fix #110
  3903. ) name = container.parentNode.tagName.toLowerCase();
  3904. if(name == WYMeditor.BODY || name == WYMeditor.DIV) wym._exec(WYMeditor.FORMAT_BLOCK, WYMeditor.P); //fix #110 for DIV
  3905. }
  3906. };
  3907. //TODO
  3908. WYMeditor.WymClassSafari.prototype.setFocusToNode = function(node) {
  3909. /*var range = document.createRange();
  3910. range.selectNode(node);
  3911. var selected = this._iframe.contentWindow.getSelection();
  3912. selected.addRange(range);
  3913. selected.collapse(node, node.childNodes.length);
  3914. this._iframe.contentWindow.focus();*/
  3915. };
  3916. WYMeditor.WymClassSafari.prototype.openBlockTag = function(tag, attributes)
  3917. {
  3918. var attributes = this.validator.getValidTagAttributes(tag, attributes);
  3919. // Handle Safari styled spans
  3920. if(tag == 'span' && attributes.style) {
  3921. var new_tag = this.getTagForStyle(attributes.style);
  3922. if(new_tag){
  3923. this._tag_stack.pop();
  3924. var tag = new_tag;
  3925. this._tag_stack.push(new_tag);
  3926. attributes.style = '';
  3927. } else {
  3928. return;
  3929. }
  3930. }
  3931. this.output += this.helper.tag(tag, attributes, true);
  3932. };
  3933. WYMeditor.WymClassSafari.prototype.getTagForStyle = function(style) {
  3934. if(/bold/.test(style)) return 'strong';
  3935. if(/italic/.test(style)) return 'em';
  3936. if(/sub/.test(style)) return 'sub';
  3937. if(/super/.test(style)) return 'sup';
  3938. return false;
  3939. };
  3940. /* @name xhtml
  3941. * @description Cleans up the HTML
  3942. */
  3943. WYMeditor.editor.prototype.xhtml = function() {
  3944. jQuery('.Apple-style-span', this._doc.body).removeClass('Apple-style-span');
  3945. return this.parser.parse(this.html());
  3946. };