diff --git a/web/ApiControllers/BlogsController.cs b/web/ApiControllers/BlogsController.cs index 5f068c9b..ba187681 100644 --- a/web/ApiControllers/BlogsController.cs +++ b/web/ApiControllers/BlogsController.cs @@ -94,6 +94,7 @@ namespace Yavsc.ApiControllers string root = HttpContext.Current.Server.MapPath("~/bfiles/"+id); DirectoryInfo di = new DirectoryInfo (root); if (!di.Exists) di.Create (); + var provider = new MultipartFormDataStreamProvider(root); try { @@ -105,12 +106,15 @@ namespace Yavsc.ApiControllers string filename = provider.BodyPartFileNames[fkey]; Trace.WriteLine(filename); - string nicename=fkey; + string nicename = HttpUtility.UrlDecode(fkey) ; if (fkey.StartsWith("\"") && fkey.EndsWith("\"") && fkey.Length > 2) nicename = fkey.Substring(1,fkey.Length-2); - var filtered = new string (nicename.Where( x=> !invalidChars.Contains(x)).ToArray()); - File.Move(Path.Combine(root,filename), - Path.Combine(root,filtered)); + nicename = new string (nicename.Where( x=> !invalidChars.Contains(x)).ToArray()); + nicename = nicename.Replace(' ','_'); + var dest = Path.Combine(root,nicename); + var fi = new FileInfo(dest); + if (fi.Exists) fi.Delete(); + File.Move(filename, fi.FullName); } return Request.CreateResponse(HttpStatusCode.OK); diff --git a/web/ChangeLog b/web/ChangeLog index 7431dfcd..7b5abf2a 100644 --- a/web/ChangeLog +++ b/web/ChangeLog @@ -1,3 +1,28 @@ +2015-09-25 Paul Schneider + + * BlogsController.cs: Fixes the Markdown usage of the uploaded + files: + Files are now renamed by dropping invalid characters and + replacing spaces by underscores. + An existing destination file is removed at upload. + + + * showdown.js: Renders audio and video at client side + + * to-markdown.js: converts html audio and video to Markdown + + * TestByteA.cs: adds a prefix, an underscore, to the test + table `_testbytea` + + * Edit.aspx: Uses the new `showdown` javascript module (that + previously was named `Showdown`) + + * Web.csproj: cleaning + + * MarkdownDeep.dll: Renders video an audio + + * GruntFile.js: Was pollution + 2015-09-23 Paul Schneider * font-awesome.css: an awesome css diff --git a/web/Scripts/GruntFile.js b/web/Scripts/GruntFile.js deleted file mode 100644 index ca3e653b..00000000 --- a/web/Scripts/GruntFile.js +++ /dev/null @@ -1,29 +0,0 @@ -module.exports = function(grunt) { - - grunt.initConfig({ - pkg: grunt.file.readJSON('package.json'), - meta: { - banner : '/*!\n' + - ' * <%= pkg.title %> v<%= pkg.version %> - <%= pkg.description %>\n' + - ' * Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %> - <%= pkg.homepage %>\n' + - ' * License: <%= pkg.license %>\n' + - ' */\n\n' - }, - uglify: { - options : { - banner : '<%= meta.banner %>', - report: 'gzip' - }, - dist: { - files: { - 'jquery.timepicker.min.js': ['jquery.timepicker.js'] - } - } - } - }); - - grunt.loadNpmTasks('grunt-contrib-uglify'); - - grunt.registerTask('default', ['uglify']); - -}; diff --git a/web/Scripts/showdown.js b/web/Scripts/showdown.js index 6738a371..86b23b70 100644 --- a/web/Scripts/showdown.js +++ b/web/Scripts/showdown.js @@ -1,419 +1,2336 @@ -/* - A A L Source code at: - T C A - T K B -*/ - -var Showdown={}; -Showdown.converter=function(){ -var _1; -var _2; -var _3; -var _4=0; -this.makeHtml=function(_5){ -_1=new Array(); -_2=new Array(); -_3=new Array(); -_5=_5.replace(/~/g,"~T"); -_5=_5.replace(/\$/g,"~D"); -_5=_5.replace(/\r\n/g,"\n"); -_5=_5.replace(/\r/g,"\n"); -_5="\n\n"+_5+"\n\n"; -_5=_6(_5); -_5=_5.replace(/^[ \t]+$/mg,""); -_5=_7(_5); -_5=_8(_5); -_5=_9(_5); -_5=_a(_5); -_5=_5.replace(/~D/g,"$$"); -_5=_5.replace(/~T/g,"~"); -return _5; -}; -var _8=function(_b){ -var _b=_b.replace(/^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*?[ \t]*\n?[ \t]*(?:(\n*)["(](.+?)[")][ \t]*)?(?:\n+|\Z)/gm,function(_c,m1,m2,m3,m4){ -m1=m1.toLowerCase(); -_1[m1]=_11(m2); -if(m3){ -return m3+m4; -}else{ -if(m4){ -_2[m1]=m4.replace(/"/g,"""); -} +;/*! showdown 25-09-2015 */ +(function(){ +/** + * Created by Tivie on 13-07-2015. + */ + +function getDefaultOpts(simple) { + 'use strict'; + + var defaultOptions = { + omitExtraWLInCodeBlocks: { + default: false, + describe: 'Omit the default extra whiteline added to code blocks', + type: 'boolean' + }, + noHeaderId: { + default: false, + describe: 'Turn on/off generated header id', + type: 'boolean' + }, + prefixHeaderId: { + default: false, + describe: 'Specify a prefix to generated header ids', + type: 'string' + }, + headerLevelStart: { + default: false, + describe: 'The header blocks level start', + type: 'integer' + }, + parseImgDimensions: { + default: false, + describe: 'Turn on/off image dimension parsing', + type: 'boolean' + }, + simplifiedAutoLink: { + default: false, + describe: 'Turn on/off GFM autolink style', + type: 'boolean' + }, + literalMidWordUnderscores: { + default: false, + describe: 'Parse midword underscores as literal underscores', + type: 'boolean' + }, + strikethrough: { + default: false, + describe: 'Turn on/off strikethrough support', + type: 'boolean' + }, + tables: { + default: false, + describe: 'Turn on/off tables support', + type: 'boolean' + }, + tablesHeaderId: { + default: false, + describe: 'Add an id to table headers', + type: 'boolean' + }, + ghCodeBlocks: { + default: true, + describe: 'Turn on/off GFM fenced code blocks support', + type: 'boolean' + }, + tasklists: { + default: false, + describe: 'Turn on/off GFM tasklist support', + type: 'boolean' + }, + smoothLivePreview: { + default: false, + describe: 'Prevents weird effects in live previews due to incomplete input', + type: 'boolean' + } + }; + if (simple === false) { + return JSON.parse(JSON.stringify(defaultOptions)); + } + var ret = {}; + for (var opt in defaultOptions) { + if (defaultOptions.hasOwnProperty(opt)) { + ret[opt] = defaultOptions[opt].default; + } + } + return ret; } -return ""; -}); -return _b; + +/** + * Created by Tivie on 06-01-2015. + */ + +// Private properties +var showdown = {}, + parsers = {}, + extensions = {}, + globalOptions = getDefaultOpts(true), + flavor = { + github: { + omitExtraWLInCodeBlocks: true, + prefixHeaderId: 'user-content-', + simplifiedAutoLink: true, + literalMidWordUnderscores: true, + strikethrough: true, + tables: true, + tablesHeaderId: true, + ghCodeBlocks: true, + tasklists: true + }, + vanilla: getDefaultOpts(true) + }; + +/** + * helper namespace + * @type {{}} + */ +showdown.helper = {}; + +/** + * TODO LEGACY SUPPORT CODE + * @type {{}} + */ +showdown.extensions = {}; + +/** + * Set a global option + * @static + * @param {string} key + * @param {*} value + * @returns {showdown} + */ +showdown.setOption = function (key, value) { + 'use strict'; + globalOptions[key] = value; + return this; }; -var _7=function(_12){ -_12=_12.replace(/\n/g,"\n\n"); -var _13="p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del"; -var _14="p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math"; -_12=_12.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del)\b[^\r]*?\n<\/\2>[ \t]*(?=\n+))/gm,_15); -_12=_12.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math)\b[^\r]*?.*<\/\2>[ \t]*(?=\n+)\n)/gm,_15); -_12=_12.replace(/(\n[ ]{0,3}(<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g,_15); -_12=_12.replace(/(\n\n[ ]{0,3}[ \t]*(?=\n{2,}))/g,_15); -_12=_12.replace(/(?:\n\n)([ ]{0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g,_15); -_12=_12.replace(/\n\n/g,"\n"); -return _12; + +/** + * Get a global option + * @static + * @param {string} key + * @returns {*} + */ +showdown.getOption = function (key) { + 'use strict'; + return globalOptions[key]; }; -var _15=function(_16,m1){ -var _18=m1; -_18=_18.replace(/\n\n/g,"\n"); -_18=_18.replace(/^\n/,""); -_18=_18.replace(/\n+$/g,""); -_18="\n\n~K"+(_3.push(_18)-1)+"K\n\n"; -return _18; + +/** + * Get the global options + * @static + * @returns {{}} + */ +showdown.getOptions = function () { + 'use strict'; + return globalOptions; }; -var _9=function(_19){ -_19=_1a(_19); -var key=_1c("
"); -_19=_19.replace(/^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$/gm,key); -_19=_19.replace(/^[ ]{0,2}([ ]?\-[ ]?){3,}[ \t]*$/gm,key); -_19=_19.replace(/^[ ]{0,2}([ ]?\_[ ]?){3,}[ \t]*$/gm,key); -_19=_1d(_19); -_19=_1e(_19); -_19=_1f(_19); -_19=_7(_19); -_19=_20(_19); -return _19; + +/** + * Reset global options to the default values + * @static + */ +showdown.resetOptions = function () { + 'use strict'; + globalOptions = getDefaultOpts(true); }; -var _21=function(_22){ -_22=_23(_22); -_22=_24(_22); -_22=_25(_22); -_22=_26(_22); -_22=_27(_22); -_22=_28(_22); -_22=_11(_22); -_22=_29(_22); -_22=_22.replace(/ +\n/g,"
\n"); -return _22; + +/** + * Set the flavor showdown should use as default + * @param {string} name + */ +showdown.setFlavor = function (name) { + 'use strict'; + if (flavor.hasOwnProperty(name)) { + var preset = flavor[name]; + for (var option in preset) { + if (preset.hasOwnProperty(option)) { + globalOptions[option] = preset[option]; + } + } + } }; -var _24=function(_2a){ -var _2b=/(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|)/gi; -_2a=_2a.replace(_2b,function(_2c){ -var tag=_2c.replace(/(.)<\/?code>(?=.)/g,"$1`"); -tag=_2e(tag,"\\`*_"); -return tag; -}); -return _2a; + +/** + * Get the default options + * @static + * @param {boolean} [simple=true] + * @returns {{}} + */ +showdown.getDefaultOptions = function (simple) { + 'use strict'; + return getDefaultOpts(simple); }; -var _27=function(_2f){ -_2f=_2f.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,_30); -_2f=_2f.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\]\([ \t]*()?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,_30); -_2f=_2f.replace(/(\[([^\[\]]+)\])()()()()()/g,_30); -return _2f; + +/** + * Get or set a subParser + * + * subParser(name) - Get a registered subParser + * subParser(name, func) - Register a subParser + * @static + * @param {string} name + * @param {function} [func] + * @returns {*} + */ +showdown.subParser = function (name, func) { + 'use strict'; + if (showdown.helper.isString(name)) { + if (typeof func !== 'undefined') { + parsers[name] = func; + } else { + if (parsers.hasOwnProperty(name)) { + return parsers[name]; + } else { + throw Error('SubParser named ' + name + ' not registered!'); + } + } + } }; -var _30=function(_31,m1,m2,m3,m4,m5,m6,m7){ -if(m7==undefined){ -m7=""; -} -var _39=m1; -var _3a=m2; -var _3b=m3.toLowerCase(); -var url=m4; -var _3d=m7; -if(url==""){ -if(_3b==""){ -_3b=_3a.toLowerCase().replace(/ ?\n/g," "); -} -url="#"+_3b; -if(_1[_3b]!=undefined){ -url=_1[_3b]; -if(_2[_3b]!=undefined){ -_3d=_2[_3b]; -} -}else{ -if(_39.search(/\(\s*\)$/m)>-1){ -url=""; -}else{ -return _39; -} -} -} -url=_2e(url,"*_"); -var _3e=""; -return _3e; + +/** + * Gets or registers an extension + * @static + * @param {string} name + * @param {object|function=} ext + * @returns {*} + */ +showdown.extension = function (name, ext) { + 'use strict'; + + if (!showdown.helper.isString(name)) { + throw Error('Extension \'name\' must be a string'); + } + + name = showdown.helper.stdExtName(name); + + // Getter + if (showdown.helper.isUndefined(ext)) { + if (!extensions.hasOwnProperty(name)) { + throw Error('Extension named ' + name + ' is not registered!'); + } + return extensions[name]; + + // Setter + } else { + // Expand extension if it's wrapped in a function + if (typeof ext === 'function') { + ext = ext(); + } + + // Ensure extension is an array + if (!showdown.helper.isArray(ext)) { + ext = [ext]; + } + + var validExtension = validate(ext, name); + + if (validExtension.valid) { + extensions[name] = ext; + } else { + throw Error(validExtension.error); + } + } }; -var _26=function(_3f){ -_3f=_3f.replace(/(!\[(.*?)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,_40); -_3f=_3f.replace(/(!\[(.*?)\]\s?\([ \t]*()?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,_40); -return _3f; + +/** + * Gets all extensions registered + * @returns {{}} + */ +showdown.getAllExtensions = function () { + 'use strict'; + return extensions; }; -var _40=function(_41,m1,m2,m3,m4,m5,m6,m7){ -var _49=m1; -var _4a=m2; -var _4b=m3.toLowerCase(); -var url=m4; -var _4d=m7; -if(!_4d){ -_4d=""; -} -if(url==""){ -if(_4b==""){ -_4b=_4a.toLowerCase().replace(/ ?\n/g," "); + +/** + * Remove an extension + * @param {string} name + */ +showdown.removeExtension = function (name) { + 'use strict'; + delete extensions[name]; +}; + +/** + * Removes all extensions + */ +showdown.resetExtensions = function () { + 'use strict'; + extensions = {}; +}; + +/** + * Validate extension + * @param {array} extension + * @param {string} name + * @returns {{valid: boolean, error: string}} + */ +function validate(extension, name) { + 'use strict'; + + var errMsg = (name) ? 'Error in ' + name + ' extension->' : 'Error in unnamed extension', + ret = { + valid: true, + error: '' + }; + + if (!showdown.helper.isArray(extension)) { + extension = [extension]; + } + + for (var i = 0; i < extension.length; ++i) { + var baseMsg = errMsg + ' sub-extension ' + i + ': ', + ext = extension[i]; + if (typeof ext !== 'object') { + ret.valid = false; + ret.error = baseMsg + 'must be an object, but ' + typeof ext + ' given'; + return ret; + } + + if (!showdown.helper.isString(ext.type)) { + ret.valid = false; + ret.error = baseMsg + 'property "type" must be a string, but ' + typeof ext.type + ' given'; + return ret; + } + + var type = ext.type = ext.type.toLowerCase(); + + // normalize extension type + if (type === 'language') { + type = ext.type = 'lang'; + } + + if (type === 'html') { + type = ext.type = 'output'; + } + + if (type !== 'lang' && type !== 'output') { + ret.valid = false; + ret.error = baseMsg + 'type ' + type + ' is not recognized. Valid values: "lang" or "output"'; + return ret; + } + + if (ext.filter) { + if (typeof ext.filter !== 'function') { + ret.valid = false; + ret.error = baseMsg + '"filter" must be a function, but ' + typeof ext.filter + ' given'; + return ret; + } + + } else if (ext.regex) { + if (showdown.helper.isString(ext.regex)) { + ext.regex = new RegExp(ext.regex, 'g'); + } + if (!ext.regex instanceof RegExp) { + ret.valid = false; + ret.error = baseMsg + '"regex" property must either be a string or a RegExp object, but ' + + typeof ext.regex + ' given'; + return ret; + } + if (showdown.helper.isUndefined(ext.replace)) { + ret.valid = false; + ret.error = baseMsg + '"regex" extensions must implement a replace string or function'; + return ret; + } + + } else { + ret.valid = false; + ret.error = baseMsg + 'extensions must define either a "regex" property or a "filter" method'; + return ret; + } + + if (showdown.helper.isUndefined(ext.filter) && showdown.helper.isUndefined(ext.regex)) { + ret.valid = false; + ret.error = baseMsg + 'output extensions must define a filter property'; + return ret; + } + } + return ret; } -url="#"+_4b; -if(_1[_4b]!=undefined){ -url=_1[_4b]; -if(_2[_4b]!=undefined){ -_4d=_2[_4b]; + +/** + * Validate extension + * @param {object} ext + * @returns {boolean} + */ +showdown.validateExtension = function (ext) { + 'use strict'; + + var validateExtension = validate(ext, null); + if (!validateExtension.valid) { + console.warn(validateExtension.error); + return false; + } + return true; +}; + +/** + * showdownjs helper functions + */ + +if (!showdown.hasOwnProperty('helper')) { + showdown.helper = {}; } -}else{ -return _49; + +/** + * Check if var is string + * @static + * @param {string} a + * @returns {boolean} + */ +showdown.helper.isString = function isString(a) { + 'use strict'; + return (typeof a === 'string' || a instanceof String); +}; + +/** + * ForEach helper function + * @static + * @param {*} obj + * @param {function} callback + */ +showdown.helper.forEach = function forEach(obj, callback) { + 'use strict'; + if (typeof obj.forEach === 'function') { + obj.forEach(callback); + } else { + for (var i = 0; i < obj.length; i++) { + callback(obj[i], i, obj); + } + } +}; + +/** + * isArray helper function + * @static + * @param {*} a + * @returns {boolean} + */ +showdown.helper.isArray = function isArray(a) { + 'use strict'; + return a.constructor === Array; +}; + +/** + * Check if value is undefined + * @static + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is `undefined`, else `false`. + */ +showdown.helper.isUndefined = function isUndefined(value) { + 'use strict'; + return typeof value === 'undefined'; +}; + +/** + * Standardidize extension name + * @static + * @param {string} s extension name + * @returns {string} + */ +showdown.helper.stdExtName = function (s) { + 'use strict'; + return s.replace(/[_-]||\s/g, '').toLowerCase(); +}; + +function escapeCharactersCallback(wholeMatch, m1) { + 'use strict'; + var charCodeToEscape = m1.charCodeAt(0); + return '~E' + charCodeToEscape + 'E'; } + +/** + * Callback used to escape characters when passing through String.replace + * @static + * @param {string} wholeMatch + * @param {string} m1 + * @returns {string} + */ +showdown.helper.escapeCharactersCallback = escapeCharactersCallback; + +/** + * Escape characters in a string + * @static + * @param {string} text + * @param {string} charsToEscape + * @param {boolean} afterBackslash + * @returns {XML|string|void|*} + */ +showdown.helper.escapeCharacters = function escapeCharacters(text, charsToEscape, afterBackslash) { + 'use strict'; + // First we have to escape the escape characters so that + // we can build a character class out of them + var regexString = '([' + charsToEscape.replace(/([\[\]\\])/g, '\\$1') + '])'; + + if (afterBackslash) { + regexString = '\\\\' + regexString; + } + + var regex = new RegExp(regexString, 'g'); + text = text.replace(regex, escapeCharactersCallback); + + return text; +}; + +/** + * POLYFILLS + */ +if (showdown.helper.isUndefined(console)) { + console = { + warn: function (msg) { + 'use strict'; + alert(msg); + }, + log: function (msg) { + 'use strict'; + alert(msg); + } + }; } -_4a=_4a.replace(/"/g,"""); -url=_2e(url,"*_"); -var _4e="\""+_4a+"\"";"+_21(m1)+""); + +/** + * Turn Markdown link shortcuts into XHTML tags. + */ +showdown.subParser('anchors', function (text, config, globals) { + 'use strict'; + + var writeAnchorTag = function (wholeMatch, m1, m2, m3, m4, m5, m6, m7) { + if (showdown.helper.isUndefined(m7)) { + m7 = ''; + } + wholeMatch = m1; + var linkText = m2, + linkId = m3.toLowerCase(), + url = m4, + title = m7; + + if (!url) { + if (!linkId) { + // lower-case and turn embedded newlines into spaces + linkId = linkText.toLowerCase().replace(/ ?\n/g, ' '); + } + url = '#' + linkId; + + if (!showdown.helper.isUndefined(globals.gUrls[linkId])) { + url = globals.gUrls[linkId]; + if (!showdown.helper.isUndefined(globals.gTitles[linkId])) { + title = globals.gTitles[linkId]; + } + } else { + if (wholeMatch.search(/\(\s*\)$/m) > -1) { + // Special case for explicit empty url + url = ''; + } else { + return wholeMatch; + } + } + } + + url = showdown.helper.escapeCharacters(url, '*_', false); + var result = ''; + + return result; + }; + + // First, handle reference-style links: [link text] [id] + /* + text = text.replace(/ + ( // wrap whole match in $1 + \[ + ( + (?: + \[[^\]]*\] // allow brackets nested one level + | + [^\[] // or anything else + )* + ) + \] + + [ ]? // one optional space + (?:\n[ ]*)? // one optional newline followed by spaces + + \[ + (.*?) // id = $3 + \] + )()()()() // pad remaining backreferences + /g,_DoAnchors_callback); + */ + text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g, writeAnchorTag); + + // + // Next, inline-style links: [link text](url "optional title") + // + + /* + text = text.replace(/ + ( // wrap whole match in $1 + \[ + ( + (?: + \[[^\]]*\] // allow brackets nested one level + | + [^\[\]] // or anything else + ) + ) + \] + \( // literal paren + [ \t]* + () // no id, so leave $3 empty + ? // href = $4 + [ \t]* + ( // $5 + (['"]) // quote char = $6 + (.*?) // Title = $7 + \6 // matching quote + [ \t]* // ignore any spaces/tabs between closing quote and ) + )? // title is optional + \) + ) + /g,writeAnchorTag); + */ + text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\]\([ \t]*()?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g, + writeAnchorTag); + + // + // Last, handle reference-style shortcuts: [link text] + // These must come last in case you've also got [link test][1] + // or [link test](/foo) + // + + /* + text = text.replace(/ + ( // wrap whole match in $1 + \[ + ([^\[\]]+) // link text = $2; can't contain '[' or ']' + \] + )()()()()() // pad rest of backreferences + /g, writeAnchorTag); + */ + text = text.replace(/(\[([^\[\]]+)\])()()()()()/g, writeAnchorTag); + + return text; + }); -_4f=_4f.replace(/^(.+)[ \t]*\n-+[ \t]*\n+/gm,function(_52,m1){ -return _1c("

"+_21(m1)+"

"); + +showdown.subParser('autoLinks', function (text, options) { + 'use strict'; + + //simpleURLRegex = /\b(((https?|ftp|dict):\/\/|www\.)[-.+~:?#@!$&'()*,;=[\]\w]+)\b/gi, + + var simpleURLRegex = /\b(((https?|ftp|dict):\/\/|www\.)[^'">\s]+\.[^'">\s]+)(?=\s|$)(?!["<>])/gi, + delimUrlRegex = /<(((https?|ftp|dict):\/\/|www\.)[^'">\s]+)>/gi, + simpleMailRegex = /\b(?:mailto:)?([-.\w]+@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)\b/gi, + delimMailRegex = /<(?:mailto:)?([-.\w]+@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi; + + text = text.replace(delimUrlRegex, '
$1'); + text = text.replace(delimMailRegex, replaceMail); + //simpleURLRegex = /\b(((https?|ftp|dict):\/\/|www\.)[-.+~:?#@!$&'()*,;=[\]\w]+)\b/gi, + // Email addresses: + + if (options.simplifiedAutoLink) { + text = text.replace(simpleURLRegex, '$1'); + text = text.replace(simpleMailRegex, replaceMail); + } + + function replaceMail(wholeMatch, m1) { + var unescapedStr = showdown.subParser('unescapeSpecialChars')(m1); + return showdown.subParser('encodeEmailAddress')(unescapedStr); + } + + return text; }); -_4f=_4f.replace(/^(\#{1,6})[ \t]*(.+?)[ \t]*\#*\n+/gm,function(_54,m1,m2){ -var _57=m1.length; -return _1c(""+_21(m2)+""); + +/** + * These are all the transformations that form block-level + * tags like paragraphs, headers, and list items. + */ +showdown.subParser('blockGamut', function (text, options, globals) { + 'use strict'; + + // we parse blockquotes first so that we can have headings and hrs + // inside blockquotes + text = showdown.subParser('blockQuotes')(text, options, globals); + text = showdown.subParser('headers')(text, options, globals); + + // Do Horizontal Rules: + var key = showdown.subParser('hashBlock')('
', options, globals); + text = text.replace(/^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$/gm, key); + text = text.replace(/^[ ]{0,2}([ ]?\-[ ]?){3,}[ \t]*$/gm, key); + text = text.replace(/^[ ]{0,2}([ ]?_[ ]?){3,}[ \t]*$/gm, key); + + text = showdown.subParser('lists')(text, options, globals); + text = showdown.subParser('codeBlocks')(text, options, globals); + text = showdown.subParser('tables')(text, options, globals); + + // We already ran _HashHTMLBlocks() before, in Markdown(), but that + // was to escape raw HTML in the original Markdown source. This time, + // we're escaping the markup we've just created, so that we don't wrap + //

tags around block-level tags. + text = showdown.subParser('hashHTMLBlocks')(text, options, globals); + text = showdown.subParser('paragraphs')(text, options, globals); + + return text; + }); -return _4f; -}; -var _58; -var _1d=function(_59){ -_59+="~0"; -var _5a=/^(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm; -if(_4){ -_59=_59.replace(_5a,function(_5b,m1,m2){ -var _5e=m1; -var _5f=(m2.search(/[*+-]/g)>-1)?"ul":"ol"; -_5e=_5e.replace(/\n{2,}/g,"\n\n\n"); -var _60=_58(_5e); -_60=_60.replace(/\s+$/,""); -_60="<"+_5f+">"+_60+"\n"; -return _60; + +showdown.subParser('blockQuotes', function (text, options, globals) { + 'use strict'; + + /* + text = text.replace(/ + ( // Wrap whole match in $1 + ( + ^[ \t]*>[ \t]? // '>' at the start of a line + .+\n // rest of the first line + (.+\n)* // subsequent consecutive lines + \n* // blanks + )+ + ) + /gm, function(){...}); + */ + + text = text.replace(/((^[ \t]{0,3}>[ \t]?.+\n(.+\n)*\n*)+)/gm, function (wholeMatch, m1) { + var bq = m1; + + // attacklab: hack around Konqueror 3.5.4 bug: + // "----------bug".replace(/^-/g,"") == "bug" + bq = bq.replace(/^[ \t]*>[ \t]?/gm, '~0'); // trim one level of quoting + + // attacklab: clean up hack + bq = bq.replace(/~0/g, ''); + + bq = bq.replace(/^[ \t]+$/gm, ''); // trim whitespace-only lines + bq = showdown.subParser('githubCodeBlocks')(bq, options, globals); + bq = showdown.subParser('blockGamut')(bq, options, globals); // recurse + + bq = bq.replace(/(^|\n)/g, '$1 '); + // These leading spaces screw with

 content, so we need to fix that:
+    bq = bq.replace(/(\s*
[^\r]+?<\/pre>)/gm, function (wholeMatch, m1) {
+      var pre = m1;
+      // attacklab: hack around Konqueror 3.5.4 bug:
+      pre = pre.replace(/^  /mg, '~0');
+      pre = pre.replace(/~0/g, '');
+      return pre;
+    });
+
+    return showdown.subParser('hashBlock')('
\n' + bq + '\n
', options, globals); + }); + return text; }); -}else{ -_5a=/(\n\n|^\n?)(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/g; -_59=_59.replace(_5a,function(_61,m1,m2,m3){ -var _65=m1; -var _66=m2; -var _67=(m3.search(/[*+-]/g)>-1)?"ul":"ol"; -var _66=_66.replace(/\n{2,}/g,"\n\n\n"); -var _68=_58(_66); -_68=_65+"<"+_67+">\n"+_68+"\n"; -return _68; + +/** + * Process Markdown `
` blocks.
+ */
+showdown.subParser('codeBlocks', function (text, options, globals) {
+  'use strict';
+
+  /*
+   text = text.replace(text,
+   /(?:\n\n|^)
+   (								// $1 = the code block -- one or more lines, starting with a space/tab
+   (?:
+   (?:[ ]{4}|\t)			// Lines must start with a tab or a tab-width of spaces - attacklab: g_tab_width
+   .*\n+
+   )+
+   )
+   (\n*[ ]{0,3}[^ \t\n]|(?=~0))	// attacklab: g_tab_width
+   /g,function(){...});
+   */
+
+  // attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug
+  text += '~0';
+
+  var pattern = /(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=~0))/g;
+  text = text.replace(pattern, function (wholeMatch, m1, m2) {
+    var codeblock = m1,
+        nextChar = m2,
+        end = '\n';
+
+    codeblock = showdown.subParser('outdent')(codeblock);
+    codeblock = showdown.subParser('encodeCode')(codeblock);
+    codeblock = showdown.subParser('detab')(codeblock);
+    codeblock = codeblock.replace(/^\n+/g, ''); // trim leading newlines
+    codeblock = codeblock.replace(/\n+$/g, ''); // trim trailing newlines
+
+    if (options.omitExtraWLInCodeBlocks) {
+      end = '';
+    }
+
+    codeblock = '
' + codeblock + end + '
'; + + return showdown.subParser('hashBlock')(codeblock, options, globals) + nextChar; + }); + + // attacklab: strip sentinel + text = text.replace(/~0/, ''); + + return text; }); -} -_59=_59.replace(/~0/,""); -return _59; -}; -_58=function(_69){ -_4++; -_69=_69.replace(/\n{2,}$/,"\n"); -_69+="~0"; -_69=_69.replace(/(\n)?(^[ \t]*)([*+-]|\d+[.])[ \t]+([^\r]+?(\n{1,2}))(?=\n*(~0|\2([*+-]|\d+[.])[ \t]+))/gm,function(_6a,m1,m2,m3,m4){ -var _6f=m4; -var _70=m1; -var _71=m2; -if(_70||(_6f.search(/\n{2,}/)>-1)){ -_6f=_9(_72(_6f)); -}else{ -_6f=_1d(_72(_6f)); -_6f=_6f.replace(/\n$/,""); -_6f=_21(_6f); -} -return "
  • "+_6f+"
  • \n"; + +/** + * + * * Backtick quotes are used for spans. + * + * * You can use multiple backticks as the delimiters if you want to + * include literal backticks in the code span. So, this input: + * + * Just type ``foo `bar` baz`` at the prompt. + * + * Will translate to: + * + *

    Just type foo `bar` baz at the prompt.

    + * + * There's no arbitrary limit to the number of backticks you + * can use as delimters. If you need three consecutive backticks + * in your code, use four for delimiters, etc. + * + * * You can use spaces to get literal backticks at the edges: + * + * ... type `` `bar` `` ... + * + * Turns to: + * + * ... type `bar` ... + */ +showdown.subParser('codeSpans', function (text) { + 'use strict'; + + //special case -> literal html code tag + text = text.replace(/(<]*?>)([^]*?)<\/code>/g, function (wholeMatch, tag, c) { + c = c.replace(/^([ \t]*)/g, ''); // leading whitespace + c = c.replace(/[ \t]*$/g, ''); // trailing whitespace + c = showdown.subParser('encodeCode')(c); + return tag + c + '
    '; + }); + + /* + text = text.replace(/ + (^|[^\\]) // Character before opening ` can't be a backslash + (`+) // $2 = Opening run of ` + ( // $3 = The code block + [^\r]*? + [^`] // attacklab: work around lack of lookbehind + ) + \2 // Matching closer + (?!`) + /gm, function(){...}); + */ + text = text.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm, + function (wholeMatch, m1, m2, m3) { + var c = m3; + c = c.replace(/^([ \t]*)/g, ''); // leading whitespace + c = c.replace(/[ \t]*$/g, ''); // trailing whitespace + c = showdown.subParser('encodeCode')(c); + return m1 + '' + c + ''; + } + ); + + return text; }); -_69=_69.replace(/~0/g,""); -_4--; -return _69; -}; -var _1e=function(_73){ -_73+="~0"; -_73=_73.replace(/(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=~0))/g,function(_74,m1,m2){ -var _77=m1; -var _78=m2; -_77=_79(_72(_77)); -_77=_6(_77); -_77=_77.replace(/^\n+/g,""); -_77=_77.replace(/\n+$/g,""); -_77="
    "+_77+"\n
    "; -return _1c(_77)+_78; + +/** + * Convert all tabs to spaces + */ +showdown.subParser('detab', function (text) { + 'use strict'; + + // expand first n-1 tabs + text = text.replace(/\t(?=\t)/g, ' '); // g_tab_width + + // replace the nth with two sentinels + text = text.replace(/\t/g, '~A~B'); + + // use the sentinel to anchor our regex so it doesn't explode + text = text.replace(/~B(.+?)~A/g, function (wholeMatch, m1) { + var leadingText = m1, + numSpaces = 4 - leadingText.length % 4; // g_tab_width + + // there *must* be a better way to do this: + for (var i = 0; i < numSpaces; i++) { + leadingText += ' '; + } + + return leadingText; + }); + + // clean up sentinels + text = text.replace(/~A/g, ' '); // g_tab_width + text = text.replace(/~B/g, ''); + + return text; + }); -_73=_73.replace(/~0/,""); -return _73; -}; -var _1c=function(_7a){ -_7a=_7a.replace(/(^\n+|\n+$)/g,""); -return "\n\n~K"+(_3.push(_7a)-1)+"K\n\n"; -}; -var _23=function(_7b){ -_7b=_7b.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm,function(_7c,m1,m2,m3,m4){ -var c=m3; -c=c.replace(/^([ \t]*)/g,""); -c=c.replace(/[ \t]*$/g,""); -c=_79(c); -return m1+""+c+""; + +/** + * Smart processing for ampersands and angle brackets that need to be encoded. + */ +showdown.subParser('encodeAmpsAndAngles', function (text) { + 'use strict'; + // Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin: + // http://bumppo.net/projects/amputator/ + text = text.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g, '&'); + + // Encode naked <'s + text = text.replace(/<(?![a-z\/?\$!])/gi, '<'); + + return text; }); -return _7b; -}; -var _79=function(_82){ -_82=_82.replace(/&/g,"&"); -_82=_82.replace(//g,">"); -_82=_2e(_82,"*_{}[]\\",false); -return _82; -}; -var _29=function(_83){ -_83=_83.replace(/(\*\*|__)(?=\S)([^\r]*?\S[*_]*)\1/g,"$2"); -_83=_83.replace(/(\*|_)(?=\S)([^\r]*?\S)\1/g,"$2"); -return _83; -}; -var _1f=function(_84){ -_84=_84.replace(/((^[ \t]*>[ \t]?.+\n(.+\n)*\n*)+)/gm,function(_85,m1){ -var bq=m1; -bq=bq.replace(/^[ \t]*>[ \t]?/gm,"~0"); -bq=bq.replace(/~0/g,""); -bq=bq.replace(/^[ \t]+$/gm,""); -bq=_9(bq); -bq=bq.replace(/(^|\n)/g,"$1 "); -bq=bq.replace(/(\s*
    [^\r]+?<\/pre>)/gm,function(_88,m1){
    -var pre=m1;
    -pre=pre.replace(/^  /mg,"~0");
    -pre=pre.replace(/~0/g,"");
    -return pre;
    +
    +/**
    + * Returns the string, with after processing the following backslash escape sequences.
    + *
    + * attacklab: The polite way to do this is with the new escapeCharacters() function:
    + *
    + *    text = escapeCharacters(text,"\\",true);
    + *    text = escapeCharacters(text,"`*_{}[]()>#+-.!",true);
    + *
    + * ...but we're sidestepping its use of the (slow) RegExp constructor
    + * as an optimization for Firefox.  This function gets called a LOT.
    + */
    +showdown.subParser('encodeBackslashEscapes', function (text) {
    +  'use strict';
    +  text = text.replace(/\\(\\)/g, showdown.helper.escapeCharactersCallback);
    +  text = text.replace(/\\([`*_{}\[\]()>#+-.!])/g, showdown.helper.escapeCharactersCallback);
    +  return text;
     });
    -return _1c("
    \n"+bq+"\n
    "); + +/** + * Encode/escape certain characters inside Markdown code runs. + * The point is that in code, these characters are literals, + * and lose their special Markdown meanings. + */ +showdown.subParser('encodeCode', function (text) { + 'use strict'; + + // Encode all ampersands; HTML entities are not + // entities within a Markdown code span. + text = text.replace(/&/g, '&'); + + // Do the angle bracket song and dance: + text = text.replace(//g, '>'); + + // Now, escape characters that are magic in Markdown: + text = showdown.helper.escapeCharacters(text, '*_{}[]\\', false); + + // jj the line above breaks this: + //--- + //* Item + // 1. Subitem + // special char: * + // --- + + return text; }); -return _84; -}; -var _20=function(_8b){ -_8b=_8b.replace(/^\n+/g,""); -_8b=_8b.replace(/\n+$/g,""); -var _8c=_8b.split(/\n{2,}/g); -var _8d=new Array(); -var end=_8c.length; -for(var i=0;i=0){ -_8d.push(str); -}else{ -if(str.search(/\S/)>=0){ -str=_21(str); -str=str.replace(/^([ \t]*)/g,"

    "); -str+="

    "; -_8d.push(str); -} -} -} -end=_8d.length; -for(var i=0;i=0){ -var _91=_3[RegExp.$1]; -_91=_91.replace(/\$/g,"$$$$"); -_8d[i]=_8d[i].replace(/~K\d+K/,_91); -} -} -return _8d.join("\n\n"); -}; -var _11=function(_92){ -_92=_92.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g,"&"); -_92=_92.replace(/<(?![a-z\/?\$!])/gi,"<"); -return _92; -}; -var _25=function(_93){ -_93=_93.replace(/\\(\\)/g,_94); -_93=_93.replace(/\\([`*_{}\[\]()>#+-.!])/g,_94); -return _93; -}; -var _28=function(_95){ -_95=_95.replace(/<((https?|ftp|dict):[^'">\s]+)>/gi,"$1"); -_95=_95.replace(/<(?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi,function(_96,m1){ -return _98(_a(m1)); + +/** + * Input: an email address, e.g. "foo@example.com" + * + * Output: the email address as a mailto link, with each character + * of the address encoded as either a decimal or hex entity, in + * the hopes of foiling most address harvesting spam bots. E.g.: + * + * foo + * @example.com + * + * Based on a filter by Matthew Wickline, posted to the BBEdit-Talk + * mailing list: + * + */ +showdown.subParser('encodeEmailAddress', function (addr) { + 'use strict'; + + var encode = [ + function (ch) { + return '&#' + ch.charCodeAt(0) + ';'; + }, + function (ch) { + return '&#x' + ch.charCodeAt(0).toString(16) + ';'; + }, + function (ch) { + return ch; + } + ]; + + addr = 'mailto:' + addr; + + addr = addr.replace(/./g, function (ch) { + if (ch === '@') { + // this *must* be encoded. I insist. + ch = encode[Math.floor(Math.random() * 2)](ch); + } else if (ch !== ':') { + // leave ':' alone (to spot mailto: later) + var r = Math.random(); + // roughly 10% raw, 45% hex, 45% dec + ch = ( + r > 0.9 ? encode[2](ch) : r > 0.45 ? encode[1](ch) : encode[0](ch) + ); + } + return ch; + }); + + addr = '' + addr + ''; + addr = addr.replace(/">.+:/g, '">'); // strip the mailto: from the visible part + + return addr; }); -return _95; -}; -var _98=function(_99){ -function char2hex(ch){ -var _9b="0123456789ABCDEF"; -var dec=ch.charCodeAt(0); -return (_9b.charAt(dec>>4)+_9b.charAt(dec&15)); -} -var _9d=[function(ch){ -return "&#"+ch.charCodeAt(0)+";"; -},function(ch){ -return "&#x"+char2hex(ch)+";"; -},function(ch){ -return ch; -}]; -_99="mailto:"+_99; -_99=_99.replace(/./g,function(ch){ -if(ch=="@"){ -ch=_9d[Math.floor(Math.random()*2)](ch); -}else{ -if(ch!=":"){ -var r=Math.random(); -ch=(r>0.9?_9d[2](ch):r>0.45?_9d[1](ch):_9d[0](ch)); -} -} -return ch; + +/** + * Within tags -- meaning between < and > -- encode [\ ` * _] so they + * don't conflict with their use in Markdown for code, italics and strong. + */ +showdown.subParser('escapeSpecialCharsWithinTagAttributes', function (text) { + 'use strict'; + + // Build a regex to find HTML tags and comments. See Friedl's + // "Mastering Regular Expressions", 2nd Ed., pp. 200-201. + var regex = /(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|)/gi; + + text = text.replace(regex, function (wholeMatch) { + var tag = wholeMatch.replace(/(.)<\/?code>(?=.)/g, '$1`'); + tag = showdown.helper.escapeCharacters(tag, '\\`*_', false); + return tag; + }); + + return text; }); -_99=""+_99+""; -_99=_99.replace(/">.+:/g,"\">"); -return _99; -}; -var _a=function(_a3){ -_a3=_a3.replace(/~E(\d+)E/g,function(_a4,m1){ -var _a6=parseInt(m1); -return String.fromCharCode(_a6); + +/** + * Handle github codeblocks prior to running HashHTML so that + * HTML contained within the codeblock gets escaped properly + * Example: + * ```ruby + * def hello_world(x) + * puts "Hello, #{x}" + * end + * ``` + */ +showdown.subParser('githubCodeBlocks', function (text, options, globals) { + 'use strict'; + + // early exit if option is not enabled + if (!options.ghCodeBlocks) { + return text; + } + + text += '~0'; + + text = text.replace(/(?:^|\n)```(.*)\n([\s\S]*?)\n```/g, function (wholeMatch, language, codeblock) { + var end = (options.omitExtraWLInCodeBlocks) ? '' : '\n'; + + codeblock = showdown.subParser('encodeCode')(codeblock); + codeblock = showdown.subParser('detab')(codeblock); + codeblock = codeblock.replace(/^\n+/g, ''); // trim leading newlines + codeblock = codeblock.replace(/\n+$/g, ''); // trim trailing whitespace + + codeblock = '
    ' + codeblock + end + '
    '; + + return showdown.subParser('hashBlock')(codeblock, options, globals); + }); + + // attacklab: strip sentinel + text = text.replace(/~0/, ''); + + return text; + }); -return _a3; -}; -var _72=function(_a7){ -_a7=_a7.replace(/^(\t|[ ]{1,4})/gm,"~0"); -_a7=_a7.replace(/~0/g,""); -return _a7; -}; -var _6=function(_a8){ -_a8=_a8.replace(/\t(?=\t)/g," "); -_a8=_a8.replace(/\t/g,"~A~B"); -_a8=_a8.replace(/~B(.+?)~A/g,function(_a9,m1,m2){ -var _ac=m1; -var _ad=4-_ac.length%4; -for(var i=0;i<_ad;i++){ -_ac+=" "; -} -return _ac; + +showdown.subParser('hashBlock', function (text, options, globals) { + 'use strict'; + text = text.replace(/(^\n+|\n+$)/g, ''); + return '\n\n~K' + (globals.gHtmlBlocks.push(text) - 1) + 'K\n\n'; }); -_a8=_a8.replace(/~A/g," "); -_a8=_a8.replace(/~B/g,""); -return _a8; -}; -var _2e=function(_af,_b0,_b1){ -var _b2="(["+_b0.replace(/([\[\]\\])/g,"\\$1")+"])"; -if(_b1){ -_b2="\\\\"+_b2; + +showdown.subParser('hashElement', function (text, options, globals) { + 'use strict'; + + return function (wholeMatch, m1) { + var blockText = m1; + + // Undo double lines + blockText = blockText.replace(/\n\n/g, '\n'); + blockText = blockText.replace(/^\n/, ''); + + // strip trailing blank lines + blockText = blockText.replace(/\n+$/g, ''); + + // Replace the element text with a marker ("~KxK" where x is its key) + blockText = '\n\n~K' + (globals.gHtmlBlocks.push(blockText) - 1) + 'K\n\n'; + + return blockText; + }; +}); + +showdown.subParser('hashHTMLBlocks', function (text, options, globals) { + 'use strict'; + + // attacklab: Double up blank lines to reduce lookaround + text = text.replace(/\n/g, '\n\n'); + + // Hashify HTML blocks: + // We only want to do this for block-level HTML tags, such as headers, + // lists, and tables. That's because we still want to wrap

    s around + // "paragraphs" that are wrapped in non-block-level tags, such as anchors, + // phrase emphasis, and spans. The list of tags we're looking for is + // hard-coded: + //var block_tags_a = + // 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del|style|section|header|footer|nav|article|aside'; + // var block_tags_b = + // 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|style|section|header|footer|nav|article|aside'; + + // First, look for nested blocks, e.g.: + //

    + //
    + // tags for inner block must be indented. + //
    + //
    + // + // The outermost tags must start at the left margin for this to match, and + // the inner nested divs must be indented. + // We need to do this before the next, more liberal match, because the next + // match will start at the first `
    ` and stop at the first `
    `. + + // attacklab: This regex can be expensive when it fails. + /* + var text = text.replace(/ + ( // save in $1 + ^ // start of line (with /m) + <($block_tags_a) // start tag = $2 + \b // word break + // attacklab: hack around khtml/pcre bug... + [^\r]*?\n // any number of lines, minimally matching + // the matching end tag + [ \t]* // trailing spaces/tabs + (?=\n+) // followed by a newline + ) // attacklab: there are sentinel newlines at end of document + /gm,function(){...}}; + */ + text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del)\b[^\r]*?\n<\/\2>[ \t]*(?=\n+))/gm, + showdown.subParser('hashElement')(text, options, globals)); + + // + // Now match more liberally, simply from `\n` to `\n` + // + + /* + var text = text.replace(/ + ( // save in $1 + ^ // start of line (with /m) + <($block_tags_b) // start tag = $2 + \b // word break + // attacklab: hack around khtml/pcre bug... + [^\r]*? // any number of lines, minimally matching + // the matching end tag + [ \t]* // trailing spaces/tabs + (?=\n+) // followed by a newline + ) // attacklab: there are sentinel newlines at end of document + /gm,function(){...}}; + */ + text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|style|section|header|footer|nav|article|aside|address|audio|canvas|figure|hgroup|output|video)\b[^\r]*?<\/\2>[ \t]*(?=\n+)\n)/gm, + showdown.subParser('hashElement')(text, options, globals)); + + // Special case just for
    . It was easier to make a special case than + // to make the other regex more complicated. + + /* + text = text.replace(/ + ( // save in $1 + \n\n // Starting after a blank line + [ ]{0,3} + (<(hr) // start tag = $2 + \b // word break + ([^<>])*? // + \/?>) // the matching end tag + [ \t]* + (?=\n{2,}) // followed by a blank line + ) + /g,showdown.subParser('hashElement')(text, options, globals)); + */ + text = text.replace(/(\n[ ]{0,3}(<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g, + showdown.subParser('hashElement')(text, options, globals)); + + // Special case for standalone HTML comments: + + /* + text = text.replace(/ + ( // save in $1 + \n\n // Starting after a blank line + [ ]{0,3} // attacklab: g_tab_width - 1 + + [ \t]* + (?=\n{2,}) // followed by a blank line + ) + /g,showdown.subParser('hashElement')(text, options, globals)); + */ + text = text.replace(/(\n\n[ ]{0,3}[ \t]*(?=\n{2,}))/g, + showdown.subParser('hashElement')(text, options, globals)); + + // PHP and ASP-style processor instructions ( and <%...%>) + + /* + text = text.replace(/ + (?: + \n\n // Starting after a blank line + ) + ( // save in $1 + [ ]{0,3} // attacklab: g_tab_width - 1 + (?: + <([?%]) // $2 + [^\r]*? + \2> + ) + [ \t]* + (?=\n{2,}) // followed by a blank line + ) + /g,showdown.subParser('hashElement')(text, options, globals)); + */ + text = text.replace(/(?:\n\n)([ ]{0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g, + showdown.subParser('hashElement')(text, options, globals)); + + // attacklab: Undo double lines (see comment at top of this function) + text = text.replace(/\n\n/g, '\n'); + return text; + +}); + +showdown.subParser('headers', function (text, options, globals) { + 'use strict'; + + var prefixHeader = options.prefixHeaderId, + headerLevelStart = (isNaN(parseInt(options.headerLevelStart))) ? 1 : parseInt(options.headerLevelStart), + + // Set text-style headers: + // Header 1 + // ======== + // + // Header 2 + // -------- + // + setextRegexH1 = (options.smoothLivePreview) ? /^(.+)[ \t]*\n={2,}[ \t]*\n+/gm : /^(.+)[ \t]*\n=+[ \t]*\n+/gm, + setextRegexH2 = (options.smoothLivePreview) ? /^(.+)[ \t]*\n-{2,}[ \t]*\n+/gm : /^(.+)[ \t]*\n-+[ \t]*\n+/gm; + + text = text.replace(setextRegexH1, function (wholeMatch, m1) { + + var spanGamut = showdown.subParser('spanGamut')(m1, options, globals), + hID = (options.noHeaderId) ? '' : ' id="' + headerId(m1) + '"', + hLevel = headerLevelStart, + hashBlock = '' + spanGamut + ''; + return showdown.subParser('hashBlock')(hashBlock, options, globals); + }); + + text = text.replace(setextRegexH2, function (matchFound, m1) { + var spanGamut = showdown.subParser('spanGamut')(m1, options, globals), + hID = (options.noHeaderId) ? '' : ' id="' + headerId(m1) + '"', + hLevel = headerLevelStart + 1, + hashBlock = '' + spanGamut + ''; + return showdown.subParser('hashBlock')(hashBlock, options, globals); + }); + + // atx-style headers: + // # Header 1 + // ## Header 2 + // ## Header 2 with closing hashes ## + // ... + // ###### Header 6 + // + text = text.replace(/^(#{1,6})[ \t]*(.+?)[ \t]*#*\n+/gm, function (wholeMatch, m1, m2) { + var span = showdown.subParser('spanGamut')(m2, options, globals), + hID = (options.noHeaderId) ? '' : ' id="' + headerId(m2) + '"', + hLevel = headerLevelStart - 1 + m1.length, + header = '' + span + ''; + + return showdown.subParser('hashBlock')(header, options, globals); + }); + + function headerId(m) { + var title, escapedId = m.replace(/[^\w]/g, '').toLowerCase(); + + if (globals.hashLinkCounts[escapedId]) { + title = escapedId + '-' + (globals.hashLinkCounts[escapedId]++); + } else { + title = escapedId; + globals.hashLinkCounts[escapedId] = 1; + } + + // Prefix id to prevent causing inadvertent pre-existing style matches. + if (prefixHeader === true) { + prefixHeader = 'section'; + } + + if (showdown.helper.isString(prefixHeader)) { + return prefixHeader + title; + } + return title; + } + + return text; +}); + +/** + * Turn Markdown image shortcuts into ,