From: dsc Date: Thu, 23 Feb 2012 10:13:53 +0000 (-0800) Subject: Vastly improves appearance of options, which means I've figured out how to use Bootst... X-Git-Url: http://git.less.ly:3516/?a=commitdiff_plain;h=262eb699a8cf61c27a293d1bbf6fe801927423a6;p=kraken-ui.git Vastly improves appearance of options, which means I've figured out how to use Bootstrap. :) --- diff --git a/lib/graph.co b/lib/graph.co index 0d23855..eda01aa 100644 --- a/lib/graph.co +++ b/lib/graph.co @@ -31,6 +31,7 @@ GraphOptionList = exports.GraphOptionList = FieldList.extend do # {{{ * The view for a single configurable option. */ GraphOptionView = exports.GraphOptionView = FieldView.extend do # {{{ + # __bind__ : <[ onClick ]> ctorName : 'GraphOptionView' tagName : 'div' className : 'field option' @@ -41,37 +42,38 @@ GraphOptionView = exports.GraphOptionView = FieldView.extend do # {{{ events : 'blur .value' : 'update' 'submit .value' : 'update' + 'click .close' : 'toggleCollapsed' + 'click' : 'onClick' - initialize: -> - # console.log "#this.initialize!" - FieldView::initialize ... - @$el.on 'click', (evt) ~> - target = $ evt.target - @toggleCollapsed() if @el is evt.target or not target.is '.value, label, input' - + # initialize: -> + # console.log "#this.initialize!" + # FieldView::initialize ... + # update: -> val = @$el.find('.value').val() current = @model.get 'value' return if val is current - console.log "#this.update( #current -> #val )" @model.setValue val, {+silent} render: -> - FieldView::render.call this + @__super__.render ... @$el.addClass 'collapsed' if @isCollapsed - # outer = $ @template @model.toJSON() - # @$el.html outer.html() - # .attr do - # id : outer.attr 'id' - # class : outer.attr('class') + if @isCollapsed then ' collapsed' else '' this + onClick: (evt) -> + target = $ evt.target + console.log "#this.onClick()", target + @toggleCollapsed() if @$el.hasClass('collapsed') and not target.hasClass('close') + toggleCollapsed: -> + starting = @$el.hasClass 'collapsed' #@isCollapsed @$el.toggleClass 'collapsed' @isCollapsed = @$el.hasClass 'collapsed' + console.log "#this.toggleCollapsed!", starting, '->', @isCollapsed + @trigger 'change:collapse', this this # }}} @@ -85,18 +87,26 @@ GraphOptionsScaffold = exports.GraphOptionsScaffold = Scaffold.extend do # {{{ collectionType : GraphOptionList subviewType : GraphOptionView - # initialize : -> - # Scaffold::initialize ... + + initialize : -> + Scaffold::initialize ... + @render = _.debounce @render.bind(this), 50 render: -> + # console.log "#this.render() -> .isotope()" @__super__.render ... @$el.isotope do itemSelector : '.field.option' layoutMode : 'masonry' - masonry : columnWidth : 100 + masonry : columnWidth : 10 + # itemPositionDataEnabled : true # animationEngine : 'jquery' - itemPositionDataEnabled : true - + + addOne: -> + # console.log "#this.addOne!" + view = @__super__.addOne ... + view.on 'change:collapse render', @render + view # }}} @@ -141,6 +151,7 @@ GraphView = exports.GraphView = BaseView.extend do # {{{ initialize : (o={}) -> @model or= new GraphModel BaseView::initialize ... + # console.log "#this.initialize!" @model.on 'change', @render, this @model.on 'destroy', @remove, this @@ -172,8 +183,6 @@ GraphView = exports.GraphView = BaseView.extend do # {{{ options.values() render: -> - # BaseView::render ... - options = @chartOptions() w = options.width or= @scaffold.get 'width' .getValue() or 480 h = options.height or= @scaffold.get 'height' .getValue() or 320 diff --git a/lib/scaffold/index.co b/lib/scaffold/index.co new file mode 100644 index 0000000..03f7aa7 --- /dev/null +++ b/lib/scaffold/index.co @@ -0,0 +1,3 @@ +models = require 'kraken/scaffold/model' +views = require 'kraken/scaffold/view' +exports import models import views diff --git a/lib/scaffold/model.co b/lib/scaffold/model.co new file mode 100644 index 0000000..422de92 --- /dev/null +++ b/lib/scaffold/model.co @@ -0,0 +1,88 @@ +_ = require 'kraken/underscore' +op = require 'kraken/util/op' + + + +### Scaffold Models + +Field = exports.Field = Backbone.Model.extend do # {{{ + ctorName : 'Field' + idAttribute : 'name' + + initialize: -> + @set 'value', @get('default'), {+silent} if not @has 'value' + # console.log "#this.initialize!" + + defaults: -> + { + name : '' + type : 'String' + default : null + desc : '' + category : 'General' + include : 'diff' + tags : [] + examples : [] + } + + getParser: (type) -> + type or= @get 'type' + t = _ type.toLowerCase() + + parser = op.toStr + if t.startsWith 'integer' + parser = op.toInt + if t.startsWith 'float' + parser = op.toFloat + if t.startsWith 'boolean' + parser = op.toBool + if t.startsWith 'object' or t.startsWith 'array' + parser = op.toObject + if t.startsWith 'function' + parser = (fn) -> eval "(#fn)" + + # TODO: handle 'or' by returning an array of parsers + parser + + + getValue: (def) -> + @getParser() @get 'value', def + + setValue: (v, options) -> + def = @get 'default' + if not v and def == null + val = null + else + val = @getParser()(v) + @set 'value', val, options + + clearValue: -> + @set 'value', @get('default') + + isDefault: -> + @get('value') is @get('default') + + toJSON: -> + {id:@id} import do + _.clone(@attributes) import { value:@getValue(), def:@get('default') } + + toString: -> "(#{@id}: #{@get 'value'})" +# }}} + + +FieldList = exports.FieldList = Backbone.Collection.extend do # {{{ + ctorName : 'FieldList' + model : Field + + /** + * Collects a map of fields to their values, excluding those set to `null` or their default. + * @returns {Object} + */ + values: -> + _.synthesize do + @models.filter -> not it.isDefault() + -> [ it.get('name'), it.getValue() ] + + toString: -> "#{@ctorName}(length=#{@length})" +# }}} + diff --git a/lib/scaffold.co b/lib/scaffold/view.co similarity index 61% rename from lib/scaffold.co rename to lib/scaffold/view.co index 474ff3c..332e289 100644 --- a/lib/scaffold.co +++ b/lib/scaffold/view.co @@ -1,92 +1,7 @@ _ = require 'kraken/underscore' op = require 'kraken/util/op' - - - -### Scaffold Models - -Field = exports.Field = Backbone.Model.extend do # {{{ - ctorName : 'Field' - idAttribute : 'name' - - initialize: -> - @set 'value', @get('default'), {+silent} if not @has 'value' - # console.log "#this.initialize!" - - defaults: -> - { - name : '' - type : 'String' - default : null - desc : '' - category : 'General' - include : 'diff' - tags : [] - examples : [] - } - - getParser: (type) -> - type or= @get 'type' - t = _ type.toLowerCase() - - parser = op.toStr - if t.startsWith 'integer' - parser = op.toInt - if t.startsWith 'float' - parser = op.toFloat - if t.startsWith 'boolean' - parser = op.toBool - if t.startsWith 'object' or t.startsWith 'array' - parser = op.toObject - if t.startsWith 'function' - parser = (fn) -> eval "(#fn)" - - # TODO: handle 'or' by returning an array of parsers - parser - - - getValue: (def) -> - @getParser() @get 'value', def - - setValue: (v, options) -> - def = @get 'default' - if not v and def == null - val = null - else - val = @getParser()(v) - @set 'value', val, options - - clearValue: -> - @set 'value', @get('default') - - isDefault: -> - @get('value') is @get('default') - - toJSON: -> - {id:@id} import do - _.clone(@attributes) import { value:@getValue(), def:@get('default') } - - toString: -> "(#{@id}: #{@get 'value'})" -# }}} - - -FieldList = exports.FieldList = Backbone.Collection.extend do # {{{ - ctorName : 'FieldList' - model : Field - - /** - * Collects a map of fields to their values, excluding those set to `null` or their default. - * @returns {Object} - */ - values: -> - _.synthesize do - @models.filter -> not it.isDefault() - -> [ it.get('name'), it.getValue() ] - - toString: -> "#{@ctorName}(length=#{@length})" -# }}} - - +{ Field, FieldList, +} = require 'kraken/scaffold/model' ### Views @@ -97,9 +12,8 @@ BaseView = exports.BaseView = Backbone.View.extend do # {{{ initialize: -> - _.bindAll this, ...@__bind__ + _.bindAll this, ...@__bind__ if @__bind__.length @__super__ = @constructor.__super__ - Backbone.View::initialize ... @model.view = this @$el.data { @model, view:this } @@ -127,6 +41,8 @@ BaseView = exports.BaseView = Backbone.View.extend do # {{{ render: -> @build() + @trigger 'render', this + this hide : -> @$el.hide(); this show : -> @$el.show(); this @@ -170,11 +86,11 @@ FieldView = exports.FieldView = BaseView.extend do # {{{ val = @$el.find('.value').val() current = @model.get 'value' return if val is current - console.log "#this.onUIChange( #current -> #val )" + # console.log "#this.onUIChange( #current -> #val )" @model.setValue val, {+silent} render: -> - return @remove() if @model.get 'hidden' + return @remove() if @model.get 'hidden', false return BaseView::render ... if @template name = @model.get 'name' @@ -219,12 +135,15 @@ Scaffold = exports.Scaffold = BaseView.extend do # {{{ addOne: (field) -> + # console.log "[S] #this.addOne!", @__super__ _.remove @subviews, field.view if field.view SubviewType = @subviewType view = new SubviewType model:field @subviews.push view - @$el.append view.render().el + @$el.append view.render().el unless field.get 'hidden' + + @render() view addAll: -> diff --git a/lib/template/browser-helpers.jade b/lib/template/browser-helpers.jade new file mode 100644 index 0000000..db4034f --- /dev/null +++ b/lib/template/browser-helpers.jade @@ -0,0 +1,3 @@ +- window.Markdown || (window.Markdown = new (require('showdown').Showdown).converter()); +- (jade.filters || (jade.filters = {})).markdown = function (s, name){ return s && Markdown.makeHtml(s.replace(/\n/g, '\n\n')); }; + diff --git a/lib/template/graph-option.jade b/lib/template/graph-option.jade index 0da61a1..01fae16 100644 --- a/lib/template/graph-option.jade +++ b/lib/template/graph-option.jade @@ -1,25 +1,27 @@ -- var _ = require('kraken/underscore'); +include browser-helpers +- var option_id = _.domize('option', id); - var value_id = _.domize('value', id); - var category_cls = _.domize('category', category); - var tags_cls = tags.map(_.domize('tag')).join(' '); - var type_cls = _.domize('type', type); -.field.option(id=_.domize('option', id), class="#{category_cls} #{tags_cls}") +.field.option(id=option_id, class="#{category_cls} #{tags_cls}") + a.close × + h3.shortname #{_.shortname(name)} label.name(for=value_id) #{name} - input.value(type="text", id=value_id, name=name, value=value) + input.value(type="text", id=value_id, name=name, class=type_cls, value=value) .type(class=type_cls) #{type} - .default(class=type_cls) #{def} + .default(class=type_cls, title="Default: #{def} (#{type})") #{def} .desc - #{desc} - //- != jade.filters.markdown(desc) + != (jade.filters || {}).markdown(desc) - .tags: ul + .tags(data-toggle="collapse", data-target="ul"): ul.collapse for tag in tags li.tag(class=_.domize('tag', tag)) #{tag} - .examples: ul + .examples(data-toggle="collapse", data-target="ul"): ul.collapse for example in examples li.example a(href="http://dygraphs.com/tests/#{example}.html", target="_blank") #{example} diff --git a/lib/template/graph.jade b/lib/template/graph.jade index 29b193a..7dd5df7 100644 --- a/lib/template/graph.jade +++ b/lib/template/graph.jade @@ -7,6 +7,6 @@ section.graph(id=id) form.details label.name(for='#{id}_name') Name - fieldset + fieldset.options legend Graph Options diff --git a/lib/underscore/functions.co b/lib/underscore/functions.co new file mode 100644 index 0000000..0dc8bf6 --- /dev/null +++ b/lib/underscore/functions.co @@ -0,0 +1,248 @@ +_ = require 'underscore' + + +slice = [].slice +hasOwn = {}.hasOwnProperty +objToString = {}.toString + +toArray = _.toArray + + + +decorate = (fn) -> + if not fn.__decorated__ + for name of _pet.FUNCTION_METHODS + m = _[name] + fn[name] = m.__methodized__ or methodize m + fn.__decorated__ = true + return fn + +methodize = (fn) -> + m = fn.__methodized__ + return m if m + + g = fn.__genericized__ + return g.__wraps__ if g and g.__wraps__ + + m = fn.__methodized__ = (args...) -> + args.unshift this + return fn.apply this, args + + m.__wraps__ = fn + return decorate m + + + +_pet = module.exports = \ + function pet (o, start=0, end=undefined) -> + if _.isArguments o + o = _.toArray o, start, end + + return decorate o if typeof o is 'function' + return _ o + +# function methods to be attached on call to _(fn) +_pet.FUNCTION_METHODS = [ + 'bind', 'bindAll', 'memoize', + 'delay', 'defer', 'throttle', 'debounce', 'once', 'after', + 'wrap', 'compose', + 'unwrap', 'partial', 'curry', 'flip', 'methodize', 'aritize', 'limit' +] + + +class2name = "Boolean Number String Function Array Date RegExp Object" + .split(" ") + .reduce ((class2name, name) -> + class2name[ "[object "+name+"]" ] = name + return class2name), {} + + +## Objects +_.mixin + + has: (o, v) -> + vals = if _.isArray(o) then o else _.values(o) + return vals.indexOf(v) is not -1 + + remove: (o, vs...) -> + if _.isArray(o) + _.each vs, (v) -> + idx = o.indexOf v + if idx is not -1 + o.splice idx, 1 + else + _.each o, (v, k) -> + if vs.indexOf(v) != -1 + delete o[k] + return o + + set: (o, key, value, def) -> + if o and key? and (value? or def?) + o[key] = value ? def + return o + + attr: (o, key, value, def) -> + return o if not o or key is undefined + + if _.isPlainObject key + return _.extend o, key + + if (value ? def) is not undefined + return _.set o, key, value, def + + return o[key] + + + +## Types +_.mixin + + basicTypeName: (o) -> + return if o is null then "null" else (class2name[objToString.call(o)] || "Object") + + isWindow: (o) -> + return o and typeof o is "object" and "setInterval" of o + + isPlainObject: (o) -> + # Must be an Object. + # Because of IE, we also have to check the presence of the constructor property. + # Make sure that DOM nodes and window objects don't pass through, as well + if not o or basicTypeName(o) is not "Object" or o.nodeType or _.isWindow(o) + return false + + # Not own constructor property? must be Object + C = o.constructor + if C and not hasOwn.call(o, "constructor") and not hasOwn.call(C.prototype, "isPrototypeOf") + return false + + # Own properties are enumerated firstly, so to speed up, + # if last one is own, then all properties are own. + for key in o + ; # semicolon **on new line** is required by coffeescript to denote empty statement. + + return key is undefined or hasOwn.call(o, key) + + +## Arrays +_.mixin + + toArray: (iterable, start=0, end=undefined) -> + _.slice toArray(iterable), start, end + + flatten: (A) -> + _.reduce do + slice.call(arguments) + (flat, v) -> + flat.concat( if _.isArray v then _.reduce(v, arguments.callee, []) else v ) + [] + + + +## Functions +_ofArity = _.memoize( + (n, limit) -> + args = ( '$'+i for i from 0 til n ).join(',') + name = ( if limit then 'limited' else 'artized' ) + apply_with = ( if limit then "[].slice.call(arguments, 0, #{n})" else 'arguments' ) + return eval " + (function #{name}(fn){ + var _fn = function(#{args}){ return fn.apply(this, #{apply_with}); }; + _fn.__wraps__ = fn; + return _(_fn); + })" + ) + +_.mixin + methodize: methodize + + unwrap: (fn) -> + (fn and _.isFunction(fn) and _.unwrap(fn.__wraps__)) or fn + + + partial: (fn, args...) -> + partially = -> + fn.apply this, args.concat(slice.call(arguments)) + partially.__wraps__ = fn + return _ partially + + + genericize: (fn) -> + g = fn.__genericized__ + return g if g + + m = fn.__methodized__ + return m.__wraps__ if m and m.__wraps__ + + g = fn.__genericized__ = (args...) -> + fn.apply args.shift(), args + + g.__wraps__ = fn + return _ g + + + curry: (fn, args...) -> + if not _.isFunction fn + return fn + + if fn.__curried__ + return fn.apply this, args + + L = fn.length or _.unwrap(fn).length + if args.length >= L + return fn.apply this, args + + curried = -> + _args = args.concat slice.call(arguments) + if _args.length >= L + return fn.apply this, _args + _args.unshift fn + return _.curry.apply this, _args + + curried.__wraps__ = fn + curried.__curried__ = args + return _ curried + + + flip: (fn) -> + f = fn.__flipped__ + return f if f + + f = fn.__flipped__ = \ + flipped = -> + args = arguments + hd = args[0] + args[0] = args[1] + args[1] = hd + return fn.apply this, args + + f.__wraps__ = fn + return _ f + + + aritize: (fn, n) -> + return fn if fn.length is n + + cache = fn.__aritized__ + if not cache + cache = fn.__aritized__ = {} + else if cache[n] + return cache[n] + + return ( cache[n] = _ofArity(n, false)(fn) ) + + + limit: (fn, n) -> + cache = fn.__limited__ + if not cache + cache = fn.__limited__ = {} + else if cache[n] + return cache[n] + + return ( cache[n] = _ofArity(n, true)(fn) ) + + + + + + +_.extend _pet, _ diff --git a/msc/dygraph-options/data.yaml b/msc/dygraph-options/data.yaml index 92e3213..7f7931d 100644 --- a/msc/dygraph-options/data.yaml +++ b/msc/dygraph-options/data.yaml @@ -340,7 +340,9 @@ default: null desc: A function which parses x-values (i.e. the dependent series). Must return a number, even when the values are dates. In this case, millis since epoch are used. This is used primarily for parsing - CSV data. *=Dygraphs is slightly more accepting in the dates which it will parse. See code for + CSV data. + + * Dygraphs is slightly more accepting in the dates which it will parse. See code for details. category: CSV parsing tags: @@ -726,6 +728,7 @@ type: Integer default: null desc: 'Prefer axes { x: { pixelsPerLabel } }' + hidden: true category: Deprecated tags: - deprecated @@ -734,6 +737,7 @@ type: Integer default: null desc: 'Prefer axes: { y: { pixelsPerLabel } }' + hidden: true category: Deprecated tags: - deprecated @@ -744,6 +748,7 @@ type: function default: null desc: 'Prefer axes { x: { axisLabelFormatter } }' + hidden: true category: Deprecated tags: - deprecated @@ -752,6 +757,7 @@ type: function default: null desc: 'Prefer axes: { x: { valueFormatter } }' + hidden: true category: Deprecated tags: - deprecated @@ -760,6 +766,7 @@ type: function default: null desc: 'Prefer axes: { y: { axisLabelFormatter } }' + hidden: true category: Deprecated tags: - deprecated @@ -768,6 +775,7 @@ type: function default: null desc: 'Prefer axes: { y: { valueFormatter } }' + hidden: true category: Deprecated tags: - deprecated diff --git a/static/vendor/showdown.js b/static/vendor/showdown.js new file mode 100644 index 0000000..43920d9 --- /dev/null +++ b/static/vendor/showdown.js @@ -0,0 +1,1302 @@ +// +// showdown.js -- A javascript port of Markdown. +// +// Copyright (c) 2007 John Fraser. +// +// Original Markdown Copyright (c) 2004-2005 John Gruber +// +// +// Redistributable under a BSD-style open source license. +// See license.txt for more information. +// +// The full source distribution is at: +// +// A A L +// T C A +// T K B +// +// +// + +// +// 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. +// + + +// +// Showdown usage: +// +// var text = "Markdown *rocks*."; +// +// var converter = new Showdown.converter(); +// var html = converter.makeHtml(text); +// +// alert(html); +// +// Note: move the sample code to the bottom of this +// file before uncommenting it. +// + + +// +// Showdown namespace +// +var Showdown = {}; + +// +// converter +// +// Wraps all "globals" so that the only thing +// exposed is makeHtml(). +// +Showdown.converter = function() { + +// +// Globals: +// + +// 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 = 0; + + +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. +// + + // Clear the global hashes. If we don't clear these, you get conflicts + // from other articles when generating a page which contains more than + // one article (e.g. an index page that shows the N most recent + // articles): + g_urls = new Array(); + g_titles = new Array(); + g_html_blocks = new Array(); + + // 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,"~"); + + return text; +} + + +var _StripLinkDefinitions = function(text) { +// +// Strips link definitions from text, stores the URLs and titles in +// hash references. +// + + // Link defs are in the form: ^[id]: url "optional title" + + /* + var text = text.replace(/ + ^[ ]{0,3}\[(.+)\]: // id = $1 attacklab: g_tab_width - 1 + [ \t]* + \n? // maybe *one* newline + [ \t]* + ? // url = $2 + [ \t]* + \n? // maybe one newline + [ \t]* + (?: + (\n*) // any lines skipped = $3 attacklab: lookbehind removed + ["(] + (.+?) // title = $4 + [")] + [ \t]* + )? // title is optional + (?:\n+|$) + /gm, + function(){...}); + */ + var text = text.replace(/^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*?[ \t]*\n?[ \t]*(?:(\n*)["(](.+?)[")][ \t]*)?(?:\n+|\Z)/gm, + function (wholeMatch,m1,m2,m3,m4) { + m1 = m1.toLowerCase(); + g_urls[m1] = _EncodeAmpsAndAngles(m2); // Link IDs are case-insensitive + if (m3) { + // Oops, found blank lines, so it's not a title. + // Put back the parenthetical statement we stole. + return m3+m4; + } else if (m4) { + g_titles[m1] = m4.replace(/"/g,"""); + } + + // Completely remove the definition from the text + return ""; + } + ); + + return text; +} + + +var _HashHTMLBlocks = function(text) { + // 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" + 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. + /* + 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,hashElement); + + // + // 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)\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(/ + ( // 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,hashElement); + */ + text = text.replace(/(\n[ ]{0,3}(<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g,hashElement); + + // 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,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); + + // attacklab: Undo double lines (see comment at top of this function) + text = text.replace(/\n\n/g,"\n"); + return text; +} + +var hashElement = 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" + (g_html_blocks.push(blockText)-1) + "K\n\n"; + + return blockText; +}; + +var _RunBlockGamut = function(text) { +// +// These are all the transformations that form block-level +// tags like paragraphs, headers, and list items. +// + text = _DoHeaders(text); + + // Do Horizontal Rules: + var key = hashBlock("
"); + 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 = _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); + + return text; +} + + +var _RunSpanGamut = function(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 = _EncodeAmpsAndAngles(text); + text = _DoItalicsAndBold(text); + + // Do hard breaks: + text = text.replace(/ +\n/g,"
\n"); + + return text; +} + +var _EscapeSpecialCharsWithinTagAttributes = function(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. + var regex = /(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|)/gi; + + text = text.replace(regex, function(wholeMatch) { + var tag = wholeMatch.replace(/(.)<\/?code>(?=.)/g,"$1`"); + tag = escapeCharacters(tag,"\\`*_"); + return tag; + }); + + return text; +} + +var _DoAnchors = function(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,_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; +} + +var writeAnchorTag = function(wholeMatch,m1,m2,m3,m4,m5,m6,m7) { + if (m7 == undefined) m7 = ""; + var whole_match = m1; + var link_text = m2; + 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[link_id] != undefined) { + url = g_urls[link_id]; + if (g_titles[link_id] != undefined) { + title = g_titles[link_id]; + } + } + else { + if (whole_match.search(/\(\s*\)$/m)>-1) { + // Special case for explicit empty url + url = ""; + } else { + return whole_match; + } + } + } + + url = escapeCharacters(url,"*_"); + var result = ""; + + return result; +} + + +var _DoImages = function(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; +} + +var writeImageTag = function(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[link_id] != undefined) { + url = g_urls[link_id]; + if (g_titles[link_id] != undefined) { + title = g_titles[link_id]; + } + } + else { + return whole_match; + } + } + + alt_text = alt_text.replace(/"/g,"""); + url = escapeCharacters(url,"*_"); + var result = "\""' + _RunSpanGamut(m1) + "");}); + + text = text.replace(/^(.+)[ \t]*\n-+[ \t]*\n+/gm, + function(matchFound,m1){return hashBlock('

' + _RunSpanGamut(m1) + "

");}); + + // 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 hashBlock("' + _RunSpanGamut(m2) + ""); + }); + + function headerId(m) { + return m.replace(/[^\w]/g, '').toLowerCase(); + } + return text; +} + +// This declaration keeps Dojo compressor from outputting garbage: +var _ProcessListItems; + +var _DoLists = function(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"; + + // Turn double returns into triple returns, so that we can make a + // paragraph for the last item in a list, if necessary: + list = list.replace(/\n{2,}/g,"\n\n\n");; + var result = _ProcessListItems(list); + + // 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"; + // Turn double returns into triple returns, so that we can make a + // paragraph for the last item in a list, if necessary: + var list = list.replace(/\n{2,}/g,"\n\n\n");; + var result = _ProcessListItems(list); + result = runup + "<"+list_type+">\n" + result + "\n"; + return result; + }); + } + + // attacklab: strip sentinel + text = text.replace(/~0/,""); + + return text; +} + +_ProcessListItems = function(list_str) { +// +// Process the contents of a single ordered or unordered list, splitting it +// into individual list items. +// + // 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"; + + /* + list_str = list_str.replace(/ + (\n)? // leading line = $1 + (^[ \t]*) // leading whitespace = $2 + ([*+-]|\d+[.]) [ \t]+ // list marker = $3 + ([^\r]+? // list item text = $4 + (\n{1,2})) + (?= \n* (~0 | \2 ([*+-]|\d+[.]) [ \t]+)) + /gm, function(){...}); + */ + list_str = list_str.replace(/(\n)?(^[ \t]*)([*+-]|\d+[.])[ \t]+([^\r]+?(\n{1,2}))(?=\n*(~0|\2([*+-]|\d+[.])[ \t]+))/gm, + function(wholeMatch,m1,m2,m3,m4){ + var item = m4; + var leading_line = m1; + var leading_space = m2; + + if (leading_line || (item.search(/\n{2,}/)>-1)) { + item = _RunBlockGamut(_Outdent(item)); + } + else { + // Recursion for sub-lists: + item = _DoLists(_Outdent(item)); + item = item.replace(/\n$/,""); // chomp(item) + item = _RunSpanGamut(item); + } + + return "
  • " + item + "
  • \n"; + } + ); + + // attacklab: strip sentinel + list_str = list_str.replace(/~0/g,""); + + g_list_level--; + return list_str; +} + + +var _DoCodeBlocks = function(text) { +// +// Process Markdown `
    ` blocks.
    +//  
    +
    +	/*
    +		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";
    +	
    +	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 hashBlock(codeblock) + nextChar; + } + ); + + // attacklab: strip sentinel + text = text.replace(/~0/,""); + + return text; +} + +var hashBlock = function(text) { + text = text.replace(/(^\n+|\n+$)/g,""); + return "\n\n~K" + (g_html_blocks.push(text)-1) + "K\n\n"; +} + + +var _DoCodeSpans = function(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; +} + + +var _EncodeCode = function(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; +} + + +var _DoItalicsAndBold = function(text) { + + // must go first: + text = text.replace(/(\*\*|__)(?=\S)([^\r]*?\S[*_]*)\1/g, + "$2"); + + text = text.replace(/(\*|_)(?=\S)([^\r]*?\S)\1/g, + "$2"); + + return text; +} + + +var _DoBlockQuotes = function(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; +} + + +var _FormParagraphs = function(text) { +// +// 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 = new Array(); + + // + // Wrap

    tags. + // + var end = grafs.length; + for (var i=0; i= 0) { + grafsOut.push(str); + } + else if (str.search(/\S/) >= 0) { + str = _RunSpanGamut(str); + str = str.replace(/^([ \t]*)/g,"

    "); + str += "

    " + grafsOut.push(str); + } + + } + + // + // Unhashify HTML blocks + // + end = grafsOut.length; + for (var i=0; i= 0) { + var blockText = g_html_blocks[RegExp.$1]; + blockText = blockText.replace(/\$/g,"$$$$"); // Escape any dollar signs + grafsOut[i] = grafsOut[i].replace(/~K\d+K/,blockText); + } + } + + return grafsOut.join("\n\n"); +} + + +var _EncodeAmpsAndAngles = function(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; +} + + +var _EncodeBackslashEscapes = function(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; +} + + +var _DoAutoLinks = function(text) { + + text = text.replace(/<((https?|ftp|dict):[^'">\s]+)>/gi,"
    $1"); + + // Email addresses: + + /* + text = text.replace(/ + < + (?:mailto:)? + ( + [-.\w]+ + \@ + [-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+ + ) + > + /gi, _DoAutoLinks_callback()); + */ + text = text.replace(/<(?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi, + function(wholeMatch,m1) { + return _EncodeEmailAddress( _UnescapeSpecialChars(m1) ); + } + ); + + return text; +} + + +var _EncodeEmailAddress = function(addr) { +// +// 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: +// + + // attacklab: why can't javascript speak hex? + function char2hex(ch) { + var hexDigits = '0123456789ABCDEF'; + var dec = ch.charCodeAt(0); + return(hexDigits.charAt(dec>>4) + hexDigits.charAt(dec&15)); + } + + var encode = [ + function(ch){return "&#"+ch.charCodeAt(0)+";";}, + function(ch){return "&#x"+char2hex(ch)+";";}, + 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 > .9 ? encode[2](ch) : + r > .45 ? encode[1](ch) : + encode[0](ch) + ); + } + return ch; + }); + + addr = "" + addr + ""; + addr = addr.replace(/">.+:/g,"\">"); // strip the mailto: from the visible part + + return addr; +} + + +var _UnescapeSpecialChars = function(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; +} + + +var _Outdent = function(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; +} + +var _Detab = function(text) { +// attacklab: Detab's completely rewritten for speed. +// In perl we could fix it by anchoring the regexp with \G. +// In javascript we're less fortunate. + + // expand first n-1 tabs + text = text.replace(/\t(?=\t)/g," "); // attacklab: 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,m2) { + var leadingText = m1; + var numSpaces = 4 - leadingText.length % 4; // attacklab: g_tab_width + + // there *must* be a better way to do this: + for (var i=0; i +// +// Redistributable under a BSD-style open source license. +// See license.txt for more information. +// +// The full source distribution is at: +// +// A A L +// T C A +// T K B +// +// +// +// +// 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. +// +// +// Showdown usage: +// +// var text = "Markdown *rocks*."; +// +// var converter = new Showdown.converter(); +// var html = converter.makeHtml(text); +// +// alert(html); +// +// Note: move the sample code to the bottom of this +// file before uncommenting it. +// +// +// Showdown namespace +// +var Showdown={};Showdown.converter=function(){var a,b,c,d=0;this.makeHtml=function(d){a=[],b=[],c=[],d=d.replace(/~/g,"~T"),d=d.replace(/\$/g,"~D"),d=d.replace(/\r\n/g,"\n"),d=d.replace(/\r/g,"\n"),d="\n\n"+d+"\n\n",d=E(d),d=d.replace(/^[ \t]+$/mg,""),d=f(d),d=e(d),d=h(d),d=C(d),d=d.replace(/~D/g,"$$"),d=d.replace(/~T/g,"~");return d};var e=function(c){var c=c.replace(/^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*?[ \t]*\n?[ \t]*(?:(\n*)["(](.+?)[")][ \t]*)?(?:\n+|\Z)/gm,function(c,d,e,f,g){d=d.toLowerCase(),a[d]=y(e);if(f)return f+g;g&&(b[d]=g.replace(/"/g,"""));return""});return c},f=function(a){a=a.replace(/\n/g,"\n\n");var b="p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del",c="p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math";a=a.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,g),a=a.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,g),a=a.replace(/(\n[ ]{0,3}(<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g,g),a=a.replace(/(\n\n[ ]{0,3}[ \t]*(?=\n{2,}))/g,g),a=a.replace(/(?:\n\n)([ ]{0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g,g),a=a.replace(/\n\n/g,"\n");return a},g=function(a,b){var d=b;d=d.replace(/\n\n/g,"\n"),d=d.replace(/^\n/,""),d=d.replace(/\n+$/g,""),d="\n\n~K"+(c.push(d)-1)+"K\n\n";return d},h=function(a){a=o(a);var b=s("
    ");a=a.replace(/^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$/gm,b),a=a.replace(/^[ ]{0,2}([ ]?\-[ ]?){3,}[ \t]*$/gm,b),a=a.replace(/^[ ]{0,2}([ ]?\_[ ]?){3,}[ \t]*$/gm,b),a=q(a),a=r(a),a=w(a),a=f(a),a=x(a);return a},i=function(a){a=t(a),a=j(a),a=z(a),a=m(a),a=k(a),a=A(a),a=y(a),a=v(a),a=a.replace(/ +\n/g,"
    \n");return a},j=function(a){var b=/(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|)/gi;a=a.replace(b,function(a){var b=a.replace(/(.)<\/?code>(?=.)/g,"$1`");b=F(b,"\\`*_");return b});return a},k=function(a){a=a.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,l),a=a.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\]\([ \t]*()?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,l),a=a.replace(/(\[([^\[\]]+)\])()()()()()/g,l);return a},l=function(c,d,e,f,g,h,i,j){j==undefined&&(j="");var k=d,l=e,m=f.toLowerCase(),n=g,o=j;if(n==""){m==""&&(m=l.toLowerCase().replace(/ ?\n/g," ")),n="#"+m;if(a[m]!=undefined)n=a[m],b[m]!=undefined&&(o=b[m]);else if(k.search(/\(\s*\)$/m)>-1)n="";else return k}n=F(n,"*_");var p='";return p},m=function(a){a=a.replace(/(!\[(.*?)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,n),a=a.replace(/(!\[(.*?)\]\s?\([ \t]*()?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,n);return a},n=function(c,d,e,f,g,h,i,j){var k=d,l=e,m=f.toLowerCase(),n=g,o=j;o||(o="");if(n==""){m==""&&(m=l.toLowerCase().replace(/ ?\n/g," ")),n="#"+m;if(a[m]!=undefined)n=a[m],b[m]!=undefined&&(o=b[m]);else return k}l=l.replace(/"/g,"""),n=F(n,"*_");var p=''+l+''+i(c)+"")}),a=a.replace(/^(.+)[ \t]*\n-+[ \t]*\n+/gm,function(a,c){return s('

    '+i(c)+"

    ")}),a=a.replace(/^(\#{1,6})[ \t]*(.+?)[ \t]*\#*\n+/gm,function(a,c,d){var e=c.length;return s("'+i(d)+"")});return a},p,q=function(a){a+="~0";var b=/^(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm;d?a=a.replace(b,function(a,b,c){var d=b,e=c.search(/[*+-]/g)>-1?"ul":"ol";d=d.replace(/\n{2,}/g,"\n\n\n");var f=p(d);f=f.replace(/\s+$/,""),f="<"+e+">"+f+"\n";return f}):(b=/(\n\n|^\n?)(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/g,a=a.replace(b,function(a,b,c,d){var e=b,f=c,g=d.search(/[*+-]/g)>-1?"ul":"ol",f=f.replace(/\n{2,}/g,"\n\n\n"),h=p(f);h=e+"<"+g+">\n"+h+"\n";return h})),a=a.replace(/~0/,"");return a};p=function(a){d++,a=a.replace(/\n{2,}$/,"\n"),a+="~0",a=a.replace(/(\n)?(^[ \t]*)([*+-]|\d+[.])[ \t]+([^\r]+?(\n{1,2}))(?=\n*(~0|\2([*+-]|\d+[.])[ \t]+))/gm,function(a,b,c,d,e){var f=e,g=b,j=c;g||f.search(/\n{2,}/)>-1?f=h(D(f)):(f=q(D(f)),f=f.replace(/\n$/,""),f=i(f));return"
  • "+f+"
  • \n"}),a=a.replace(/~0/g,""),d--;return a};var r=function(a){a+="~0",a=a.replace(/(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=~0))/g,function(a,b,c){var d=b,e=c;d=u(D(d)),d=E(d),d=d.replace(/^\n+/g,""),d=d.replace(/\n+$/g,""),d="
    "+d+"\n
    ";return s(d)+e}),a=a.replace(/~0/,"");return a},s=function(a){a=a.replace(/(^\n+|\n+$)/g,"");return"\n\n~K"+(c.push(a)-1)+"K\n\n"},t=function(a){a=a.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm,function(a,b,c,d,e){var f=d;f=f.replace(/^([ \t]*)/g,""),f=f.replace(/[ \t]*$/g,""),f=u(f);return b+""+f+""});return a},u=function(a){a=a.replace(/&/g,"&"),a=a.replace(//g,">"),a=F(a,"*_{}[]\\",!1);return a},v=function(a){a=a.replace(/(\*\*|__)(?=\S)([^\r]*?\S[*_]*)\1/g,"$2"),a=a.replace(/(\*|_)(?=\S)([^\r]*?\S)\1/g,"$2");return a},w=function(a){a=a.replace(/((^[ \t]*>[ \t]?.+\n(.+\n)*\n*)+)/gm,function(a,b){var c=b;c=c.replace(/^[ \t]*>[ \t]?/gm,"~0"),c=c.replace(/~0/g,""),c=c.replace(/^[ \t]+$/gm,""),c=h(c),c=c.replace(/(^|\n)/g,"$1 "),c=c.replace(/(\s*
    [^\r]+?<\/pre>)/gm,function(a,b){var c=b;c=c.replace(/^  /mg,"~0"),c=c.replace(/~0/g,"");return c});return s("
    \n"+c+"\n
    ")});return a},x=function(a){a=a.replace(/^\n+/g,""),a=a.replace(/\n+$/g,"");var b=a.split(/\n{2,}/g),d=[],e=b.length;for(var f=0;f=0?d.push(g):g.search(/\S/)>=0&&(g=i(g),g=g.replace(/^([ \t]*)/g,"

    "),g+="

    ",d.push(g))}e=d.length;for(var f=0;f=0){var h=c[RegExp.$1];h=h.replace(/\$/g,"$$$$"),d[f]=d[f].replace(/~K\d+K/,h)}return d.join("\n\n")},y=function(a){a=a.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g,"&"),a=a.replace(/<(?![a-z\/?\$!])/gi,"<");return a},z=function(a){a=a.replace(/\\(\\)/g,G),a=a.replace(/\\([`*_{}\[\]()>#+-.!])/g,G);return a},A=function(a){a=a.replace(/<((https?|ftp|dict):[^'">\s]+)>/gi,'
    $1'),a=a.replace(/<(?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi,function(a,b){return B(C(b))});return a},B=function(a){function b(a){var b="0123456789ABCDEF",c=a.charCodeAt(0);return b.charAt(c>>4)+b.charAt(c&15)}var c=[function(a){return"&#"+a.charCodeAt(0)+";"},function(a){return"&#x"+b(a)+";"},function(a){return a}];a="mailto:"+a,a=a.replace(/./g,function(a){if(a=="@")a=c[Math.floor(Math.random()*2)](a);else if(a!=":"){var b=Math.random();a=b>.9?c[2](a):b>.45?c[1](a):c[0](a)}return a}),a=''+a+"",a=a.replace(/">.+:/g,'">');return a},C=function(a){a=a.replace(/~E(\d+)E/g,function(a,b){var c=parseInt(b);return String.fromCharCode(c)});return a},D=function(a){a=a.replace(/^(\t|[ ]{1,4})/gm,"~0"),a=a.replace(/~0/g,"");return a},E=function(a){a=a.replace(/\t(?=\t)/g," "),a=a.replace(/\t/g,"~A~B"),a=a.replace(/~B(.+?)~A/g,function(a,b,c){var d=b,e=4-d.length%4;for(var f=0;f