From 75089931cf6e262dab00eebd0a7f809ea1bedf6d Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Tue, 27 Sep 2011 16:36:48 -0700 Subject: [PATCH] Adding Markdown module --HG-- branch : 1.x --- lib/markdown/MarkdownSharp.dll | Bin 0 -> 62464 bytes lib/markdown/MarkdownSharp.pdb | Bin 0 -> 58880 bytes lib/markdown/MarkdownSharp.xml | 433 ++++ .../Markdown/Content/Admin/Images/grippie.png | Bin 0 -> 162 bytes .../Content/Admin/Images/wmd-buttons.png | Bin 0 -> 2697 bytes .../Modules/Markdown/Content/Web.config | 21 + .../Modules/Markdown/Markdown.csproj | 138 ++ src/Orchard.Web/Modules/Markdown/Module.txt | 10 + .../Modules/Markdown/ResourceManifest.cs | 16 + .../Markdown/Scripts/Markdown.Converter.js | 1325 +++++++++++ .../Markdown/Scripts/Markdown.Editor.js | 2109 +++++++++++++++++ .../Markdown/Scripts/Markdown.Sanitizer.js | 108 + .../Modules/Markdown/Scripts/Web.config | 21 + .../Scripts/jquery.textarearesizer.min.js | 1 + .../Markdown/Scripts/orchard-markdown.js | 43 + .../Markdown/Services/MarkdownFilter.cs | 18 + .../Modules/Markdown/Styles/Web.config | 21 + .../Markdown/Styles/admin-markdown.css | 87 + .../Views/Body-Markdown.Editor.cshtml | 20 + .../Modules/Markdown/Views/Web.config | 41 + src/Orchard.Web/Modules/Markdown/web.config | 39 + src/Orchard.sln | 13 + 22 files changed, 4464 insertions(+) create mode 100644 lib/markdown/MarkdownSharp.dll create mode 100644 lib/markdown/MarkdownSharp.pdb create mode 100644 lib/markdown/MarkdownSharp.xml create mode 100644 src/Orchard.Web/Modules/Markdown/Content/Admin/Images/grippie.png create mode 100644 src/Orchard.Web/Modules/Markdown/Content/Admin/Images/wmd-buttons.png create mode 100644 src/Orchard.Web/Modules/Markdown/Content/Web.config create mode 100644 src/Orchard.Web/Modules/Markdown/Markdown.csproj create mode 100644 src/Orchard.Web/Modules/Markdown/Module.txt create mode 100644 src/Orchard.Web/Modules/Markdown/ResourceManifest.cs create mode 100644 src/Orchard.Web/Modules/Markdown/Scripts/Markdown.Converter.js create mode 100644 src/Orchard.Web/Modules/Markdown/Scripts/Markdown.Editor.js create mode 100644 src/Orchard.Web/Modules/Markdown/Scripts/Markdown.Sanitizer.js create mode 100644 src/Orchard.Web/Modules/Markdown/Scripts/Web.config create mode 100644 src/Orchard.Web/Modules/Markdown/Scripts/jquery.textarearesizer.min.js create mode 100644 src/Orchard.Web/Modules/Markdown/Scripts/orchard-markdown.js create mode 100644 src/Orchard.Web/Modules/Markdown/Services/MarkdownFilter.cs create mode 100644 src/Orchard.Web/Modules/Markdown/Styles/Web.config create mode 100644 src/Orchard.Web/Modules/Markdown/Styles/admin-markdown.css create mode 100644 src/Orchard.Web/Modules/Markdown/Views/Body-Markdown.Editor.cshtml create mode 100644 src/Orchard.Web/Modules/Markdown/Views/Web.config create mode 100644 src/Orchard.Web/Modules/Markdown/web.config diff --git a/lib/markdown/MarkdownSharp.dll b/lib/markdown/MarkdownSharp.dll new file mode 100644 index 0000000000000000000000000000000000000000..b779a466b362890c861072062636ca96142fdb8f GIT binary patch literal 62464 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∾hTktzQEj;h?A&TX5|!9q!nO0|?G>Aat5jPAZXK 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 NWgg!9_g$)i{|lSc%?AJg literal 0 HcmV?d00001 diff --git a/lib/markdown/MarkdownSharp.xml b/lib/markdown/MarkdownSharp.xml new file mode 100644 index 000000000..e76600659 --- /dev/null +++ b/lib/markdown/MarkdownSharp.xml @@ -0,0 +1,433 @@ + + + + MarkdownSharp + + + + + when true, (most) bare plain URLs are auto-hyperlinked + WARNING: this is a significant deviation from the markdown spec + + + + + when true, RETURN becomes a literal newline + WARNING: this is a significant deviation from the markdown spec + + + + + use ">" for HTML output, or " />" for XHTML output + + + + + when true, problematic URL characters like [, ], (, and so forth will be encoded + WARNING: this is a significant deviation from the markdown spec + + + + + when false, email addresses will never be auto-linked + WARNING: this is a significant deviation from the markdown spec + + + + + when true, bold and italic require non-word characters on either side + WARNING: this is a significant deviation from the markdown spec + + + + + Markdown is a text-to-HTML conversion tool for web writers. + Markdown allows you to write using an easy-to-read, easy-to-write plain text format, + then convert it to structurally valid XHTML (or HTML). + + + + + maximum nested depth of [] and () supported by the transform; implementation detail + + + + + Tabs are automatically converted to spaces as part of the transform + this constant determines how "wide" those tabs become in spaces + + + + + Create a new Markdown instance using default options + + + + + Create a new Markdown instance and optionally load options from the supplied options parameter. + + + + + Create a new Markdown instance and optionally load options from a configuration + file. There they should be stored in the appSettings section, available options are: + + Markdown.StrictBoldItalic (true/false) + Markdown.EmptyElementSuffix (">" or " />" without the quotes) + Markdown.LinkEmails (true/false) + Markdown.AutoNewLines (true/false) + Markdown.AutoHyperlink (true/false) + Markdown.EncodeProblemUrlCharacters (true/false) + + + + + In the static constuctor we'll initialize what stays the same across all transforms. + + + + + Transforms the provided Markdown-formatted text to HTML; + see http://en.wikipedia.org/wiki/Markdown + + + The order in which other subs are called here is + essential. Link and image substitutions need to happen before + EscapeSpecialChars(), so that any *'s or _'s in the a + and img tags get encoded. + + + + + Perform transformations that form block-level tags like paragraphs, headers, and list items. + + + + + Perform transformations that occur *within* block-level tags like paragraphs, headers, and list items. + + + + + splits on two or more newlines, to form "paragraphs"; + each paragraph is then unhashed (if it is a hash) or wrapped in HTML p tag + + + + + Reusable pattern to match balanced [brackets]. See Friedl's + "Mastering Regular Expressions", 2nd Ed., pp. 328-331. + + + + + Reusable pattern to match balanced (parens). See Friedl's + "Mastering Regular Expressions", 2nd Ed., pp. 328-331. + + + + + Strips link definitions from text, stores the URLs and titles in hash references. + + + ^[id]: url "optional title" + + + + + derived pretty much verbatim from PHP Markdown + + + + + replaces any block-level HTML blocks with hash entries + + + + + returns an array of HTML tokens comprising the input string. Each token is + either a tag (possibly with nested, tags contained therein, such + as <a href="<MTFoo>">, or a run of text between tags. Each element of the + array is a two-element array; the first is either 'tag' or 'text'; the second is + the actual value. + + + + + Turn Markdown link shortcuts into HTML anchor tags + + + [link text](url "title") + [link text][id] + [id] + + + + + Turn Markdown image shortcuts into HTML img tags. + + + ![alt text][id] + ![alt text](url "optional title") + + + + + Turn Markdown headers into HTML header tags + + + Header 1 + ======== + + Header 2 + -------- + + # Header 1 + ## Header 2 + ## Header 2 with closing hashes ## + ... + ###### Header 6 + + + + + Turn Markdown horizontal rules into HTML hr tags + + + *** + * * * + --- + - - - + + + + + Turn Markdown lists into HTML ul and ol and li tags + + + + + Process the contents of a single ordered or unordered list, splitting it + into individual list items. + + + + + /// Turn Markdown 4-space indented code into HTML pre code blocks + + + + + Turn Markdown `code spans` into HTML code tags + + + + + Turn Markdown *italics* and **bold** into HTML strong and em tags + + + + + Turn markdown line breaks (two space at end of line) into HTML break tags + + + + + Turn Markdown > quoted blocks into HTML blockquote blocks + + + + + Turn angle-delimited URLs into HTML anchor tags + + + <http://www.example.com> + + + + + Remove one level of line-leading spaces + + + + + encodes email address randomly + roughly 10% raw, 45% hex, 45% dec + note that @ is always encoded and : never is + + + + + Encode/escape certain Markdown characters inside code blocks and spans where they are literals + + + + + Encode any ampersands (that aren't part of an HTML entity) and left or right angle brackets + + + + + Encodes any escaped characters such as \`, \*, \[ etc + + + + + swap back in all the special characters we've hidden + + + + + escapes Bold [ * ] and Italic [ _ ] characters + + + + + hex-encodes some unusual "problem" chars in URLs to avoid URL detection problems + + + + + Within tags -- meaning between < and > -- encode [\ ` * _] so they + don't conflict with their use in Markdown for code, italics and strong. + We're replacing each such character with its corresponding hash + value; this is likely overkill, but it should prevent us from colliding + with the escape values by accident. + + + + + convert all tabs to _tabWidth spaces; + standardizes line endings from DOS (CR LF) or Mac (CR) to UNIX (LF); + makes sure text ends with a couple of newlines; + removes any blank lines (only spaces) in the text + + + + + this is to emulate what's evailable in PHP + + + + + current version of MarkdownSharp; + see http://code.google.com/p/markdownsharp/ for the latest code or to contribute + + + + + Static constructor + + + In the static constuctor we'll initialize what stays the same across all transforms. + + + + + + Strips link definitions from text, stores the URLs and titles in hash references. + + Link defs are in the form: ^[id]: url "optional title" + + + + Hashify HTML blocks + + + + + These are all the transformations that form block-level + tags like paragraphs, headers, and list items. + + + + + These are all the transformations that occur *within* block-level + tags like paragraphs, headers, and list items. + + + + + + + + Process the contents of a single ordered or unordered list, splitting it + into individual list items. + + + + + Encode/escape certain characters inside Markdown code runs. + + + The point is that in code, these characters are literals, and lose their + special Markdown meanings. + + + + + Smart processing for ampersands and angle brackets that need to be encoded. + + + + + Swap back in all the special characters we've hidden. + + + + + Remove one level of line-leading tabs or spaces + + + + + This is to emulate what's evailable in PHP + + + + + + + + Calculate an MD5 hash of an arbitrary string + + + + + + + when true, (most) bare plain URLs are auto-hyperlinked + WARNING: this is a significant deviation from the markdown spec + + + + + when true, RETURN becomes a literal newline + WARNING: this is a significant deviation from the markdown spec + + + + + use ">" for HTML output, or " />" for XHTML output + + + + + when true, problematic URL characters like [, ], (, and so forth will be encoded + WARNING: this is a significant deviation from the markdown spec + + + + + when false, email addresses will never be auto-linked + WARNING: this is a significant deviation from the markdown spec + + + + + when true, bold and italic require non-word characters on either side + WARNING: this is a significant deviation from the markdown spec + + + + diff --git a/src/Orchard.Web/Modules/Markdown/Content/Admin/Images/grippie.png b/src/Orchard.Web/Modules/Markdown/Content/Admin/Images/grippie.png new file mode 100644 index 0000000000000000000000000000000000000000..6524d4167d2d6e3939cfcf3111ce520acd66bc21 GIT binary patch literal 162 zcmeAS@N?(olHy`uVBq!ia0vp^(m>40!3-puuch|@DVB6cUq=Rp^(V|(yIunMk|nMY zCBgY=CFO}lsSJ)O`AMk?p1FzXsX?iUDV2pMQ*D5X*aCb)TzBu@{r~^}iVZzqfg(&L zL4Lvi8J=!8@B;EgJY5_^DsCku9AK6xWM-3k!OUU6z#qV1KKathOF(%BPgg&ebxsLQ E0Ke)mAOHXW literal 0 HcmV?d00001 diff --git a/src/Orchard.Web/Modules/Markdown/Content/Admin/Images/wmd-buttons.png b/src/Orchard.Web/Modules/Markdown/Content/Admin/Images/wmd-buttons.png new file mode 100644 index 0000000000000000000000000000000000000000..36ac79a286b96e73be5b439de789ec59f8123f90 GIT binary patch literal 2697 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 + + + + + + + + + + + + + + + + + diff --git a/src/Orchard.Web/Modules/Markdown/Markdown.csproj b/src/Orchard.Web/Modules/Markdown/Markdown.csproj new file mode 100644 index 000000000..87b6de8ad --- /dev/null +++ b/src/Orchard.Web/Modules/Markdown/Markdown.csproj @@ -0,0 +1,138 @@ + + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {3158C928-888C-4A84-8BC1-4A8257489538} + {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} + Library + Properties + Markdown + Markdown + v4.0 + false + + + 3.5 + + + false + + + true + full + false + bin\ + DEBUG;TRACE + prompt + 4 + ..\..\..\OrchardBasicCorrectness.ruleset + + + pdbonly + true + bin\ + TRACE + prompt + 4 + AllRules.ruleset + + + + ..\..\..\..\lib\markdown\MarkdownSharp.dll + True + + + + + False + ..\..\..\..\lib\aspnetmvc\System.Web.Mvc.dll + + + + + {2D1D92BB-4555-4CBE-8D0E-63563D6CE4C6} + Orchard.Framework + + + {9916839C-39FC-4CEB-A5AF-89CA7E87119F} + Orchard.Core + + + + + + + + + + + + + + Designer + + + + + + + + + + + + + + + + Designer + + + + + Designer + + + + + + + + $(ProjectDir)\..\Manifests + + + + + + + + + + + + False + True + 53593 + / + + + False + True + http://orchard.codeplex.com + False + + + + + \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Markdown/Module.txt b/src/Orchard.Web/Modules/Markdown/Module.txt new file mode 100644 index 000000000..e51756127 --- /dev/null +++ b/src/Orchard.Web/Modules/Markdown/Module.txt @@ -0,0 +1,10 @@ +Name: Markdown +AntiForgery: enabled +Author: The Orchard Team +Website: http://orchardproject.net +Version: 1.2.0 +OrchardVersion: 1.2.0 +Description: The Markdown module enables rich text contents to be created using the Markdown syntax. +FeatureDescription: Markdown editor. +Category: Input Editor +Dependencies: Common \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Markdown/ResourceManifest.cs b/src/Orchard.Web/Modules/Markdown/ResourceManifest.cs new file mode 100644 index 000000000..28773913a --- /dev/null +++ b/src/Orchard.Web/Modules/Markdown/ResourceManifest.cs @@ -0,0 +1,16 @@ +using Orchard.UI.Resources; + +namespace Markdown { + public class ResourceManifest : IResourceManifestProvider { + public void BuildManifests(ResourceManifestBuilder builder) { + var manifest = builder.Add(); + manifest.DefineScript("Markdown_Converter").SetUrl("Markdown.Converter.js"); + manifest.DefineScript("Markdown_Sanitizer").SetUrl("Markdown.Sanitizer.js").SetDependencies("Markdown_Converter"); + manifest.DefineScript("Markdown_Editor").SetUrl("Markdown.Editor.js").SetDependencies("Markdown_Sanitizer"); + manifest.DefineScript("Resizer").SetUrl("jquery.textarearesizer.min.js"); + + manifest.DefineScript("OrchardMarkdown").SetUrl("orchard-markdown.js").SetDependencies("Resizer", "Markdown_Editor"); ; + manifest.DefineStyle("OrchardMarkdown").SetUrl("admin-markdown.css"); + } + } +} diff --git a/src/Orchard.Web/Modules/Markdown/Scripts/Markdown.Converter.js b/src/Orchard.Web/Modules/Markdown/Scripts/Markdown.Converter.js new file mode 100644 index 000000000..da7025ad9 --- /dev/null +++ b/src/Orchard.Web/Modules/Markdown/Scripts/Markdown.Converter.js @@ -0,0 +1,1325 @@ +var Markdown; + +if (typeof exports === "object" && typeof require === "function") // we're in a CommonJS (e.g. Node.js) module + Markdown = exports; +else + Markdown = {}; + +// The following text is included for historical reasons, but should +// be taken with a pinch of salt; it's not all true anymore. + +// +// Wherever possible, Showdown is a straight, line-by-line port +// of the Perl version of Markdown. +// +// This is not a normal parser design; it's basically just a +// series of string substitutions. It's hard to read and +// maintain this way, but keeping Showdown close to the original +// design makes it easier to port new features. +// +// More importantly, Showdown behaves like markdown.pl in most +// edge cases. So web applications can do client-side preview +// in Javascript, and then build identical HTML on the server. +// +// This port needs the new RegExp functionality of ECMA 262, +// 3rd Edition (i.e. Javascript 1.5). Most modern web browsers +// should do fine. Even with the new regular expression features, +// We do a lot of work to emulate Perl's regex functionality. +// The tricky changes in this file mostly have the "attacklab:" +// label. Major or self-explanatory changes don't. +// +// Smart diff tools like Araxis Merge will be able to match up +// this file with markdown.pl in a useful way. A little tweaking +// helps: in a copy of markdown.pl, replace "#" with "//" and +// replace "$text" with "text". Be sure to ignore whitespace +// and line endings. +// + + +// +// Usage: +// +// var text = "Markdown *rocks*."; +// +// var converter = new Markdown.Converter(); +// var html = converter.makeHtml(text); +// +// alert(html); +// +// Note: move the sample code to the bottom of this +// file before uncommenting it. +// + +(function () { + + function identity(x) { return x; } + function returnFalse(x) { return false; } + + function HookCollection() { } + + HookCollection.prototype = { + + chain: function (hookname, func) { + var original = this[hookname]; + if (!original) + throw new Error("unknown hook " + hookname); + + if (original === identity) + this[hookname] = func; + else + this[hookname] = function (x) { return func(original(x)); } + }, + set: function (hookname, func) { + if (!this[hookname]) + throw new Error("unknown hook " + hookname); + this[hookname] = func; + }, + addNoop: function (hookname) { + this[hookname] = identity; + }, + addFalse: function (hookname) { + this[hookname] = returnFalse; + } + }; + + Markdown.HookCollection = HookCollection; + + // g_urls and g_titles allow arbitrary user-entered strings as keys. This + // caused an exception (and hence stopped the rendering) when the user entered + // e.g. [push] or [__proto__]. Adding a prefix to the actual key prevents this + // (since no builtin property starts with "s_"). See + // http://meta.stackoverflow.com/questions/64655/strange-wmd-bug + // (granted, switching from Array() to Object() alone would have left only __proto__ + // to be a problem) + function SaveHash() { } + SaveHash.prototype = { + set: function (key, value) { + this["s_" + key] = value; + }, + get: function (key) { + return this["s_" + key]; + } + }; + + Markdown.Converter = function () { + var pluginHooks = this.hooks = new HookCollection(); + pluginHooks.addNoop("plainLinkText"); // given a URL that was encountered by itself (without markup), should return the link text that's to be given to this link + pluginHooks.addNoop("preConversion"); // called with the orignal text as given to makeHtml. The result of this plugin hook is the actual markdown source that will be cooked + pluginHooks.addNoop("postConversion"); // called with the final cooked HTML code. The result of this plugin hook is the actual output of makeHtml + + // + // Private state of the converter instance: + // + + // Global hashes, used by various utility routines + var g_urls; + var g_titles; + var g_html_blocks; + + // Used to track when we're inside an ordered or unordered list + // (see _ProcessListItems() for details): + var g_list_level; + + this.makeHtml = function (text) { + + // + // Main function. The order in which other subs are called here is + // essential. Link and image substitutions need to happen before + // _EscapeSpecialCharsWithinTagAttributes(), so that any *'s or _'s in the + // and tags get encoded. + // + + // This will only happen if makeHtml on the same converter instance is called from a plugin hook. + // Don't do that. + if (g_urls) + throw new Error("Recursive call to converter.makeHtml"); + + // Create the private state objects. + g_urls = new SaveHash(); + g_titles = new SaveHash(); + g_html_blocks = []; + g_list_level = 0; + + text = pluginHooks.preConversion(text); + + // attacklab: Replace ~ with ~T + // This lets us use tilde as an escape char to avoid md5 hashes + // The choice of character is arbitray; anything that isn't + // magic in Markdown will work. + text = text.replace(/~/g, "~T"); + + // attacklab: Replace $ with ~D + // RegExp interprets $ as a special character + // when it's in a replacement string + text = text.replace(/\$/g, "~D"); + + // Standardize line endings + text = text.replace(/\r\n/g, "\n"); // DOS to Unix + text = text.replace(/\r/g, "\n"); // Mac to Unix + + // Make sure text begins and ends with a couple of newlines: + text = "\n\n" + text + "\n\n"; + + // Convert all tabs to spaces. + text = _Detab(text); + + // Strip any lines consisting only of spaces and tabs. + // This makes subsequent regexen easier to write, because we can + // match consecutive blank lines with /\n+/ instead of something + // contorted like /[ \t]*\n+/ . + text = text.replace(/^[ \t]+$/mg, ""); + + // Turn block-level HTML blocks into hash entries + text = _HashHTMLBlocks(text); + + // Strip link definitions, store in hashes. + text = _StripLinkDefinitions(text); + + text = _RunBlockGamut(text); + + text = _UnescapeSpecialChars(text); + + // attacklab: Restore dollar signs + text = text.replace(/~D/g, "$$"); + + // attacklab: Restore tildes + text = text.replace(/~T/g, "~"); + + text = pluginHooks.postConversion(text); + + g_html_blocks = g_titles = g_urls = null; + + return text; + }; + + function _StripLinkDefinitions(text) { + // + // Strips link definitions from text, stores the URLs and titles in + // hash references. + // + + // Link defs are in the form: ^[id]: url "optional title" + + /* + text = text.replace(/ + ^[ ]{0,3}\[(.+)\]: // id = $1 attacklab: g_tab_width - 1 + [ \t]* + \n? // maybe *one* newline + [ \t]* + ? // url = $2 + (?=\s|$) // lookahead for whitespace instead of the lookbehind removed below + [ \t]* + \n? // maybe one newline + [ \t]* + ( // (potential) title = $3 + (\n*) // any lines skipped = $4 attacklab: lookbehind removed + [ \t]+ + ["(] + (.+?) // title = $5 + [")] + [ \t]* + )? // title is optional + (?:\n+|$) + /gm, function(){...}); + */ + + text = text.replace(/^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*?(?=\s|$)[ \t]*\n?[ \t]*((\n*)["(](.+?)[")][ \t]*)?(?:\n+)/gm, + function (wholeMatch, m1, m2, m3, m4, m5) { + m1 = m1.toLowerCase(); + g_urls.set(m1, _EncodeAmpsAndAngles(m2)); // Link IDs are case-insensitive + if (m4) { + // Oops, found blank lines, so it's not a title. + // Put back the parenthetical statement we stole. + return m3; + } else if (m5) { + g_titles.set(m1, m5.replace(/"/g, """)); + } + + // Completely remove the definition from the text + return ""; + } + ); + + return text; + } + + function _HashHTMLBlocks(text) { + + // 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" + var block_tags_b = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math" + + // 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. + + /* + 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, hashElement); + + // + // Now match more liberally, simply from `\n` to `\n` + // + + /* + 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)\b[^\r]*?.*<\/\2>[ \t]*(?=\n+)\n)/gm, hashElement); + + // Special case just for
. It was easier to make a special case than + // to make the other regex more complicated. + + /* + text = text.replace(/ + \n // Starting after a blank line + [ ]{0,3} + ( // save in $1 + (<(hr) // start tag = $2 + \b // word break + ([^<>])*? + \/?>) // the matching end tag + [ \t]* + (?=\n{2,}) // followed by a blank line + ) + /g,hashElement); + */ + text = text.replace(/\n[ ]{0,3}((<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g, hashElement); + + // Special case for standalone HTML comments: + + /* + text = text.replace(/ + \n\n // Starting after a blank line + [ ]{0,3} // attacklab: g_tab_width - 1 + ( // save in $1 + -]|-[^>])(?:[^-]|-[^-])*)--) // see http://www.w3.org/TR/html-markup/syntax.html#comments and http://meta.stackoverflow.com/q/95256 + > + [ \t]* + (?=\n{2,}) // followed by a blank line + ) + /g,hashElement); + */ + text = text.replace(/\n\n[ ]{0,3}(-]|-[^>])(?:[^-]|-[^-])*)--)>[ \t]*(?=\n{2,}))/g, hashElement); + + // 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,hashElement); + */ + text = text.replace(/(?:\n\n)([ ]{0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g, hashElement); + + return text; + } + + function hashElement(wholeMatch, m1) { + var blockText = m1; + + // Undo double lines + 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" + (g_html_blocks.push(blockText) - 1) + "K\n\n"; + + return blockText; + } + + function _RunBlockGamut(text, doNotUnhash) { + // + // These are all the transformations that form block-level + // tags like paragraphs, headers, and list items. + // + text = _DoHeaders(text); + + // Do Horizontal Rules: + var replacement = "
\n"; + text = text.replace(/^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$/gm, replacement); + text = text.replace(/^[ ]{0,2}([ ]?-[ ]?){3,}[ \t]*$/gm, replacement); + text = text.replace(/^[ ]{0,2}([ ]?_[ ]?){3,}[ \t]*$/gm, replacement); + + text = _DoLists(text); + text = _DoCodeBlocks(text); + text = _DoBlockQuotes(text); + + // 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 = _HashHTMLBlocks(text); + text = _FormParagraphs(text, doNotUnhash); + + return text; + } + + function _RunSpanGamut(text) { + // + // These are all the transformations that occur *within* block-level + // tags like paragraphs, headers, and list items. + // + + text = _DoCodeSpans(text); + text = _EscapeSpecialCharsWithinTagAttributes(text); + text = _EncodeBackslashEscapes(text); + + // Process anchor and image tags. Images must come first, + // because ![foo][f] looks like an anchor. + text = _DoImages(text); + text = _DoAnchors(text); + + // Make links out of things like `` + // Must come after _DoAnchors(), because you can use < and > + // delimiters in inline links like [this](). + text = _DoAutoLinks(text); + + text = text.replace(/~P/g, "://"); // put in place to prevent autolinking; reset now + + text = _EncodeAmpsAndAngles(text); + text = _DoItalicsAndBold(text); + + // Do hard breaks: + text = text.replace(/ +\n/g, "
\n"); + + return text; + } + + function _EscapeSpecialCharsWithinTagAttributes(text) { + // + // Within tags -- meaning between < and > -- encode [\ ` * _] so they + // don't conflict with their use in Markdown for code, italics and strong. + // + + // Build a regex to find HTML tags and comments. See Friedl's + // "Mastering Regular Expressions", 2nd Ed., pp. 200-201. + + // SE: changed the comment part of the regex + + var regex = /(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|-]|-[^>])(?:[^-]|-[^-])*)--)>)/gi; + + text = text.replace(regex, function (wholeMatch) { + var tag = wholeMatch.replace(/(.)<\/?code>(?=.)/g, "$1`"); + tag = escapeCharacters(tag, wholeMatch.charAt(1) == "!" ? "\\`*_/" : "\\`*_"); // also escape slashes in comments to prevent autolinking there -- http://meta.stackoverflow.com/questions/95987 + return tag; + }); + + return text; + } + + function _DoAnchors(text) { + // + // Turn Markdown link shortcuts into XHTML
tags. + // + // + // 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, writeAnchorTag); + */ + 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 + ? + [ \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; + } + + function writeAnchorTag(wholeMatch, m1, m2, m3, m4, m5, m6, m7) { + if (m7 == undefined) m7 = ""; + var whole_match = m1; + var link_text = m2.replace(/:\/\//g, "~P"); // to prevent auto-linking withing the link. will be converted back after the auto-linker runs + var link_id = m3.toLowerCase(); + var url = m4; + var title = m7; + + if (url == "") { + if (link_id == "") { + // lower-case and turn embedded newlines into spaces + link_id = link_text.toLowerCase().replace(/ ?\n/g, " "); + } + url = "#" + link_id; + + if (g_urls.get(link_id) != undefined) { + url = g_urls.get(link_id); + if (g_titles.get(link_id) != undefined) { + title = g_titles.get(link_id); + } + } + else { + if (whole_match.search(/\(\s*\)$/m) > -1) { + // Special case for explicit empty url + url = ""; + } else { + return whole_match; + } + } + } + url = encodeProblemUrlChars(url); + url = escapeCharacters(url, "*_"); + var result = ""; + + return result; + } + + function _DoImages(text) { + // + // Turn Markdown image shortcuts into tags. + // + + // + // First, handle reference-style labeled images: ![alt text][id] + // + + /* + text = text.replace(/ + ( // wrap whole match in $1 + !\[ + (.*?) // alt text = $2 + \] + + [ ]? // one optional space + (?:\n[ ]*)? // one optional newline followed by spaces + + \[ + (.*?) // id = $3 + \] + ) + ()()()() // pad rest of backreferences + /g, writeImageTag); + */ + text = text.replace(/(!\[(.*?)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g, writeImageTag); + + // + // Next, handle inline images: ![alt text](url "optional title") + // Don't forget: encode * and _ + + /* + text = text.replace(/ + ( // wrap whole match in $1 + !\[ + (.*?) // alt text = $2 + \] + \s? // One optional whitespace character + \( // literal paren + [ \t]* + () // no id, so leave $3 empty + ? // src url = $4 + [ \t]* + ( // $5 + (['"]) // quote char = $6 + (.*?) // title = $7 + \6 // matching quote + [ \t]* + )? // title is optional + \) + ) + /g, writeImageTag); + */ + text = text.replace(/(!\[(.*?)\]\s?\([ \t]*()?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g, writeImageTag); + + return text; + } + + function writeImageTag(wholeMatch, m1, m2, m3, m4, m5, m6, m7) { + var whole_match = m1; + var alt_text = m2; + var link_id = m3.toLowerCase(); + var url = m4; + var title = m7; + + if (!title) title = ""; + + if (url == "") { + if (link_id == "") { + // lower-case and turn embedded newlines into spaces + link_id = alt_text.toLowerCase().replace(/ ?\n/g, " "); + } + url = "#" + link_id; + + if (g_urls.get(link_id) != undefined) { + url = g_urls.get(link_id); + if (g_titles.get(link_id) != undefined) { + title = g_titles.get(link_id); + } + } + else { + return whole_match; + } + } + + alt_text = alt_text.replace(/"/g, """); + url = escapeCharacters(url, "*_"); + var result = "\""" + _RunSpanGamut(m1) + "\n\n"; } + ); + + text = text.replace(/^(.+)[ \t]*\n-+[ \t]*\n+/gm, + function (matchFound, m1) { return "

" + _RunSpanGamut(m1) + "

\n\n"; } + ); + + // atx-style headers: + // # Header 1 + // ## Header 2 + // ## Header 2 with closing hashes ## + // ... + // ###### Header 6 + // + + /* + text = text.replace(/ + ^(\#{1,6}) // $1 = string of #'s + [ \t]* + (.+?) // $2 = Header text + [ \t]* + \#* // optional closing #'s (not counted) + \n+ + /gm, function() {...}); + */ + + text = text.replace(/^(\#{1,6})[ \t]*(.+?)[ \t]*\#*\n+/gm, + function (wholeMatch, m1, m2) { + var h_level = m1.length; + return "" + _RunSpanGamut(m2) + "\n\n"; + } + ); + + return text; + } + + function _DoLists(text) { + // + // Form HTML ordered (numbered) and unordered (bulleted) lists. + // + + // attacklab: add sentinel to hack around khtml/safari bug: + // http://bugs.webkit.org/show_bug.cgi?id=11231 + text += "~0"; + + // Re-usable pattern to match any entirel ul or ol list: + + /* + var whole_list = / + ( // $1 = whole list + ( // $2 + [ ]{0,3} // attacklab: g_tab_width - 1 + ([*+-]|\d+[.]) // $3 = first list item marker + [ \t]+ + ) + [^\r]+? + ( // $4 + ~0 // sentinel for workaround; should be $ + | + \n{2,} + (?=\S) + (?! // Negative lookahead for another list item marker + [ \t]* + (?:[*+-]|\d+[.])[ \t]+ + ) + ) + ) + /g + */ + var whole_list = /^(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm; + + if (g_list_level) { + text = text.replace(whole_list, function (wholeMatch, m1, m2) { + var list = m1; + var list_type = (m2.search(/[*+-]/g) > -1) ? "ul" : "ol"; + + var result = _ProcessListItems(list, list_type); + + // Trim any trailing whitespace, to put the closing `` + // up on the preceding line, to get it past the current stupid + // HTML block parser. This is a hack to work around the terrible + // hack that is the HTML block parser. + result = result.replace(/\s+$/, ""); + result = "<" + list_type + ">" + result + "\n"; + return result; + }); + } else { + whole_list = /(\n\n|^\n?)(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/g; + text = text.replace(whole_list, function (wholeMatch, m1, m2, m3) { + var runup = m1; + var list = m2; + + var list_type = (m3.search(/[*+-]/g) > -1) ? "ul" : "ol"; + var result = _ProcessListItems(list, list_type); + result = runup + "<" + list_type + ">\n" + result + "\n"; + return result; + }); + } + + // attacklab: strip sentinel + text = text.replace(/~0/, ""); + + return text; + } + + var _listItemMarkers = { ol: "\\d+[.]", ul: "[*+-]" }; + + function _ProcessListItems(list_str, list_type) { + // + // Process the contents of a single ordered or unordered list, splitting it + // into individual list items. + // + // list_type is either "ul" or "ol". + + // The $g_list_level global keeps track of when we're inside a list. + // Each time we enter a list, we increment it; when we leave a list, + // we decrement. If it's zero, we're not in a list anymore. + // + // We do this because when we're not inside a list, we want to treat + // something like this: + // + // I recommend upgrading to version + // 8. Oops, now this line is treated + // as a sub-list. + // + // As a single paragraph, despite the fact that the second line starts + // with a digit-period-space sequence. + // + // Whereas when we're inside a list (or sub-list), that line will be + // treated as the start of a sub-list. What a kludge, huh? This is + // an aspect of Markdown's syntax that's hard to parse perfectly + // without resorting to mind-reading. Perhaps the solution is to + // change the syntax rules such that sub-lists must start with a + // starting cardinal number; e.g. "1." or "a.". + + g_list_level++; + + // trim trailing blank lines: + list_str = list_str.replace(/\n{2,}$/, "\n"); + + // attacklab: add sentinel to emulate \z + list_str += "~0"; + + // In the original attacklab showdown, list_type was not given to this function, and anything + // that matched /[*+-]|\d+[.]/ would just create the next
  • , causing this mismatch: + // + // Markdown rendered by WMD rendered by MarkdownSharp + // ------------------------------------------------------------------ + // 1. first 1. first 1. first + // 2. second 2. second 2. second + // - third 3. third * third + // + // We changed this to behave identical to MarkdownSharp. This is the constructed RegEx, + // with {MARKER} being one of \d+[.] or [*+-], depending on list_type: + + /* + list_str = list_str.replace(/ + (^[ \t]*) // leading whitespace = $1 + ({MARKER}) [ \t]+ // list marker = $2 + ([^\r]+? // list item text = $3 + (\n+) + ) + (?= + (~0 | \2 ({MARKER}) [ \t]+) + ) + /gm, function(){...}); + */ + + var marker = _listItemMarkers[list_type]; + var re = new RegExp("(^[ \\t]*)(" + marker + ")[ \\t]+([^\\r]+?(\\n+))(?=(~0|\\1(" + marker + ")[ \\t]+))", "gm"); + var last_item_had_a_double_newline = false; + list_str = list_str.replace(re, + function (wholeMatch, m1, m2, m3) { + var item = m3; + var leading_space = m1; + var ends_with_double_newline = /\n\n$/.test(item); + var contains_double_newline = ends_with_double_newline || item.search(/\n{2,}/) > -1; + + if (contains_double_newline || last_item_had_a_double_newline) { + item = _RunBlockGamut(_Outdent(item), /* doNotUnhash = */true); + } + else { + // Recursion for sub-lists: + item = _DoLists(_Outdent(item)); + item = item.replace(/\n$/, ""); // chomp(item) + item = _RunSpanGamut(item); + } + last_item_had_a_double_newline = ends_with_double_newline; + return "
  • " + item + "
  • \n"; + } + ); + + // attacklab: strip sentinel + list_str = list_str.replace(/~0/g, ""); + + g_list_level--; + return list_str; + } + + function _DoCodeBlocks(text) { + // + // Process Markdown `
    ` blocks.
    +            //  
    +
    +            /*
    +            text = text.replace(/
    +                (?:\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";
    +
    +            text = text.replace(/(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=~0))/g,
    +                function (wholeMatch, m1, m2) {
    +                    var codeblock = m1;
    +                    var nextChar = m2;
    +
    +                    codeblock = _EncodeCode(_Outdent(codeblock));
    +                    codeblock = _Detab(codeblock);
    +                    codeblock = codeblock.replace(/^\n+/g, ""); // trim leading newlines
    +                    codeblock = codeblock.replace(/\n+$/g, ""); // trim trailing whitespace
    +
    +                    codeblock = "
    " + codeblock + "\n
    "; + + return "\n\n" + codeblock + "\n\n" + nextChar; + } + ); + + // attacklab: strip sentinel + text = text.replace(/~0/, ""); + + return text; + } + + function hashBlock(text) { + text = text.replace(/(^\n+|\n+$)/g, ""); + return "\n\n~K" + (g_html_blocks.push(text) - 1) + "K\n\n"; + } + + function _DoCodeSpans(text) { + // + // * 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` ... + // + + /* + 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, m4) { + var c = m3; + c = c.replace(/^([ \t]*)/g, ""); // leading whitespace + c = c.replace(/[ \t]*$/g, ""); // trailing whitespace + c = _EncodeCode(c); + return m1 + "" + c + ""; + } + ); + + return text; + } + + function _EncodeCode(text) { + // + // Encode/escape certain characters inside Markdown code runs. + // The point is that in code, these characters are literals, + // and lose their special Markdown meanings. + // + // 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 = escapeCharacters(text, "\*_{}[]\\", false); + + // jj the line above breaks this: + //--- + + //* Item + + // 1. Subitem + + // special char: * + //--- + + return text; + } + + function _DoItalicsAndBold(text) { + + // must go first: + text = text.replace(/([\W_]|^)(\*\*|__)(?=\S)([^\r]*?\S[\*_]*)\2([\W_]|$)/g, + "$1$3$4"); + + text = text.replace(/([\W_]|^)(\*|_)(?=\S)([^\r\*_]*?\S)\2([\W_]|$)/g, + "$1$3$4"); + + return text; + } + + function _DoBlockQuotes(text) { + + /* + 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]*>[ \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 = _RunBlockGamut(bq); // 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 hashBlock("
    \n" + bq + "\n
    "); + } + ); + return text; + } + + function _FormParagraphs(text, doNotUnhash) { + // + // Params: + // $text - string to process with html

    tags + // + + // Strip leading and trailing lines: + text = text.replace(/^\n+/g, ""); + text = text.replace(/\n+$/g, ""); + + var grafs = text.split(/\n{2,}/g); + var grafsOut = []; + + var markerRe = /~K(\d+)K/; + + // + // Wrap

    tags. + // + var end = grafs.length; + for (var i = 0; i < end; i++) { + var str = grafs[i]; + + // if this is an HTML marker, copy it + if (markerRe.test(str)) { + grafsOut.push(str); + } + else if (/\S/.test(str)) { + str = _RunSpanGamut(str); + str = str.replace(/^([ \t]*)/g, "

    "); + str += "

    " + grafsOut.push(str); + } + + } + // + // Unhashify HTML blocks + // + if (!doNotUnhash) { + end = grafsOut.length; + for (var i = 0; i < end; i++) { + var foundAny = true; + while (foundAny) { // we may need several runs, since the data may be nested + foundAny = false; + grafsOut[i] = grafsOut[i].replace(/~K(\d+)K/g, function (wholeMatch, id) { + foundAny = true; + return g_html_blocks[id]; + }); + } + } + } + return grafsOut.join("\n\n"); + } + + function _EncodeAmpsAndAngles(text) { + // Smart processing for ampersands and angle brackets that need to be encoded. + + // 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; + } + + function _EncodeBackslashEscapes(text) { + // + // Parameter: String. + // 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. + + text = text.replace(/\\(\\)/g, escapeCharacters_callback); + text = text.replace(/\\([`*_{}\[\]()>#+-.!])/g, escapeCharacters_callback); + return text; + } + + function _DoAutoLinks(text) { + + // note that at this point, all other URL in the text are already hyperlinked as
    + // *except* for the case + + // automatically add < and > around unadorned raw hyperlinks + // must be preceded by space/BOF and followed by non-word/EOF character + text = text.replace(/(^|\s)(https?|ftp)(:\/\/[-A-Z0-9+&@#\/%?=~_|\[\]\(\)!:,\.;]*[-A-Z0-9+&@#\/%=~_|\[\]])($|\W)/gi, "$1<$2$3>$4"); + + // autolink anything like + + var replacer = function (wholematch, m1) { return "" + pluginHooks.plainLinkText(m1) + ""; } + text = text.replace(/<((https?|ftp):[^'">\s]+)>/gi, replacer); + + // Email addresses: + /* + text = text.replace(/ + < + (?:mailto:)? + ( + [-.\w]+ + \@ + [-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+ + ) + > + /gi, _DoAutoLinks_callback()); + */ + + /* disabling email autolinking, since we don't do that on the server, either + text = text.replace(/<(?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi, + function(wholeMatch,m1) { + return _EncodeEmailAddress( _UnescapeSpecialChars(m1) ); + } + ); + */ + return text; + } + + function _UnescapeSpecialChars(text) { + // + // Swap back in all the special characters we've hidden. + // + text = text.replace(/~E(\d+)E/g, + function (wholeMatch, m1) { + var charCodeToReplace = parseInt(m1); + return String.fromCharCode(charCodeToReplace); + } + ); + return text; + } + + function _Outdent(text) { + // + // Remove one level of line-leading tabs or spaces + // + + // attacklab: hack around Konqueror 3.5.4 bug: + // "----------bug".replace(/^-/g,"") == "bug" + + text = text.replace(/^(\t|[ ]{1,4})/gm, "~0"); // attacklab: g_tab_width + + // attacklab: clean up hack + text = text.replace(/~0/g, "") + + return text; + } + + function _Detab(text) { + if (!/\t/.test(text)) + return text; + + var spaces = [" ", " ", " ", " "], + skew = 0, + v; + + return text.replace(/[\n\t]/g, function (match, offset) { + if (match === "\n") { + skew = offset + 1; + return match; + } + v = (offset - skew) % 4; + skew = offset + 1; + return spaces[v]; + }); + } + + // + // attacklab: Utility functions + // + + var _problemUrlChars = /(?:["'*()[\]:]|~D)/g; + + // hex-encodes some unusual "problem" chars in URLs to avoid URL detection problems + function encodeProblemUrlChars(url) { + if (!url) + return ""; + + var len = url.length; + + return url.replace(_problemUrlChars, function (match, offset) { + if (match == "~D") // escape for dollar + return "%24"; + if (match == ":") { + if (offset == len - 1 || /[0-9\/]/.test(url.charAt(offset + 1))) + return ":" + } + return "%" + match.charCodeAt(0).toString(16); + }); + } + + + function escapeCharacters(text, charsToEscape, afterBackslash) { + // 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, escapeCharacters_callback); + + return text; + } + + + function escapeCharacters_callback(wholeMatch, m1) { + var charCodeToEscape = m1.charCodeAt(0); + return "~E" + charCodeToEscape + "E"; + } + + }; // end of the Markdown.Converter constructor + +})(); diff --git a/src/Orchard.Web/Modules/Markdown/Scripts/Markdown.Editor.js b/src/Orchard.Web/Modules/Markdown/Scripts/Markdown.Editor.js new file mode 100644 index 000000000..17f43112c --- /dev/null +++ b/src/Orchard.Web/Modules/Markdown/Scripts/Markdown.Editor.js @@ -0,0 +1,2109 @@ +// needs Markdown.Converter.js at the moment + +(function () { + + var util = {}, + position = {}, + ui = {}, + doc = window.document, + re = window.RegExp, + nav = window.navigator, + SETTINGS = { lineLength: 72 }, + + // Used to work around some browser bugs where we can't use feature testing. + uaSniffed = { + isIE: /msie/.test(nav.userAgent.toLowerCase()), + isIE_5or6: /msie 6/.test(nav.userAgent.toLowerCase()) || /msie 5/.test(nav.userAgent.toLowerCase()), + isOpera: /opera/.test(nav.userAgent.toLowerCase()) + }; + + + // ------------------------------------------------------------------- + // YOUR CHANGES GO HERE + // + // I've tried to localize the things you are likely to change to + // this area. + // ------------------------------------------------------------------- + + // The text that appears on the upper part of the dialog box when + // entering links. + var linkDialogText = "

    Insert Hyperlink

    http://example.com/ \"optional title\"

    "; + var imageDialogText = "

    Insert Image

    http://example.com/images/diagram.jpg \"optional title\"

    Need free image hosting?

    "; + + // The default text that appears in the dialog input box when entering + // links. + var imageDefaultText = "http://"; + var linkDefaultText = "http://"; + + var defaultHelpHoverTitle = "Markdown Editing Help"; + + // ------------------------------------------------------------------- + // END OF YOUR CHANGES + // ------------------------------------------------------------------- + + // help, if given, should have a property "handler", the click handler for the help button, + // and can have an optional property "title" for the button's tooltip (defaults to "Markdown Editing Help"). + // If help isn't given, not help button is created. + // + // The constructed editor object has the methods: + // - getConverter() returns the markdown converter object that was passed to the constructor + // - run() actually starts the editor; should be called after all necessary plugins are registered. Calling this more than once is a no-op. + // - refreshPreview() forces the preview to be updated. This method is only available after run() was called. + Markdown.Editor = function (markdownConverter, idPostfix, help) { + + idPostfix = idPostfix || ""; + + var hooks = this.hooks = new Markdown.HookCollection(); + hooks.addNoop("onPreviewRefresh"); // called with no arguments after the preview has been refreshed + hooks.addNoop("postBlockquoteCreation"); // called with the user's selection *after* the blockquote was created; should return the actual to-be-inserted text + hooks.addFalse("insertImageDialog"); /* called with one parameter: a callback to be called with the URL of the image. If the application creates + * its own image insertion dialog, this hook should return true, and the callback should be called with the chosen + * image url (or null if the user cancelled). If this hook returns false, the default dialog will be used. + */ + + this.getConverter = function () { return markdownConverter; } + + var that = this, + panels; + + this.run = function () { + if (panels) + return; // already initialized + + panels = new PanelCollection(idPostfix); + var commandManager = new CommandManager(hooks); + var previewManager = new PreviewManager(markdownConverter, panels, function () { hooks.onPreviewRefresh(); }); + var undoManager, uiManager; + + if (!/\?noundo/.test(doc.location.href)) { + undoManager = new UndoManager(function () { + previewManager.refresh(); + if (uiManager) // not available on the first call + uiManager.setUndoRedoButtonStates(); + }, panels); + } + + uiManager = new UIManager(idPostfix, panels, undoManager, previewManager, commandManager, help); + uiManager.setUndoRedoButtonStates(); + + var forceRefresh = that.refreshPreview = function () { previewManager.refresh(true); }; + + forceRefresh(); + }; + + } + + // before: contains all the text in the input box BEFORE the selection. + // after: contains all the text in the input box AFTER the selection. + function Chunks() { } + + // startRegex: a regular expression to find the start tag + // endRegex: a regular expresssion to find the end tag + Chunks.prototype.findTags = function (startRegex, endRegex) { + + var chunkObj = this; + var regex; + + if (startRegex) { + + regex = util.extendRegExp(startRegex, "", "$"); + + this.before = this.before.replace(regex, + function (match) { + chunkObj.startTag = chunkObj.startTag + match; + return ""; + }); + + regex = util.extendRegExp(startRegex, "^", ""); + + this.selection = this.selection.replace(regex, + function (match) { + chunkObj.startTag = chunkObj.startTag + match; + return ""; + }); + } + + if (endRegex) { + + regex = util.extendRegExp(endRegex, "", "$"); + + this.selection = this.selection.replace(regex, + function (match) { + chunkObj.endTag = match + chunkObj.endTag; + return ""; + }); + + regex = util.extendRegExp(endRegex, "^", ""); + + this.after = this.after.replace(regex, + function (match) { + chunkObj.endTag = match + chunkObj.endTag; + return ""; + }); + } + }; + + // If remove is false, the whitespace is transferred + // to the before/after regions. + // + // If remove is true, the whitespace disappears. + Chunks.prototype.trimWhitespace = function (remove) { + var beforeReplacer, afterReplacer, that = this; + if (remove) { + beforeReplacer = afterReplacer = ""; + } else { + beforeReplacer = function (s) { that.before += s; return ""; } + afterReplacer = function (s) { that.after = s + that.after; return ""; } + } + + this.selection = this.selection.replace(/^(\s*)/, beforeReplacer).replace(/(\s*)$/, afterReplacer); + }; + + + Chunks.prototype.skipLines = function (nLinesBefore, nLinesAfter, findExtraNewlines) { + + if (nLinesBefore === undefined) { + nLinesBefore = 1; + } + + if (nLinesAfter === undefined) { + nLinesAfter = 1; + } + + nLinesBefore++; + nLinesAfter++; + + var regexText; + var replacementText; + + // chrome bug ... documented at: http://meta.stackoverflow.com/questions/63307/blockquote-glitch-in-editor-in-chrome-6-and-7/65985#65985 + if (navigator.userAgent.match(/Chrome/)) { + "X".match(/()./); + } + + this.selection = this.selection.replace(/(^\n*)/, ""); + + this.startTag = this.startTag + re.$1; + + this.selection = this.selection.replace(/(\n*$)/, ""); + this.endTag = this.endTag + re.$1; + this.startTag = this.startTag.replace(/(^\n*)/, ""); + this.before = this.before + re.$1; + this.endTag = this.endTag.replace(/(\n*$)/, ""); + this.after = this.after + re.$1; + + if (this.before) { + + regexText = replacementText = ""; + + while (nLinesBefore--) { + regexText += "\\n?"; + replacementText += "\n"; + } + + if (findExtraNewlines) { + regexText = "\\n*"; + } + this.before = this.before.replace(new re(regexText + "$", ""), replacementText); + } + + if (this.after) { + + regexText = replacementText = ""; + + while (nLinesAfter--) { + regexText += "\\n?"; + replacementText += "\n"; + } + if (findExtraNewlines) { + regexText = "\\n*"; + } + + this.after = this.after.replace(new re(regexText, ""), replacementText); + } + }; + + // end of Chunks + + // A collection of the important regions on the page. + // Cached so we don't have to keep traversing the DOM. + // Also holds ieCachedRange and ieCachedScrollTop, where necessary; working around + // this issue: + // Internet explorer has problems with CSS sprite buttons that use HTML + // lists. When you click on the background image "button", IE will + // select the non-existent link text and discard the selection in the + // textarea. The solution to this is to cache the textarea selection + // on the button's mousedown event and set a flag. In the part of the + // code where we need to grab the selection, we check for the flag + // and, if it's set, use the cached area instead of querying the + // textarea. + // + // This ONLY affects Internet Explorer (tested on versions 6, 7 + // and 8) and ONLY on button clicks. Keyboard shortcuts work + // normally since the focus never leaves the textarea. + function PanelCollection(postfix) { + this.buttonBar = doc.getElementById("wmd-button-bar" + postfix); + this.preview = doc.getElementById("wmd-preview" + postfix); + this.input = doc.getElementById("wmd-input" + postfix); + }; + + // Returns true if the DOM element is visible, false if it's hidden. + // Checks if display is anything other than none. + util.isVisible = function (elem) { + + if (window.getComputedStyle) { + // Most browsers + return window.getComputedStyle(elem, null).getPropertyValue("display") !== "none"; + } + else if (elem.currentStyle) { + // IE + return elem.currentStyle["display"] !== "none"; + } + }; + + + // Adds a listener callback to a DOM element which is fired on a specified + // event. + util.addEvent = function (elem, event, listener) { + if (elem.attachEvent) { + // IE only. The "on" is mandatory. + elem.attachEvent("on" + event, listener); + } + else { + // Other browsers. + elem.addEventListener(event, listener, false); + } + }; + + + // Removes a listener callback from a DOM element which is fired on a specified + // event. + util.removeEvent = function (elem, event, listener) { + if (elem.detachEvent) { + // IE only. The "on" is mandatory. + elem.detachEvent("on" + event, listener); + } + else { + // Other browsers. + elem.removeEventListener(event, listener, false); + } + }; + + // Converts \r\n and \r to \n. + util.fixEolChars = function (text) { + text = text.replace(/\r\n/g, "\n"); + text = text.replace(/\r/g, "\n"); + return text; + }; + + // Extends a regular expression. Returns a new RegExp + // using pre + regex + post as the expression. + // Used in a few functions where we have a base + // expression and we want to pre- or append some + // conditions to it (e.g. adding "$" to the end). + // The flags are unchanged. + // + // regex is a RegExp, pre and post are strings. + util.extendRegExp = function (regex, pre, post) { + + if (pre === null || pre === undefined) { + pre = ""; + } + if (post === null || post === undefined) { + post = ""; + } + + var pattern = regex.toString(); + var flags; + + // Replace the flags with empty space and store them. + pattern = pattern.replace(/\/([gim]*)$/, ""); + flags = re.$1; + + // Remove the slash delimiters on the regular expression. + pattern = pattern.replace(/(^\/|\/$)/g, ""); + pattern = pre + pattern + post; + + return new re(pattern, flags); + } + + // UNFINISHED + // The assignment in the while loop makes jslint cranky. + // I'll change it to a better loop later. + position.getTop = function (elem, isInner) { + var result = elem.offsetTop; + if (!isInner) { + while (elem = elem.offsetParent) { + result += elem.offsetTop; + } + } + return result; + }; + + position.getHeight = function (elem) { + return elem.offsetHeight || elem.scrollHeight; + }; + + position.getWidth = function (elem) { + return elem.offsetWidth || elem.scrollWidth; + }; + + position.getPageSize = function () { + + var scrollWidth, scrollHeight; + var innerWidth, innerHeight; + + // It's not very clear which blocks work with which browsers. + if (self.innerHeight && self.scrollMaxY) { + scrollWidth = doc.body.scrollWidth; + scrollHeight = self.innerHeight + self.scrollMaxY; + } + else if (doc.body.scrollHeight > doc.body.offsetHeight) { + scrollWidth = doc.body.scrollWidth; + scrollHeight = doc.body.scrollHeight; + } + else { + scrollWidth = doc.body.offsetWidth; + scrollHeight = doc.body.offsetHeight; + } + + if (self.innerHeight) { + // Non-IE browser + innerWidth = self.innerWidth; + innerHeight = self.innerHeight; + } + else if (doc.documentElement && doc.documentElement.clientHeight) { + // Some versions of IE (IE 6 w/ a DOCTYPE declaration) + innerWidth = doc.documentElement.clientWidth; + innerHeight = doc.documentElement.clientHeight; + } + else if (doc.body) { + // Other versions of IE + innerWidth = doc.body.clientWidth; + innerHeight = doc.body.clientHeight; + } + + var maxWidth = Math.max(scrollWidth, innerWidth); + var maxHeight = Math.max(scrollHeight, innerHeight); + return [maxWidth, maxHeight, innerWidth, innerHeight]; + }; + + // Handles pushing and popping TextareaStates for undo/redo commands. + // I should rename the stack variables to list. + function UndoManager(callback, panels) { + + var undoObj = this; + var undoStack = []; // A stack of undo states + var stackPtr = 0; // The index of the current state + var mode = "none"; + var lastState; // The last state + var timer; // The setTimeout handle for cancelling the timer + var inputStateObj; + + // Set the mode for later logic steps. + var setMode = function (newMode, noSave) { + if (mode != newMode) { + mode = newMode; + if (!noSave) { + saveState(); + } + } + + if (!uaSniffed.isIE || mode != "moving") { + timer = setTimeout(refreshState, 1); + } + else { + inputStateObj = null; + } + }; + + var refreshState = function (isInitialState) { + inputStateObj = new TextareaState(panels, isInitialState); + timer = undefined; + }; + + this.setCommandMode = function () { + mode = "command"; + saveState(); + timer = setTimeout(refreshState, 0); + }; + + this.canUndo = function () { + return stackPtr > 1; + }; + + this.canRedo = function () { + if (undoStack[stackPtr + 1]) { + return true; + } + return false; + }; + + // Removes the last state and restores it. + this.undo = function () { + + if (undoObj.canUndo()) { + if (lastState) { + // What about setting state -1 to null or checking for undefined? + lastState.restore(); + lastState = null; + } + else { + undoStack[stackPtr] = new TextareaState(panels); + undoStack[--stackPtr].restore(); + + if (callback) { + callback(); + } + } + } + + mode = "none"; + panels.input.focus(); + refreshState(); + }; + + // Redo an action. + this.redo = function () { + + if (undoObj.canRedo()) { + + undoStack[++stackPtr].restore(); + + if (callback) { + callback(); + } + } + + mode = "none"; + panels.input.focus(); + refreshState(); + }; + + // Push the input area state to the stack. + var saveState = function () { + var currState = inputStateObj || new TextareaState(panels); + + if (!currState) { + return false; + } + if (mode == "moving") { + if (!lastState) { + lastState = currState; + } + return; + } + if (lastState) { + if (undoStack[stackPtr - 1].text != lastState.text) { + undoStack[stackPtr++] = lastState; + } + lastState = null; + } + undoStack[stackPtr++] = currState; + undoStack[stackPtr + 1] = null; + if (callback) { + callback(); + } + }; + + var handleCtrlYZ = function (event) { + + var handled = false; + + if (event.ctrlKey || event.metaKey) { + + // IE and Opera do not support charCode. + var keyCode = event.charCode || event.keyCode; + var keyCodeChar = String.fromCharCode(keyCode); + + switch (keyCodeChar) { + + case "y": + undoObj.redo(); + handled = true; + break; + + case "z": + if (!event.shiftKey) { + undoObj.undo(); + } + else { + undoObj.redo(); + } + handled = true; + break; + } + } + + if (handled) { + if (event.preventDefault) { + event.preventDefault(); + } + if (window.event) { + window.event.returnValue = false; + } + return; + } + }; + + // Set the mode depending on what is going on in the input area. + var handleModeChange = function (event) { + + if (!event.ctrlKey && !event.metaKey) { + + var keyCode = event.keyCode; + + if ((keyCode >= 33 && keyCode <= 40) || (keyCode >= 63232 && keyCode <= 63235)) { + // 33 - 40: page up/dn and arrow keys + // 63232 - 63235: page up/dn and arrow keys on safari + setMode("moving"); + } + else if (keyCode == 8 || keyCode == 46 || keyCode == 127) { + // 8: backspace + // 46: delete + // 127: delete + setMode("deleting"); + } + else if (keyCode == 13) { + // 13: Enter + setMode("newlines"); + } + else if (keyCode == 27) { + // 27: escape + setMode("escape"); + } + else if ((keyCode < 16 || keyCode > 20) && keyCode != 91) { + // 16-20 are shift, etc. + // 91: left window key + // I think this might be a little messed up since there are + // a lot of nonprinting keys above 20. + setMode("typing"); + } + } + }; + + var setEventHandlers = function () { + util.addEvent(panels.input, "keypress", function (event) { + // keyCode 89: y + // keyCode 90: z + if ((event.ctrlKey || event.metaKey) && (event.keyCode == 89 || event.keyCode == 90)) { + event.preventDefault(); + } + }); + + var handlePaste = function () { + if (uaSniffed.isIE || (inputStateObj && inputStateObj.text != panels.input.value)) { + if (timer == undefined) { + mode = "paste"; + saveState(); + refreshState(); + } + } + }; + + util.addEvent(panels.input, "keydown", handleCtrlYZ); + util.addEvent(panels.input, "keydown", handleModeChange); + util.addEvent(panels.input, "mousedown", function () { + setMode("moving"); + }); + + panels.input.onpaste = handlePaste; + panels.input.ondrop = handlePaste; + }; + + var init = function () { + setEventHandlers(); + refreshState(true); + saveState(); + }; + + init(); + } + + // end of UndoManager + + // The input textarea state/contents. + // This is used to implement undo/redo by the undo manager. + function TextareaState(panels, isInitialState) { + + // Aliases + var stateObj = this; + var inputArea = panels.input; + this.init = function () { + if (!util.isVisible(inputArea)) { + return; + } + if (!isInitialState && doc.activeElement && doc.activeElement !== inputArea) { // this happens when tabbing out of the input box + return; + } + + this.setInputAreaSelectionStartEnd(); + this.scrollTop = inputArea.scrollTop; + if (!this.text && inputArea.selectionStart || inputArea.selectionStart === 0) { + this.text = inputArea.value; + } + + } + + // Sets the selected text in the input box after we've performed an + // operation. + this.setInputAreaSelection = function () { + + if (!util.isVisible(inputArea)) { + return; + } + + if (inputArea.selectionStart !== undefined && !uaSniffed.isOpera) { + + inputArea.focus(); + inputArea.selectionStart = stateObj.start; + inputArea.selectionEnd = stateObj.end; + inputArea.scrollTop = stateObj.scrollTop; + } + else if (doc.selection) { + + if (doc.activeElement && doc.activeElement !== inputArea) { + return; + } + + inputArea.focus(); + var range = inputArea.createTextRange(); + range.moveStart("character", -inputArea.value.length); + range.moveEnd("character", -inputArea.value.length); + range.moveEnd("character", stateObj.end); + range.moveStart("character", stateObj.start); + range.select(); + } + }; + + this.setInputAreaSelectionStartEnd = function () { + + if (!panels.ieCachedRange && (inputArea.selectionStart || inputArea.selectionStart === 0)) { + + stateObj.start = inputArea.selectionStart; + stateObj.end = inputArea.selectionEnd; + } + else if (doc.selection) { + + stateObj.text = util.fixEolChars(inputArea.value); + + // IE loses the selection in the textarea when buttons are + // clicked. On IE we cache the selection. Here, if something is cached, + // we take it. + var range = panels.ieCachedRange || doc.selection.createRange(); + + var fixedRange = util.fixEolChars(range.text); + var marker = "\x07"; + var markedRange = marker + fixedRange + marker; + range.text = markedRange; + var inputText = util.fixEolChars(inputArea.value); + + range.moveStart("character", -markedRange.length); + range.text = fixedRange; + + stateObj.start = inputText.indexOf(marker); + stateObj.end = inputText.lastIndexOf(marker) - marker.length; + + var len = stateObj.text.length - util.fixEolChars(inputArea.value).length; + + if (len) { + range.moveStart("character", -fixedRange.length); + while (len--) { + fixedRange += "\n"; + stateObj.end += 1; + } + range.text = fixedRange; + } + + if (panels.ieCachedRange) + stateObj.scrollTop = panels.ieCachedScrollTop; // this is set alongside with ieCachedRange + + panels.ieCachedRange = null; + + this.setInputAreaSelection(); + } + }; + + // Restore this state into the input area. + this.restore = function () { + + if (stateObj.text != undefined && stateObj.text != inputArea.value) { + inputArea.value = stateObj.text; + } + this.setInputAreaSelection(); + inputArea.scrollTop = stateObj.scrollTop; + }; + + // Gets a collection of HTML chunks from the inptut textarea. + this.getChunks = function () { + + var chunk = new Chunks(); + chunk.before = util.fixEolChars(stateObj.text.substring(0, stateObj.start)); + chunk.startTag = ""; + chunk.selection = util.fixEolChars(stateObj.text.substring(stateObj.start, stateObj.end)); + chunk.endTag = ""; + chunk.after = util.fixEolChars(stateObj.text.substring(stateObj.end)); + chunk.scrollTop = stateObj.scrollTop; + + return chunk; + }; + + // Sets the TextareaState properties given a chunk of markdown. + this.setChunks = function (chunk) { + + chunk.before = chunk.before + chunk.startTag; + chunk.after = chunk.endTag + chunk.after; + + this.start = chunk.before.length; + this.end = chunk.before.length + chunk.selection.length; + this.text = chunk.before + chunk.selection + chunk.after; + this.scrollTop = chunk.scrollTop; + }; + this.init(); + }; + + function PreviewManager(converter, panels, previewRefreshCallback) { + + var managerObj = this; + var timeout; + var elapsedTime; + var oldInputText; + var maxDelay = 3000; + var startType = "delayed"; // The other legal value is "manual" + + // Adds event listeners to elements + var setupEvents = function (inputElem, listener) { + + util.addEvent(inputElem, "input", listener); + inputElem.onpaste = listener; + inputElem.ondrop = listener; + + util.addEvent(inputElem, "keypress", listener); + util.addEvent(inputElem, "keydown", listener); + }; + + var getDocScrollTop = function () { + + var result = 0; + + if (window.innerHeight) { + result = window.pageYOffset; + } + else + if (doc.documentElement && doc.documentElement.scrollTop) { + result = doc.documentElement.scrollTop; + } + else + if (doc.body) { + result = doc.body.scrollTop; + } + + return result; + }; + + var makePreviewHtml = function () { + + // If there is no registered preview panel + // there is nothing to do. + if (!panels.preview) + return; + + + var text = panels.input.value; + if (text && text == oldInputText) { + return; // Input text hasn't changed. + } + else { + oldInputText = text; + } + + var prevTime = new Date().getTime(); + + text = converter.makeHtml(text); + + // Calculate the processing time of the HTML creation. + // It's used as the delay time in the event listener. + var currTime = new Date().getTime(); + elapsedTime = currTime - prevTime; + + pushPreviewHtml(text); + }; + + // setTimeout is already used. Used as an event listener. + var applyTimeout = function () { + + if (timeout) { + clearTimeout(timeout); + timeout = undefined; + } + + if (startType !== "manual") { + + var delay = 0; + + if (startType === "delayed") { + delay = elapsedTime; + } + + if (delay > maxDelay) { + delay = maxDelay; + } + timeout = setTimeout(makePreviewHtml, delay); + } + }; + + var getScaleFactor = function (panel) { + if (panel.scrollHeight <= panel.clientHeight) { + return 1; + } + return panel.scrollTop / (panel.scrollHeight - panel.clientHeight); + }; + + var setPanelScrollTops = function () { + if (panels.preview) { + panels.preview.scrollTop = (panels.preview.scrollHeight - panels.preview.clientHeight) * getScaleFactor(panels.preview); + } + }; + + this.refresh = function (requiresRefresh) { + + if (requiresRefresh) { + oldInputText = ""; + makePreviewHtml(); + } + else { + applyTimeout(); + } + }; + + this.processingTime = function () { + return elapsedTime; + }; + + var isFirstTimeFilled = true; + + // IE doesn't let you use innerHTML if the element is contained somewhere in a table + // (which is the case for inline editing) -- in that case, detach the element, set the + // value, and reattach. Yes, that *is* ridiculous. + var ieSafePreviewSet = function (text) { + var preview = panels.preview; + var parent = preview.parentNode; + var sibling = preview.nextSibling; + parent.removeChild(preview); + preview.innerHTML = text; + if (!sibling) + parent.appendChild(preview); + else + parent.insertBefore(preview, sibling); + } + + var nonSuckyBrowserPreviewSet = function (text) { + panels.preview.innerHTML = text; + } + + var previewSetter; + + var previewSet = function (text) { + if (previewSetter) + return previewSetter(text); + + try { + nonSuckyBrowserPreviewSet(text); + previewSetter = nonSuckyBrowserPreviewSet; + } catch (e) { + previewSetter = ieSafePreviewSet; + previewSetter(text); + } + }; + + var pushPreviewHtml = function (text) { + + var emptyTop = position.getTop(panels.input) - getDocScrollTop(); + + if (panels.preview) { + previewSet(text); + previewRefreshCallback(); + } + + setPanelScrollTops(); + + if (isFirstTimeFilled) { + isFirstTimeFilled = false; + return; + } + + var fullTop = position.getTop(panels.input) - getDocScrollTop(); + + if (uaSniffed.isIE) { + setTimeout(function () { + window.scrollBy(0, fullTop - emptyTop); + }, 0); + } + else { + window.scrollBy(0, fullTop - emptyTop); + } + }; + + var init = function () { + + setupEvents(panels.input, applyTimeout); + makePreviewHtml(); + + if (panels.preview) { + panels.preview.scrollTop = 0; + } + }; + + init(); + }; + + // Creates the background behind the hyperlink text entry box. + // And download dialog + // Most of this has been moved to CSS but the div creation and + // browser-specific hacks remain here. + ui.createBackground = function () { + + var background = doc.createElement("div"); + background.className = "wmd-prompt-background"; + style = background.style; + style.position = "absolute"; + style.top = "0"; + + style.zIndex = "1000"; + + if (uaSniffed.isIE) { + style.filter = "alpha(opacity=50)"; + } + else { + style.opacity = "0.5"; + } + + var pageSize = position.getPageSize(); + style.height = pageSize[1] + "px"; + + if (uaSniffed.isIE) { + style.left = doc.documentElement.scrollLeft; + style.width = doc.documentElement.clientWidth; + } + else { + style.left = "0"; + style.width = "100%"; + } + + doc.body.appendChild(background); + return background; + }; + + // This simulates a modal dialog box and asks for the URL when you + // click the hyperlink or image buttons. + // + // text: The html for the input box. + // defaultInputText: The default value that appears in the input box. + // callback: The function which is executed when the prompt is dismissed, either via OK or Cancel. + // It receives a single argument; either the entered text (if OK was chosen) or null (if Cancel + // was chosen). + ui.prompt = function (text, defaultInputText, callback) { + + // These variables need to be declared at this level since they are used + // in multiple functions. + var dialog; // The dialog box. + var input; // The text box where you enter the hyperlink. + + + if (defaultInputText === undefined) { + defaultInputText = ""; + } + + // Used as a keydown event handler. Esc dismisses the prompt. + // Key code 27 is ESC. + var checkEscape = function (key) { + var code = (key.charCode || key.keyCode); + if (code === 27) { + close(true); + } + }; + + // Dismisses the hyperlink input box. + // isCancel is true if we don't care about the input text. + // isCancel is false if we are going to keep the text. + var close = function (isCancel) { + util.removeEvent(doc.body, "keydown", checkEscape); + var text = input.value; + + if (isCancel) { + text = null; + } + else { + // Fixes common pasting errors. + text = text.replace('http://http://', 'http://'); + text = text.replace('http://https://', 'https://'); + text = text.replace('http://ftp://', 'ftp://'); + + if (text.indexOf('http://') === -1 && text.indexOf('ftp://') === -1 && text.indexOf('https://') === -1) { + text = 'http://' + text; + } + } + + dialog.parentNode.removeChild(dialog); + + callback(text); + return false; + }; + + + + // Create the text input box form/window. + var createDialog = function () { + + // The main dialog box. + dialog = doc.createElement("div"); + dialog.className = "wmd-prompt-dialog"; + dialog.style.padding = "10px;"; + dialog.style.position = "fixed"; + dialog.style.width = "400px"; + dialog.style.zIndex = "1001"; + + // The dialog text. + var question = doc.createElement("div"); + question.innerHTML = text; + question.style.padding = "5px"; + dialog.appendChild(question); + + // The web form container for the text box and buttons. + var form = doc.createElement("form"); + form.onsubmit = function () { return close(false); }; + style = form.style; + style.padding = "0"; + style.margin = "0"; + style.cssFloat = "left"; + style.width = "100%"; + style.textAlign = "center"; + style.position = "relative"; + dialog.appendChild(form); + + // The input text box + input = doc.createElement("input"); + input.type = "text"; + input.value = defaultInputText; + style = input.style; + style.display = "block"; + style.width = "80%"; + style.marginLeft = style.marginRight = "auto"; + form.appendChild(input); + + // The ok button + var okButton = doc.createElement("input"); + okButton.type = "button"; + okButton.onclick = function () { return close(false); }; + okButton.value = "OK"; + style = okButton.style; + style.margin = "10px"; + style.display = "inline"; + style.width = "7em"; + + + // The cancel button + var cancelButton = doc.createElement("input"); + cancelButton.type = "button"; + cancelButton.onclick = function () { return close(true); }; + cancelButton.value = "Cancel"; + style = cancelButton.style; + style.margin = "10px"; + style.display = "inline"; + style.width = "7em"; + + form.appendChild(okButton); + form.appendChild(cancelButton); + + util.addEvent(doc.body, "keydown", checkEscape); + dialog.style.top = "50%"; + dialog.style.left = "50%"; + dialog.style.display = "block"; + if (uaSniffed.isIE_5or6) { + dialog.style.position = "absolute"; + dialog.style.top = doc.documentElement.scrollTop + 200 + "px"; + dialog.style.left = "50%"; + } + doc.body.appendChild(dialog); + + // This has to be done AFTER adding the dialog to the form if you + // want it to be centered. + dialog.style.marginTop = -(position.getHeight(dialog) / 2) + "px"; + dialog.style.marginLeft = -(position.getWidth(dialog) / 2) + "px"; + + }; + + // Why is this in a zero-length timeout? + // Is it working around a browser bug? + setTimeout(function () { + + createDialog(); + + var defTextLen = defaultInputText.length; + if (input.selectionStart !== undefined) { + input.selectionStart = 0; + input.selectionEnd = defTextLen; + } + else if (input.createTextRange) { + var range = input.createTextRange(); + range.collapse(false); + range.moveStart("character", -defTextLen); + range.moveEnd("character", defTextLen); + range.select(); + } + + input.focus(); + }, 0); + }; + + function UIManager(postfix, panels, undoManager, previewManager, commandManager, helpOptions) { + + var inputBox = panels.input, + buttons = {}; // buttons.undo, buttons.link, etc. The actual DOM elements. + + makeSpritedButtonRow(); + + var keyEvent = "keydown"; + if (uaSniffed.isOpera) { + keyEvent = "keypress"; + } + + util.addEvent(inputBox, keyEvent, function (key) { + + // Check to see if we have a button key and, if so execute the callback. + if ((key.ctrlKey || key.metaKey) && !key.altKey) { + + var keyCode = key.charCode || key.keyCode; + var keyCodeStr = String.fromCharCode(keyCode).toLowerCase(); + + switch (keyCodeStr) { + case "b": + doClick(buttons.bold); + break; + case "i": + doClick(buttons.italic); + break; + case "l": + doClick(buttons.link); + break; + case "q": + doClick(buttons.quote); + break; + case "k": + doClick(buttons.code); + break; + case "g": + doClick(buttons.image); + break; + case "o": + doClick(buttons.olist); + break; + case "u": + doClick(buttons.ulist); + break; + case "h": + doClick(buttons.heading); + break; + case "r": + doClick(buttons.hr); + break; + case "y": + doClick(buttons.redo); + break; + case "z": + if (key.shiftKey) { + doClick(buttons.redo); + } + else { + doClick(buttons.undo); + } + break; + default: + return; + } + + + if (key.preventDefault) { + key.preventDefault(); + } + + if (window.event) { + window.event.returnValue = false; + } + } + }); + + // Auto-indent on shift-enter + util.addEvent(inputBox, "keyup", function (key) { + if (key.shiftKey && !key.ctrlKey && !key.metaKey) { + var keyCode = key.charCode || key.keyCode; + // Character 13 is Enter + if (keyCode === 13) { + fakeButton = {}; + fakeButton.textOp = bindCommand("doAutoindent"); + doClick(fakeButton); + } + } + }); + + // special handler because IE clears the context of the textbox on ESC + if (uaSniffed.isIE) { + util.addEvent(inputBox, "keydown", function (key) { + var code = key.keyCode; + if (code === 27) { + return false; + } + }); + } + + + // Perform the button's action. + function doClick(button) { + + inputBox.focus(); + + if (button.textOp) { + + if (undoManager) { + undoManager.setCommandMode(); + } + + var state = new TextareaState(panels); + + if (!state) { + return; + } + + var chunks = state.getChunks(); + + // Some commands launch a "modal" prompt dialog. Javascript + // can't really make a modal dialog box and the WMD code + // will continue to execute while the dialog is displayed. + // This prevents the dialog pattern I'm used to and means + // I can't do something like this: + // + // var link = CreateLinkDialog(); + // makeMarkdownLink(link); + // + // Instead of this straightforward method of handling a + // dialog I have to pass any code which would execute + // after the dialog is dismissed (e.g. link creation) + // in a function parameter. + // + // Yes this is awkward and I think it sucks, but there's + // no real workaround. Only the image and link code + // create dialogs and require the function pointers. + var fixupInputArea = function () { + + inputBox.focus(); + + if (chunks) { + state.setChunks(chunks); + } + + state.restore(); + previewManager.refresh(); + }; + + var noCleanup = button.textOp(chunks, fixupInputArea); + + if (!noCleanup) { + fixupInputArea(); + } + + } + + if (button.execute) { + button.execute(undoManager); + } + }; + + function setupButton(button, isEnabled) { + + var normalYShift = "0px"; + var disabledYShift = "-20px"; + var highlightYShift = "-40px"; + var image = button.getElementsByTagName("span")[0]; + if (isEnabled) { + image.style.backgroundPosition = button.XShift + " " + normalYShift; + button.onmouseover = function () { + image.style.backgroundPosition = this.XShift + " " + highlightYShift; + }; + + button.onmouseout = function () { + image.style.backgroundPosition = this.XShift + " " + normalYShift; + }; + + // IE tries to select the background image "button" text (it's + // implemented in a list item) so we have to cache the selection + // on mousedown. + if (uaSniffed.isIE) { + button.onmousedown = function () { + if (doc.activeElement && doc.activeElement !== panels.input) { // we're not even in the input box, so there's no selection + return; + } + panels.ieCachedRange = document.selection.createRange(); + panels.ieCachedScrollTop = panels.input.scrollTop; + }; + } + + if (!button.isHelp) { + button.onclick = function () { + if (this.onmouseout) { + this.onmouseout(); + } + doClick(this); + return false; + } + } + } + else { + image.style.backgroundPosition = button.XShift + " " + disabledYShift; + button.onmouseover = button.onmouseout = button.onclick = function () { }; + } + } + + function bindCommand(method) { + if (typeof method === "string") + method = commandManager[method]; + return function () { method.apply(commandManager, arguments); } + } + + function makeSpritedButtonRow() { + + var buttonBar = panels.buttonBar; + + var normalYShift = "0px"; + var disabledYShift = "-20px"; + var highlightYShift = "-40px"; + + var buttonRow = document.createElement("ul"); + buttonRow.id = "wmd-button-row" + postfix; + buttonRow.className = 'wmd-button-row'; + buttonRow = buttonBar.appendChild(buttonRow); + var xPosition = 0; + var makeButton = function (id, title, XShift, textOp) { + var button = document.createElement("li"); + button.className = "wmd-button"; + button.style.left = xPosition + "px"; + xPosition += 25; + var buttonImage = document.createElement("span"); + button.id = id + postfix; + button.appendChild(buttonImage); + button.title = title; + button.XShift = XShift; + if (textOp) + button.textOp = textOp; + setupButton(button, true); + buttonRow.appendChild(button); + return button; + }; + var makeSpacer = function (num) { + var spacer = document.createElement("li"); + spacer.className = "wmd-spacer wmd-spacer" + num; + spacer.id = "wmd-spacer" + num + postfix; + buttonRow.appendChild(spacer); + xPosition += 25; + } + + buttons.bold = makeButton("wmd-bold-button", "Strong Ctrl+B", "0px", bindCommand("doBold")); + buttons.italic = makeButton("wmd-italic-button", "Emphasis Ctrl+I", "-20px", bindCommand("doItalic")); + makeSpacer(1); + buttons.link = makeButton("wmd-link-button", "Hyperlink Ctrl+L", "-40px", bindCommand(function (chunk, postProcessing) { + return this.doLinkOrImage(chunk, postProcessing, false); + })); + buttons.quote = makeButton("wmd-quote-button", "Blockquote
    Ctrl+Q", "-60px", bindCommand("doBlockquote")); + buttons.code = makeButton("wmd-code-button", "Code Sample
     Ctrl+K", "-80px", bindCommand("doCode"));
    +            buttons.image = makeButton("wmd-image-button", "Image  Ctrl+G", "-100px", bindCommand(function (chunk, postProcessing) {
    +                return this.doLinkOrImage(chunk, postProcessing, true);
    +            }));
    +            makeSpacer(2);
    +            buttons.olist = makeButton("wmd-olist-button", "Numbered List 
      Ctrl+O", "-120px", bindCommand(function (chunk, postProcessing) { + this.doList(chunk, postProcessing, true); + })); + buttons.ulist = makeButton("wmd-ulist-button", "Bulleted List