o: ActiveSupport::Cache::Entry :@compressedF:@expires_in0:@created_atf1337334245.102462: @value"™{I" class:EFI"BundledAsset;FI"logical_path;FI"ems/news.js;TI" pathname;F"c/private/var/www/html/development/projects/rails/ems/app/assets/javascripts/ems/news.js.coffeeI"content_type;FI"application/javascript;FI" mtime;FI"2012-05-18T10:40:41+01:00;FI" length;Fi<—I" digest;F"%fcce4a4d3e046171d83f65f1cb481527I" source;FI"<—; (function () { WMDEditor = function (options) { this.options = WMDEditor.util.extend({}, WMDEditor.defaults, options || {}); wmdBase(this, this.options); this.startEditor(); }; window.WMDEditor = WMDEditor; WMDEditor.defaults = { // {{{ version: 2.1, output_format: "markdown", lineLength: 40, button_bar: "wmd-button-bar", preview: "wmd-preview", output: "wmd-output", input: "wmd-input", // The text that appears on the upper part of the dialog box when // entering links. imageDialogText: "

Enter the image URL.

You can also add a title, which will be displayed as a tool tip.

Example:
http://i.imgur.com/1cZl4.jpg

", linkDialogText: "

Enter the web address.

You can also add a title, which will be displayed as a tool tip.

Example:
http://www.google.com/

", // The default text that appears in the dialog input box when entering // links. imageDefaultText: "http://", linkDefaultText: "http://", imageDirectory: "images/", // The link and title for the help button helpLink: "/wmd/markdownhelp.html", helpHoverTitle: "Markdown Syntax", helpTarget: "_blank", // Some intervals in ms. These can be adjusted to reduce the control's load. previewPollInterval: 500, pastePollInterval: 100, buttons: "bold italic link blockquote code image ol ul heading hr undo redo help", autoFormatting: { list: true, quote: true, code: true, }, modifierKeys: { //replace this with null or false to disable key-combos bold: "b", italic: "i", link: "l", quote: "q", code: "k", image: "g", orderedList: "o", unorderedList: "u", heading: "h", horizontalRule: "r", redo: "y", undo: "z" }, tagFilter: { enabled: false, allowedTags: /^(<\/?(b|blockquote|code|del|dd|dl|dt|em|h1|h2|h3|i|kbd|li|ol|p|pre|s|sup|sub|strong|strike|ul)>|<(br|hr)\s?\/?>)$/i, patternLink: /^(]+")?\s?>|<\/a>)$/i, patternImage: /^(]*")?(\stitle="[^"<>]*")?\s?\/?>)$/i } }; // }}} WMDEditor.prototype = { getPanels: function () { return { buttonBar: (typeof this.options.button_bar == 'string') ? document.getElementById(this.options.button_bar) : this.options.button_bar, preview: (typeof this.options.preview == 'string') ? document.getElementById(this.options.preview) : this.options.preview, output: (typeof this.options.output == 'string') ? document.getElementById(this.options.output) : this.options.output, input: (typeof this.options.input == 'string') ? document.getElementById(this.options.input) : this.options.input }; }, startEditor: function () { this.panels = this.getPanels(); this.previewMgr = new PreviewManager(this); edit = new this.editor(this.previewMgr.refresh); this.previewMgr.refresh(true); } }; var util = { // {{{ // Returns true if the DOM element is visible, false if it's hidden. // Checks if display is anything other than none. isVisible: function (elem) { // shamelessly copied from jQuery return elem.offsetWidth > 0 || elem.offsetHeight > 0; }, // Adds a listener callback to a DOM element which is fired on a specified // event. addEvent: function (elem, event, listener) { if (elem.attachEvent) { // IE only. The "on" is mandatory. elem.attachEvent("on" + event, listener); } else { // Other browsers. elem.addEventListener(event, listener, false); } }, // Removes a listener callback from a DOM element which is fired on a specified // event. removeEvent: function (elem, event, listener) { if (elem.detachEvent) { // IE only. The "on" is mandatory. elem.detachEvent("on" + event, listener); } else { // Other browsers. elem.removeEventListener(event, listener, false); } }, // Converts \r\n and \r to \n. fixEolChars: function (text) { text = text.replace(/\r\n/g, "\n"); text = text.replace(/\r/g, "\n"); return text; }, // Extends a regular expression. Returns a new RegExp // using pre + regex + post as the expression. // Used in a few functions where we have a base // expression and we want to pre- or append some // conditions to it (e.g. adding "$" to the end). // The flags are unchanged. // // regex is a RegExp, pre and post are strings. extendRegExp: function (regex, pre, post) { if (pre === null || pre === undefined) { pre = ""; } if (post === null || post === undefined) { post = ""; } var pattern = regex.toString(); var flags = ""; // Replace the flags with empty space and store them. // Technically, this can match incorrect flags like "gmm". var result = pattern.match(/\/([gim]*)$/); if (result === null) { flags = result[0]; } else { flags = ""; } // Remove the flags and slash delimiters from the regular expression. pattern = pattern.replace(/(^\/|\/[gim]*$)/g, ""); pattern = pre + pattern + post; return new RegExp(pattern, flags); }, // Sets the image for a button passed to the WMD editor. // Returns a new element with the image attached. // Adds several style properties to the image. // // XXX-ANAND: Is this used anywhere? createImage: function (img) { var imgPath = imageDirectory + img; var elem = document.createElement("img"); elem.className = "wmd-button"; elem.src = imgPath; return elem; }, // This simulates a modal dialog box and asks for the URL when you // click the hyperlink or image buttons. // // text: The html for the input box. // defaultInputText: The default value that appears in the input box. // makeLinkMarkdown: The function which is executed when the prompt is dismissed, either via OK or Cancel prompt: function (text, defaultInputText, makeLinkMarkdown, promptType) { // These variables need to be declared at this level since they are used // in multiple functions. var dialog; // The dialog box. var background; // The background beind the dialog box. var input; // The text box where you enter the hyperlink. var titleInput; // The text box for the image's title text var newWinCheckbox; //The checkbox to choose if a link should be opened in a new window. if (defaultInputText === undefined) { defaultInputText = ""; } // Used as a keydown event handler. Esc dismisses the prompt. // Key code 27 is ESC. var checkEscape = function (key) { var code = (key.charCode || key.keyCode); if (code === 27) { close(true); } }; // Dismisses the hyperlink input box. // isCancel is true if we don't care about the input text. // isCancel is false if we are going to keep the text. var close = function (isCancel) { util.removeEvent(document.body, "keydown", checkEscape); var text = input.value+ (titleInput.value?' "'+titleInput.value+'"':''); if (isCancel) { text = null; } else { // Fixes common pasting errors. text = text.replace('http://http://', 'http://'); text = text.replace('http://https://', 'https://'); text = text.replace('http://ftp://', 'ftp://'); if (promptType=='link' && newWinCheckbox.checked) text = '!'+text; } dialog.parentNode.removeChild(dialog); background.parentNode.removeChild(background); makeLinkMarkdown(text); return false; }; // Creates the background behind the hyperlink text entry box. // Most of this has been moved to CSS but the div creation and // browser-specific hacks remain here. var createBackground = function () { background = document.createElement("div"); background.className = "wmd-prompt-background"; style = background.style; style.position = "absolute"; style.top = "0"; style.zIndex = "10000"; // Some versions of Konqueror don't support transparent colors // so we make the whole window transparent. // // Is this necessary on modern konqueror browsers? if (browser.isKonqueror) { style.backgroundColor = "transparent"; } else if (browser.isIE) { style.filter = "alpha(opacity=50)"; } else { style.opacity = "0.5"; } var pageSize = position.getPageSize(); style.height = pageSize[1] + "px"; if (browser.isIE) { style.left = document.documentElement.scrollLeft; style.width = document.documentElement.clientWidth; } else { style.left = "0"; style.width = "100%"; } document.body.appendChild(background); }; // Create the text input box form/window. var createDialog = function () { // The main dialog box. dialog = document.createElement("div"); dialog.className = "wmd-prompt-dialog"; dialog.style.padding = "10px;"; dialog.style.position = "fixed"; dialog.style.width = "400px"; dialog.style.zIndex = "10001"; // The dialog text. var question = document.createElement("div"); question.innerHTML = text; question.style.padding = "5px"; dialog.appendChild(question); // The web form container for the text box and buttons. var form = document.createElement("form"); form.onsubmit = function () { return close(false); }; var style = form.style; style.padding = "0"; style.margin = "0"; style.cssFloat = "left"; style.width = "100%"; style.textAlign = "center"; style.position = "relative"; dialog.appendChild(form); var label = document.createElement("label"); style = label.style; style.display = "block"; style.width = "80%"; style.marginLeft = style.marginRight = "auto"; style.textAlign = "left"; form.appendChild(label); label.appendChild(document.createTextNode(promptType+" URL:")); // The input text box input = document.createElement("input"); input.type = "text"; input.value = defaultInputText; style = input.style; style.display = "block"; style.width = "100%"; style.marginLeft = style.marginRight = "auto"; label.appendChild(input); label = document.createElement("label"); style = label.style; style.display = "block"; style.width = "80%"; style.marginLeft = style.marginRight = "auto"; style.textAlign = "left"; form.appendChild(label); label.appendChild(document.createTextNode(promptType+" Title (Hover Text):")); // The input text box titleInput = document.createElement("input"); titleInput.type = "text"; style = titleInput.style; style.display = "block"; style.width = "100%"; style.marginLeft = style.marginRight = "auto"; label.appendChild(titleInput); if (promptType=='link') { label = document.createElement("label"); style = label.style; style.display = "block"; style.textAlign = "center"; form.appendChild(label); newWinCheckbox = document.createElement("input"); newWinCheckbox.type = 'checkbox'; newWinCheckbox.value = '!'; label.appendChild(newWinCheckbox); label.appendChild(document.createTextNode(" Have this link open in a new window")); } // The ok button var okButton = document.createElement("input"); okButton.type = "button"; okButton.onclick = function () { return close(false); }; okButton.value = "OK"; style = okButton.style; style.margin = "10px"; style.display = "inline"; style.width = "7em"; // The cancel button var cancelButton = document.createElement("input"); cancelButton.type = "button"; cancelButton.onclick = function () { return close(true); }; cancelButton.value = "Cancel"; style = cancelButton.style; style.margin = "10px"; style.display = "inline"; style.width = "7em"; // The order of these buttons is different on macs. if (/mac/.test(nav.platform.toLowerCase())) { form.appendChild(cancelButton); form.appendChild(okButton); } else { form.appendChild(okButton); form.appendChild(cancelButton); } util.addEvent(document.body, "keydown", checkEscape); dialog.style.top = "50%"; dialog.style.left = "50%"; dialog.style.display = "block"; if (browser.isIE_5or6) { dialog.style.position = "absolute"; dialog.style.top = document.documentElement.scrollTop + 200 + "px"; dialog.style.left = "50%"; } document.body.appendChild(dialog); // This has to be done AFTER adding the dialog to the form if you // want it to be centered. dialog.style.marginTop = -(position.getHeight(dialog) / 2) + "px"; dialog.style.marginLeft = -(position.getWidth(dialog) / 2) + "px"; }; createBackground(); // Why is this in a zero-length timeout? // Is it working around a browser bug? window.setTimeout(function () { createDialog(); var defTextLen = defaultInputText.length; if (input.selectionStart !== undefined) { input.selectionStart = 0; input.selectionEnd = defTextLen; } else if (input.createTextRange) { var range = input.createTextRange(); range.collapse(false); range.moveStart("character", -defTextLen); range.moveEnd("character", defTextLen); range.select(); } input.focus(); }, 0); }, extend: function () { function _update(a, b) { for (var k in b) if (b.hasOwnProperty(k)){ if (typeof a[k] === 'object' && typeof b[k] === 'object') _update(a[k], b[k]); //if property is an object or array, merge the contents instead of overwriting else a[k] = b[k]; } return a; } var d = {}; for (var i = 0; i < arguments.length; i++) { _update(d, arguments[i]); } return d; } }; // }}} var position = { // {{{ // UNFINISHED // The assignment in the while loop makes jslint cranky. // I'll change it to a better loop later. getTop: function (elem, isInner) { var result = elem.offsetTop; if (!isInner) { while (elem = elem.offsetParent) { result += elem.offsetTop; } } return result; }, getHeight: function (elem) { return elem.offsetHeight || elem.scrollHeight; }, getWidth: function (elem) { return elem.offsetWidth || elem.scrollWidth; }, getPageSize: function () { var scrollWidth, scrollHeight; var innerWidth, innerHeight; // It's not very clear which blocks work with which browsers. if (self.innerHeight && self.scrollMaxY) { scrollWidth = document.body.scrollWidth; scrollHeight = self.innerHeight + self.scrollMaxY; } else if (document.body.scrollHeight > document.body.offsetHeight) { scrollWidth = document.body.scrollWidth; scrollHeight = document.body.scrollHeight; } else { scrollWidth = document.body.offsetWidth; scrollHeight = document.body.offsetHeight; } if (self.innerHeight) { // Non-IE browser innerWidth = self.innerWidth; innerHeight = self.innerHeight; } else if (document.documentElement && document.documentElement.clientHeight) { // Some versions of IE (IE 6 w/ a DOCTYPE declaration) innerWidth = document.documentElement.clientWidth; innerHeight = document.documentElement.clientHeight; } else if (document.body) { // Other versions of IE innerWidth = document.body.clientWidth; innerHeight = document.body.clientHeight; } var maxWidth = Math.max(scrollWidth, innerWidth); var maxHeight = Math.max(scrollHeight, innerHeight); return [maxWidth, maxHeight, innerWidth, innerHeight]; } }; // }}} // The input textarea state/contents. // This is used to implement undo/redo by the undo manager. var TextareaState = function (textarea, wmd) { // {{{ // Aliases var stateObj = this; var inputArea = textarea; this.init = function () { if (!util.isVisible(inputArea)) { return; } this.setInputAreaSelectionStartEnd(); this.scrollTop = inputArea.scrollTop; if (!this.text && inputArea.selectionStart || inputArea.selectionStart === 0) { this.text = inputArea.value; } }; // Sets the selected text in the input box after we've performed an // operation. this.setInputAreaSelection = function () { if (!util.isVisible(inputArea)) { return; } if (inputArea.selectionStart !== undefined && !browser.isOpera) { inputArea.focus(); inputArea.selectionStart = stateObj.start; inputArea.selectionEnd = stateObj.end; inputArea.scrollTop = stateObj.scrollTop; } else if (document.selection) { if (typeof(document.activeElement)!="unknown" && document.activeElement && document.activeElement !== inputArea) { return; } inputArea.focus(); var range = inputArea.createTextRange(); range.moveStart("character", -inputArea.value.length); range.moveEnd("character", -inputArea.value.length); range.moveEnd("character", stateObj.end); range.moveStart("character", stateObj.start); range.select(); } }; this.setInputAreaSelectionStartEnd = function () { if (inputArea.selectionStart || inputArea.selectionStart === 0) { stateObj.start = inputArea.selectionStart; stateObj.end = inputArea.selectionEnd; } else if (document.selection) { stateObj.text = util.fixEolChars(inputArea.value); // IE loses the selection in the textarea when buttons are // clicked. On IE we cache the selection and set a flag // which we check for here. var range; if (wmd.ieRetardedClick && wmd.ieCachedRange) { range = wmd.ieCachedRange; wmd.ieRetardedClick = false; } else { range = document.selection.createRange(); } var fixedRange = util.fixEolChars(range.text); var marker = "\x07"; var markedRange = marker + fixedRange + marker; range.text = markedRange; var inputText = util.fixEolChars(inputArea.value); range.moveStart("character", -markedRange.length); range.text = fixedRange; stateObj.start = inputText.indexOf(marker); stateObj.end = inputText.lastIndexOf(marker) - marker.length; var len = stateObj.text.length - util.fixEolChars(inputArea.value).length; if (len) { range.moveStart("character", -fixedRange.length); while (len--) { fixedRange += "\n"; stateObj.end += 1; } range.text = fixedRange; } this.setInputAreaSelection(); } }; // Restore this state into the input area. this.restore = function () { if (stateObj.text != undefined && stateObj.text != inputArea.value) { inputArea.value = stateObj.text; } this.setInputAreaSelection(); inputArea.scrollTop = stateObj.scrollTop; }; // Gets a collection of HTML chunks from the inptut textarea. this.getChunks = function () { var chunk = new Chunks(); chunk.before = util.fixEolChars(stateObj.text.substring(0, stateObj.start)); chunk.startTag = ""; chunk.selection = util.fixEolChars(stateObj.text.substring(stateObj.start, stateObj.end)); chunk.endTag = ""; chunk.after = util.fixEolChars(stateObj.text.substring(stateObj.end)); chunk.scrollTop = stateObj.scrollTop; return chunk; }; // Sets the TextareaState properties given a chunk of markdown. this.setChunks = function (chunk) { chunk.before = chunk.before + chunk.startTag; chunk.after = chunk.endTag + chunk.after; if (browser.isOpera) { chunk.before = chunk.before.replace(/\n/g, "\r\n"); chunk.selection = chunk.selection.replace(/\n/g, "\r\n"); chunk.after = chunk.after.replace(/\n/g, "\r\n"); } this.start = chunk.before.length; this.end = chunk.before.length + chunk.selection.length; this.text = chunk.before + chunk.selection + chunk.after; this.scrollTop = chunk.scrollTop; }; this.init(); }; // }}} // Chunks {{{ // before: contains all the text in the input box BEFORE the selection. // after: contains all the text in the input box AFTER the selection. var Chunks = function () {}; // startRegex: a regular expression to find the start tag // endRegex: a regular expresssion to find the end tag Chunks.prototype.findTags = function (startRegex, endRegex) { var chunkObj = this; var regex; if (startRegex) { regex = util.extendRegExp(startRegex, "", "$"); this.before = this.before.replace(regex, function (match) { chunkObj.startTag = chunkObj.startTag + match; return ""; }); regex = util.extendRegExp(startRegex, "^", ""); this.selection = this.selection.replace(regex, function (match) { chunkObj.startTag = chunkObj.startTag + match; return ""; }); } if (endRegex) { regex = util.extendRegExp(endRegex, "", "$"); this.selection = this.selection.replace(regex, function (match) { chunkObj.endTag = match + chunkObj.endTag; return ""; }); regex = util.extendRegExp(endRegex, "^", ""); this.after = this.after.replace(regex, function (match) { chunkObj.endTag = match + chunkObj.endTag; return ""; }); } }; // If remove is false, the whitespace is transferred // to the before/after regions. // // If remove is true, the whitespace disappears. Chunks.prototype.trimWhitespace = function (remove) { this.selection = this.selection.replace(/^(\s*)/, ""); if (!remove) { this.before += re.$1; } this.selection = this.selection.replace(/(\s*)$/, ""); if (!remove) { this.after = re.$1 + this.after; } }; Chunks.prototype.addBlankLines = function (nLinesBefore, nLinesAfter, findExtraNewlines) { if (nLinesBefore === undefined) { nLinesBefore = 1; } if (nLinesAfter === undefined) { nLinesAfter = 1; } nLinesBefore++; nLinesAfter++; var regexText; var replacementText; // New bug discovered in Chrome, which appears to be related to use of RegExp.$1 // Hack it to hold the match results. Sucks because we're double matching... var match = /(^\n*)/.exec(this.selection); this.selection = this.selection.replace(/(^\n*)/, ""); this.startTag = this.startTag + (match ? match[1] : ""); match = /(\n*$)/.exec(this.selection); this.selection = this.selection.replace(/(\n*$)/, ""); this.endTag = this.endTag + (match ? match[1] : ""); match = /(^\n*)/.exec(this.startTag); this.startTag = this.startTag.replace(/(^\n*)/, ""); this.before = this.before + (match ? match[1] : ""); match = /(\n*$)/.exec(this.endTag); this.endTag = this.endTag.replace(/(\n*$)/, ""); this.after = this.after + (match ? match[1] : ""); if (this.before) { regexText = replacementText = ""; while (nLinesBefore--) { regexText += "\\n?"; replacementText += "\n"; } if (findExtraNewlines) { regexText = "\\n*"; } this.before = this.before.replace(new re(regexText + "$", ""), replacementText); } if (this.after) { regexText = replacementText = ""; while (nLinesAfter--) { regexText += "\\n?"; replacementText += "\n"; } if (findExtraNewlines) { regexText = "\\n*"; } this.after = this.after.replace(new re(regexText, ""), replacementText); } }; // }}} - END CHUNKS // Watches the input textarea, polling at an interval and runs // a callback function if anything has changed. var InputPoller = function (textarea, callback, interval) { // {{{ var pollerObj = this; var inputArea = textarea; // Stored start, end and text. Used to see if there are changes to the input. var lastStart; var lastEnd; var markdown; var killHandle; // Used to cancel monitoring on destruction. // Checks to see if anything has changed in the textarea. // If so, it runs the callback. this.tick = function () { if (!util.isVisible(inputArea)) { return; } // Update the selection start and end, text. if (inputArea.selectionStart || inputArea.selectionStart === 0) { var start = inputArea.selectionStart; var end = inputArea.selectionEnd; if (start != lastStart || end != lastEnd) { lastStart = start; lastEnd = end; if (markdown != inputArea.value) { markdown = inputArea.value; return true; } } } return false; }; var doTickCallback = function () { if (!util.isVisible(inputArea)) { return; } // If anything has changed, call the function. if (pollerObj.tick()) { callback(); } }; // Set how often we poll the textarea for changes. var assignInterval = function () { killHandle = window.setInterval(doTickCallback, interval); }; this.destroy = function () { window.clearInterval(killHandle); }; assignInterval(); }; // }}} var PreviewManager = function (wmd) { // {{{ var managerObj = this; var converter; var poller; var timeout; var elapsedTime; var oldInputText; var htmlOut; var maxDelay = 3000; var startType = "delayed"; // The other legal value is "manual" // Adds event listeners to elements and creates the input poller. var setupEvents = function (inputElem, listener) { util.addEvent(inputElem, "input", listener); inputElem.onpaste = listener; inputElem.ondrop = listener; util.addEvent(inputElem, "keypress", listener); util.addEvent(inputElem, "keydown", listener); // previewPollInterval is set at the top of this file. poller = new InputPoller(wmd.panels.input, listener, wmd.options.previewPollInterval); }; var getDocScrollTop = function () { var result = 0; if (window.innerHeight) { result = window.pageYOffset; } else if (document.documentElement && document.documentElement.scrollTop) { result = document.documentElement.scrollTop; } else if (document.body) { result = document.body.scrollTop; } return result; }; var makePreviewHtml = function () { // If there are no registered preview and output panels // there is nothing to do. if (!wmd.panels.preview && !wmd.panels.output) { return; } var text = wmd.panels.input.value; if (text && text == oldInputText) { return; // Input text hasn't changed. } else { oldInputText = text; } var prevTime = new Date().getTime(); if (!converter && wmd.showdown) { converter = new wmd.showdown.converter(); } if (converter) { text = converter.makeHtml(text); } // Calculate the processing time of the HTML creation. // It's used as the delay time in the event listener. var currTime = new Date().getTime(); elapsedTime = currTime - prevTime; pushPreviewHtml(text); htmlOut = text; }; // setTimeout is already used. Used as an event listener. var applyTimeout = function () { if (timeout) { window.clearTimeout(timeout); timeout = undefined; } if (startType !== "manual") { var delay = 0; if (startType === "delayed") { delay = elapsedTime; } if (delay > maxDelay) { delay = maxDelay; } timeout = window.setTimeout(makePreviewHtml, delay); } }; var getScaleFactor = function (panel) { if (panel.scrollHeight <= panel.clientHeight) { return 1; } return panel.scrollTop / (panel.scrollHeight - panel.clientHeight); }; var setPanelScrollTops = function () { if (wmd.panels.preview) { wmd.panels.preview.scrollTop = (wmd.panels.preview.scrollHeight - wmd.panels.preview.clientHeight) * getScaleFactor(wmd.panels.preview);; } if (wmd.panels.output) { wmd.panels.output.scrollTop = (wmd.panels.output.scrollHeight - wmd.panels.output.clientHeight) * getScaleFactor(wmd.panels.output);; } }; this.refresh = function (requiresRefresh) { if (requiresRefresh) { oldInputText = ""; makePreviewHtml(); } else { applyTimeout(); } }; this.processingTime = function () { return elapsedTime; }; // The output HTML this.output = function () { return htmlOut; }; // The mode can be "manual" or "delayed" this.setUpdateMode = function (mode) { startType = mode; managerObj.refresh(); }; var isFirstTimeFilled = true; var pushPreviewHtml = function (text) { var emptyTop = position.getTop(wmd.panels.input) - getDocScrollTop(); // Send the encoded HTML to the output textarea/div. if (wmd.panels.output) { // The value property is only defined if the output is a textarea. if (wmd.panels.output.value !== undefined) { wmd.panels.output.value = text; } // Otherwise we are just replacing the text in a div. // Send the HTML wrapped in

				else {
					var newText = text.replace(/&/g, "&");
					newText = newText.replace(/
"; } } if (wmd.panels.preview) { // original WMD code allowed javascript injection, like this: // // now, we first ensure elements (and attributes of IMG and A elements) are in a whitelist // and if not in whitelist, replace with blanks in preview to prevent XSS attacks // when editing malicious markdown // code courtesy of https://github.com/polestarsoft/wmd/commit/e7a09c9170ea23e7e806425f46d7423af2a74641 if (wmd.options.tagFilter.enabled) { text = text.replace(/<[^<>]*>?/gi, function (tag) { return (tag.match(wmd.options.tagFilter.allowedTags) || tag.match(wmd.options.tagFilter.patternLink) || tag.match(wmd.options.tagFilter.patternImage)) ? tag : ""; }); } wmd.panels.preview.innerHTML = text; } setPanelScrollTops(); if (isFirstTimeFilled) { isFirstTimeFilled = false; return; } var fullTop = position.getTop(wmd.panels.input) - getDocScrollTop(); if (browser.isIE) { window.setTimeout(function () { window.scrollBy(0, fullTop - emptyTop); }, 0); } else { window.scrollBy(0, fullTop - emptyTop); } }; var init = function () { setupEvents(wmd.panels.input, applyTimeout); makePreviewHtml(); if (wmd.panels.preview) { wmd.panels.preview.scrollTop = 0; } if (wmd.panels.output) { wmd.panels.output.scrollTop = 0; } }; this.destroy = function () { if (poller) { poller.destroy(); } }; init(); }; // }}} // Handles pushing and popping TextareaStates for undo/redo commands. // I should rename the stack variables to list. var UndoManager = function (wmd, textarea, pastePollInterval, callback) { // {{{ var undoObj = this; var undoStack = []; // A stack of undo states var stackPtr = 0; // The index of the current state var mode = "none"; var lastState; // The last state var poller; var timer; // The setTimeout handle for cancelling the timer var inputStateObj; // Set the mode for later logic steps. var setMode = function (newMode, noSave) { if (mode != newMode) { mode = newMode; if (!noSave) { saveState(); } } if (!browser.isIE || mode != "moving") { timer = window.setTimeout(refreshState, 1); } else { inputStateObj = null; } }; var refreshState = function () { inputStateObj = new TextareaState(textarea, wmd); poller.tick(); timer = undefined; }; this.setCommandMode = function () { mode = "command"; saveState(); timer = window.setTimeout(refreshState, 0); }; this.canUndo = function () { return stackPtr > 1; }; this.canRedo = function () { if (undoStack[stackPtr + 1]) { return true; } return false; }; // Removes the last state and restores it. this.undo = function () { if (undoObj.canUndo()) { if (lastState) { // What about setting state -1 to null or checking for undefined? lastState.restore(); lastState = null; } else { undoStack[stackPtr] = new TextareaState(textarea, wmd); undoStack[--stackPtr].restore(); if (callback) { callback(); } } } mode = "none"; textarea.focus(); refreshState(); }; // Redo an action. this.redo = function () { if (undoObj.canRedo()) { undoStack[++stackPtr].restore(); if (callback) { callback(); } } mode = "none"; textarea.focus(); refreshState(); }; // Push the input area state to the stack. var saveState = function () { var currState = inputStateObj || new TextareaState(textarea, wmd); if (!currState) { return false; } if (mode == "moving") { if (!lastState) { lastState = currState; } return; } if (lastState) { if (undoStack[stackPtr - 1].text != lastState.text) { undoStack[stackPtr++] = lastState; } lastState = null; } undoStack[stackPtr++] = currState; undoStack[stackPtr + 1] = null; if (callback) { callback(); } }; var handleCtrlYZ = function (event) { var handled = false; if (event.ctrlKey || event.metaKey) { // IE and Opera do not support charCode. var keyCode = event.charCode || event.keyCode; var keyCodeChar = String.fromCharCode(keyCode); switch (keyCodeChar) { case "y": undoObj.redo(); handled = true; break; case "z": if (!event.shiftKey) { undoObj.undo(); } else { undoObj.redo(); } handled = true; break; } } if (handled) { if (event.preventDefault) { event.preventDefault(); } if (window.event) { window.event.returnValue = false; } return; } }; // Set the mode depending on what is going on in the input area. var handleModeChange = function (event) { if (!event.ctrlKey && !event.metaKey) { var keyCode = event.keyCode; if ((keyCode >= 33 && keyCode <= 40) || (keyCode >= 63232 && keyCode <= 63235)) { // 33 - 40: page up/dn and arrow keys // 63232 - 63235: page up/dn and arrow keys on safari setMode("moving"); } else if (keyCode == 8 || keyCode == 46 || keyCode == 127) { // 8: backspace // 46: delete // 127: delete setMode("deleting"); } else if (keyCode == 13) { // 13: Enter setMode("newlines"); } else if (keyCode == 27) { // 27: escape setMode("escape"); } else if ((keyCode < 16 || keyCode > 20) && keyCode != 91) { // 16-20 are shift, etc. // 91: left window key // I think this might be a little messed up since there are // a lot of nonprinting keys above 20. setMode("typing"); } } }; var setEventHandlers = function () { util.addEvent(textarea, "keypress", function (event) { // keyCode 89: y // keyCode 90: z if ((event.ctrlKey || event.metaKey) && (event.keyCode == 89 || event.keyCode == 90)) { event.preventDefault(); } }); var handlePaste = function () { if (browser.isIE || (inputStateObj && inputStateObj.text != textarea.value)) { if (timer == undefined) { mode = "paste"; saveState(); refreshState(); } } }; poller = new InputPoller(textarea, handlePaste, pastePollInterval); util.addEvent(textarea, "keydown", handleCtrlYZ); util.addEvent(textarea, "keydown", handleModeChange); util.addEvent(textarea, "mousedown", function () { setMode("moving"); }); textarea.onpaste = handlePaste; textarea.ondrop = handlePaste; }; var init = function () { setEventHandlers(); refreshState(); saveState(); }; this.destroy = function () { if (poller) { poller.destroy(); } }; init(); }; //}}} WMDEditor.util = util; WMDEditor.position = position; WMDEditor.TextareaState = TextareaState; WMDEditor.InputPoller = InputPoller; WMDEditor.PreviewManager = PreviewManager; WMDEditor.UndoManager = UndoManager; // A few handy aliases for readability. var doc = window.document; var re = window.RegExp; var nav = window.navigator; function get_browser() { var b = {}; b.isIE = /msie/.test(nav.userAgent.toLowerCase()); b.isIE_5or6 = /msie 6/.test(nav.userAgent.toLowerCase()) || /msie 5/.test(nav.userAgent.toLowerCase()); b.isIE_7plus = b.isIE && !b.isIE_5or6; b.isOpera = /opera/.test(nav.userAgent.toLowerCase()); b.isKonqueror = /konqueror/.test(nav.userAgent.toLowerCase()); return b; } // Used to work around some browser bugs where we can't use feature testing. var browser = get_browser(); var wmdBase = function (wmd, wmd_options) { // {{{ // Some namespaces. //wmd.Util = {}; //wmd.Position = {}; wmd.Command = {}; wmd.Global = {}; wmd.buttons = {}; wmd.showdown = window.Showdown; var util = WMDEditor.util; var position = WMDEditor.position; var command = wmd.Command; // Internet explorer has problems with CSS sprite buttons that use HTML // lists. When you click on the background image "button", IE will // select the non-existent link text and discard the selection in the // textarea. The solution to this is to cache the textarea selection // on the button's mousedown event and set a flag. In the part of the // code where we need to grab the selection, we check for the flag // and, if it's set, use the cached area instead of querying the // textarea. // // This ONLY affects Internet Explorer (tested on versions 6, 7 // and 8) and ONLY on button clicks. Keyboard shortcuts work // normally since the focus never leaves the textarea. wmd.ieCachedRange = null; // cached textarea selection wmd.ieRetardedClick = false; // flag // I think my understanding of how the buttons and callbacks are stored in the array is incomplete. wmd.editor = function (previewRefreshCallback) { // {{{ if (!previewRefreshCallback) { previewRefreshCallback = function () {}; } var inputBox = wmd.panels.input; var offsetHeight = 0; var editObj = this; var mainDiv; var mainSpan; var div; // This name is pretty ambiguous. I should rename this. // Used to cancel recurring events from setInterval. var creationHandle; var undoMgr; // The undo manager // Perform the button's action. var doClick = function (button) { inputBox.focus(); if (button.textOp) { if (undoMgr) { undoMgr.setCommandMode(); } var state = new TextareaState(wmd.panels.input, wmd); if (!state) { return; } var chunks = state.getChunks(); // Some commands launch a "modal" prompt dialog. Javascript // can't really make a modal dialog box and the WMD code // will continue to execute while the dialog is displayed. // This prevents the dialog pattern I'm used to and means // I can't do something like this: // // var link = CreateLinkDialog(); // makeMarkdownLink(link); // // Instead of this straightforward method of handling a // dialog I have to pass any code which would execute // after the dialog is dismissed (e.g. link creation) // in a function parameter. // // Yes this is awkward and I think it sucks, but there's // no real workaround. Only the image and link code // create dialogs and require the function pointers. var fixupInputArea = function () { inputBox.focus(); if (chunks) { state.setChunks(chunks); } state.restore(); previewRefreshCallback(); }; var useDefaultText = true; var noCleanup = button.textOp(chunks, fixupInputArea, useDefaultText); if (!noCleanup) { fixupInputArea(); } } if (button.execute) { button.execute(editObj); } }; var setUndoRedoButtonStates = function () { if (undoMgr) { if (wmd.buttons["wmd-undo-button"]) setupButton(wmd.buttons["wmd-undo-button"], undoMgr.canUndo()); if (wmd.buttons["wmd-redo-button"]) setupButton(wmd.buttons["wmd-redo-button"], undoMgr.canRedo()); } }; var setupButton = function (button, isEnabled) { if (isEnabled) { button.className = button.className.replace(new RegExp("(^|\\s+)disabled(\\s+|$)"), ' '); // IE tries to select the background image "button" text (it's // implemented in a list item) so we have to cache the selection // on mousedown. if (browser.isIE) { button.onmousedown = function () { wmd.ieRetardedClick = true; wmd.ieCachedRange = document.selection.createRange(); }; } if (!button.isHelp) { button.onclick = function () { if (this.onmouseout) { this.onmouseout(); } doClick(this); return false; }; } } else { button.className += (button.className ? ' ' : '') + 'disabled'; button.onmouseover = button.onmouseout = button.onclick = function () {}; } }; var makeSpritedButtonRow = function () { var buttonBar = (typeof wmd_options.button_bar == 'string') ? document.getElementById(wmd_options.button_bar || "wmd-button-bar") : wmd_options.button_bar; var normalYShift = "0px"; var disabledYShift = "-20px"; var highlightYShift = "-40px"; var buttonRow = document.createElement("ul"); buttonRow.className = "wmd-button-row"; buttonRow = buttonBar.appendChild(buttonRow); var xoffset = 0; function createButton(name, title, textOp) { var button = document.createElement("li"); wmd.buttons[name] = button; button.className = "wmd-button " + name; button.XShift = xoffset + "px"; xoffset -= 20; if (title) button.title = title; if (textOp) button.textOp = textOp; return button; } function addButton(name, title, textOp) { var button = createButton(name, title, textOp); setupButton(button, true); buttonRow.appendChild(button); return button; } function addSpacer() { var spacer = document.createElement("li"); spacer.className = "wmd-spacer"; buttonRow.appendChild(spacer); return spacer; } var buttonlist = wmd_options.buttons.split(' '); for (var i=0;i Ctrl+B", command.doBold); break; case "italic": addButton("wmd-italic-button", "Emphasis Ctrl+I", command.doItalic); break; case 'link': addButton("wmd-link-button", "Hyperlink Ctrl+L", function (chunk, postProcessing, useDefaultText) { return command.doLinkOrImage(chunk, postProcessing, false); }); break; case 'blockquote': addButton("wmd-quote-button", "Blockquote
Ctrl+Q", command.doBlockquote); break; case 'code': addButton("wmd-code-button", "Code Sample
 Ctrl+K", command.doCode);
						break;
					case 'image':
						addButton("wmd-image-button", "Image  Ctrl+G", function (chunk, postProcessing, useDefaultText) {
							return command.doLinkOrImage(chunk, postProcessing, true);
						});
						break;
					case 'ol':
						addButton("wmd-olist-button", "Numbered List 
    Ctrl+O", function (chunk, postProcessing, useDefaultText) { command.doList(chunk, postProcessing, true, useDefaultText); }); break; case 'ul': addButton("wmd-ulist-button", "Bulleted List