diff --git a/Changelog.md b/Changelog.md index 5c4eba623de242c88094a0ed695ec36338aef267..488b0ca9d5861878db0ee4f90e37e0e01484ab25 100644 --- a/Changelog.md +++ b/Changelog.md @@ -141,6 +141,7 @@ diaspora.yml file**. The existing settings from 0.4.x and before will not work a * Don't include the content of non-public posts into notification mails [#5494](https://github.com/diaspora/diaspora/pull/5494) * Allow to set unhosted button and currency for paypal donation [#5452](https://github.com/diaspora/diaspora/pull/5452) * Add followed tags in the mobile menu [#5468](https://github.com/diaspora/diaspora/pull/5468) +* Replace Pagedown with markdown-it [#5526](https://github.com/diaspora/diaspora/pull/5526) # 0.4.1.2 diff --git a/Gemfile b/Gemfile index 9805de1c471fc9a3425f84c80c52d45741ba4439..665ff45a200f7fe4bacab326a242c6b0e6c75219 100644 --- a/Gemfile +++ b/Gemfile @@ -82,13 +82,17 @@ gem 'entypo-rails', '2.2.2' # JavaScript -gem 'backbone-on-rails', '1.1.2' -gem 'handlebars_assets', '0.18.0' -gem 'jquery-rails', '3.1.2' -gem 'rails-assets-jquery', '1.11.1' # Should be kept in sync with jquery-rails -gem 'js_image_paths', '0.0.1' -gem 'js-routes', '0.9.9' -gem 'rails-assets-punycode', '1.3.2' +gem 'backbone-on-rails', '1.1.2' +gem 'handlebars_assets', '0.18.0' +gem 'jquery-rails', '3.1.2' +gem 'rails-assets-jquery', '1.11.1' # Should be kept in sync with jquery-rails +gem 'js_image_paths', '0.0.1' +gem 'js-routes', '0.9.9' +gem 'rails-assets-punycode', '1.3.2' +gem 'rails-assets-markdown-it', '3.0.1' +gem 'rails-assets-markdown-it-hashtag', '0.2.1' +gem 'rails-assets-markdown-it-diaspora-mention', '0.1.0' +gem 'rails-assets-markdown-it--markdown-it-for-inline', '0.1.0' # jQuery plugins diff --git a/Gemfile.lock b/Gemfile.lock index 0ac8dbe9695e8b84640fe6f11b551c2ac5407038..b6e6e7943f9dafb24bfa383310fac8b667f71f2c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -438,6 +438,10 @@ GEM rails-assets-jquery (>= 1.6) rails-assets-jquery.slimscroll (1.3.3) rails-assets-jquery (>= 1.7) + rails-assets-markdown-it--markdown-it-for-inline (0.1.0) + rails-assets-markdown-it (3.0.1) + rails-assets-markdown-it-diaspora-mention (0.1.0) + rails-assets-markdown-it-hashtag (0.2.1) rails-assets-perfect-scrollbar (0.5.7) rails-assets-jquery (>= 1.10) rails-assets-punycode (1.3.2) @@ -681,6 +685,10 @@ DEPENDENCIES rails-assets-jquery-idletimer (= 1.0.1) rails-assets-jquery-placeholder (= 2.0.8) rails-assets-jquery-textchange (= 0.2.3) + rails-assets-markdown-it (= 3.0.1) + rails-assets-markdown-it--markdown-it-for-inline (= 0.1.0) + rails-assets-markdown-it-diaspora-mention (= 0.1.0) + rails-assets-markdown-it-hashtag (= 0.2.1) rails-assets-perfect-scrollbar (= 0.5.7) rails-assets-punycode (= 1.3.2) rails-i18n (= 4.0.3) diff --git a/app/assets/javascripts/app/helpers/handlebars-helpers.js b/app/assets/javascripts/app/helpers/handlebars-helpers.js index 17f42e3c2a639b752fc7571a32340cf0aa076023..0b59ba93b47a939fbb75edefa4aedb0feadf2889 100644 --- a/app/assets/javascripts/app/helpers/handlebars-helpers.js +++ b/app/assets/javascripts/app/helpers/handlebars-helpers.js @@ -99,7 +99,7 @@ Handlebars.registerHelper('fmtTags', function(tags) { }); Handlebars.registerHelper('fmtText', function(text) { - return new Handlebars.SafeString(app.helpers.textFormatter(text, null)); + return new Handlebars.SafeString(app.helpers.textFormatter(text)); }); Handlebars.registerHelper('isCurrentPage', function(path_helper, id, options){ diff --git a/app/assets/javascripts/app/helpers/text_formatter.js b/app/assets/javascripts/app/helpers/text_formatter.js index 71f07abc70e87afb7767eadee3efa6ea9fbe9de3..a4642b0e7ae5a331573fadbc62fbf5647a2dcbb6 100644 --- a/app/assets/javascripts/app/helpers/text_formatter.js +++ b/app/assets/javascripts/app/helpers/text_formatter.js @@ -1,146 +1,76 @@ // @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later -// cache url regex globally, for direct acces when testing -$(function() { - Diaspora.url_regex = /(^|\s)\b((?:(?:https?|ftp):(?:\/{1,3})|www\.)(?:[^"<>\)\s]|\(([^\s()<>]+|(\([^\s()<>]+\)))\))+)(?=\s|$)/gi; -}); - (function(){ - //make it so I take text and mentions rather than the modelapp.helpers.textFormatter( - var textFormatter = function textFormatter(text, model) { - var mentions = model ? model.get("mentioned_people") : []; - - return textFormatter.mentionify( - textFormatter.hashtagify( - textFormatter.markdownify(text) - ), mentions - ) - }; - - textFormatter.markdownify = function markdownify(text){ - var converter = Markdown.getSanitizingConverter(); - - // punycode non-ascii chars in urls - converter.hooks.chain("preConversion", function(text) { - - // add < > around plain urls, effectively making them "autolinks" - text = text.replace(Diaspora.url_regex, function() { - var url = arguments[2]; - if( url.match(/^[^\w]/) ) return url; // evil witchcraft, noop - return arguments[1]+"<"+url+">"; - }); - - // process links - // regex copied from: https://code.google.com/p/pagedown/source/browse/Markdown.Converter.js#1198 (and slightly expanded) - var linkRegex = /(\[.*\]:\s)?(<|\()((?:(https?|ftp):\/\/[^\/'">\s]|www)[^'">\s]+?)([>\)]{1,2})/gi; - text = text.replace(linkRegex, function() { - var unicodeUrl = arguments[3]; - var urlSuffix = arguments[5]; - - unicodeUrl = ( unicodeUrl.match(/^www/) ) ? ('http://' + unicodeUrl) : unicodeUrl; - - // handle parentheses, especially in case the link ends with ')' - if( urlSuffix.indexOf(')') != -1 && urlSuffix.indexOf('>') != -1 ) { - unicodeUrl += ')'; - urlSuffix = '>'; - } - - // url*DE*code as much as possible - try { - while( unicodeUrl.indexOf("%") !== -1 && unicodeUrl != decodeURI(unicodeUrl) ) { - unicodeUrl = decodeURI(unicodeUrl); - } - } - catch(e){} - - // markdown doesn't like '(' or ')' anywhere, except where it wants - var workingUrl = unicodeUrl.replace(/\(/, "%28").replace(/\)/, "%29"); + app.helpers.textFormatter = function(text, mentions) { + mentions = mentions ? mentions : []; - var addr = parse_url(unicodeUrl); - if( !addr.host ) addr.host = ""; // must not be 'undefined' - - var asciiUrl = // rebuild the url - (!addr.scheme ? '' : addr.scheme + - ( (addr.scheme.toLowerCase()=="mailto") ? ':' : '://')) + - (!addr.user ? '' : addr.user + - (!addr.pass ? '' : ':'+addr.pass) + '@') + - punycode.toASCII(addr.host) + - (!addr.port ? '' : ':' + addr.port) + - (!addr.path ? '' : encodeURI(addr.path) ) + - (!addr.query ? '' : '?' + encodeURI(addr.query) ) + - (!addr.fragment ? '' : '#' + encodeURI(addr.fragment) ); - if( !arguments[1] || arguments[1] == "") { // inline link - if(arguments[2] == "<") return "["+workingUrl+"]("+asciiUrl+")"; // without link text - else return arguments[2]+asciiUrl+urlSuffix; // with link text - } else { // reference style link - return arguments[1]+asciiUrl; - } - }); - - return text; + var punycodeURL = function(url){ + try { + while(url.indexOf("%") !== -1 && url != decodeURI(url)) url = decodeURI(url); + } + catch(e){} + + var addr = parse_url(url); + if( !addr.host ) addr.host = ""; // must not be 'undefined' + + url = // rebuild the url + (!addr.scheme ? '' : addr.scheme + + ( (addr.scheme.toLowerCase()=="mailto") ? ':' : '://')) + + (!addr.user ? '' : addr.user + + (!addr.pass ? '' : ':'+addr.pass) + '@') + + punycode.toASCII(addr.host) + + (!addr.port ? '' : ':' + addr.port) + + (!addr.path ? '' : encodeURI(addr.path) ) + + (!addr.query ? '' : '?' + encodeURI(addr.query) ) + + (!addr.fragment ? '' : '#' + encodeURI(addr.fragment) ); + return url; + }; + + var md = window.markdownit({ + breaks: true, + html: false, + linkify: true, + typographer: true }); - // make nice little utf-8 symbols - converter.hooks.chain("preConversion", function(text) { - var input_strings = [ - "<->", "->", "<-", - "(c)", "(r)", "(tm)", - "<3" - ]; - var output_symbols = [ - "↔", "→", "â†", - "©", "®", "â„¢", - "♥" - ]; - // quote function from: http://stackoverflow.com/a/494122 - var quote = function(str) { - return str.replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1"); - }; - - _.each(input_strings, function(str, idx) { - var r = new RegExp(quote(str), "gi"); - text = text.replace(r, output_symbols[idx]); - }); - return text; + var inlinePlugin = window.markdownitForInline; + md.use(inlinePlugin, 'utf8_symbols', 'text', function (tokens, idx) { + tokens[idx].content = tokens[idx].content.replace(/<->/g, "↔") + .replace(/<-/g, "â†") + .replace(/->/g, "→") + .replace(/<3/g, "♥"); }); - converter.hooks.chain("postConversion", function (text) { - return text.replace(/(\"(?:(?:http|https):\/\/)?[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(?:\/\S*)?\")(\>)/g, '$1 target="_blank">') + md.use(inlinePlugin, 'link_new_window_and_punycode', 'link_open', function (tokens, idx) { + tokens[idx].href = punycodeURL(tokens[idx].href); + tokens[idx].target = "_blank"; }); - return converter.makeHtml(text) - }; - - textFormatter.hashtagify = function hashtagify(text){ - var utf8WordCharcters =/(<a[^>]*>.*?<\/a>)|(\s|^|>)#([\u0080-\uFFFF|\w|-]+|<3)/g; + md.use(inlinePlugin, 'image_punycode', 'image', function (tokens, idx) { + tokens[idx].src = punycodeURL(tokens[idx].src); + }); - return text.replace(utf8WordCharcters, function(result, linkTag, preceeder, tagText) { - if(linkTag) - return linkTag; - else - return preceeder + "<a href='/tags/" + tagText.toLowerCase() + - "' class='tag'>#" + tagText + "</a>"; + var hashtagPlugin = window.markdownitHashtag; + md.use(hashtagPlugin, { + // compare tag_text_regexp in app/models/acts_as_taggable_on-tag.rb + hashtagRegExp: '[' + PosixBracketExpressions.alnum + '_\\-]+|<3', + // compare tag_strings in lib/diaspora/taggabe.rb + preceding: '^|\\s' }); - }; - textFormatter.mentionify = function mentionify(text, mentions) { - var mentionRegex = /@\{([^;]+); ([^\}]+)\}/g - return text.replace(mentionRegex, function(mentionText, fullName, diasporaId) { - var person = _.find(mentions, function(person){ - return (diasporaId == person.diaspora_id || person.handle) //jquery.mentionsInput gives us person.handle - }) - if(person) { - var url = person.url || "/people/" + person.guid //jquery.mentionsInput gives us person.url - , personText = "<a href='" + url + "' class='mention'>" + fullName + "</a>" - } else { - personText = fullName; - } + var mentionPlugin = window.markdownitDiasporaMention; + md.use(mentionPlugin, mentions); - return personText - }) - } + // TODO this is a temporary fix + // remove it as soon as markdown-it fixes its autolinking feature + var linkifyPlugin = window.markdownitDiasporaLinkify; + md.use(linkifyPlugin); - app.helpers.textFormatter = textFormatter; + // Bootstrap table markup + md.renderer.rules.table_open = function () { return '<table class="table table-striped">\n'; }; + + return md.render(text); + }; })(); // @license-end diff --git a/app/assets/javascripts/app/pages/single-post-viewer.js b/app/assets/javascripts/app/pages/single-post-viewer.js index 1e730890e4123101b7b23979b3be44f954b6aea4..b5af389b3d5c263f3a7497246b25b4f76b1e0eae 100644 --- a/app/assets/javascripts/app/pages/single-post-viewer.js +++ b/app/assets/javascripts/app/pages/single-post-viewer.js @@ -33,7 +33,7 @@ app.pages.SinglePostViewer = app.views.Base.extend({ postRenderTemplate : function() { if(this.model.get("title")){ // formats title to html... - var html_title = app.helpers.textFormatter(this.model.get("title"), this.model); + var html_title = app.helpers.textFormatter(this.model.get("title"), this.model.get("mentioned_people")); //... and converts html to plain text document.title = $('<div>').html(html_title).text(); } diff --git a/app/assets/javascripts/app/views/comment_view.js b/app/assets/javascripts/app/views/comment_view.js index daa5b31a64bae09201d0370013b1e2682dfd88de..f99bd7f664415f3fc5e5aae7e55dcf68e08be3ad 100644 --- a/app/assets/javascripts/app/views/comment_view.js +++ b/app/assets/javascripts/app/views/comment_view.js @@ -20,7 +20,7 @@ app.views.Comment = app.views.Content.extend({ presenter : function() { return _.extend(this.defaultPresenter(), { canRemove: this.canRemove(), - text : app.helpers.textFormatter(this.model.get("text"), this.model) + text : app.helpers.textFormatter(this.model.get("text")) }) }, diff --git a/app/assets/javascripts/app/views/content_view.js b/app/assets/javascripts/app/views/content_view.js index 01226fdabb0924bacb8c00e8db49112b9ab41920..f5e7061e5df983e66c814a081fa0f1fe69649c0f 100644 --- a/app/assets/javascripts/app/views/content_view.js +++ b/app/assets/javascripts/app/views/content_view.js @@ -7,7 +7,7 @@ app.views.Content = app.views.Base.extend({ presenter : function(){ return _.extend(this.defaultPresenter(), { - text : app.helpers.textFormatter(this.model.get("text"), this.model), + text : app.helpers.textFormatter(this.model.get("text"), this.model.get("mentioned_people")), largePhoto : this.largePhoto(), smallPhotos : this.smallPhotos(), location: this.location() diff --git a/app/assets/javascripts/app/views/post_view.js b/app/assets/javascripts/app/views/post_view.js index 48473b9959ca1934be32d9a6c089c24b8111df9a..195b468e1d1a39c26d95dfac5ad7949b07bf1ff1 100644 --- a/app/assets/javascripts/app/views/post_view.js +++ b/app/assets/javascripts/app/views/post_view.js @@ -5,7 +5,7 @@ app.views.Post = app.views.Base.extend({ return _.extend(this.defaultPresenter(), { authorIsCurrentUser : app.currentUser.isAuthorOf(this.model), showPost : this.showPost(), - text : app.helpers.textFormatter(this.model.get("text"), this.model) + text : app.helpers.textFormatter(this.model.get("text"), this.model.get("mentioned_people")) }) }, diff --git a/app/assets/javascripts/app/views/single-post-viewer/single_post_content_view.js b/app/assets/javascripts/app/views/single-post-viewer/single_post_content_view.js index 87d81d5a523711f0b13953fbb71a0d7900d4b746..dba3b08e9b41653e9983aac4339d0ca1d0a74405 100644 --- a/app/assets/javascripts/app/views/single-post-viewer/single_post_content_view.js +++ b/app/assets/javascripts/app/views/single-post-viewer/single_post_content_view.js @@ -29,7 +29,7 @@ app.views.SinglePostContent = app.views.Base.extend({ return _.extend(this.defaultPresenter(), { authorIsCurrentUser :app.currentUser.isAuthorOf(this.model), showPost : this.showPost(), - text : app.helpers.textFormatter(this.model.get("text"), this.model) + text : app.helpers.textFormatter(this.model.get("text"), this.model.get("mentioned_people")) }) }, diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index d683db631db8429fea4318af113b96b26bb6b93f..2e0e1714768e4b3db92c0a431c95b823b6f7a79c 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -23,7 +23,12 @@ //= require keycodes //= require fileuploader-custom //= require handlebars.runtime -//= require markdown +//= require posix-bracket-expressions +//= require markdown-it +//= require markdown-it-hashtag +//= require markdown-it-diaspora-linkify +//= require markdown-it-diaspora-mention +//= require markdown-it-for-inline //= require punycode //= require parse_url //= require clear-form diff --git a/config/locales/diaspora/en.yml b/config/locales/diaspora/en.yml index b046af3245932c74927f5f69e92ece08c268e634..6ab014aaa7bee7c5bb14839b59824a750fde72e1 100644 --- a/config/locales/diaspora/en.yml +++ b/config/locales/diaspora/en.yml @@ -1102,8 +1102,8 @@ en: discard_post: "Discard post" new_user_prefill: newhere: "NewHere" - hello: "Hey everyone, I'm #%{new_user_tag}. " - i_like: "I'm interested in %{tags}. " + hello: "Hey everyone, I’m #%{new_user_tag}. " + i_like: "I’m interested in %{tags}. " invited_by: "Thanks for the invite, " poll: remove_poll_answer: "Remove option" diff --git a/features/desktop/signs_up.feature b/features/desktop/signs_up.feature index d5529f641fa3148d4277c73ab62a5ec9a94bf4da..ce8bc6c797247fc8ec4cfd7dcc2697f2503a32ab 100644 --- a/features/desktop/signs_up.feature +++ b/features/desktop/signs_up.feature @@ -44,7 +44,7 @@ Feature: new user registration And I confirm the alert Then I should be on the stream page When I submit the publisher - Then "Hey everyone, I'm #NewHere." should be post 1 + Then "Hey everyone, I’m #NewHere." should be post 1 Scenario: new user with some tags posts first status message When I fill in the following: @@ -54,7 +54,7 @@ Feature: new user registration And I follow "awesome_button" Then I should be on the stream page When I submit the publisher - Then "Hey everyone, I'm #NewHere. I'm interested in #rockstar." should be post 1 + Then "Hey everyone, I’m #NewHere. I’m interested in #rockstar." should be post 1 Scenario: closing a popover clears getting started When I follow "awesome_button" diff --git a/lib/assets/javascripts/markdown-it-diaspora-linkify.js b/lib/assets/javascripts/markdown-it-diaspora-linkify.js new file mode 100644 index 0000000000000000000000000000000000000000..319497b0858027039707f0400ec7d0bce7233ab2 --- /dev/null +++ b/lib/assets/javascripts/markdown-it-diaspora-linkify.js @@ -0,0 +1,180 @@ +// TODO this is a temporary fix +// remove it as soon as markdown-it fixes its autolinking feature + +/*! markdown-it-diaspora-linkify 0.1.0 https://github.com/diaspora/markdown-it-diaspora-linkify @license MIT */!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.markdownitDiasporaLinkify=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ +'use strict'; + +var ip = require('ip-regex').v4().source; +var tlds = require('./tlds.json').join('|'); + +/** + * Regular expression for matching URLs + * + * @param {Object} opts + * @api public + */ + +module.exports = function (opts) { + opts = opts || {}; + + var auth = '(?:\\S+(?::\\S*)?@)?'; + var domain = '(?:\\.(?:xn--[a-z0-9\\-]{1,59}|(?:[a-z\\u00a1-\\uffff0-9]+-?){0,62}[a-z\\u00a1-\\uffff0-9]{1,63}))*'; + var host = '(?:xn--[a-z0-9\\-]{1,59}|(?:(?:[a-z\\u00a1-\\uffff0-9]+-?){0,62}[a-z\\u00a1-\\uffff0-9]{1,63}))'; + var path = '(?:\/[^\\s]*)?'; + var port = '(?::\\d{2,5})?'; + var protocol = '(?:(?:(?:\\w)+:)?\/\/)?'; + var tld = '(?:\\.(?:xn--[a-z0-9\\-]{1,59}|' + tlds + '+))'; + + var regex = [ + protocol + auth + '(?:' + ip + '|', + '(?:localhost)|' + host + domain + tld + ')' + port + path + ].join(''); + + return opts.exact ? new RegExp('(?:^' + regex + '$)', 'i') : + new RegExp('(?:^|\\s)(["\'])?' + regex + '\\1', 'ig'); +}; + +},{"./tlds.json":3,"ip-regex":2}],2:[function(require,module,exports){ +'use strict'; + +var v4 = '(?:25[0-5]|2[0-4][0-9]|1?[0-9][0-9]{1,2}|[0-9]){1,}(?:\\.(?:25[0-5]|2[0-4][0-9]|1?[0-9]{1,2}|0)){3}'; +var v6 = '(?:(?:[0-9a-fA-F:]){1,4}(?:(?::(?:[0-9a-fA-F]){1,4}|:)){2,7})+'; + +var ip = module.exports = function (opts) { + opts = opts || {}; + return opts.exact ? new RegExp('(?:^' + v4 + '$)|(?:^' + v6 + '$)') : + new RegExp('(?:' + v4 + ')|(?:' + v6 + ')', 'g'); +}; + +ip.v4 = function (opts) { + opts = opts || {}; + return opts.exact ? new RegExp('^' + v4 + '$') : new RegExp(v4, 'g'); +}; + +ip.v6 = function (opts) { + opts = opts || {}; + return opts.exact ? new RegExp('^' + v6 + '$') : new RegExp(v6, 'g'); +}; + +},{}],3:[function(require,module,exports){ +module.exports=["vermögensberatung","vermögensberater","cancerresearch","international","versicherung","construction","contractors","engineering","motorcycles","சிஙà¯à®•à®ªà¯à®ªà¯‚à®°à¯","accountants","investments","enterprises","williamhill","photography","blackfriday","productions","properties","healthcare","immobilien","university","republican","consulting","technology","industries","creditcard","cuisinella","foundation","restaurant","bnpparibas","associates","management","vlaanderen","furniture","bloomberg","equipment","melbourne","financial","education","directory","solutions","allfinanz","institute","christmas","community","vacations","marketing","training","capetown","pharmacy","partners","delivery","democrat","diamonds","software","discount","السعودية","saarland","catering","airforce","mortgage","attorney","services","engineer","supplies","cleaning","property","clothing","lighting","exchange","feedback","boutique","flsmidth","brussels","plumbing","budapest","computer","builders","business","yokohama","bargains","holdings","ventures","graphics","pictures","whoswho","dentist","recipes","digital","neustar","schmidt","realtor","shiksha","domains","network","support","android","youtube","college","cologne","surgery","capital","company","caravan","இநà¯à®¤à®¿à®¯à®¾","abogado","academy","limited","careers","spiegel","lacaixa","exposed","cooking","finance","country","fishing","fitness","flights","florist","reviews","kitchen","channel","forsale","cricket","frogans","cruises","systems","الجزائر","gallery","science","auction","organic","okinawa","hosting","holiday","website","wedding","hamburg","rentals","singles","guitars","travel","google","hiphop","global","онлайн","моÑква","insure","futbol","joburg","juegos","kaufen","امارات","expert","lawyer","events","london","estate","luxury","maison","الاردن","market","energy","emerck","monash","moscow","المغرب","museum","nagoya","durban","direct","dental","degree","webcam","مليسيا","voyage","dating","otsuka","gratis","credit","photos","physio","condos","coffee","clinic","quebec","claims","reisen","vision","church","repair","report","chrome","center","villas","viajes","ryukyu","career","camera","இலஙà¯à®•à¯ˆ","schule","Ùلسطين","yachts","social","yandex","berlin","bayern","supply","suzuki","sydney","alsace","taipei","tattoo","agency","active","tienda","voting","globo","mango","ایران","pizza","place","سورية","poker","praxi","press","jetzt","codes","media","vodka","homes","click","miami","citic","rehab","reise","works","horse","email","à°à°¾à°°à°¤à±","بھارت","house","cheap","koeln","world","संगठन","rocks","rodeo","glass","nexus","cards","lease","gives","ninja","build","deals","black","shoes","بازار","watch","loans","solar","wales","vegas","space","guide","autos","lotto","audio","archi","green","gifts","paris","dance","tatar","parts","gripe","actor","cymru","photo","tirol","today","tokyo","tools","gmail","trade","party","aero","à¦à¦¾à¦°à¦¤","شبكة","kiwi","pics","club","pink","army","kred","casa","pohl","land","post","cash","à¨à¨¾à¨°à¨¤","vote","prod","lgbt","prof","life","à¤à¤¾à¤°à¤¤","àªàª¾àª°àª¤","qpon","limo","link","buzz","ලංකà·","arpa","تونس","luxe","reit","cern","fail","farm","desi","blue","rest","guru","diet","rich","meet","haus","meme","menu","rsvp","ruhr","fish","help","sarl","mini","mobi","moda","work","here","scot","beer","sexy","дети","asia","camp","best","cool","sohu","name","navy","wiki","host","coop","wien","yoga","dvag","surf","Ñайт","immo","city","عمان","info","bike","wang","fund","zone","voto","组织机构","tips","موقع","band","care","gbiz","jobs","town","toys","gent","gift","ltda","top","tel","uno","uol","tax","soy","scb","sca","vet","rip","rio","ren","red","pub","pro","ovh","org","ooo","onl","ong","nyc","nrw","nra","wed","nhk","ngo","new","net","mov","wme","moe","mil","krd","wtc","wtf","kim","int","æˆ‘çˆ±ä½ ","ink","қаз","ing","ibm","Ñрб","орг","tui","hiv","мкд","ä¸æ–‡ç½‘","gov","gop","gmx","gmo","gle","укр","мон","gal","frl","foo","fly","eus","esq","edu","eat","dnp","day","dad","crs","ไทย","com","руÑ","ceo","ã¿ã‚“ãª","cat","cal","cab","مصر","قطر","bzh","boo","æ–°åŠ å¡","bmw","xxx","xyz","biz","bio","bid","bar","axa","zip","how","pk","pl","er","hr","pm","pn","ht","hu","es","pr","id","ie","il","im","ca","ae","in","et","ps","pt","eu","pw","py","qa","as","al","re","bf","bg","bh","bi","zw","io","iq","ir","is","it","je","at","jm","jo","fi","af","jp","cr","ro","au","ke","rs","kg","ru","kh","rw","ki","sa","bj","am","sb","sc","fj","km","kn","fk","kp","kr","sd","se","an","ag","sg","sh","kw","ky","si","kz","sj","sk","sl","sm","sn","so","la","cu","aw","fm","lb","lc","fo","cv","sr","st","su","li","cw","cx","fr","cy","cc","sv","sx","sy","lk","cz","sz","cd","bm","lr","ls","tc","td","lt","ga","tf","tg","th","lu","ax","bn","tj","tk","tl","tm","tn","to","lv","ly","ac","gb","de","gd","tp","tr","ge","cf","mc","tt","md","tv","tw","tz","ua","ug","uk","me","gf","gg","us","uy","uz","va","gh","vc","ve","gi","cg","mg","mh","vg","vi","ch","ao","gl","mk","vn","ml","mm","mn","mo","bo","vu","az","ba","br","gm","ci","aq","bs","wf","mp","mq","mr","ms","mt","mu","gn","mv","ws","mw","mx","佛山","集团","在线","í•œêµ","my","å…«å¦","mz","公益","å…¬å¸","移动","na","ck","cl","dj","nc","ne","gp","삼성","gq","å•†æ ‡","商城","gr","dk","dm","ä¸ä¿¡","ä¸å›½","ä¸åœ‹","nf","ng","bt","do","ni","网络","gs","香港","å°æ¹¾","å°ç£","手机","nl","no","np","nr","gt","gu","nu","ar","nz","ad","om","ai","გე","机构","gw","gy","dz","bb","рф","ec","bd","世界","pa","网å€","游æˆ","cm","ee","ä¼ä¸š","eg","hk","广东","pe","pf","pg","ph","政务","hm","hn","cn","co","ye","bv","bw","by","yt","za","bz","zm","be","ma"] +},{}],4:[function(require,module,exports){ +// Replace link-like texts with link nodes. +// +'use strict'; + +var urlRegex = require('url-regex'); +var LINK_SCAN_RE = /www|@|\:\/\//; + +function isLinkOpen(str) { + return /^<a[>\s]/i.test(str); +} +function isLinkClose(str) { + return /^<\/a\s*>/i.test(str); +} + +module.exports = function linkify_plugin(md) { + var arrayReplaceAt = md.utils.arrayReplaceAt; + + function linkify(state) { + var i, j, l, tokens, token, text, nodes, ln, pos, level, htmlLinkLevel, + blockTokens = state.tokens, links, href; + + if (!state.md.options.linkify) { return; } + + for (j = 0, l = blockTokens.length; j < l; j++) { + if (blockTokens[j].type !== 'inline') { continue; } + tokens = blockTokens[j].children; + + htmlLinkLevel = 0; + + // We scan from the end, to keep position when new tags added. + // Use reversed logic in links start/end match + for (i = tokens.length - 1; i >= 0; i--) { + token = tokens[i]; + + // Skip content of markdown links + if (token.type === 'link_close') { + i--; + while (tokens[i].level !== token.level && tokens[i].type !== 'link_open') { + i--; + } + continue; + } + + // Skip content of html tag links + if (token.type === 'html_inline') { + if (isLinkOpen(token.content) && htmlLinkLevel > 0) { + htmlLinkLevel--; + } + if (isLinkClose(token.content)) { + htmlLinkLevel++; + } + } + if (htmlLinkLevel > 0) { continue; } + + if (token.type === 'text' && LINK_SCAN_RE.test(token.content)) { + + text = token.content; + links = text.match(urlRegex()); + + if (links === null || !links.length) { continue; } + + // Now split string to nodes + nodes = []; + level = token.level; + + for (ln = 0; ln < links.length; ln++) { + + if (!state.md.inline.validateLink(links[ln])) { continue; } + + pos = text.indexOf(links[ln]); + + href = links[ln]; + + if (pos) { + level = level; + nodes.push({ + type: 'text', + content: text.slice(0, pos), + level: level + }); + } + nodes.push({ + type: 'link_open', + href: href, + target: '', + title: '', + level: level++ + }); + nodes.push({ + type: 'text', + content: links[ln], + level: level + }); + nodes.push({ + type: 'link_close', + level: --level + }); + text = text.slice(pos + links[ln].length); + } + if (text.length) { + nodes.push({ + type: 'text', + content: text, + level: level + }); + } + + // replace current node + blockTokens[j].children = tokens = arrayReplaceAt(tokens, i, nodes); + } + } + } + } + + md.core.ruler.at('linkify', linkify); +}; + +},{"url-regex":1}]},{},[4])(4) +}); diff --git a/lib/assets/javascripts/posix-bracket-expressions.js b/lib/assets/javascripts/posix-bracket-expressions.js new file mode 100644 index 0000000000000000000000000000000000000000..900c574671e1689b4bc35a6172f788cbc152f069 --- /dev/null +++ b/lib/assets/javascripts/posix-bracket-expressions.js @@ -0,0 +1,571 @@ +// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later + +var PosixBracketExpressions = { + alnum : '\\u0030-\\u0039' + + '\\u0041-\\u005a' + + '\\u0061-\\u007a' + + '\\u00aa' + + '\\u00b5' + + '\\u00ba' + + '\\u00c0-\\u00d6' + + '\\u00d8-\\u00f6' + + '\\u00f8-\\u02c1' + + '\\u02c6-\\u02d1' + + '\\u02e0-\\u02e4' + + '\\u02ec' + + '\\u02ee' + + '\\u0345' + + '\\u0370-\\u0374' + + '\\u0376-\\u0377' + + '\\u037a-\\u037d' + + '\\u0386' + + '\\u0388-\\u038a' + + '\\u038c' + + '\\u038e-\\u03a1' + + '\\u03a3-\\u03f5' + + '\\u03f7-\\u0481' + + '\\u048a-\\u0527' + + '\\u0531-\\u0556' + + '\\u0559' + + '\\u0561-\\u0587' + + '\\u05b0-\\u05bd' + + '\\u05bf' + + '\\u05c1-\\u05c2' + + '\\u05c4-\\u05c5' + + '\\u05c7' + + '\\u05d0-\\u05ea' + + '\\u05f0-\\u05f2' + + '\\u0610-\\u061a' + + '\\u0620-\\u0657' + + '\\u0659-\\u0669' + + '\\u066e-\\u06d3' + + '\\u06d5-\\u06dc' + + '\\u06e1-\\u06e8' + + '\\u06ed-\\u06fc' + + '\\u06ff' + + '\\u0710-\\u073f' + + '\\u074d-\\u07b1' + + '\\u07c0-\\u07ea' + + '\\u07f4-\\u07f5' + + '\\u07fa' + + '\\u0800-\\u0817' + + '\\u081a-\\u082c' + + '\\u0840-\\u0858' + + '\\u08a0' + + '\\u08a2-\\u08ac' + + '\\u08e4-\\u08e9' + + '\\u08f0-\\u08fe' + + '\\u0900-\\u093b' + + '\\u093d-\\u094c' + + '\\u094e-\\u0950' + + '\\u0955-\\u0963' + + '\\u0966-\\u096f' + + '\\u0971-\\u0977' + + '\\u0979-\\u097f' + + '\\u0981-\\u0983' + + '\\u0985-\\u098c' + + '\\u098f-\\u0990' + + '\\u0993-\\u09a8' + + '\\u09aa-\\u09b0' + + '\\u09b2' + + '\\u09b6-\\u09b9' + + '\\u09bd-\\u09c4' + + '\\u09c7-\\u09c8' + + '\\u09cb-\\u09cc' + + '\\u09ce' + + '\\u09d7' + + '\\u09dc-\\u09dd' + + '\\u09df-\\u09e3' + + '\\u09e6-\\u09f1' + + '\\u0a01-\\u0a03' + + '\\u0a05-\\u0a0a' + + '\\u0a0f-\\u0a10' + + '\\u0a13-\\u0a28' + + '\\u0a2a-\\u0a30' + + '\\u0a32-\\u0a33' + + '\\u0a35-\\u0a36' + + '\\u0a38-\\u0a39' + + '\\u0a3e-\\u0a42' + + '\\u0a47-\\u0a48' + + '\\u0a4b-\\u0a4c' + + '\\u0a51' + + '\\u0a59-\\u0a5c' + + '\\u0a5e' + + '\\u0a66-\\u0a75' + + '\\u0a81-\\u0a83' + + '\\u0a85-\\u0a8d' + + '\\u0a8f-\\u0a91' + + '\\u0a93-\\u0aa8' + + '\\u0aaa-\\u0ab0' + + '\\u0ab2-\\u0ab3' + + '\\u0ab5-\\u0ab9' + + '\\u0abd-\\u0ac5' + + '\\u0ac7-\\u0ac9' + + '\\u0acb-\\u0acc' + + '\\u0ad0' + + '\\u0ae0-\\u0ae3' + + '\\u0ae6-\\u0aef' + + '\\u0b01-\\u0b03' + + '\\u0b05-\\u0b0c' + + '\\u0b0f-\\u0b10' + + '\\u0b13-\\u0b28' + + '\\u0b2a-\\u0b30' + + '\\u0b32-\\u0b33' + + '\\u0b35-\\u0b39' + + '\\u0b3d-\\u0b44' + + '\\u0b47-\\u0b48' + + '\\u0b4b-\\u0b4c' + + '\\u0b56-\\u0b57' + + '\\u0b5c-\\u0b5d' + + '\\u0b5f-\\u0b63' + + '\\u0b66-\\u0b6f' + + '\\u0b71' + + '\\u0b82-\\u0b83' + + '\\u0b85-\\u0b8a' + + '\\u0b8e-\\u0b90' + + '\\u0b92-\\u0b95' + + '\\u0b99-\\u0b9a' + + '\\u0b9c' + + '\\u0b9e-\\u0b9f' + + '\\u0ba3-\\u0ba4' + + '\\u0ba8-\\u0baa' + + '\\u0bae-\\u0bb9' + + '\\u0bbe-\\u0bc2' + + '\\u0bc6-\\u0bc8' + + '\\u0bca-\\u0bcc' + + '\\u0bd0' + + '\\u0bd7' + + '\\u0be6-\\u0bef' + + '\\u0c01-\\u0c03' + + '\\u0c05-\\u0c0c' + + '\\u0c0e-\\u0c10' + + '\\u0c12-\\u0c28' + + '\\u0c2a-\\u0c33' + + '\\u0c35-\\u0c39' + + '\\u0c3d-\\u0c44' + + '\\u0c46-\\u0c48' + + '\\u0c4a-\\u0c4c' + + '\\u0c55-\\u0c56' + + '\\u0c58-\\u0c59' + + '\\u0c60-\\u0c63' + + '\\u0c66-\\u0c6f' + + '\\u0c82-\\u0c83' + + '\\u0c85-\\u0c8c' + + '\\u0c8e-\\u0c90' + + '\\u0c92-\\u0ca8' + + '\\u0caa-\\u0cb3' + + '\\u0cb5-\\u0cb9' + + '\\u0cbd-\\u0cc4' + + '\\u0cc6-\\u0cc8' + + '\\u0cca-\\u0ccc' + + '\\u0cd5-\\u0cd6' + + '\\u0cde' + + '\\u0ce0-\\u0ce3' + + '\\u0ce6-\\u0cef' + + '\\u0cf1-\\u0cf2' + + '\\u0d02-\\u0d03' + + '\\u0d05-\\u0d0c' + + '\\u0d0e-\\u0d10' + + '\\u0d12-\\u0d3a' + + '\\u0d3d-\\u0d44' + + '\\u0d46-\\u0d48' + + '\\u0d4a-\\u0d4c' + + '\\u0d4e' + + '\\u0d57' + + '\\u0d60-\\u0d63' + + '\\u0d66-\\u0d6f' + + '\\u0d7a-\\u0d7f' + + '\\u0d82-\\u0d83' + + '\\u0d85-\\u0d96' + + '\\u0d9a-\\u0db1' + + '\\u0db3-\\u0dbb' + + '\\u0dbd' + + '\\u0dc0-\\u0dc6' + + '\\u0dcf-\\u0dd4' + + '\\u0dd6' + + '\\u0dd8-\\u0ddf' + + '\\u0df2-\\u0df3' + + '\\u0e01-\\u0e3a' + + '\\u0e40-\\u0e46' + + '\\u0e4d' + + '\\u0e50-\\u0e59' + + '\\u0e81-\\u0e82' + + '\\u0e84' + + '\\u0e87-\\u0e88' + + '\\u0e8a' + + '\\u0e8d' + + '\\u0e94-\\u0e97' + + '\\u0e99-\\u0e9f' + + '\\u0ea1-\\u0ea3' + + '\\u0ea5' + + '\\u0ea7' + + '\\u0eaa-\\u0eab' + + '\\u0ead-\\u0eb9' + + '\\u0ebb-\\u0ebd' + + '\\u0ec0-\\u0ec4' + + '\\u0ec6' + + '\\u0ecd' + + '\\u0ed0-\\u0ed9' + + '\\u0edc-\\u0edf' + + '\\u0f00' + + '\\u0f20-\\u0f29' + + '\\u0f40-\\u0f47' + + '\\u0f49-\\u0f6c' + + '\\u0f71-\\u0f81' + + '\\u0f88-\\u0f97' + + '\\u0f99-\\u0fbc' + + '\\u1000-\\u1036' + + '\\u1038' + + '\\u103b-\\u1049' + + '\\u1050-\\u1062' + + '\\u1065-\\u1068' + + '\\u106e-\\u1086' + + '\\u108e' + + '\\u1090-\\u1099' + + '\\u109c-\\u109d' + + '\\u10a0-\\u10c5' + + '\\u10c7' + + '\\u10cd' + + '\\u10d0-\\u10fa' + + '\\u10fc-\\u1248' + + '\\u124a-\\u124d' + + '\\u1250-\\u1256' + + '\\u1258' + + '\\u125a-\\u125d' + + '\\u1260-\\u1288' + + '\\u128a-\\u128d' + + '\\u1290-\\u12b0' + + '\\u12b2-\\u12b5' + + '\\u12b8-\\u12be' + + '\\u12c0' + + '\\u12c2-\\u12c5' + + '\\u12c8-\\u12d6' + + '\\u12d8-\\u1310' + + '\\u1312-\\u1315' + + '\\u1318-\\u135a' + + '\\u135f' + + '\\u1380-\\u138f' + + '\\u13a0-\\u13f4' + + '\\u1401-\\u166c' + + '\\u166f-\\u167f' + + '\\u1681-\\u169a' + + '\\u16a0-\\u16ea' + + '\\u16ee-\\u16f0' + + '\\u1700-\\u170c' + + '\\u170e-\\u1713' + + '\\u1720-\\u1733' + + '\\u1740-\\u1753' + + '\\u1760-\\u176c' + + '\\u176e-\\u1770' + + '\\u1772-\\u1773' + + '\\u1780-\\u17b3' + + '\\u17b6-\\u17c8' + + '\\u17d7' + + '\\u17dc' + + '\\u17e0-\\u17e9' + + '\\u1810-\\u1819' + + '\\u1820-\\u1877' + + '\\u1880-\\u18aa' + + '\\u18b0-\\u18f5' + + '\\u1900-\\u191c' + + '\\u1920-\\u192b' + + '\\u1930-\\u1938' + + '\\u1946-\\u196d' + + '\\u1970-\\u1974' + + '\\u1980-\\u19ab' + + '\\u19b0-\\u19c9' + + '\\u19d0-\\u19d9' + + '\\u1a00-\\u1a1b' + + '\\u1a20-\\u1a5e' + + '\\u1a61-\\u1a74' + + '\\u1a80-\\u1a89' + + '\\u1a90-\\u1a99' + + '\\u1aa7' + + '\\u1b00-\\u1b33' + + '\\u1b35-\\u1b43' + + '\\u1b45-\\u1b4b' + + '\\u1b50-\\u1b59' + + '\\u1b80-\\u1ba9' + + '\\u1bac-\\u1be5' + + '\\u1be7-\\u1bf1' + + '\\u1c00-\\u1c35' + + '\\u1c40-\\u1c49' + + '\\u1c4d-\\u1c7d' + + '\\u1ce9-\\u1cec' + + '\\u1cee-\\u1cf3' + + '\\u1cf5-\\u1cf6' + + '\\u1d00-\\u1dbf' + + '\\u1e00-\\u1f15' + + '\\u1f18-\\u1f1d' + + '\\u1f20-\\u1f45' + + '\\u1f48-\\u1f4d' + + '\\u1f50-\\u1f57' + + '\\u1f59' + + '\\u1f5b' + + '\\u1f5d' + + '\\u1f5f-\\u1f7d' + + '\\u1f80-\\u1fb4' + + '\\u1fb6-\\u1fbc' + + '\\u1fbe' + + '\\u1fc2-\\u1fc4' + + '\\u1fc6-\\u1fcc' + + '\\u1fd0-\\u1fd3' + + '\\u1fd6-\\u1fdb' + + '\\u1fe0-\\u1fec' + + '\\u1ff2-\\u1ff4' + + '\\u1ff6-\\u1ffc' + + '\\u2071' + + '\\u207f' + + '\\u2090-\\u209c' + + '\\u2102' + + '\\u2107' + + '\\u210a-\\u2113' + + '\\u2115' + + '\\u2119-\\u211d' + + '\\u2124' + + '\\u2126' + + '\\u2128' + + '\\u212a-\\u212d' + + '\\u212f-\\u2139' + + '\\u213c-\\u213f' + + '\\u2145-\\u2149' + + '\\u214e' + + '\\u2160-\\u2188' + + '\\u24b6-\\u24e9' + + '\\u2c00-\\u2c2e' + + '\\u2c30-\\u2c5e' + + '\\u2c60-\\u2ce4' + + '\\u2ceb-\\u2cee' + + '\\u2cf2-\\u2cf3' + + '\\u2d00-\\u2d25' + + '\\u2d27' + + '\\u2d2d' + + '\\u2d30-\\u2d67' + + '\\u2d6f' + + '\\u2d80-\\u2d96' + + '\\u2da0-\\u2da6' + + '\\u2da8-\\u2dae' + + '\\u2db0-\\u2db6' + + '\\u2db8-\\u2dbe' + + '\\u2dc0-\\u2dc6' + + '\\u2dc8-\\u2dce' + + '\\u2dd0-\\u2dd6' + + '\\u2dd8-\\u2dde' + + '\\u2de0-\\u2dff' + + '\\u2e2f' + + '\\u3005-\\u3007' + + '\\u3021-\\u3029' + + '\\u3031-\\u3035' + + '\\u3038-\\u303c' + + '\\u3041-\\u3096' + + '\\u309d-\\u309f' + + '\\u30a1-\\u30fa' + + '\\u30fc-\\u30ff' + + '\\u3105-\\u312d' + + '\\u3131-\\u318e' + + '\\u31a0-\\u31ba' + + '\\u31f0-\\u31ff' + + '\\u3400-\\u4db5' + + '\\u4e00-\\u9fcc' + + '\\ua000-\\ua48c' + + '\\ua4d0-\\ua4fd' + + '\\ua500-\\ua60c' + + '\\ua610-\\ua62b' + + '\\ua640-\\ua66e' + + '\\ua674-\\ua67b' + + '\\ua67f-\\ua697' + + '\\ua69f-\\ua6ef' + + '\\ua717-\\ua71f' + + '\\ua722-\\ua788' + + '\\ua78b-\\ua78e' + + '\\ua790-\\ua793' + + '\\ua7a0-\\ua7aa' + + '\\ua7f8-\\ua801' + + '\\ua803-\\ua805' + + '\\ua807-\\ua80a' + + '\\ua80c-\\ua827' + + '\\ua840-\\ua873' + + '\\ua880-\\ua8c3' + + '\\ua8d0-\\ua8d9' + + '\\ua8f2-\\ua8f7' + + '\\ua8fb' + + '\\ua900-\\ua92a' + + '\\ua930-\\ua952' + + '\\ua960-\\ua97c' + + '\\ua980-\\ua9b2' + + '\\ua9b4-\\ua9bf' + + '\\ua9cf-\\ua9d9' + + '\\uaa00-\\uaa36' + + '\\uaa40-\\uaa4d' + + '\\uaa50-\\uaa59' + + '\\uaa60-\\uaa76' + + '\\uaa7a' + + '\\uaa80-\\uaabe' + + '\\uaac0' + + '\\uaac2' + + '\\uaadb-\\uaadd' + + '\\uaae0-\\uaaef' + + '\\uaaf2-\\uaaf5' + + '\\uab01-\\uab06' + + '\\uab09-\\uab0e' + + '\\uab11-\\uab16' + + '\\uab20-\\uab26' + + '\\uab28-\\uab2e' + + '\\uabc0-\\uabea' + + '\\uabf0-\\uabf9' + + '\\uac00-\\ud7a3' + + '\\ud7b0-\\ud7c6' + + '\\ud7cb-\\ud7fb' + + '\\uf900-\\ufa6d' + + '\\ufa70-\\ufad9' + + '\\ufb00-\\ufb06' + + '\\ufb13-\\ufb17' + + '\\ufb1d-\\ufb28' + + '\\ufb2a-\\ufb36' + + '\\ufb38-\\ufb3c' + + '\\ufb3e' + + '\\ufb40-\\ufb41' + + '\\ufb43-\\ufb44' + + '\\ufb46-\\ufbb1' + + '\\ufbd3-\\ufd3d' + + '\\ufd50-\\ufd8f' + + '\\ufd92-\\ufdc7' + + '\\ufdf0-\\ufdfb' + + '\\ufe70-\\ufe74' + + '\\ufe76-\\ufefc' + + '\\uff10-\\uff19' + + '\\uff21-\\uff3a' + + '\\uff41-\\uff5a' + + '\\uff66-\\uffbe' + + '\\uffc2-\\uffc7' + + '\\uffca-\\uffcf' + + '\\uffd2-\\uffd7' + + '\\uffda-\\uffdc' + + '\\u10000-\\u1000b' + + '\\u1000d-\\u10026' + + '\\u10028-\\u1003a' + + '\\u1003c-\\u1003d' + + '\\u1003f-\\u1004d' + + '\\u10050-\\u1005d' + + '\\u10080-\\u100fa' + + '\\u10140-\\u10174' + + '\\u10280-\\u1029c' + + '\\u102a0-\\u102d0' + + '\\u10300-\\u1031e' + + '\\u10330-\\u1034a' + + '\\u10380-\\u1039d' + + '\\u103a0-\\u103c3' + + '\\u103c8-\\u103cf' + + '\\u103d1-\\u103d5' + + '\\u10400-\\u1049d' + + '\\u104a0-\\u104a9' + + '\\u10800-\\u10805' + + '\\u10808' + + '\\u1080a-\\u10835' + + '\\u10837-\\u10838' + + '\\u1083c' + + '\\u1083f-\\u10855' + + '\\u10900-\\u10915' + + '\\u10920-\\u10939' + + '\\u10980-\\u109b7' + + '\\u109be-\\u109bf' + + '\\u10a00-\\u10a03' + + '\\u10a05-\\u10a06' + + '\\u10a0c-\\u10a13' + + '\\u10a15-\\u10a17' + + '\\u10a19-\\u10a33' + + '\\u10a60-\\u10a7c' + + '\\u10b00-\\u10b35' + + '\\u10b40-\\u10b55' + + '\\u10b60-\\u10b72' + + '\\u10c00-\\u10c48' + + '\\u11000-\\u11045' + + '\\u11066-\\u1106f' + + '\\u11082-\\u110b8' + + '\\u110d0-\\u110e8' + + '\\u110f0-\\u110f9' + + '\\u11100-\\u11132' + + '\\u11136-\\u1113f' + + '\\u11180-\\u111bf' + + '\\u111c1-\\u111c4' + + '\\u111d0-\\u111d9' + + '\\u11680-\\u116b5' + + '\\u116c0-\\u116c9' + + '\\u12000-\\u1236e' + + '\\u12400-\\u12462' + + '\\u13000-\\u1342e' + + '\\u16800-\\u16a38' + + '\\u16f00-\\u16f44' + + '\\u16f50-\\u16f7e' + + '\\u16f93-\\u16f9f' + + '\\u1b000-\\u1b001' + + '\\u1d400-\\u1d454' + + '\\u1d456-\\u1d49c' + + '\\u1d49e-\\u1d49f' + + '\\u1d4a2' + + '\\u1d4a5-\\u1d4a6' + + '\\u1d4a9-\\u1d4ac' + + '\\u1d4ae-\\u1d4b9' + + '\\u1d4bb' + + '\\u1d4bd-\\u1d4c3' + + '\\u1d4c5-\\u1d505' + + '\\u1d507-\\u1d50a' + + '\\u1d50d-\\u1d514' + + '\\u1d516-\\u1d51c' + + '\\u1d51e-\\u1d539' + + '\\u1d53b-\\u1d53e' + + '\\u1d540-\\u1d544' + + '\\u1d546' + + '\\u1d54a-\\u1d550' + + '\\u1d552-\\u1d6a5' + + '\\u1d6a8-\\u1d6c0' + + '\\u1d6c2-\\u1d6da' + + '\\u1d6dc-\\u1d6fa' + + '\\u1d6fc-\\u1d714' + + '\\u1d716-\\u1d734' + + '\\u1d736-\\u1d74e' + + '\\u1d750-\\u1d76e' + + '\\u1d770-\\u1d788' + + '\\u1d78a-\\u1d7a8' + + '\\u1d7aa-\\u1d7c2' + + '\\u1d7c4-\\u1d7cb' + + '\\u1d7ce-\\u1d7ff' + + '\\u1ee00-\\u1ee03' + + '\\u1ee05-\\u1ee1f' + + '\\u1ee21-\\u1ee22' + + '\\u1ee24' + + '\\u1ee27' + + '\\u1ee29-\\u1ee32' + + '\\u1ee34-\\u1ee37' + + '\\u1ee39' + + '\\u1ee3b' + + '\\u1ee42' + + '\\u1ee47' + + '\\u1ee49' + + '\\u1ee4b' + + '\\u1ee4d-\\u1ee4f' + + '\\u1ee51-\\u1ee52' + + '\\u1ee54' + + '\\u1ee57' + + '\\u1ee59' + + '\\u1ee5b' + + '\\u1ee5d' + + '\\u1ee5f' + + '\\u1ee61-\\u1ee62' + + '\\u1ee64' + + '\\u1ee67-\\u1ee6a' + + '\\u1ee6c-\\u1ee72' + + '\\u1ee74-\\u1ee77' + + '\\u1ee79-\\u1ee7c' + + '\\u1ee7e' + + '\\u1ee80-\\u1ee89' + + '\\u1ee8b-\\u1ee9b' + + '\\u1eea1-\\u1eea3' + + '\\u1eea5-\\u1eea9' + + '\\u1eeab-\\u1eebb' + + '\\u20000-\\u2a6d6' + + '\\u2a700-\\u2b734' + + '\\u2b740-\\u2b81d' + + '\\u2f800-\\u2fa1d' +}; +// @license-end diff --git a/spec/javascripts/app/helpers/text_formatter_spec.js b/spec/javascripts/app/helpers/text_formatter_spec.js index 14d8a149c8d55485ae61f626b94b692f3f13aff8..9de8e584f749c416b67d833b32898e2e3c14612c 100644 --- a/spec/javascripts/app/helpers/text_formatter_spec.js +++ b/spec/javascripts/app/helpers/text_formatter_spec.js @@ -5,26 +5,80 @@ describe("app.helpers.textFormatter", function(){ this.formatter = app.helpers.textFormatter; }) - describe("main", function(){ - it("calls mentionify, hashtagify, and markdownify", function(){ - spyOn(app.helpers.textFormatter, "mentionify") - spyOn(app.helpers.textFormatter, "hashtagify") - spyOn(app.helpers.textFormatter, "markdownify") - - app.helpers.textFormatter(this.statusMessage.get("text"), this.statusMessage) - expect(app.helpers.textFormatter.mentionify).toHaveBeenCalled() - expect(app.helpers.textFormatter.hashtagify).toHaveBeenCalled() - expect(app.helpers.textFormatter.markdownify).toHaveBeenCalled() + // Some basic specs. For more detailed specs see + // https://github.com/svbergerem/markdown-it-hashtag/tree/master/test + context("hashtags", function() { + beforeEach(function() { + this.tags = [ + "tag", + "diaspora", + "PARTIES", + "<3" + ]; + }); + + it("renders tags as links", function() { + var formattedText = this.formatter('#'+this.tags.join(" #")); + _.each(this.tags, function(tag) { + var link ='<a class="tag" href="/tags/'+tag.toLowerCase()+'">#'+tag+'</a>'; + expect(formattedText).toContain(link); + }); + }); + }); + + // Some basic specs. For more detailed specs see + // https://github.com/diaspora/markdown-it-diaspora-mention/tree/master/test + context("mentions", function() { + beforeEach(function(){ + this.alice = factory.author({ + name : "Alice Smith", + diaspora_id : "alice@example.com", + id : "555" + }) + + this.bob = factory.author({ + name : "Bob Grimm", + diaspora_id : "bob@example.com", + id : "666" + }) + + this.statusMessage.set({text: "hey there @{Alice Smith; alice@example.com} and @{Bob Grimm; bob@example.com}"}) + this.statusMessage.set({mentioned_people : [this.alice, this.bob]}) }) - // A couple of complex (intergration) test cases here would be rad. - }) + it("matches mentions", function(){ + var formattedText = this.formatter(this.statusMessage.get("text"), this.statusMessage.get("mentioned_people")) + var wrapper = $("<div>").html(formattedText); + + _.each([this.alice, this.bob], function(person) { + expect(wrapper.find("a[href='/people/" + person.guid + "']").text()).toContain(person.name) + }) + }); + + it("returns mentions for on posts that haven't been saved yet (framer posts)", function(){ + var freshBob = factory.author({ + name : "Bob Grimm", + handle : "bob@example.com", + url : 'googlebot.com', + id : "666" + }) - describe(".markdownify", function(){ - // NOTE: for some strange reason, links separated by just a whitespace character - // will not be autolinked; thus we join our URLS here with (" and "). - // This test will fail if our join is just (" ") -- an edge case that should be addressed. + this.statusMessage.set({'mentioned_people' : [freshBob] }) + var formattedText = this.formatter(this.statusMessage.get("text"), this.statusMessage.get("mentioned_people")) + var wrapper = $("<div>").html(formattedText); + expect(wrapper.find("a[href='googlebot.com']").text()).toContain(freshBob.name) + }) + + it('returns the name of the mention if the mention does not exist in the array', function(){ + var text = "hey there @{Chris Smith; chris@example.com}" + var formattedText = this.formatter(text, []) + expect(formattedText.match(/\<a/)).toBeNull(); + expect(formattedText).toContain('Chris Smith'); + }); + }); + + context("markdown", function(){ it("autolinks", function(){ var links = [ "http://google.com", @@ -37,11 +91,7 @@ describe("app.helpers.textFormatter", function(){ "www.google.com" ]; - // The join that would make this particular test fail: - // - // var formattedText = this.formatter.markdownify(links.join(" ")) - - var formattedText = this.formatter.markdownify(links.join(" and ")); + var formattedText = this.formatter(links.join(" ")); var wrapper = $("<div>").html(formattedText); _.each(links, function(link) { @@ -67,13 +117,13 @@ describe("app.helpers.textFormatter", function(){ it("correctly converts the input strings to their corresponding output symbol", function() { _.each(this.input_strings, function(str, idx) { - var text = this.formatter.markdownify(str); + var text = this.formatter(str); expect(text).toContain(this.output_symbols[idx]); }, this); }); it("converts all symbols at once", function() { - var text = this.formatter.markdownify(this.input_strings.join(" ")); + var text = this.formatter(this.input_strings.join(" ")); _.each(this.output_symbols, function(sym) { expect(text).toContain(sym); }); @@ -97,21 +147,21 @@ describe("app.helpers.textFormatter", function(){ "http://xn--4gbrim.xn----ymcbaaajlc6dj7bxne2c.xn--wgbh1c/", "http:///scholar.google.com/citations?view_op=top_venues", "http://lyricstranslate.com/en/someone-you-%E0%B4%A8%E0%B4%BF%E0%B4%A8%E0%B5%8D%E0%B4%A8%E0%B5%86-%E0%B4%AA%E0%B5%8B%E0%B4%B2%E0%B5%8A%E0%B4%B0%E0%B4%BE%E0%B4%B3%E0%B5%8D%E2%80%8D.html", - "http://de.wikipedia.org/wiki/Liste_der_Abk%C3%BCrzungen_%28Netzjargon%29", + "http://de.wikipedia.org/wiki/Liste_der_Abk%C3%BCrzungen_(Netzjargon)", "http://wiki.com/?query=Kr%E4fte", ]; }); it("correctly encodes to punycode", function() { _.each(this.evilUrls, function(url, num) { - var text = this.formatter.markdownify( "<" + url + ">" ); + var text = this.formatter( "<" + url + ">" ); expect(text).toContain(this.asciiUrls[num]); }, this); }); it("doesn't break link texts", function() { var linkText = "check out this awesome link!"; - var text = this.formatter.markdownify( "["+linkText+"]("+this.evilUrls[0]+")" ); + var text = this.formatter( "["+linkText+"]("+this.evilUrls[0]+")" ); expect(text).toContain(this.asciiUrls[0]); expect(text).toContain(linkText); @@ -119,30 +169,29 @@ describe("app.helpers.textFormatter", function(){ it("doesn't break reference style links", function() { var postContent = "blabla blab [my special link][1] bla blabla\n\n[1]: "+this.evilUrls[0]+" and an optional title)"; - var text = this.formatter.markdownify(postContent); + var text = this.formatter(postContent); - expect(text).not.toContain(this.evilUrls[0]); + expect(text).not.toContain('"'+this.evilUrls[0]+'"'); expect(text).toContain(this.asciiUrls[0]); }); it("can be used as img src", function() { var postContent = ""; var niceImg = 'src="'+ this.asciiUrls[1] +'"'; // the "" are from src="" - var text = this.formatter.markdownify(postContent); + var text = this.formatter(postContent); expect(text).toContain(niceImg); }); it("doesn't break linked images", function() { var postContent = "I am linking an image here []("+this.evilUrls[3]+")"; - var text = this.formatter.markdownify(postContent); + var text = this.formatter(postContent); var linked_image = 'src="'+this.asciiUrls[1]+'"'; var image_link = 'href="'+this.asciiUrls[3]+'"'; expect(text).toContain(linked_image); expect(text).toContain(image_link); }); - }); context("misc breakage and/or other issues with weird urls", function(){ @@ -151,10 +200,10 @@ describe("app.helpers.textFormatter", function(){ var text_part = 'Revert "rails admin is conflicting with client side validations: see https://github.com/sferik/rails_admin/issues/985"'; var link_part = 'https://github.com/diaspora/diaspora/commit/61f40fc6bfe6bb859c995023b5a17d22c9b5e6e5'; var content = '['+text_part+']('+link_part+')'; - var parsed = this.formatter.markdownify(content); + var parsed = this.formatter(content); var link = 'href="' + link_part + '"'; - var text = '>'+ text_part +'<'; + var text = '>Revert “rails admin is conflicting with client side validations: see https://github.com/sferik/rails_admin/issues/985â€<'; expect(parsed).toContain(link); expect(parsed).toContain(text); @@ -167,149 +216,16 @@ describe("app.helpers.textFormatter", function(){ }); it("doesn't get double-encoded", function(){ - var parsed = this.formatter.markdownify(this.input); + var parsed = this.formatter(this.input); expect(parsed).toContain(this.correctHref); }); it("gets correctly decoded, even when multiply encoded", function() { var uglyUrl = encodeURI(encodeURI(encodeURI(this.input))); - var parsed = this.formatter.markdownify(uglyUrl); + var parsed = this.formatter(uglyUrl); expect(parsed).toContain(this.correctHref); }); }); - - it("tests a bunch of benchmark urls", function(){ - var self = this; - $.ajax({ - async: false, - cache: false, - url: '/spec/fixtures/good_urls.txt', - success: function(data) { self.url_list = data.split("\n"); } - }); - - _.each(this.url_list, function(url) { - // 'comments' - if( url.match(/^#/) ) return; - - // regex.test is stupid, use match and boolean-ify it - var result = !!url.match(Diaspora.url_regex); - expect(result).toBeTruthy(); - if( !result && console && console.log ) { - console.log(url); - } - }); - }); - - // TODO: try to match the 'bad_urls.txt' and have as few matches as possible }); - - }) - - describe(".hashtagify", function(){ - context("changes hashtags to links", function(){ - it("creates links to hashtags", function(){ - var formattedText = this.formatter.hashtagify("I love #parties and #rockstars and #unicorns") - var wrapper = $("<div>").html(formattedText); - - _.each(["parties", "rockstars", "unicorns"], function(tagName){ - expect(wrapper.find("a[href='/tags/" + tagName + "']").text()).toContain(tagName) - }) - }) - - it("requires hashtags to be preceeded with a space", function(){ - var formattedText = this.formatter.hashtagify("I love the#parties") - expect(formattedText).not.toContain('/tags/parties') - }) - - // NOTE THIS DIVERGES FROM GRUBER'S ORIGINAL DIALECT OF MARKDOWN. - // We had to edit Markdown.Converter.js line 747 - // - // text = text.replace(/^(\#{1,6})[ \t]+(.+?)[ \t]*\#*\n+/gm, - // [ \t]* changed to [ \t]+ - // - it("doesn't create a header tag if the first word is a hashtag", function(){ - var formattedText = this.formatter.hashtagify("#parties, I love") - var wrapper = $("<div>").html(formattedText); - - expect(wrapper.find("h1").length).toBe(0) - expect(wrapper.find("a[href='/tags/parties']").text()).toContain("#parties") - }) - - it("and the resultant link has the tags name downcased", function(){ - var formattedText = this.formatter.hashtagify("#PARTIES, I love") - - expect(formattedText).toContain("/tags/parties") - }) - - it("doesn't create tag if the text is a link", function(){ - var tags = ['diaspora', 'twitter', 'hrabrahabr']; - - var text = $('<a/>', { href: 'http://me.co' }).html('#me')[0].outerHTML; - _.each(tags, function(tagName){ - text += ' #'+tagName+','; - }); - text += 'I love'; - - var formattedText = this.formatter.hashtagify(text); - var wrapper = $('<div>').html(formattedText); - - expect(wrapper.find("a[href='http://me.co']").text()).toContain('#me'); - _.each(tags, function(tagName){ - expect(wrapper.find("a[href='/tags/"+tagName+"']").text()).toContain('#'+tagName); - }); - - }) - }) - }) - - describe(".mentionify", function(){ - context("changes mention markup to links", function(){ - beforeEach(function(){ - this.alice = factory.author({ - name : "Alice Smith", - diaspora_id : "alice@example.com", - id : "555" - }) - - this.bob = factory.author({ - name : "Bob Grimm", - diaspora_id : "bob@example.com", - id : "666" - }) - - this.statusMessage.set({text: "hey there @{Alice Smith; alice@example.com} and @{Bob Grimm; bob@example.com}"}) - this.statusMessage.set({mentioned_people : [this.alice, this.bob]}) - }) - - it("matches mentions", function(){ - var formattedText = this.formatter.mentionify(this.statusMessage.get("text"), this.statusMessage.get("mentioned_people")) - var wrapper = $("<div>").html(formattedText); - - _.each([this.alice, this.bob], function(person) { - expect(wrapper.find("a[href='/people/" + person.guid + "']").text()).toContain(person.name) - }) - }); - - it("returns mentions for on posts that haven't been saved yet (framer posts)", function(){ - var freshBob = factory.author({ - name : "Bob Grimm", - handle : "bob@example.com", - url : 'googlebot.com', - id : "666" - }) - - this.statusMessage.set({'mentioned_people' : [freshBob] }) - - var formattedText = this.formatter.mentionify(this.statusMessage.get("text"), this.statusMessage.get("mentioned_people")) - var wrapper = $("<div>").html(formattedText); - expect(wrapper.find("a[href='googlebot.com']").text()).toContain(freshBob.name) - }) - - it('returns the name of the mention if the mention does not exist in the array', function(){ - var text = "hey there @{Chris Smith; chris@example.com}" - var formattedText = this.formatter.mentionify(text, []) - expect(formattedText.match(/\<a/)).toBeNull(); - }); - }) - }) + }); }) diff --git a/vendor/assets/javascripts/markdown.js b/vendor/assets/javascripts/markdown.js deleted file mode 100644 index 88dd96256e996b2665545daf64c85b95722ca406..0000000000000000000000000000000000000000 --- a/vendor/assets/javascripts/markdown.js +++ /dev/null @@ -1 +0,0 @@ -//= require_tree ./markdown \ No newline at end of file diff --git a/vendor/assets/javascripts/markdown/Markdown.Converter.js b/vendor/assets/javascripts/markdown/Markdown.Converter.js deleted file mode 100644 index 34a624b3a590c04cca3dd539b9d3db9623bae70b..0000000000000000000000000000000000000000 --- a/vendor/assets/javascripts/markdown/Markdown.Converter.js +++ /dev/null @@ -1,1332 +0,0 @@ -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 <a> - // and <img> 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]* - <?(\S+?)>? // 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+?)>?(?=\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 <p>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.: - // <div> - // <div> - // tags for inner block must be indented. - // </div> - // </div> - // - // 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 `<div>` and stop at the first `</div>`. - - // 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 - </\2> // 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<tag>` to `</tag>\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 - .*</\2> // 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 <hr />. 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 = "<hr />\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 - // <p> 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 `<http://example.com/>` - // Must come after _DoAnchors(), because you can use < and > - // delimiters in inline links like [this](<url>). - 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, " <br>\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 <a> 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 - <?( // href = $4 - (?: - \([^)]*\) // allow one level of (correctly nested) parens (think MSDN) - | - [^()] - )*? - )>? - [ \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 = "<a href=\"" + url + "\""; - - if (title != "") { - title = attributeEncode(title); - title = escapeCharacters(title, "*_"); - result += " title=\"" + title + "\""; - } - - result += ">" + link_text + "</a>"; - - return result; - } - - function _DoImages(text) { - // - // Turn Markdown image shortcuts into <img> 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:  - // 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 - <?(\S+?)>? // src url = $4 - [ \t]* - ( // $5 - (['"]) // quote char = $6 - (.*?) // title = $7 - \6 // matching quote - [ \t]* - )? // title is optional - \) - ) - /g, writeImageTag); - */ - text = text.replace(/(!\[(.*?)\]\s?\([ \t]*()<?(\S+?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g, writeImageTag); - - return text; - } - - function attributeEncode(text) { - // unconditionally replace angle brackets here -- what ends up in an attribute (e.g. alt or title) - // never makes sense to have verbatim HTML in it (and the sanitizer would totally break it) - return text.replace(/>/g, ">").replace(/</g, "<").replace(/"/g, """); - } - - 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 = escapeCharacters(attributeEncode(alt_text), "*_[]()"); - url = escapeCharacters(url, "*_"); - var result = "<img src=\"" + url + "\" alt=\"" + alt_text + "\""; - - // attacklab: Markdown.pl adds empty title attributes to images. - // Replicate this bug. - - //if (title != "") { - title = attributeEncode(title); - title = escapeCharacters(title, "*_"); - result += " title=\"" + title + "\""; - //} - - result += " />"; - - return result; - } - - function _DoHeaders(text) { - - // Setext-style headers: - // Header 1 - // ======== - // - // Header 2 - // -------- - // - text = text.replace(/^(.+)[ \t]*\n=+[ \t]*\n+/gm, - function (wholeMatch, m1) { return "<h1>" + _RunSpanGamut(m1) + "</h1>\n\n"; } - ); - - text = text.replace(/^(.+)[ \t]*\n-+[ \t]*\n+/gm, - function (matchFound, m1) { return "<h2>" + _RunSpanGamut(m1) + "</h2>\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 "<h" + h_level + ">" + _RunSpanGamut(m2) + "</h" + h_level + ">\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 `</$list_type>` - // 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 + "</" + list_type + ">\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 + "</" + list_type + ">\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 <li>, 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 "<li>" + item + "</li>\n"; - } - ); - - // attacklab: strip sentinel - list_str = list_str.replace(/~0/g, ""); - - g_list_level--; - return list_str; - } - - function _DoCodeBlocks(text) { - // - // Process Markdown `<pre><code>` 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 = "<pre><code>" + codeblock + "\n</code></pre>"; - - 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 <code></code> 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: - // - // <p>Just type <code>foo `bar` baz</code> at the prompt.</p> - // - // 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 <code>`bar`</code> ... - // - - /* - 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); - c = c.replace(/:\/\//g, "~P"); // to prevent auto-linking. Not necessary in code *blocks*, but in code spans. Will be converted back after the auto-linker runs. - return m1 + "<code>" + c + "</code>"; - } - ); - - 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, "<"); - 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) { - - // <strong> must go first: - text = text.replace(/([\W_]|^)(\*\*|__)(?=\S)([^\r]*?\S[\*_]*)\2([\W_]|$)/g, - "$1<strong>$3</strong>$4"); - - text = text.replace(/([\W_]|^)(\*|_)(?=\S)([^\r\*_]*?\S)\2([\W_]|$)/g, - "$1<em>$3</em>$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 <pre> content, so we need to fix that: - bq = bq.replace( - /(\s*<pre>[^\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("<blockquote>\n" + bq + "\n</blockquote>"); - } - ); - return text; - } - - function _FormParagraphs(text, doNotUnhash) { - // - // Params: - // $text - string to process with html <p> 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 <p> 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, "<p>"); - str += "</p>" - 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 <a href=""></a> - // *except* for the <http://www.foo.com> 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 <http://example.com> - - var replacer = function (wholematch, m1) { return "<a href=\"" + m1 + "\">" + pluginHooks.plainLinkText(m1) + "</a>"; } - text = text.replace(/<((https?|ftp):[^'">\s]+)>/gi, replacer); - - // Email addresses: <address@domain.foo> - /* - 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/vendor/assets/javascripts/markdown/Markdown.Sanitizer.js b/vendor/assets/javascripts/markdown/Markdown.Sanitizer.js deleted file mode 100644 index cc5826fa8fafa166d4bc7d39f7485ef4cca2afba..0000000000000000000000000000000000000000 --- a/vendor/assets/javascripts/markdown/Markdown.Sanitizer.js +++ /dev/null @@ -1,108 +0,0 @@ -(function () { - var output, Converter; - if (typeof exports === "object" && typeof require === "function") { // we're in a CommonJS (e.g. Node.js) module - output = exports; - Converter = require("./Markdown.Converter").Converter; - } else { - output = window.Markdown; - Converter = output.Converter; - } - - output.getSanitizingConverter = function () { - var converter = new Converter(); - converter.hooks.chain("postConversion", sanitizeHtml); - converter.hooks.chain("postConversion", balanceTags); - return converter; - } - - function sanitizeHtml(html) { - return html.replace(/<[^>]*>?/gi, sanitizeTag); - } - - // (tags that can be opened/closed) | (tags that stand alone) - var basic_tag_whitelist = /^(<\/?(b|blockquote|code|del|dd|dl|dt|em|h1|h2|h3|i|kbd|li|ol|p|pre|s|sup|sub|strong|strike|ul)>|<(br|hr)\s?\/?>)$/i; - // <a href="url..." optional title>|</a> - var a_white = /^(<a\shref="((https?|ftp):\/\/|\/)[-A-Za-z0-9+&@#\/%?=~_|!:,.;\(\)]+"(\stitle="[^"<>]+")?\s?>|<\/a>)$/i; - - // <img src="url..." optional width optional height optional alt optional title - var img_white = /^(<img\ssrc="(https?:\/\/|\/)[-A-Za-z0-9+&@#\/%?=~_|!:,.;\(\)]+"(\swidth="\d{1,3}")?(\sheight="\d{1,3}")?(\salt="[^"<>]*")?(\stitle="[^"<>]*")?\s?\/?>)$/i; - - function sanitizeTag(tag) { - if (tag.match(basic_tag_whitelist) || tag.match(a_white) || tag.match(img_white)) - return tag; - else - return ""; - } - - /// <summary> - /// attempt to balance HTML tags in the html string - /// by removing any unmatched opening or closing tags - /// IMPORTANT: we *assume* HTML has *already* been - /// sanitized and is safe/sane before balancing! - /// - /// adapted from CODESNIPPET: A8591DBA-D1D3-11DE-947C-BA5556D89593 - /// </summary> - function balanceTags(html) { - - if (html == "") - return ""; - - var re = /<\/?\w+[^>]*(\s|$|>)/g; - // convert everything to lower case; this makes - // our case insensitive comparisons easier - var tags = html.toLowerCase().match(re); - - // no HTML tags present? nothing to do; exit now - var tagcount = (tags || []).length; - if (tagcount == 0) - return html; - - var tagname, tag; - var ignoredtags = "<p><img><br><li><hr>"; - var match; - var tagpaired = []; - var tagremove = []; - var needsRemoval = false; - - // loop through matched tags in forward order - for (var ctag = 0; ctag < tagcount; ctag++) { - tagname = tags[ctag].replace(/<\/?(\w+).*/, "$1"); - // skip any already paired tags - // and skip tags in our ignore list; assume they're self-closed - if (tagpaired[ctag] || ignoredtags.search("<" + tagname + ">") > -1) - continue; - - tag = tags[ctag]; - match = -1; - - if (!/^<\//.test(tag)) { - // this is an opening tag - // search forwards (next tags), look for closing tags - for (var ntag = ctag + 1; ntag < tagcount; ntag++) { - if (!tagpaired[ntag] && tags[ntag] == "</" + tagname + ">") { - match = ntag; - break; - } - } - } - - if (match == -1) - needsRemoval = tagremove[ctag] = true; // mark for removal - else - tagpaired[match] = true; // mark paired - } - - if (!needsRemoval) - return html; - - // delete all orphaned tags from the string - - var ctag = 0; - html = html.replace(re, function (match) { - var res = tagremove[ctag] ? "" : match; - ctag++; - return res; - }); - return html; - } -})();