'
+ + (escaped ? code : escape(code, true))
+ + '\n
';
+ }
+
+ return ''
+ + (escaped ? code : escape(code, true))
+ + '\n
\n';
+};
+
+Renderer.prototype.blockquote = function(quote) {
+ return '\n' + quote + '\n'; +}; + +Renderer.prototype.html = function(html) { + return html; +}; + +Renderer.prototype.heading = function(text, level, raw) { + return '
' + text + '
\n'; +}; + +Renderer.prototype.table = function(header, body) { + return '' + text + '
';
+};
+
+Renderer.prototype.br = function() {
+ return this.options.xhtml ? 'An error occured:
' + + escape(e.message + '', true) + + ''; + } + throw e; + } +} + +/** + * Options + */ + +marked.options = +marked.setOptions = function(opt) { + merge(marked.defaults, opt); + return marked; +}; + +marked.defaults = { + gfm: true, + tables: true, + breaks: false, + pedantic: false, + sanitize: false, + sanitizer: null, + mangle: true, + smartLists: false, + silent: false, + highlight: null, + langPrefix: 'lang-', + smartypants: false, + headerPrefix: '', + renderer: new Renderer, + xhtml: false +}; + +/** + * Expose + */ + +marked.Parser = Parser; +marked.parser = Parser.parse; + +marked.Renderer = Renderer; + +marked.Lexer = Lexer; +marked.lexer = Lexer.lex; + +marked.InlineLexer = InlineLexer; +marked.inlineLexer = InlineLexer.output; + +marked.parse = marked; + +if (typeof module !== 'undefined' && typeof exports === 'object') { + module.exports = marked; +} else if (typeof define === 'function' && define.amd) { + define(function() { return marked; }); +} else { + this.marked = marked; +} + +}).call(function() { + return this || (typeof window !== 'undefined' ? window : global); +}()); + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{}],18:[function(require,module,exports){ +(function (Buffer,__dirname){ +'use strict'; + +/** + * Typo is a JavaScript implementation of a spellchecker using hunspell-style + * dictionaries. + */ + +/** + * Typo constructor. + * + * @param {String} [dictionary] The locale code of the dictionary being used. e.g., + * "en_US". This is only used to auto-load dictionaries. + * @param {String} [affData] The data from the dictionary's .aff file. If omitted + * and Typo.js is being used in a Chrome extension, the .aff + * file will be loaded automatically from + * lib/typo/dictionaries/[dictionary]/[dictionary].aff + * In other environments, it will be loaded from + * [settings.dictionaryPath]/dictionaries/[dictionary]/[dictionary].aff + * @param {String} [wordsData] The data from the dictionary's .dic file. If omitted + * and Typo.js is being used in a Chrome extension, the .dic + * file will be loaded automatically from + * lib/typo/dictionaries/[dictionary]/[dictionary].dic + * In other environments, it will be loaded from + * [settings.dictionaryPath]/dictionaries/[dictionary]/[dictionary].dic + * @param {Object} [settings] Constructor settings. Available properties are: + * {String} [dictionaryPath]: path to load dictionary from in non-chrome + * environment. + * {Object} [flags]: flag information. + * + * + * @returns {Typo} A Typo object. + */ + +var Typo = function (dictionary, affData, wordsData, settings) { + settings = settings || {}; + + this.dictionary = null; + + this.rules = {}; + this.dictionaryTable = {}; + + this.compoundRules = []; + this.compoundRuleCodes = {}; + + this.replacementTable = []; + + this.flags = settings.flags || {}; + + if (dictionary) { + this.dictionary = dictionary; + + if (typeof window !== 'undefined' && 'chrome' in window && 'extension' in window.chrome && 'getURL' in window.chrome.extension) { + if (!affData) affData = this._readFile(chrome.extension.getURL("lib/typo/dictionaries/" + dictionary + "/" + dictionary + ".aff")); + if (!wordsData) wordsData = this._readFile(chrome.extension.getURL("lib/typo/dictionaries/" + dictionary + "/" + dictionary + ".dic")); + } else { + if (settings.dictionaryPath) { + var path = settings.dictionaryPath; + } + else if (typeof __dirname !== 'undefined') { + var path = __dirname + '/dictionaries'; + } + else { + var path = './dictionaries'; + } + + if (!affData) affData = this._readFile(path + "/" + dictionary + "/" + dictionary + ".aff"); + if (!wordsData) wordsData = this._readFile(path + "/" + dictionary + "/" + dictionary + ".dic"); + } + + this.rules = this._parseAFF(affData); + + // Save the rule codes that are used in compound rules. + this.compoundRuleCodes = {}; + + for (var i = 0, _len = this.compoundRules.length; i < _len; i++) { + var rule = this.compoundRules[i]; + + for (var j = 0, _jlen = rule.length; j < _jlen; j++) { + this.compoundRuleCodes[rule[j]] = []; + } + } + + // If we add this ONLYINCOMPOUND flag to this.compoundRuleCodes, then _parseDIC + // will do the work of saving the list of words that are compound-only. + if ("ONLYINCOMPOUND" in this.flags) { + this.compoundRuleCodes[this.flags.ONLYINCOMPOUND] = []; + } + + this.dictionaryTable = this._parseDIC(wordsData); + + // Get rid of any codes from the compound rule codes that are never used + // (or that were special regex characters). Not especially necessary... + for (var i in this.compoundRuleCodes) { + if (this.compoundRuleCodes[i].length == 0) { + delete this.compoundRuleCodes[i]; + } + } + + // Build the full regular expressions for each compound rule. + // I have a feeling (but no confirmation yet) that this method of + // testing for compound words is probably slow. + for (var i = 0, _len = this.compoundRules.length; i < _len; i++) { + var ruleText = this.compoundRules[i]; + + var expressionText = ""; + + for (var j = 0, _jlen = ruleText.length; j < _jlen; j++) { + var character = ruleText[j]; + + if (character in this.compoundRuleCodes) { + expressionText += "(" + this.compoundRuleCodes[character].join("|") + ")"; + } + else { + expressionText += character; + } + } + + this.compoundRules[i] = new RegExp(expressionText, "i"); + } + } + + return this; +}; + +Typo.prototype = { + /** + * Loads a Typo instance from a hash of all of the Typo properties. + * + * @param object obj A hash of Typo properties, probably gotten from a JSON.parse(JSON.stringify(typo_instance)). + */ + + load : function (obj) { + for (var i in obj) { + this[i] = obj[i]; + } + + return this; + }, + + /** + * Read the contents of a file. + * + * @param {String} path The path (relative) to the file. + * @param {String} [charset="ISO8859-1"] The expected charset of the file + * @returns string The file data. + */ + + _readFile : function (path, charset) { + if (!charset) charset = "utf8"; + + if (typeof XMLHttpRequest !== 'undefined') { + var req = new XMLHttpRequest(); + req.open("GET", path, false); + + if (req.overrideMimeType) + req.overrideMimeType("text/plain; charset=" + charset); + + req.send(null); + + return req.responseText; + } + else if (typeof require !== 'undefined') { + // Node.js + var fs = require("fs"); + + try { + if (fs.existsSync(path)) { + var stats = fs.statSync(path); + + var fileDescriptor = fs.openSync(path, 'r'); + + var buffer = new Buffer(stats.size); + + fs.readSync(fileDescriptor, buffer, 0, buffer.length, null); + + return buffer.toString(charset, 0, buffer.length); + } + else { + console.log("Path " + path + " does not exist."); + } + } catch (e) { + console.log(e); + return ''; + } + } + }, + + /** + * Parse the rules out from a .aff file. + * + * @param {String} data The contents of the affix file. + * @returns object The rules from the file. + */ + + _parseAFF : function (data) { + var rules = {}; + + // Remove comment lines + data = this._removeAffixComments(data); + + var lines = data.split("\n"); + + for (var i = 0, _len = lines.length; i < _len; i++) { + var line = lines[i]; + + var definitionParts = line.split(/\s+/); + + var ruleType = definitionParts[0]; + + if (ruleType == "PFX" || ruleType == "SFX") { + var ruleCode = definitionParts[1]; + var combineable = definitionParts[2]; + var numEntries = parseInt(definitionParts[3], 10); + + var entries = []; + + for (var j = i + 1, _jlen = i + 1 + numEntries; j < _jlen; j++) { + var line = lines[j]; + + var lineParts = line.split(/\s+/); + var charactersToRemove = lineParts[2]; + + var additionParts = lineParts[3].split("/"); + + var charactersToAdd = additionParts[0]; + if (charactersToAdd === "0") charactersToAdd = ""; + + var continuationClasses = this.parseRuleCodes(additionParts[1]); + + var regexToMatch = lineParts[4]; + + var entry = {}; + entry.add = charactersToAdd; + + if (continuationClasses.length > 0) entry.continuationClasses = continuationClasses; + + if (regexToMatch !== ".") { + if (ruleType === "SFX") { + entry.match = new RegExp(regexToMatch + "$"); + } + else { + entry.match = new RegExp("^" + regexToMatch); + } + } + + if (charactersToRemove != "0") { + if (ruleType === "SFX") { + entry.remove = new RegExp(charactersToRemove + "$"); + } + else { + entry.remove = charactersToRemove; + } + } + + entries.push(entry); + } + + rules[ruleCode] = { "type" : ruleType, "combineable" : (combineable == "Y"), "entries" : entries }; + + i += numEntries; + } + else if (ruleType === "COMPOUNDRULE") { + var numEntries = parseInt(definitionParts[1], 10); + + for (var j = i + 1, _jlen = i + 1 + numEntries; j < _jlen; j++) { + var line = lines[j]; + + var lineParts = line.split(/\s+/); + this.compoundRules.push(lineParts[1]); + } + + i += numEntries; + } + else if (ruleType === "REP") { + var lineParts = line.split(/\s+/); + + if (lineParts.length === 3) { + this.replacementTable.push([ lineParts[1], lineParts[2] ]); + } + } + else { + // ONLYINCOMPOUND + // COMPOUNDMIN + // FLAG + // KEEPCASE + // NEEDAFFIX + + this.flags[ruleType] = definitionParts[1]; + } + } + + return rules; + }, + + /** + * Removes comment lines and then cleans up blank lines and trailing whitespace. + * + * @param {String} data The data from an affix file. + * @return {String} The cleaned-up data. + */ + + _removeAffixComments : function (data) { + // Remove comments + data = data.replace(/#.*$/mg, ""); + + // Trim each line + data = data.replace(/^\s\s*/m, '').replace(/\s\s*$/m, ''); + + // Remove blank lines. + data = data.replace(/\n{2,}/g, "\n"); + + // Trim the entire string + data = data.replace(/^\s\s*/, '').replace(/\s\s*$/, ''); + + return data; + }, + + /** + * Parses the words out from the .dic file. + * + * @param {String} data The data from the dictionary file. + * @returns object The lookup table containing all of the words and + * word forms from the dictionary. + */ + + _parseDIC : function (data) { + data = this._removeDicComments(data); + + var lines = data.split("\n"); + var dictionaryTable = {}; + + function addWord(word, rules) { + // Some dictionaries will list the same word multiple times with different rule sets. + if (!(word in dictionaryTable) || typeof dictionaryTable[word] != 'object') { + dictionaryTable[word] = []; + } + + dictionaryTable[word].push(rules); + } + + // The first line is the number of words in the dictionary. + for (var i = 1, _len = lines.length; i < _len; i++) { + var line = lines[i]; + + var parts = line.split("/", 2); + + var word = parts[0]; + + // Now for each affix rule, generate that form of the word. + if (parts.length > 1) { + var ruleCodesArray = this.parseRuleCodes(parts[1]); + + // Save the ruleCodes for compound word situations. + if (!("NEEDAFFIX" in this.flags) || ruleCodesArray.indexOf(this.flags.NEEDAFFIX) == -1) { + addWord(word, ruleCodesArray); + } + + for (var j = 0, _jlen = ruleCodesArray.length; j < _jlen; j++) { + var code = ruleCodesArray[j]; + + var rule = this.rules[code]; + + if (rule) { + var newWords = this._applyRule(word, rule); + + for (var ii = 0, _iilen = newWords.length; ii < _iilen; ii++) { + var newWord = newWords[ii]; + + addWord(newWord, []); + + if (rule.combineable) { + for (var k = j + 1; k < _jlen; k++) { + var combineCode = ruleCodesArray[k]; + + var combineRule = this.rules[combineCode]; + + if (combineRule) { + if (combineRule.combineable && (rule.type != combineRule.type)) { + var otherNewWords = this._applyRule(newWord, combineRule); + + for (var iii = 0, _iiilen = otherNewWords.length; iii < _iiilen; iii++) { + var otherNewWord = otherNewWords[iii]; + addWord(otherNewWord, []); + } + } + } + } + } + } + } + + if (code in this.compoundRuleCodes) { + this.compoundRuleCodes[code].push(word); + } + } + } + else { + addWord(word.trim(), []); + } + } + + return dictionaryTable; + }, + + + /** + * Removes comment lines and then cleans up blank lines and trailing whitespace. + * + * @param {String} data The data from a .dic file. + * @return {String} The cleaned-up data. + */ + + _removeDicComments : function (data) { + // I can't find any official documentation on it, but at least the de_DE + // dictionary uses tab-indented lines as comments. + + // Remove comments + data = data.replace(/^\t.*$/mg, ""); + + return data; + }, + + parseRuleCodes : function (textCodes) { + if (!textCodes) { + return []; + } + else if (!("FLAG" in this.flags)) { + return textCodes.split(""); + } + else if (this.flags.FLAG === "long") { + var flags = []; + + for (var i = 0, _len = textCodes.length; i < _len; i += 2) { + flags.push(textCodes.substr(i, 2)); + } + + return flags; + } + else if (this.flags.FLAG === "num") { + return textCode.split(","); + } + }, + + /** + * Applies an affix rule to a word. + * + * @param {String} word The base word. + * @param {Object} rule The affix rule. + * @returns {String[]} The new words generated by the rule. + */ + + _applyRule : function (word, rule) { + var entries = rule.entries; + var newWords = []; + + for (var i = 0, _len = entries.length; i < _len; i++) { + var entry = entries[i]; + + if (!entry.match || word.match(entry.match)) { + var newWord = word; + + if (entry.remove) { + newWord = newWord.replace(entry.remove, ""); + } + + if (rule.type === "SFX") { + newWord = newWord + entry.add; + } + else { + newWord = entry.add + newWord; + } + + newWords.push(newWord); + + if ("continuationClasses" in entry) { + for (var j = 0, _jlen = entry.continuationClasses.length; j < _jlen; j++) { + var continuationRule = this.rules[entry.continuationClasses[j]]; + + if (continuationRule) { + newWords = newWords.concat(this._applyRule(newWord, continuationRule)); + } + /* + else { + // This shouldn't happen, but it does, at least in the de_DE dictionary. + // I think the author mistakenly supplied lower-case rule codes instead + // of upper-case. + } + */ + } + } + } + } + + return newWords; + }, + + /** + * Checks whether a word or a capitalization variant exists in the current dictionary. + * The word is trimmed and several variations of capitalizations are checked. + * If you want to check a word without any changes made to it, call checkExact() + * + * @see http://blog.stevenlevithan.com/archives/faster-trim-javascript re:trimming function + * + * @param {String} aWord The word to check. + * @returns {Boolean} + */ + + check : function (aWord) { + // Remove leading and trailing whitespace + var trimmedWord = aWord.replace(/^\s\s*/, '').replace(/\s\s*$/, ''); + + if (this.checkExact(trimmedWord)) { + return true; + } + + // The exact word is not in the dictionary. + if (trimmedWord.toUpperCase() === trimmedWord) { + // The word was supplied in all uppercase. + // Check for a capitalized form of the word. + var capitalizedWord = trimmedWord[0] + trimmedWord.substring(1).toLowerCase(); + + if (this.hasFlag(capitalizedWord, "KEEPCASE")) { + // Capitalization variants are not allowed for this word. + return false; + } + + if (this.checkExact(capitalizedWord)) { + return true; + } + } + + var lowercaseWord = trimmedWord.toLowerCase(); + + if (lowercaseWord !== trimmedWord) { + if (this.hasFlag(lowercaseWord, "KEEPCASE")) { + // Capitalization variants are not allowed for this word. + return false; + } + + // Check for a lowercase form + if (this.checkExact(lowercaseWord)) { + return true; + } + } + + return false; + }, + + /** + * Checks whether a word exists in the current dictionary. + * + * @param {String} word The word to check. + * @returns {Boolean} + */ + + checkExact : function (word) { + var ruleCodes = this.dictionaryTable[word]; + + if (typeof ruleCodes === 'undefined') { + // Check if this might be a compound word. + if ("COMPOUNDMIN" in this.flags && word.length >= this.flags.COMPOUNDMIN) { + for (var i = 0, _len = this.compoundRules.length; i < _len; i++) { + if (word.match(this.compoundRules[i])) { + return true; + } + } + } + + return false; + } + else if (typeof ruleCodes === 'object') { // this.dictionary['hasOwnProperty'] will be a function. + for (var i = 0, _len = ruleCodes.length; i < _len; i++) { + if (!this.hasFlag(word, "ONLYINCOMPOUND", ruleCodes[i])) { + return true; + } + } + + return false; + } + }, + + /** + * Looks up whether a given word is flagged with a given flag. + * + * @param {String} word The word in question. + * @param {String} flag The flag in question. + * @return {Boolean} + */ + + hasFlag : function (word, flag, wordFlags) { + if (flag in this.flags) { + if (typeof wordFlags === 'undefined') { + var wordFlags = Array.prototype.concat.apply([], this.dictionaryTable[word]); + } + + if (wordFlags && wordFlags.indexOf(this.flags[flag]) !== -1) { + return true; + } + } + + return false; + }, + + /** + * Returns a list of suggestions for a misspelled word. + * + * @see http://www.norvig.com/spell-correct.html for the basis of this suggestor. + * This suggestor is primitive, but it works. + * + * @param {String} word The misspelling. + * @param {Number} [limit=5] The maximum number of suggestions to return. + * @returns {String[]} The array of suggestions. + */ + + alphabet : "", + + suggest : function (word, limit) { + if (!limit) limit = 5; + + if (this.check(word)) return []; + + // Check the replacement table. + for (var i = 0, _len = this.replacementTable.length; i < _len; i++) { + var replacementEntry = this.replacementTable[i]; + + if (word.indexOf(replacementEntry[0]) !== -1) { + var correctedWord = word.replace(replacementEntry[0], replacementEntry[1]); + + if (this.check(correctedWord)) { + return [ correctedWord ]; + } + } + } + + var self = this; + self.alphabet = "abcdefghijklmnopqrstuvwxyz"; + + /* + if (!self.alphabet) { + // Use the alphabet as implicitly defined by the words in the dictionary. + var alphaHash = {}; + + for (var i in self.dictionaryTable) { + for (var j = 0, _len = i.length; j < _len; j++) { + alphaHash[i[j]] = true; + } + } + + for (var i in alphaHash) { + self.alphabet += i; + } + + var alphaArray = self.alphabet.split(""); + alphaArray.sort(); + self.alphabet = alphaArray.join(""); + } + */ + + function edits1(words) { + var rv = []; + + for (var ii = 0, _iilen = words.length; ii < _iilen; ii++) { + var word = words[ii]; + + var splits = []; + + for (var i = 0, _len = word.length + 1; i < _len; i++) { + splits.push([ word.substring(0, i), word.substring(i, word.length) ]); + } + + var deletes = []; + + for (var i = 0, _len = splits.length; i < _len; i++) { + var s = splits[i]; + + if (s[1]) { + deletes.push(s[0] + s[1].substring(1)); + } + } + + var transposes = []; + + for (var i = 0, _len = splits.length; i < _len; i++) { + var s = splits[i]; + + if (s[1].length > 1) { + transposes.push(s[0] + s[1][1] + s[1][0] + s[1].substring(2)); + } + } + + var replaces = []; + + for (var i = 0, _len = splits.length; i < _len; i++) { + var s = splits[i]; + + if (s[1]) { + for (var j = 0, _jlen = self.alphabet.length; j < _jlen; j++) { + replaces.push(s[0] + self.alphabet[j] + s[1].substring(1)); + } + } + } + + var inserts = []; + + for (var i = 0, _len = splits.length; i < _len; i++) { + var s = splits[i]; + + if (s[1]) { + for (var j = 0, _jlen = self.alphabet.length; j < _jlen; j++) { + replaces.push(s[0] + self.alphabet[j] + s[1]); + } + } + } + + rv = rv.concat(deletes); + rv = rv.concat(transposes); + rv = rv.concat(replaces); + rv = rv.concat(inserts); + } + + return rv; + } + + function known(words) { + var rv = []; + + for (var i = 0; i < words.length; i++) { + if (self.check(words[i])) { + rv.push(words[i]); + } + } + + return rv; + } + + function correct(word) { + // Get the edit-distance-1 and edit-distance-2 forms of this word. + var ed1 = edits1([word]); + var ed2 = edits1(ed1); + + var corrections = known(ed1).concat(known(ed2)); + + // Sort the edits based on how many different ways they were created. + var weighted_corrections = {}; + + for (var i = 0, _len = corrections.length; i < _len; i++) { + if (!(corrections[i] in weighted_corrections)) { + weighted_corrections[corrections[i]] = 1; + } + else { + weighted_corrections[corrections[i]] += 1; + } + } + + var sorted_corrections = []; + + for (var i in weighted_corrections) { + sorted_corrections.push([ i, weighted_corrections[i] ]); + } + + function sorter(a, b) { + if (a[1] < b[1]) { + return -1; + } + + return 1; + } + + sorted_corrections.sort(sorter).reverse(); + + var rv = []; + + for (var i = 0, _len = Math.min(limit, sorted_corrections.length); i < _len; i++) { + if (!self.hasFlag(sorted_corrections[i][0], "NOSUGGEST")) { + rv.push(sorted_corrections[i][0]); + } + } + + return rv; + } + + return correct(word); + } +}; + +// Support for use as a node.js module. +if (typeof module !== 'undefined') { + module.exports = Typo; +} +}).call(this,require("buffer").Buffer,"/node_modules/typo-js") +},{"buffer":3,"fs":2}],19:[function(require,module,exports){ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +var CodeMirror = require("codemirror"); + +CodeMirror.commands.tabAndIndentMarkdownList = function (cm) { + var ranges = cm.listSelections(); + var pos = ranges[0].head; + var eolState = cm.getStateAfter(pos.line); + var inList = eolState.list !== false; + + if (inList) { + cm.execCommand("indentMore"); + return; + } + + if (cm.options.indentWithTabs) { + cm.execCommand("insertTab"); + } + else { + var spaces = Array(cm.options.tabSize + 1).join(" "); + cm.replaceSelection(spaces); + } +}; + +CodeMirror.commands.shiftTabAndUnindentMarkdownList = function (cm) { + var ranges = cm.listSelections(); + var pos = ranges[0].head; + var eolState = cm.getStateAfter(pos.line); + var inList = eolState.list !== false; + + if (inList) { + cm.execCommand("indentLess"); + return; + } + + if (cm.options.indentWithTabs) { + cm.execCommand("insertTab"); + } + else { + var spaces = Array(cm.options.tabSize + 1).join(" "); + cm.replaceSelection(spaces); + } +}; + +},{"codemirror":10}],20:[function(require,module,exports){ +/*global require,module*/ +"use strict"; +var CodeMirror = require("codemirror"); +require("codemirror/addon/edit/continuelist.js"); +require("./codemirror/tablist"); +require("codemirror/addon/display/fullscreen.js"); +require("codemirror/mode/markdown/markdown.js"); +require("codemirror/addon/mode/overlay.js"); +require("codemirror/addon/display/placeholder.js"); +require("codemirror/addon/selection/mark-selection.js"); +require("codemirror/mode/gfm/gfm.js"); +require("codemirror/mode/xml/xml.js"); +var CodeMirrorSpellChecker = require("codemirror-spell-checker"); +var marked = require("marked"); + + +// Some variables +var isMac = /Mac/.test(navigator.platform); + +// Mapping of actions that can be bound to keyboard shortcuts or toolbar buttons +var bindings = { + "toggleBold": toggleBold, + "toggleItalic": toggleItalic, + "drawLink": drawLink, + "toggleHeadingSmaller": toggleHeadingSmaller, + "toggleHeadingBigger": toggleHeadingBigger, + "drawImage": drawImage, + "toggleBlockquote": toggleBlockquote, + "toggleOrderedList": toggleOrderedList, + "toggleUnorderedList": toggleUnorderedList, + "toggleCodeBlock": toggleCodeBlock, + "togglePreview": togglePreview, + "toggleStrikethrough": toggleStrikethrough, + "toggleHeading1": toggleHeading1, + "toggleHeading2": toggleHeading2, + "toggleHeading3": toggleHeading3, + "cleanBlock": cleanBlock, + "drawTable": drawTable, + "drawHorizontalRule": drawHorizontalRule, + "undo": undo, + "redo": redo, + "toggleSideBySide": toggleSideBySide, + "toggleFullScreen": toggleFullScreen +}; + +var shortcuts = { + "toggleBold": "Cmd-B", + "toggleItalic": "Cmd-I", + "drawLink": "Cmd-K", + "toggleHeadingSmaller": "Cmd-H", + "toggleHeadingBigger": "Shift-Cmd-H", + "cleanBlock": "Cmd-E", + "drawImage": "Cmd-Alt-I", + "toggleBlockquote": "Cmd-'", + "toggleOrderedList": "Cmd-Alt-L", + "toggleUnorderedList": "Cmd-L", + "toggleCodeBlock": "Cmd-Alt-C", + "togglePreview": "Cmd-P", + "toggleSideBySide": "F9", + "toggleFullScreen": "F11" +}; + +var getBindingName = function(f) { + for(var key in bindings) { + if(bindings[key] === f) { + return key; + } + } + return null; +}; + +var isMobile = function() { + var check = false; + (function(a) { + if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test(a) || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0, 4))) check = true; + })(navigator.userAgent || navigator.vendor || window.opera); + return check; +}; + + +/** + * Fix shortcut. Mac use Command, others use Ctrl. + */ +function fixShortcut(name) { + if(isMac) { + name = name.replace("Ctrl", "Cmd"); + } else { + name = name.replace("Cmd", "Ctrl"); + } + return name; +} + + +/** + * Create icon element for toolbar. + */ +function createIcon(options, enableTooltips, shortcuts) { + options = options || {}; + var el = document.createElement("a"); + enableTooltips = (enableTooltips == undefined) ? true : enableTooltips; + + if(options.title && enableTooltips) { + el.title = createTootlip(options.title, options.action, shortcuts); + + if(isMac) { + el.title = el.title.replace("Ctrl", "⌘"); + el.title = el.title.replace("Alt", "⌥"); + } + } + + el.tabIndex = -1; + el.className = options.className; + return el; +} + +function createSep() { + var el = document.createElement("i"); + el.className = "separator"; + el.innerHTML = "|"; + return el; +} + +function createTootlip(title, action, shortcuts) { + var actionName; + var tooltip = title; + + if(action) { + actionName = getBindingName(action); + if(shortcuts[actionName]) { + tooltip += " (" + fixShortcut(shortcuts[actionName]) + ")"; + } + } + + return tooltip; +} + +/** + * The state of CodeMirror at the given position. + */ +function getState(cm, pos) { + pos = pos || cm.getCursor("start"); + var stat = cm.getTokenAt(pos); + if(!stat.type) return {}; + + var types = stat.type.split(" "); + + var ret = {}, + data, text; + for(var i = 0; i < types.length; i++) { + data = types[i]; + if(data === "strong") { + ret.bold = true; + } else if(data === "variable-2") { + text = cm.getLine(pos.line); + if(/^\s*\d+\.\s/.test(text)) { + ret["ordered-list"] = true; + } else { + ret["unordered-list"] = true; + } + } else if(data === "atom") { + ret.quote = true; + } else if(data === "em") { + ret.italic = true; + } else if(data === "quote") { + ret.quote = true; + } else if(data === "strikethrough") { + ret.strikethrough = true; + } else if(data === "comment") { + ret.code = true; + } else if(data === "link") { + ret.link = true; + } else if(data === "tag") { + ret.image = true; + } else if(data.match(/^header(\-[1-6])?$/)) { + ret[data.replace("header", "heading")] = true; + } + } + return ret; +} + + +// Saved overflow setting +var saved_overflow = ""; + +/** + * Toggle full screen of the editor. + */ +function toggleFullScreen(editor) { + // Set fullscreen + var cm = editor.codemirror; + cm.setOption("fullScreen", !cm.getOption("fullScreen")); + + + // Prevent scrolling on body during fullscreen active + if(cm.getOption("fullScreen")) { + saved_overflow = document.body.style.overflow; + document.body.style.overflow = "hidden"; + } else { + document.body.style.overflow = saved_overflow; + } + + + // Update toolbar class + var wrap = cm.getWrapperElement(); + + if(!/fullscreen/.test(wrap.previousSibling.className)) { + wrap.previousSibling.className += " fullscreen"; + } else { + wrap.previousSibling.className = wrap.previousSibling.className.replace(/\s*fullscreen\b/, ""); + } + + + // Update toolbar button + var toolbarButton = editor.toolbarElements.fullscreen; + + if(!/active/.test(toolbarButton.className)) { + toolbarButton.className += " active"; + } else { + toolbarButton.className = toolbarButton.className.replace(/\s*active\s*/g, ""); + } + + + // Hide side by side if needed + var sidebyside = cm.getWrapperElement().nextSibling; + if(/editor-preview-active-side/.test(sidebyside.className)) + toggleSideBySide(editor); +} + + +/** + * Action for toggling bold. + */ +function toggleBold(editor) { + _toggleBlock(editor, "bold", editor.options.blockStyles.bold); +} + + +/** + * Action for toggling italic. + */ +function toggleItalic(editor) { + _toggleBlock(editor, "italic", editor.options.blockStyles.italic); +} + + +/** + * Action for toggling strikethrough. + */ +function toggleStrikethrough(editor) { + _toggleBlock(editor, "strikethrough", "~~"); +} + +/** + * Action for toggling code block. + */ +function toggleCodeBlock(editor) { + var fenceCharsToInsert = editor.options.blockStyles.code; + + function fencing_line(line) { + /* return true, if this is a ``` or ~~~ line */ + if(typeof line !== "object") { + throw "fencing_line() takes a 'line' object (not a line number, or line text). Got: " + typeof line + ": " + line; + } + return line.styles && line.styles[2] && line.styles[2].indexOf("formatting-code-block") !== -1; + } + + function token_state(token) { + // base goes an extra level deep when mode backdrops are used, e.g. spellchecker on + return token.state.base.base || token.state.base; + } + + function code_type(cm, line_num, line, firstTok, lastTok) { + /* + * Return "single", "indented", "fenced" or false + * + * cm and line_num are required. Others are optional for efficiency + * To check in the middle of a line, pass in firstTok yourself. + */ + line = line || cm.getLineHandle(line_num); + firstTok = firstTok || cm.getTokenAt({ + line: line_num, + ch: 1 + }); + lastTok = lastTok || (!!line.text && cm.getTokenAt({ + line: line_num, + ch: line.text.length - 1 + })); + var types = firstTok.type ? firstTok.type.split(" ") : []; + if(lastTok && token_state(lastTok).indentedCode) { + // have to check last char, since first chars of first line aren"t marked as indented + return "indented"; + } else if(types.indexOf("comment") === -1) { + // has to be after "indented" check, since first chars of first indented line aren"t marked as such + return false; + } else if(token_state(firstTok).fencedChars || token_state(lastTok).fencedChars || fencing_line(line)) { + return "fenced"; + } else { + return "single"; + } + } + + function insertFencingAtSelection(cm, cur_start, cur_end, fenceCharsToInsert) { + var start_line_sel = cur_start.line + 1, + end_line_sel = cur_end.line + 1, + sel_multi = cur_start.line !== cur_end.line, + repl_start = fenceCharsToInsert + "\n", + repl_end = "\n" + fenceCharsToInsert; + if(sel_multi) { + end_line_sel++; + } + // handle last char including \n or not + if(sel_multi && cur_end.ch === 0) { + repl_end = fenceCharsToInsert + "\n"; + end_line_sel--; + } + _replaceSelection(cm, false, [repl_start, repl_end]); + cm.setSelection({ + line: start_line_sel, + ch: 0 + }, { + line: end_line_sel, + ch: 0 + }); + } + + var cm = editor.codemirror, + cur_start = cm.getCursor("start"), + cur_end = cm.getCursor("end"), + tok = cm.getTokenAt({ + line: cur_start.line, + ch: cur_start.ch || 1 + }), // avoid ch 0 which is a cursor pos but not token + line = cm.getLineHandle(cur_start.line), + is_code = code_type(cm, cur_start.line, line, tok); + var block_start, block_end, lineCount; + + if(is_code === "single") { + // similar to some SimpleMDE _toggleBlock logic + var start = line.text.slice(0, cur_start.ch).replace("`", ""), + end = line.text.slice(cur_start.ch).replace("`", ""); + cm.replaceRange(start + end, { + line: cur_start.line, + ch: 0 + }, { + line: cur_start.line, + ch: 99999999999999 + }); + cur_start.ch--; + if(cur_start !== cur_end) { + cur_end.ch--; + } + cm.setSelection(cur_start, cur_end); + cm.focus(); + } else if(is_code === "fenced") { + if(cur_start.line !== cur_end.line || cur_start.ch !== cur_end.ch) { + // use selection + + // find the fenced line so we know what type it is (tilde, backticks, number of them) + for(block_start = cur_start.line; block_start >= 0; block_start--) { + line = cm.getLineHandle(block_start); + if(fencing_line(line)) { + break; + } + } + var fencedTok = cm.getTokenAt({ + line: block_start, + ch: 1 + }); + var fence_chars = token_state(fencedTok).fencedChars; + var start_text, start_line; + var end_text, end_line; + // check for selection going up against fenced lines, in which case we don't want to add more fencing + if(fencing_line(cm.getLineHandle(cur_start.line))) { + start_text = ""; + start_line = cur_start.line; + } else if(fencing_line(cm.getLineHandle(cur_start.line - 1))) { + start_text = ""; + start_line = cur_start.line - 1; + } else { + start_text = fence_chars + "\n"; + start_line = cur_start.line; + } + if(fencing_line(cm.getLineHandle(cur_end.line))) { + end_text = ""; + end_line = cur_end.line; + if(cur_end.ch === 0) { + end_line += 1; + } + } else if(cur_end.ch !== 0 && fencing_line(cm.getLineHandle(cur_end.line + 1))) { + end_text = ""; + end_line = cur_end.line + 1; + } else { + end_text = fence_chars + "\n"; + end_line = cur_end.line + 1; + } + if(cur_end.ch === 0) { + // full last line selected, putting cursor at beginning of next + end_line -= 1; + } + cm.operation(function() { + // end line first, so that line numbers don't change + cm.replaceRange(end_text, { + line: end_line, + ch: 0 + }, { + line: end_line + (end_text ? 0 : 1), + ch: 0 + }); + cm.replaceRange(start_text, { + line: start_line, + ch: 0 + }, { + line: start_line + (start_text ? 0 : 1), + ch: 0 + }); + }); + cm.setSelection({ + line: start_line + (start_text ? 1 : 0), + ch: 0 + }, { + line: end_line + (start_text ? 1 : -1), + ch: 0 + }); + cm.focus(); + } else { + // no selection, search for ends of this fenced block + var search_from = cur_start.line; + if(fencing_line(cm.getLineHandle(cur_start.line))) { // gets a little tricky if cursor is right on a fenced line + if(code_type(cm, cur_start.line + 1) === "fenced") { + block_start = cur_start.line; + search_from = cur_start.line + 1; // for searching for "end" + } else { + block_end = cur_start.line; + search_from = cur_start.line - 1; // for searching for "start" + } + } + if(block_start === undefined) { + for(block_start = search_from; block_start >= 0; block_start--) { + line = cm.getLineHandle(block_start); + if(fencing_line(line)) { + break; + } + } + } + if(block_end === undefined) { + lineCount = cm.lineCount(); + for(block_end = search_from; block_end < lineCount; block_end++) { + line = cm.getLineHandle(block_end); + if(fencing_line(line)) { + break; + } + } + } + cm.operation(function() { + cm.replaceRange("", { + line: block_start, + ch: 0 + }, { + line: block_start + 1, + ch: 0 + }); + cm.replaceRange("", { + line: block_end - 1, + ch: 0 + }, { + line: block_end, + ch: 0 + }); + }); + cm.focus(); + } + } else if(is_code === "indented") { + if(cur_start.line !== cur_end.line || cur_start.ch !== cur_end.ch) { + // use selection + block_start = cur_start.line; + block_end = cur_end.line; + if(cur_end.ch === 0) { + block_end--; + } + } else { + // no selection, search for ends of this indented block + for(block_start = cur_start.line; block_start >= 0; block_start--) { + line = cm.getLineHandle(block_start); + if(line.text.match(/^\s*$/)) { + // empty or all whitespace - keep going + continue; + } else { + if(code_type(cm, block_start, line) !== "indented") { + block_start += 1; + break; + } + } + } + lineCount = cm.lineCount(); + for(block_end = cur_start.line; block_end < lineCount; block_end++) { + line = cm.getLineHandle(block_end); + if(line.text.match(/^\s*$/)) { + // empty or all whitespace - keep going + continue; + } else { + if(code_type(cm, block_end, line) !== "indented") { + block_end -= 1; + break; + } + } + } + } + // if we are going to un-indent based on a selected set of lines, and the next line is indented too, we need to + // insert a blank line so that the next line(s) continue to be indented code + var next_line = cm.getLineHandle(block_end + 1), + next_line_last_tok = next_line && cm.getTokenAt({ + line: block_end + 1, + ch: next_line.text.length - 1 + }), + next_line_indented = next_line_last_tok && token_state(next_line_last_tok).indentedCode; + if(next_line_indented) { + cm.replaceRange("\n", { + line: block_end + 1, + ch: 0 + }); + } + + for(var i = block_start; i <= block_end; i++) { + cm.indentLine(i, "subtract"); // TODO: this doesn't get tracked in the history, so can't be undone :( + } + cm.focus(); + } else { + // insert code formatting + var no_sel_and_starting_of_line = (cur_start.line === cur_end.line && cur_start.ch === cur_end.ch && cur_start.ch === 0); + var sel_multi = cur_start.line !== cur_end.line; + if(no_sel_and_starting_of_line || sel_multi) { + insertFencingAtSelection(cm, cur_start, cur_end, fenceCharsToInsert); + } else { + _replaceSelection(cm, false, ["`", "`"]); + } + } +} + +/** + * Action for toggling blockquote. + */ +function toggleBlockquote(editor) { + var cm = editor.codemirror; + _toggleLine(cm, "quote"); +} + +/** + * Action for toggling heading size: normal -> h1 -> h2 -> h3 -> h4 -> h5 -> h6 -> normal + */ +function toggleHeadingSmaller(editor) { + var cm = editor.codemirror; + _toggleHeading(cm, "smaller"); +} + +/** + * Action for toggling heading size: normal -> h6 -> h5 -> h4 -> h3 -> h2 -> h1 -> normal + */ +function toggleHeadingBigger(editor) { + var cm = editor.codemirror; + _toggleHeading(cm, "bigger"); +} + +/** + * Action for toggling heading size 1 + */ +function toggleHeading1(editor) { + var cm = editor.codemirror; + _toggleHeading(cm, undefined, 1); +} + +/** + * Action for toggling heading size 2 + */ +function toggleHeading2(editor) { + var cm = editor.codemirror; + _toggleHeading(cm, undefined, 2); +} + +/** + * Action for toggling heading size 3 + */ +function toggleHeading3(editor) { + var cm = editor.codemirror; + _toggleHeading(cm, undefined, 3); +} + + +/** + * Action for toggling ul. + */ +function toggleUnorderedList(editor) { + var cm = editor.codemirror; + _toggleLine(cm, "unordered-list"); +} + + +/** + * Action for toggling ol. + */ +function toggleOrderedList(editor) { + var cm = editor.codemirror; + _toggleLine(cm, "ordered-list"); +} + +/** + * Action for clean block (remove headline, list, blockquote code, markers) + */ +function cleanBlock(editor) { + var cm = editor.codemirror; + _cleanBlock(cm); +} + +/** + * Action for drawing a link. + */ +function drawLink(editor) { + var cm = editor.codemirror; + var stat = getState(cm); + var options = editor.options; + var url = "http://"; + if(options.promptURLs) { + url = prompt(options.promptTexts.link); + if(!url) { + return false; + } + } + _replaceSelection(cm, stat.link, options.insertTexts.link, url); +} + +/** + * Action for drawing an img. + */ +function drawImage(editor) { + var cm = editor.codemirror; + var stat = getState(cm); + var options = editor.options; + var url = "http://"; + if(options.promptURLs) { + url = prompt(options.promptTexts.image); + if(!url) { + return false; + } + } + _replaceSelection(cm, stat.image, options.insertTexts.image, url); +} + +/** + * Action for drawing a table. + */ +function drawTable(editor) { + var cm = editor.codemirror; + var stat = getState(cm); + var options = editor.options; + _replaceSelection(cm, stat.table, options.insertTexts.table); +} + +/** + * Action for drawing a horizontal rule. + */ +function drawHorizontalRule(editor) { + var cm = editor.codemirror; + var stat = getState(cm); + var options = editor.options; + _replaceSelection(cm, stat.image, options.insertTexts.horizontalRule); +} + + +/** + * Undo action. + */ +function undo(editor) { + var cm = editor.codemirror; + cm.undo(); + cm.focus(); +} + + +/** + * Redo action. + */ +function redo(editor) { + var cm = editor.codemirror; + cm.redo(); + cm.focus(); +} + + +/** + * Toggle side by side preview + */ +function toggleSideBySide(editor) { + var cm = editor.codemirror; + var wrapper = cm.getWrapperElement(); + var preview = wrapper.nextSibling; + var toolbarButton = editor.toolbarElements["side-by-side"]; + var useSideBySideListener = false; + if(/editor-preview-active-side/.test(preview.className)) { + preview.className = preview.className.replace( + /\s*editor-preview-active-side\s*/g, "" + ); + toolbarButton.className = toolbarButton.className.replace(/\s*active\s*/g, ""); + wrapper.className = wrapper.className.replace(/\s*CodeMirror-sided\s*/g, " "); + } else { + // When the preview button is clicked for the first time, + // give some time for the transition from editor.css to fire and the view to slide from right to left, + // instead of just appearing. + setTimeout(function() { + if(!cm.getOption("fullScreen")) + toggleFullScreen(editor); + preview.className += " editor-preview-active-side"; + }, 1); + toolbarButton.className += " active"; + wrapper.className += " CodeMirror-sided"; + useSideBySideListener = true; + } + + // Hide normal preview if active + var previewNormal = wrapper.lastChild; + if(/editor-preview-active/.test(previewNormal.className)) { + previewNormal.className = previewNormal.className.replace( + /\s*editor-preview-active\s*/g, "" + ); + var toolbar = editor.toolbarElements.preview; + var toolbar_div = wrapper.previousSibling; + toolbar.className = toolbar.className.replace(/\s*active\s*/g, ""); + toolbar_div.className = toolbar_div.className.replace(/\s*disabled-for-preview*/g, ""); + } + + var sideBySideRenderingFunction = function() { + preview.innerHTML = editor.options.previewRender(editor.value(), preview); + }; + + if(!cm.sideBySideRenderingFunction) { + cm.sideBySideRenderingFunction = sideBySideRenderingFunction; + } + + if(useSideBySideListener) { + preview.innerHTML = editor.options.previewRender(editor.value(), preview); + cm.on("update", cm.sideBySideRenderingFunction); + } else { + cm.off("update", cm.sideBySideRenderingFunction); + } + + // Refresh to fix selection being off (#309) + cm.refresh(); +} + + +/** + * Preview action. + */ +function togglePreview(editor) { + var cm = editor.codemirror; + var wrapper = cm.getWrapperElement(); + var toolbar_div = wrapper.previousSibling; + var toolbar = editor.options.toolbar ? editor.toolbarElements.preview : false; + var preview = wrapper.lastChild; + if(!preview || !/editor-preview/.test(preview.className)) { + preview = document.createElement("div"); + preview.className = "editor-preview"; + wrapper.appendChild(preview); + } + if(/editor-preview-active/.test(preview.className)) { + preview.className = preview.className.replace( + /\s*editor-preview-active\s*/g, "" + ); + if(toolbar) { + toolbar.className = toolbar.className.replace(/\s*active\s*/g, ""); + toolbar_div.className = toolbar_div.className.replace(/\s*disabled-for-preview*/g, ""); + } + } else { + // When the preview button is clicked for the first time, + // give some time for the transition from editor.css to fire and the view to slide from right to left, + // instead of just appearing. + setTimeout(function() { + preview.className += " editor-preview-active"; + }, 1); + if(toolbar) { + toolbar.className += " active"; + toolbar_div.className += " disabled-for-preview"; + } + } + preview.innerHTML = editor.options.previewRender(editor.value(), preview); + + // Turn off side by side if needed + var sidebyside = cm.getWrapperElement().nextSibling; + if(/editor-preview-active-side/.test(sidebyside.className)) + toggleSideBySide(editor); +} + +function _replaceSelection(cm, active, startEnd, url) { + if(/editor-preview-active/.test(cm.getWrapperElement().lastChild.className)) + return; + + var text; + var start = startEnd[0]; + var end = startEnd[1]; + var startPoint = cm.getCursor("start"); + var endPoint = cm.getCursor("end"); + if(url) { + end = end.replace("#url#", url); + } + if(active) { + text = cm.getLine(startPoint.line); + start = text.slice(0, startPoint.ch); + end = text.slice(startPoint.ch); + cm.replaceRange(start + end, { + line: startPoint.line, + ch: 0 + }); + } else { + text = cm.getSelection(); + cm.replaceSelection(start + text + end); + + startPoint.ch += start.length; + if(startPoint !== endPoint) { + endPoint.ch += start.length; + } + } + cm.setSelection(startPoint, endPoint); + cm.focus(); +} + + +function _toggleHeading(cm, direction, size) { + if(/editor-preview-active/.test(cm.getWrapperElement().lastChild.className)) + return; + + var startPoint = cm.getCursor("start"); + var endPoint = cm.getCursor("end"); + for(var i = startPoint.line; i <= endPoint.line; i++) { + (function(i) { + var text = cm.getLine(i); + var currHeadingLevel = text.search(/[^#]/); + + if(direction !== undefined) { + if(currHeadingLevel <= 0) { + if(direction == "bigger") { + text = "###### " + text; + } else { + text = "# " + text; + } + } else if(currHeadingLevel == 6 && direction == "smaller") { + text = text.substr(7); + } else if(currHeadingLevel == 1 && direction == "bigger") { + text = text.substr(2); + } else { + if(direction == "bigger") { + text = text.substr(1); + } else { + text = "#" + text; + } + } + } else { + if(size == 1) { + if(currHeadingLevel <= 0) { + text = "# " + text; + } else if(currHeadingLevel == size) { + text = text.substr(currHeadingLevel + 1); + } else { + text = "# " + text.substr(currHeadingLevel + 1); + } + } else if(size == 2) { + if(currHeadingLevel <= 0) { + text = "## " + text; + } else if(currHeadingLevel == size) { + text = text.substr(currHeadingLevel + 1); + } else { + text = "## " + text.substr(currHeadingLevel + 1); + } + } else { + if(currHeadingLevel <= 0) { + text = "### " + text; + } else if(currHeadingLevel == size) { + text = text.substr(currHeadingLevel + 1); + } else { + text = "### " + text.substr(currHeadingLevel + 1); + } + } + } + + cm.replaceRange(text, { + line: i, + ch: 0 + }, { + line: i, + ch: 99999999999999 + }); + })(i); + } + cm.focus(); +} + + +function _toggleLine(cm, name) { + if(/editor-preview-active/.test(cm.getWrapperElement().lastChild.className)) + return; + + var stat = getState(cm); + var startPoint = cm.getCursor("start"); + var endPoint = cm.getCursor("end"); + var repl = { + "quote": /^(\s*)\>\s+/, + "unordered-list": /^(\s*)(\*|\-|\+)\s+/, + "ordered-list": /^(\s*)\d+\.\s+/ + }; + var map = { + "quote": "> ", + "unordered-list": "* ", + "ordered-list": "1. " + }; + for(var i = startPoint.line; i <= endPoint.line; i++) { + (function(i) { + var text = cm.getLine(i); + if(stat[name]) { + text = text.replace(repl[name], "$1"); + } else { + text = map[name] + text; + } + cm.replaceRange(text, { + line: i, + ch: 0 + }, { + line: i, + ch: 99999999999999 + }); + })(i); + } + cm.focus(); +} + +function _toggleBlock(editor, type, start_chars, end_chars) { + if(/editor-preview-active/.test(editor.codemirror.getWrapperElement().lastChild.className)) + return; + + end_chars = (typeof end_chars === "undefined") ? start_chars : end_chars; + var cm = editor.codemirror; + var stat = getState(cm); + + var text; + var start = start_chars; + var end = end_chars; + + var startPoint = cm.getCursor("start"); + var endPoint = cm.getCursor("end"); + + if(stat[type]) { + text = cm.getLine(startPoint.line); + start = text.slice(0, startPoint.ch); + end = text.slice(startPoint.ch); + if(type == "bold") { + start = start.replace(/(\*\*|__)(?![\s\S]*(\*\*|__))/, ""); + end = end.replace(/(\*\*|__)/, ""); + } else if(type == "italic") { + start = start.replace(/(\*|_)(?![\s\S]*(\*|_))/, ""); + end = end.replace(/(\*|_)/, ""); + } else if(type == "strikethrough") { + start = start.replace(/(\*\*|~~)(?![\s\S]*(\*\*|~~))/, ""); + end = end.replace(/(\*\*|~~)/, ""); + } + cm.replaceRange(start + end, { + line: startPoint.line, + ch: 0 + }, { + line: startPoint.line, + ch: 99999999999999 + }); + + if(type == "bold" || type == "strikethrough") { + startPoint.ch -= 2; + if(startPoint !== endPoint) { + endPoint.ch -= 2; + } + } else if(type == "italic") { + startPoint.ch -= 1; + if(startPoint !== endPoint) { + endPoint.ch -= 1; + } + } + } else { + text = cm.getSelection(); + if(type == "bold") { + text = text.split("**").join(""); + text = text.split("__").join(""); + } else if(type == "italic") { + text = text.split("*").join(""); + text = text.split("_").join(""); + } else if(type == "strikethrough") { + text = text.split("~~").join(""); + } + cm.replaceSelection(start + text + end); + + startPoint.ch += start_chars.length; + endPoint.ch = startPoint.ch + text.length; + } + + cm.setSelection(startPoint, endPoint); + cm.focus(); +} + +function _cleanBlock(cm) { + if(/editor-preview-active/.test(cm.getWrapperElement().lastChild.className)) + return; + + var startPoint = cm.getCursor("start"); + var endPoint = cm.getCursor("end"); + var text; + + for(var line = startPoint.line; line <= endPoint.line; line++) { + text = cm.getLine(line); + text = text.replace(/^[ ]*([# ]+|\*|\-|[> ]+|[0-9]+(.|\)))[ ]*/, ""); + + cm.replaceRange(text, { + line: line, + ch: 0 + }, { + line: line, + ch: 99999999999999 + }); + } +} + +// Merge the properties of one object into another. +function _mergeProperties(target, source) { + for(var property in source) { + if(source.hasOwnProperty(property)) { + if(source[property] instanceof Array) { + target[property] = source[property].concat(target[property] instanceof Array ? target[property] : []); + } else if( + source[property] !== null && + typeof source[property] === "object" && + source[property].constructor === Object + ) { + target[property] = _mergeProperties(target[property] || {}, source[property]); + } else { + target[property] = source[property]; + } + } + } + + return target; +} + +// Merge an arbitrary number of objects into one. +function extend(target) { + for(var i = 1; i < arguments.length; i++) { + target = _mergeProperties(target, arguments[i]); + } + + return target; +} + +/* The right word count in respect for CJK. */ +function wordCount(data) { + var pattern = /[a-zA-Z0-9_\u0392-\u03c9\u0410-\u04F9]+|[\u4E00-\u9FFF\u3400-\u4dbf\uf900-\ufaff\u3040-\u309f\uac00-\ud7af]+/g; + var m = data.match(pattern); + var count = 0; + if(m === null) return count; + for(var i = 0; i < m.length; i++) { + if(m[i].charCodeAt(0) >= 0x4E00) { + count += m[i].length; + } else { + count += 1; + } + } + return count; +} + +var toolbarBuiltInButtons = { + "bold": { + name: "bold", + action: toggleBold, + className: "fa fa-bold", + title: "Bold", + default: true + }, + "italic": { + name: "italic", + action: toggleItalic, + className: "fa fa-italic", + title: "Italic", + default: true + }, + "strikethrough": { + name: "strikethrough", + action: toggleStrikethrough, + className: "fa fa-strikethrough", + title: "Strikethrough" + }, + "heading": { + name: "heading", + action: toggleHeadingSmaller, + className: "fa fa-header", + title: "Heading", + default: true + }, + "heading-smaller": { + name: "heading-smaller", + action: toggleHeadingSmaller, + className: "fa fa-header fa-header-x fa-header-smaller", + title: "Smaller Heading" + }, + "heading-bigger": { + name: "heading-bigger", + action: toggleHeadingBigger, + className: "fa fa-header fa-header-x fa-header-bigger", + title: "Bigger Heading" + }, + "heading-1": { + name: "heading-1", + action: toggleHeading1, + className: "fa fa-header fa-header-x fa-header-1", + title: "Big Heading" + }, + "heading-2": { + name: "heading-2", + action: toggleHeading2, + className: "fa fa-header fa-header-x fa-header-2", + title: "Medium Heading" + }, + "heading-3": { + name: "heading-3", + action: toggleHeading3, + className: "fa fa-header fa-header-x fa-header-3", + title: "Small Heading" + }, + "separator-1": { + name: "separator-1" + }, + "code": { + name: "code", + action: toggleCodeBlock, + className: "fa fa-code", + title: "Code" + }, + "quote": { + name: "quote", + action: toggleBlockquote, + className: "fa fa-quote-left", + title: "Quote", + default: true + }, + "unordered-list": { + name: "unordered-list", + action: toggleUnorderedList, + className: "fa fa-list-ul", + title: "Generic List", + default: true + }, + "ordered-list": { + name: "ordered-list", + action: toggleOrderedList, + className: "fa fa-list-ol", + title: "Numbered List", + default: true + }, + "clean-block": { + name: "clean-block", + action: cleanBlock, + className: "fa fa-eraser fa-clean-block", + title: "Clean block" + }, + "separator-2": { + name: "separator-2" + }, + "link": { + name: "link", + action: drawLink, + className: "fa fa-link", + title: "Create Link", + default: true + }, + "image": { + name: "image", + action: drawImage, + className: "fa fa-picture-o", + title: "Insert Image", + default: true + }, + "table": { + name: "table", + action: drawTable, + className: "fa fa-table", + title: "Insert Table" + }, + "horizontal-rule": { + name: "horizontal-rule", + action: drawHorizontalRule, + className: "fa fa-minus", + title: "Insert Horizontal Line" + }, + "separator-3": { + name: "separator-3" + }, + "preview": { + name: "preview", + action: togglePreview, + className: "fa fa-eye no-disable", + title: "Toggle Preview", + default: true + }, + "side-by-side": { + name: "side-by-side", + action: toggleSideBySide, + className: "fa fa-columns no-disable no-mobile", + title: "Toggle Side by Side", + default: true + }, + "fullscreen": { + name: "fullscreen", + action: toggleFullScreen, + className: "fa fa-arrows-alt no-disable no-mobile", + title: "Toggle Fullscreen", + default: true + }, + "separator-4": { + name: "separator-4" + }, + "guide": { + name: "guide", + action: "https://simplemde.com/markdown-guide", + className: "fa fa-question-circle", + title: "Markdown Guide", + default: true + }, + "separator-5": { + name: "separator-5" + }, + "undo": { + name: "undo", + action: undo, + className: "fa fa-undo no-disable", + title: "Undo" + }, + "redo": { + name: "redo", + action: redo, + className: "fa fa-repeat no-disable", + title: "Redo" + } +}; + +var insertTexts = { + link: ["[", "](#url#)"], + image: ["![](", "#url#)"], + table: ["", "\n\n| Column 1 | Column 2 | Column 3 |\n| -------- | -------- | -------- |\n| Text | Text | Text |\n\n"], + horizontalRule: ["", "\n\n-----\n\n"] +}; + +var promptTexts = { + link: "URL for the link:", + image: "URL of the image:" +}; + +var blockStyles = { + "bold": "**", + "code": "```", + "italic": "*" +}; + +/** + * Interface of SimpleMDE. + */ +function SimpleMDE(options) { + // Handle options parameter + options = options || {}; + + + // Used later to refer to it"s parent + options.parent = this; + + + // Check if Font Awesome needs to be auto downloaded + var autoDownloadFA = true; + + if(options.autoDownloadFontAwesome === false) { + autoDownloadFA = false; + } + + if(options.autoDownloadFontAwesome !== true) { + var styleSheets = document.styleSheets; + for(var i = 0; i < styleSheets.length; i++) { + if(!styleSheets[i].href) + continue; + + if(styleSheets[i].href.indexOf("//maxcdn.bootstrapcdn.com/font-awesome/") > -1) { + autoDownloadFA = false; + } + } + } + + if(autoDownloadFA) { + var link = document.createElement("link"); + link.rel = "stylesheet"; + link.href = "https://maxcdn.bootstrapcdn.com/font-awesome/latest/css/font-awesome.min.css"; + document.getElementsByTagName("head")[0].appendChild(link); + } + + + // Find the textarea to use + if(options.element) { + this.element = options.element; + } else if(options.element === null) { + // This means that the element option was specified, but no element was found + console.log("SimpleMDE: Error. No element was found."); + return; + } + + + // Handle toolbar + if(options.toolbar === undefined) { + // Initialize + options.toolbar = []; + + + // Loop over the built in buttons, to get the preferred order + for(var key in toolbarBuiltInButtons) { + if(toolbarBuiltInButtons.hasOwnProperty(key)) { + if(key.indexOf("separator-") != -1) { + options.toolbar.push("|"); + } + + if(toolbarBuiltInButtons[key].default === true || (options.showIcons && options.showIcons.constructor === Array && options.showIcons.indexOf(key) != -1)) { + options.toolbar.push(key); + } + } + } + } + + + // Handle status bar + if(!options.hasOwnProperty("status")) { + options.status = ["autosave", "lines", "words", "cursor"]; + } + + + // Add default preview rendering function + if(!options.previewRender) { + options.previewRender = function(plainText) { + // Note: "this" refers to the options object + return this.parent.markdown(plainText); + }; + } + + + // Set default options for parsing config + options.parsingConfig = extend({ + highlightFormatting: true // needed for toggleCodeBlock to detect types of code + }, options.parsingConfig || {}); + + + // Merging the insertTexts, with the given options + options.insertTexts = extend({}, insertTexts, options.insertTexts || {}); + + + // Merging the promptTexts, with the given options + options.promptTexts = promptTexts; + + + // Merging the blockStyles, with the given options + options.blockStyles = extend({}, blockStyles, options.blockStyles || {}); + + + // Merging the shortcuts, with the given options + options.shortcuts = extend({}, shortcuts, options.shortcuts || {}); + + + // Change unique_id to uniqueId for backwards compatibility + if(options.autosave != undefined && options.autosave.unique_id != undefined && options.autosave.unique_id != "") + options.autosave.uniqueId = options.autosave.unique_id; + + + // Update this options + this.options = options; + + + // Auto render + this.render(); + + + // The codemirror component is only available after rendering + // so, the setter for the initialValue can only run after + // the element has been rendered + if(options.initialValue && (!this.options.autosave || this.options.autosave.foundSavedValue !== true)) { + this.value(options.initialValue); + } +} + +/** + * Default markdown render. + */ +SimpleMDE.prototype.markdown = function(text) { + if(marked) { + // Initialize + var markedOptions = {}; + + + // Update options + if(this.options && this.options.renderingConfig && this.options.renderingConfig.singleLineBreaks === false) { + markedOptions.breaks = false; + } else { + markedOptions.breaks = true; + } + + if(this.options && this.options.renderingConfig && this.options.renderingConfig.codeSyntaxHighlighting === true && window.hljs) { + markedOptions.highlight = function(code) { + return window.hljs.highlightAuto(code).value; + }; + } + + + // Set options + marked.setOptions(markedOptions); + + + // Return + return marked(text); + } +}; + +/** + * Render editor to the given element. + */ +SimpleMDE.prototype.render = function(el) { + if(!el) { + el = this.element || document.getElementsByTagName("textarea")[0]; + } + + if(this._rendered && this._rendered === el) { + // Already rendered. + return; + } + + this.element = el; + var options = this.options; + + var self = this; + var keyMaps = {}; + + for(var key in options.shortcuts) { + // null stands for "do not bind this command" + if(options.shortcuts[key] !== null && bindings[key] !== null) { + (function(key) { + keyMaps[fixShortcut(options.shortcuts[key])] = function() { + bindings[key](self); + }; + })(key); + } + } + + keyMaps["Enter"] = "newlineAndIndentContinueMarkdownList"; + keyMaps["Tab"] = "tabAndIndentMarkdownList"; + keyMaps["Shift-Tab"] = "shiftTabAndUnindentMarkdownList"; + keyMaps["Esc"] = function(cm) { + if(cm.getOption("fullScreen")) toggleFullScreen(self); + }; + + document.addEventListener("keydown", function(e) { + e = e || window.event; + + if(e.keyCode == 27) { + if(self.codemirror.getOption("fullScreen")) toggleFullScreen(self); + } + }, false); + + var mode, backdrop; + if(options.spellChecker !== false) { + mode = "spell-checker"; + backdrop = options.parsingConfig; + backdrop.name = "gfm"; + backdrop.gitHubSpice = false; + + CodeMirrorSpellChecker({ + codeMirrorInstance: CodeMirror + }); + } else { + mode = options.parsingConfig; + mode.name = "gfm"; + mode.gitHubSpice = false; + } + + this.codemirror = CodeMirror.fromTextArea(el, { + mode: mode, + backdrop: backdrop, + theme: "paper", + tabSize: (options.tabSize != undefined) ? options.tabSize : 2, + indentUnit: (options.tabSize != undefined) ? options.tabSize : 2, + indentWithTabs: (options.indentWithTabs === false) ? false : true, + lineNumbers: false, + autofocus: (options.autofocus === true) ? true : false, + extraKeys: keyMaps, + lineWrapping: (options.lineWrapping === false) ? false : true, + allowDropFileTypes: ["text/plain"], + placeholder: options.placeholder || el.getAttribute("placeholder") || "", + styleSelectedText: (options.styleSelectedText != undefined) ? options.styleSelectedText : true + }); + + if(options.forceSync === true) { + var cm = this.codemirror; + cm.on("change", function() { + cm.save(); + }); + } + + this.gui = {}; + + if(options.toolbar !== false) { + this.gui.toolbar = this.createToolbar(); + } + if(options.status !== false) { + this.gui.statusbar = this.createStatusbar(); + } + if(options.autosave != undefined && options.autosave.enabled === true) { + this.autosave(); + } + + this.gui.sideBySide = this.createSideBySide(); + + this._rendered = this.element; + + + // Fixes CodeMirror bug (#344) + var temp_cm = this.codemirror; + setTimeout(function() { + temp_cm.refresh(); + }.bind(temp_cm), 0); +}; + +// Safari, in Private Browsing Mode, looks like it supports localStorage but all calls to setItem throw QuotaExceededError. We're going to detect this and set a variable accordingly. +function isLocalStorageAvailable() { + if(typeof localStorage === "object") { + try { + localStorage.setItem("smde_localStorage", 1); + localStorage.removeItem("smde_localStorage"); + } catch(e) { + return false; + } + } else { + return false; + } + + return true; +} + +SimpleMDE.prototype.autosave = function() { + if(isLocalStorageAvailable()) { + var simplemde = this; + + if(this.options.autosave.uniqueId == undefined || this.options.autosave.uniqueId == "") { + console.log("SimpleMDE: You must set a uniqueId to use the autosave feature"); + return; + } + + if(simplemde.element.form != null && simplemde.element.form != undefined) { + simplemde.element.form.addEventListener("submit", function() { + localStorage.removeItem("smde_" + simplemde.options.autosave.uniqueId); + }); + } + + if(this.options.autosave.loaded !== true) { + if(typeof localStorage.getItem("smde_" + this.options.autosave.uniqueId) == "string" && localStorage.getItem("smde_" + this.options.autosave.uniqueId) != "") { + this.codemirror.setValue(localStorage.getItem("smde_" + this.options.autosave.uniqueId)); + this.options.autosave.foundSavedValue = true; + } + + this.options.autosave.loaded = true; + } + + localStorage.setItem("smde_" + this.options.autosave.uniqueId, simplemde.value()); + + var el = document.getElementById("autosaved"); + if(el != null && el != undefined && el != "") { + var d = new Date(); + var hh = d.getHours(); + var m = d.getMinutes(); + var dd = "am"; + var h = hh; + if(h >= 12) { + h = hh - 12; + dd = "pm"; + } + if(h == 0) { + h = 12; + } + m = m < 10 ? "0" + m : m; + + el.innerHTML = "Autosaved: " + h + ":" + m + " " + dd; + } + + this.autosaveTimeoutId = setTimeout(function() { + simplemde.autosave(); + }, this.options.autosave.delay || 10000); + } else { + console.log("SimpleMDE: localStorage not available, cannot autosave"); + } +}; + +SimpleMDE.prototype.clearAutosavedValue = function() { + if(isLocalStorageAvailable()) { + if(this.options.autosave == undefined || this.options.autosave.uniqueId == undefined || this.options.autosave.uniqueId == "") { + console.log("SimpleMDE: You must set a uniqueId to clear the autosave value"); + return; + } + + localStorage.removeItem("smde_" + this.options.autosave.uniqueId); + } else { + console.log("SimpleMDE: localStorage not available, cannot autosave"); + } +}; + +SimpleMDE.prototype.createSideBySide = function() { + var cm = this.codemirror; + var wrapper = cm.getWrapperElement(); + var preview = wrapper.nextSibling; + + if(!preview || !/editor-preview-side/.test(preview.className)) { + preview = document.createElement("div"); + preview.className = "editor-preview-side"; + wrapper.parentNode.insertBefore(preview, wrapper.nextSibling); + } + + // Syncs scroll editor -> preview + var cScroll = false; + var pScroll = false; + cm.on("scroll", function(v) { + if(cScroll) { + cScroll = false; + return; + } + pScroll = true; + var height = v.getScrollInfo().height - v.getScrollInfo().clientHeight; + var ratio = parseFloat(v.getScrollInfo().top) / height; + var move = (preview.scrollHeight - preview.clientHeight) * ratio; + preview.scrollTop = move; + }); + + // Syncs scroll preview -> editor + preview.onscroll = function() { + if(pScroll) { + pScroll = false; + return; + } + cScroll = true; + var height = preview.scrollHeight - preview.clientHeight; + var ratio = parseFloat(preview.scrollTop) / height; + var move = (cm.getScrollInfo().height - cm.getScrollInfo().clientHeight) * ratio; + cm.scrollTo(0, move); + }; + return preview; +}; + +SimpleMDE.prototype.createToolbar = function(items) { + items = items || this.options.toolbar; + + if(!items || items.length === 0) { + return; + } + var i; + for(i = 0; i < items.length; i++) { + if(toolbarBuiltInButtons[items[i]] != undefined) { + items[i] = toolbarBuiltInButtons[items[i]]; + } + } + + var bar = document.createElement("div"); + bar.className = "editor-toolbar"; + + var self = this; + + var toolbarData = {}; + self.toolbar = items; + + for(i = 0; i < items.length; i++) { + if(items[i].name == "guide" && self.options.toolbarGuideIcon === false) + continue; + + if(self.options.hideIcons && self.options.hideIcons.indexOf(items[i].name) != -1) + continue; + + // Fullscreen does not work well on mobile devices (even tablets) + // In the future, hopefully this can be resolved + if((items[i].name == "fullscreen" || items[i].name == "side-by-side") && isMobile()) + continue; + + + // Don't include trailing separators + if(items[i] === "|") { + var nonSeparatorIconsFollow = false; + + for(var x = (i + 1); x < items.length; x++) { + if(items[x] !== "|" && (!self.options.hideIcons || self.options.hideIcons.indexOf(items[x].name) == -1)) { + nonSeparatorIconsFollow = true; + } + } + + if(!nonSeparatorIconsFollow) + continue; + } + + + // Create the icon and append to the toolbar + (function(item) { + var el; + if(item === "|") { + el = createSep(); + } else { + el = createIcon(item, self.options.toolbarTips, self.options.shortcuts); + } + + // bind events, special for info + if(item.action) { + if(typeof item.action === "function") { + el.onclick = function(e) { + e.preventDefault(); + item.action(self); + }; + } else if(typeof item.action === "string") { + el.href = item.action; + el.target = "_blank"; + } + } + + toolbarData[item.name || item] = el; + bar.appendChild(el); + })(items[i]); + } + + self.toolbarElements = toolbarData; + + var cm = this.codemirror; + cm.on("cursorActivity", function() { + var stat = getState(cm); + + for(var key in toolbarData) { + (function(key) { + var el = toolbarData[key]; + if(stat[key]) { + el.className += " active"; + } else if(key != "fullscreen" && key != "side-by-side") { + el.className = el.className.replace(/\s*active\s*/g, ""); + } + })(key); + } + }); + + var cmWrapper = cm.getWrapperElement(); + cmWrapper.parentNode.insertBefore(bar, cmWrapper); + return bar; +}; + +SimpleMDE.prototype.createStatusbar = function(status) { + // Initialize + status = status || this.options.status; + var options = this.options; + var cm = this.codemirror; + + + // Make sure the status variable is valid + if(!status || status.length === 0) + return; + + + // Set up the built-in items + var items = []; + var i, onUpdate, defaultValue; + + for(i = 0; i < status.length; i++) { + // Reset some values + onUpdate = undefined; + defaultValue = undefined; + + + // Handle if custom or not + if(typeof status[i] === "object") { + items.push({ + className: status[i].className, + defaultValue: status[i].defaultValue, + onUpdate: status[i].onUpdate + }); + } else { + var name = status[i]; + + if(name === "words") { + defaultValue = function(el) { + el.innerHTML = wordCount(cm.getValue()); + }; + onUpdate = function(el) { + el.innerHTML = wordCount(cm.getValue()); + }; + } else if(name === "lines") { + defaultValue = function(el) { + el.innerHTML = cm.lineCount(); + }; + onUpdate = function(el) { + el.innerHTML = cm.lineCount(); + }; + } else if(name === "cursor") { + defaultValue = function(el) { + el.innerHTML = "0:0"; + }; + onUpdate = function(el) { + var pos = cm.getCursor(); + el.innerHTML = pos.line + ":" + pos.ch; + }; + } else if(name === "autosave") { + defaultValue = function(el) { + if(options.autosave != undefined && options.autosave.enabled === true) { + el.setAttribute("id", "autosaved"); + } + }; + } + + items.push({ + className: name, + defaultValue: defaultValue, + onUpdate: onUpdate + }); + } + } + + + // Create element for the status bar + var bar = document.createElement("div"); + bar.className = "editor-statusbar"; + + + // Create a new span for each item + for(i = 0; i < items.length; i++) { + // Store in temporary variable + var item = items[i]; + + + // Create span element + var el = document.createElement("span"); + el.className = item.className; + + + // Ensure the defaultValue is a function + if(typeof item.defaultValue === "function") { + item.defaultValue(el); + } + + + // Ensure the onUpdate is a function + if(typeof item.onUpdate === "function") { + // Create a closure around the span of the current action, then execute the onUpdate handler + this.codemirror.on("update", (function(el, item) { + return function() { + item.onUpdate(el); + }; + }(el, item))); + } + + + // Append the item to the status bar + bar.appendChild(el); + } + + + // Insert the status bar into the DOM + var cmWrapper = this.codemirror.getWrapperElement(); + cmWrapper.parentNode.insertBefore(bar, cmWrapper.nextSibling); + return bar; +}; + +/** + * Get or set the text content. + */ +SimpleMDE.prototype.value = function(val) { + if(val === undefined) { + return this.codemirror.getValue(); + } else { + this.codemirror.getDoc().setValue(val); + return this; + } +}; + + +/** + * Bind static methods for exports. + */ +SimpleMDE.toggleBold = toggleBold; +SimpleMDE.toggleItalic = toggleItalic; +SimpleMDE.toggleStrikethrough = toggleStrikethrough; +SimpleMDE.toggleBlockquote = toggleBlockquote; +SimpleMDE.toggleHeadingSmaller = toggleHeadingSmaller; +SimpleMDE.toggleHeadingBigger = toggleHeadingBigger; +SimpleMDE.toggleHeading1 = toggleHeading1; +SimpleMDE.toggleHeading2 = toggleHeading2; +SimpleMDE.toggleHeading3 = toggleHeading3; +SimpleMDE.toggleCodeBlock = toggleCodeBlock; +SimpleMDE.toggleUnorderedList = toggleUnorderedList; +SimpleMDE.toggleOrderedList = toggleOrderedList; +SimpleMDE.cleanBlock = cleanBlock; +SimpleMDE.drawLink = drawLink; +SimpleMDE.drawImage = drawImage; +SimpleMDE.drawTable = drawTable; +SimpleMDE.drawHorizontalRule = drawHorizontalRule; +SimpleMDE.undo = undo; +SimpleMDE.redo = redo; +SimpleMDE.togglePreview = togglePreview; +SimpleMDE.toggleSideBySide = toggleSideBySide; +SimpleMDE.toggleFullScreen = toggleFullScreen; + +/** + * Bind instance methods for exports. + */ +SimpleMDE.prototype.toggleBold = function() { + toggleBold(this); +}; +SimpleMDE.prototype.toggleItalic = function() { + toggleItalic(this); +}; +SimpleMDE.prototype.toggleStrikethrough = function() { + toggleStrikethrough(this); +}; +SimpleMDE.prototype.toggleBlockquote = function() { + toggleBlockquote(this); +}; +SimpleMDE.prototype.toggleHeadingSmaller = function() { + toggleHeadingSmaller(this); +}; +SimpleMDE.prototype.toggleHeadingBigger = function() { + toggleHeadingBigger(this); +}; +SimpleMDE.prototype.toggleHeading1 = function() { + toggleHeading1(this); +}; +SimpleMDE.prototype.toggleHeading2 = function() { + toggleHeading2(this); +}; +SimpleMDE.prototype.toggleHeading3 = function() { + toggleHeading3(this); +}; +SimpleMDE.prototype.toggleCodeBlock = function() { + toggleCodeBlock(this); +}; +SimpleMDE.prototype.toggleUnorderedList = function() { + toggleUnorderedList(this); +}; +SimpleMDE.prototype.toggleOrderedList = function() { + toggleOrderedList(this); +}; +SimpleMDE.prototype.cleanBlock = function() { + cleanBlock(this); +}; +SimpleMDE.prototype.drawLink = function() { + drawLink(this); +}; +SimpleMDE.prototype.drawImage = function() { + drawImage(this); +}; +SimpleMDE.prototype.drawTable = function() { + drawTable(this); +}; +SimpleMDE.prototype.drawHorizontalRule = function() { + drawHorizontalRule(this); +}; +SimpleMDE.prototype.undo = function() { + undo(this); +}; +SimpleMDE.prototype.redo = function() { + redo(this); +}; +SimpleMDE.prototype.togglePreview = function() { + togglePreview(this); +}; +SimpleMDE.prototype.toggleSideBySide = function() { + toggleSideBySide(this); +}; +SimpleMDE.prototype.toggleFullScreen = function() { + toggleFullScreen(this); +}; + +SimpleMDE.prototype.isPreviewActive = function() { + var cm = this.codemirror; + var wrapper = cm.getWrapperElement(); + var preview = wrapper.lastChild; + + return /editor-preview-active/.test(preview.className); +}; + +SimpleMDE.prototype.isSideBySideActive = function() { + var cm = this.codemirror; + var wrapper = cm.getWrapperElement(); + var preview = wrapper.nextSibling; + + return /editor-preview-active-side/.test(preview.className); +}; + +SimpleMDE.prototype.isFullscreenActive = function() { + var cm = this.codemirror; + + return cm.getOption("fullScreen"); +}; + +SimpleMDE.prototype.getState = function() { + var cm = this.codemirror; + + return getState(cm); +}; + +SimpleMDE.prototype.toTextArea = function() { + var cm = this.codemirror; + var wrapper = cm.getWrapperElement(); + + if(wrapper.parentNode) { + if(this.gui.toolbar) { + wrapper.parentNode.removeChild(this.gui.toolbar); + } + if(this.gui.statusbar) { + wrapper.parentNode.removeChild(this.gui.statusbar); + } + if(this.gui.sideBySide) { + wrapper.parentNode.removeChild(this.gui.sideBySide); + } + } + + cm.toTextArea(); + + if(this.autosaveTimeoutId) { + clearTimeout(this.autosaveTimeoutId); + this.autosaveTimeoutId = undefined; + this.clearAutosavedValue(); + } +}; + +module.exports = SimpleMDE; +},{"./codemirror/tablist":19,"codemirror":10,"codemirror-spell-checker":4,"codemirror/addon/display/fullscreen.js":5,"codemirror/addon/display/placeholder.js":6,"codemirror/addon/edit/continuelist.js":7,"codemirror/addon/mode/overlay.js":8,"codemirror/addon/selection/mark-selection.js":9,"codemirror/mode/gfm/gfm.js":11,"codemirror/mode/markdown/markdown.js":12,"codemirror/mode/xml/xml.js":14,"marked":17}]},{},[20])(20) +}); \ No newline at end of file diff --git a/Project-Unite/Views/Shared/_Layout.cshtml b/Project-Unite/Views/Shared/_Layout.cshtml index 48d7d37..e52b33d 100644 --- a/Project-Unite/Views/Shared/_Layout.cshtml +++ b/Project-Unite/Views/Shared/_Layout.cshtml @@ -21,8 +21,9 @@ @Styles.Render("~/Content/css") @Styles.Render("~/Content/bootstrap-theme.css") @Styles.Render("~/Content/Site.css") + @Styles.Render("~/Content/simplemde.css") - + @Scripts.Render("~/Scripts/simplemde.js") @Scripts.Render("~/bundles/modernizr")