From ab41a0ccf66c4eeea0cd3aeba7b1994cc06a52b0 Mon Sep 17 00:00:00 2001 From: Pawel Kozlowski Date: Sun, 3 Mar 2013 22:04:09 +0100 Subject: [PATCH 01/54] chore(release): starting 0.3.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index afff784a4c..0e6f1d88c7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "author": "https://github.com/angular-ui/bootstrap/graphs/contributors", "name": "angular-ui-bootstrap", - "version": "0.2.0", + "version": "0.3.0-SNAPSHOT", "dependencies": {}, "devDependencies": { "node-markdown": "*", From 4d3fc3b71c910b0cae8bfbe8acf8e46ba570595f Mon Sep 17 00:00:00 2001 From: Pawel Kozlowski Date: Mon, 4 Mar 2013 19:14:39 +0100 Subject: [PATCH 02/54] chore(changelog): add changelog --- CHANGELOG.md | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000000..55e48eeb40 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,74 @@ +# 0.2.0 (2013-03-03) + +## Features + +### dialog + +* Make $dialog 'resolve' property to work the same way of $routeProvider.when (739f86f) + +### modal + +* allow global override of modal options (acaf72b) + +### buttons + +* add checkbox and radio buttons (571ccf4) + +### carousel + +* add slide indicators (3b677ee) + +### typeahead + +* add typeahead directive (6a97da2) + +### accordion + +* enable HTML in accordion headings (3afcaa4) + +### pagination + +* add first/last link & constant congif options (0ff0454) + +## Bug fixes + +### dialog + +* update resolve section to new syntax (1f87486) +* $compile entire modal (7575b3c) + +### tooltip + +* don't show tooltips if there is no content to show (030901e) +* fix placement issues (a2bbf4d) + +### collapse + +* Avoids fixed height on collapse (ff5d119) + +### accordion + +* fix minification issues (f4da4d6) + +### typeahead + +* update inputs value on mapping where label is not derived from the model (a5f64de) + +# 0.1.0 (2013-02-02) + +_Very first, initial release_. + +## Features + +Version `0.1.0` was released with the following directives: + +* accordion +* alert +* carousel +* dialog +* dropdownToggle +* modal +* pagination +* popover +* tabs +* tooltip \ No newline at end of file From 0c24ad92df6595ad50925d30a8efb72c36c08ea0 Mon Sep 17 00:00:00 2001 From: Pawel Kozlowski Date: Sat, 9 Mar 2013 13:13:41 +0100 Subject: [PATCH 03/54] demo(buttons): remove superfluous data-toggle attribute Closes #200 --- src/buttons/docs/demo.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/buttons/docs/demo.html b/src/buttons/docs/demo.html index b810b685b4..bd3d455e0c 100644 --- a/src/buttons/docs/demo.html +++ b/src/buttons/docs/demo.html @@ -6,14 +6,14 @@

Single toggle

Checkbox

{{checkModel}}
-
+

Radio

{{radioModel}}
-
+
From 98b5f8e053c6ffe4ad0b379b666c75d5fb447d14 Mon Sep 17 00:00:00 2001 From: Pawel Kozlowski Date: Sat, 9 Mar 2013 23:49:26 +0100 Subject: [PATCH 04/54] chore(build): switch build to grunt 0.4 --- grunt.js => Gruntfile.js | 66 +- README.md | 2 +- node_modules/node-markdown/LICENSE | 38 - node_modules/node-markdown/README.md | 57 - node_modules/node-markdown/examples/test.js | 12 - node_modules/node-markdown/lib/markdown.js | 125 -- .../vendor/showdown/compressed/showdown.js | 419 ----- .../vendor/showdown/example/showdown-gui.html | 720 -------- .../vendor/showdown/example/showdown-gui.js | 349 ---- .../lib/vendor/showdown/example/showdown.js | 1296 ------------- .../lib/vendor/showdown/license.txt | 34 - .../perlMarkdown/Markdown License.txt | 30 - .../showdown/perlMarkdown/Markdown-1.0.2b2.pl | 1509 --------------- .../showdown/perlMarkdown/Markdown-1.0.2b7.pl | 1642 ----------------- .../vendor/showdown/perlMarkdown/readme.txt | 13 - .../lib/vendor/showdown/readme.txt | 156 -- .../lib/vendor/showdown/src/showdown.js | 1299 ------------- node_modules/node-markdown/package.json | 34 - package.json | 7 +- 19 files changed, 43 insertions(+), 7765 deletions(-) rename grunt.js => Gruntfile.js (85%) delete mode 100644 node_modules/node-markdown/LICENSE delete mode 100644 node_modules/node-markdown/README.md delete mode 100644 node_modules/node-markdown/examples/test.js delete mode 100644 node_modules/node-markdown/lib/markdown.js delete mode 100644 node_modules/node-markdown/lib/vendor/showdown/compressed/showdown.js delete mode 100644 node_modules/node-markdown/lib/vendor/showdown/example/showdown-gui.html delete mode 100644 node_modules/node-markdown/lib/vendor/showdown/example/showdown-gui.js delete mode 100644 node_modules/node-markdown/lib/vendor/showdown/example/showdown.js delete mode 100644 node_modules/node-markdown/lib/vendor/showdown/license.txt delete mode 100644 node_modules/node-markdown/lib/vendor/showdown/perlMarkdown/Markdown License.txt delete mode 100644 node_modules/node-markdown/lib/vendor/showdown/perlMarkdown/Markdown-1.0.2b2.pl delete mode 100644 node_modules/node-markdown/lib/vendor/showdown/perlMarkdown/Markdown-1.0.2b7.pl delete mode 100644 node_modules/node-markdown/lib/vendor/showdown/perlMarkdown/readme.txt delete mode 100644 node_modules/node-markdown/lib/vendor/showdown/readme.txt delete mode 100644 node_modules/node-markdown/lib/vendor/showdown/src/showdown.js delete mode 100644 node_modules/node-markdown/package.json diff --git a/grunt.js b/Gruntfile.js similarity index 85% rename from grunt.js rename to Gruntfile.js index 0e10acdebc..06acbce84e 100644 --- a/grunt.js +++ b/Gruntfile.js @@ -1,15 +1,18 @@ -var fs = require('fs'); var markdown = require('node-markdown').Markdown; module.exports = function(grunt) { + grunt.loadNpmTasks('grunt-contrib-concat'); + grunt.loadNpmTasks('grunt-contrib-jshint'); + grunt.loadNpmTasks('grunt-contrib-uglify'); + // Project configuration. grunt.initConfig({ ngversion: '1.0.5', bsversion: '2.3.1', srcModules: [], //to be filled in by find-modules task tplModules: [], - pkg:'', + pkg: grunt.file.readJSON('package.json'), dist: 'dist', filename: 'ui-bootstrap', meta: { @@ -17,24 +20,27 @@ module.exports = function(grunt) { tplmodules: 'angular.module("ui.bootstrap.tpls", [<%= tplModules %>]);', all: 'angular.module("ui.bootstrap", ["ui.bootstrap.tpls", <%= srcModules %>]);' }, - lint: { - files: ['grunt.js','src/**/*.js'] - }, watch: { - files: ['', 'template/**/*.html'], + files: ['<%= jshint.files %>', 'template/**/*.html'], tasks: 'before-test test-run' }, concat: { dist: { - src: [''], + options: { + banner: '<%= meta.modules %>\n' + }, + src: [], dest: '<%= dist %>/<%= filename %>-<%= pkg.version %>.js' }, dist_tpls: { - src: ['', ''], + options: { + banner: '<%= meta.all %>\n<%= meta.tplmodules %>\n' + }, + src: [], dest: '<%= dist %>/<%= filename %>-tpls-<%= pkg.version %>.js' } }, - min: { + uglify: { dist:{ src:['<%= dist %>/<%= filename %>-<%= pkg.version %>.js'], dest:'<%= dist %>/<%= filename %>-<%= pkg.version %>.min.js' @@ -48,6 +54,7 @@ module.exports = function(grunt) { src: ['template/**/*.html'] }, jshint: { + files: ['Gruntfile.js','src/**/*.js'], options: { curly: true, immed: true, @@ -55,18 +62,20 @@ module.exports = function(grunt) { noarg: true, sub: true, boss: true, - eqnull: true - }, - globals: {} + eqnull: true, + globals: { + angular: true + } + } } }); //register before and after test tasks so we've don't have to change cli options on the goole's CI server - grunt.registerTask('before-test', 'lint html2js'); - grunt.registerTask('after-test', 'build site'); + grunt.registerTask('before-test', ['jshint', 'html2js']); + grunt.registerTask('after-test', ['build', 'site']); // Default task. - grunt.registerTask('default', 'before-test test after-test'); + grunt.registerTask('default', ['before-test', 'test', 'after-test']); //Common ui.bootstrap module containing all modules for src and templates //findModule: Adds a given module to config @@ -140,18 +149,17 @@ module.exports = function(grunt) { srcFiles = ['src/*/*.js']; tplFiles = ['template/*/*.html.js']; - grunt.file.expandDirs('src/*').forEach(function(dir) { + grunt.file.expand({filter: 'isDirectory'}, 'src/*').forEach(function(dir) { findModule(dir.split('/')[1]); }); } grunt.config('concat.dist.src', grunt.config('concat.dist.src').concat(srcFiles)); grunt.config('concat.dist_tpls.src', grunt.config('concat.dist_tpls.src').concat(srcFiles).concat(tplFiles)); - grunt.task.run('concat min'); + grunt.task.run(['concat', 'uglify']); }); grunt.registerTask('site', 'Create grunt demo site from every module\'s files', function() { - this.requires('concat html2js'); function breakup(text, separator) { return text.replace(/[A-Z]/g, function (match) { @@ -165,15 +173,15 @@ module.exports = function(grunt) { }); } - var modules = grunt.file.expandDirs('src/*').map(function(dir) { + var modules = grunt.file.expand({filter: 'isDirectory'}, 'src/*').map(function(dir) { var moduleName = dir.split("/")[1]; - if (fs.existsSync(dir + "docs")) { + if (grunt.file.isDir(dir + "/docs")) { return { name: moduleName, displayName: ucwords(breakup(moduleName, ' ')), - js: grunt.file.expand(dir + "docs/*.js").map(grunt.file.read).join(''), - html: grunt.file.expand(dir + "docs/*.html").map(grunt.file.read).join(''), - description: grunt.file.expand(dir + "docs/*.md").map(grunt.file.read).map(markdown).join('') + js: grunt.file.expand(dir + "/docs/*.js").map(grunt.file.read).join(''), + html: grunt.file.expand(dir + "/docs/*.html").map(grunt.file.read).join(''), + description: grunt.file.expand(dir + "/docs/*.md").map(grunt.file.read).map(markdown).join('') }; } }).filter(function(module){ @@ -181,10 +189,10 @@ module.exports = function(grunt) { }); var templateFiles = grunt.file.expand("template/**/*.html.js"); - + grunt.file.write( 'dist/index.html', - grunt.template.process(grunt.file.read('misc/demo-template.html'), { + grunt.template.process(grunt.file.read('misc/demo-template.html'), {data: { modules: modules, templateModules: templateFiles.map(function(fileName) { return "'"+fileName.substr(0, fileName.length - 3)+"'"; @@ -193,7 +201,7 @@ module.exports = function(grunt) { version : grunt.config('pkg.version'), ngversion: grunt.config('ngversion'), bsversion: grunt.config('bsversion') - }) + }}) ); grunt.file.expand('misc/demo-assets/*.*').forEach(function(path) { @@ -213,10 +221,10 @@ module.exports = function(grunt) { return content.replace(/"/g, '\\"').replace(/\n/g, '" +\n "').replace(/\r/g, ''); } function html2js(template) { - grunt.file.write(template + ".js", grunt.template.process(TPL, { + grunt.file.write(template + ".js", grunt.template.process(TPL, {data: { file: template, content: escapeContent(grunt.file.read(template)) - })); + }})); } grunt.registerMultiTask('html2js', 'Generate js versions of html template', function() { var files = grunt._watch_changed_files || grunt.file.expand(this.data); @@ -228,7 +236,7 @@ module.exports = function(grunt) { var testacularCmd = process.platform === 'win32' ? 'testacular.cmd' : 'testacular'; var args = [command].concat(options); var done = grunt.task.current.async(); - var child = grunt.utils.spawn({ + var child = grunt.util.spawn({ cmd: testacularCmd, args: args }, function(err, result, code) { diff --git a/README.md b/README.md index 30eb331c13..7b8e35e6b0 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ We are always looking for the quality contributions! Please check the [CONTRIBUT ### Development #### Prepare your environment * Install [Node.js](http://nodejs.org/) and NPM (should come with) -* Install global dev dependencies: `npm install -g grunt testacular` +* Install global dev dependencies: `npm install -g grunt-cli testacular` * Instal local dev dependencies: `npm install` while current directory is bootstrap repo #### Run unit tests diff --git a/node_modules/node-markdown/LICENSE b/node_modules/node-markdown/LICENSE deleted file mode 100644 index f4b71d89a3..0000000000 --- a/node_modules/node-markdown/LICENSE +++ /dev/null @@ -1,38 +0,0 @@ -Copyright (c) 2010, Andris Reinman - -All rights reserved. - -Original Showdown copyright (c) 2007, John Fraser - -All rights reserved. - -Original Markdown copyright (c) 2004, John Gruber - -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - -* Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -* Neither the name "Markdown" nor the names of its contributors may - be used to endorse or promote products derived from this software - without specific prior written permission. - -This software is provided by the copyright holders and contributors "as -is" and any express or implied warranties, including, but not limited -to, the implied warranties of merchantability and fitness for a -particular purpose are disclaimed. In no event shall the copyright owner -or contributors be liable for any direct, indirect, incidental, special, -exemplary, or consequential damages (including, but not limited to, -procurement of substitute goods or services; loss of use, data, or -profits; or business interruption) however caused and on any theory of -liability, whether in contract, strict liability, or tort (including -negligence or otherwise) arising in any way out of the use of this -software, even if advised of the possibility of such damage. diff --git a/node_modules/node-markdown/README.md b/node_modules/node-markdown/README.md deleted file mode 100644 index 362dae6213..0000000000 --- a/node_modules/node-markdown/README.md +++ /dev/null @@ -1,57 +0,0 @@ -node-markdown -============= - -**node-markdown** is based on [Showdown](http://attacklab.net/showdown/) parser and is meant to parse [Markdown](http://daringfireball.net/projects/markdown/) syntax into HTML code. - -Installation ------------- - -Use `npm` package manager - - npm install node-markdown - -Usage ------ - -Include Markdown parser - - var md = require("node-markdown").Markdown; - -Parse Markdown syntax into HTML - - var html = md("**markdown** string"); - -Allow only [default set](http://github.com/andris9/node-markdown/blob/master/lib/markdown.js#L38) of HTML tags to be used - - var html = md("**markdown** string", true); - -Allow only specified HTML tags to be used (default set of allowed attributes is used) - - var html = md("**markdown** string", true, "p|strong|span"); - -Allow specified HTML tags and specified attributes - - var html = md("**markdown** string", true, "p|strong|span", { - "a":"href", // 'href' for links - "*":"title|style" // 'title' and 'style' for all - }); - -Complete example - - var md_text = "**bold** *italic* [link](http://www.neti.ee) `code block`", - md_parser = require("node-markdown").Markdown; - - // simple - console.log(md_parser(md_text)); - - // limit HTML tags and attributes - console.log(md_parser(md_text, true, 'h1|p|span')); - - // limit HTML tags and keep attributes for allowed tags - var allowedTags = 'a|img'; - allowedAttributes = { - 'a':'href|style', - 'img': 'src', - '*': 'title' - } - console.log(md_parser(md_text, true, allowedTags, allowedAttributes)); diff --git a/node_modules/node-markdown/examples/test.js b/node_modules/node-markdown/examples/test.js deleted file mode 100644 index 8b02d0110b..0000000000 --- a/node_modules/node-markdown/examples/test.js +++ /dev/null @@ -1,12 +0,0 @@ -var md = require("../lib/markdown.js").Markdown; - -var md_text = "**bold** *italic* [link](http://www.neti.ee) `code block`"; - -console.log("--all"); -console.log(md(md_text)); - -console.log("--only and ") -console.log(md(md_text, true, 'strong|code')); - -console.log("--only with _href_") -console.log(md(md_text, true, 'a', {a:'href'})); \ No newline at end of file diff --git a/node_modules/node-markdown/lib/markdown.js b/node_modules/node-markdown/lib/markdown.js deleted file mode 100644 index c8b2b7ad3e..0000000000 --- a/node_modules/node-markdown/lib/markdown.js +++ /dev/null @@ -1,125 +0,0 @@ -/* node-markdown is based on Showdown parser (see vendor/showdown) */ -/* usage: html = require("markdown").Markdown(markdown_string); */ - -// import Showdown parser -var Showdown = new (require("./vendor/showdown/src/showdown.js").Showdown.converter)(); - -/** - * Markdown(text, stripUnwanted, allowedtags, allowedAttribs) -> String - * - text (String): Markdown syntax to be parsed - * - stripUnwanted (Boolean): if TRUE strip all unwanted tags and attributes - * - allowedTags (String): allowed HTML tags in the form of "tag1|tag2|tag3" - * - allowedAttributes (Object): allowed attributes for specific tags - * format: {"tag1":"attrib1|attrib2|attrib3", "tag2":...} - * wildcard for all tags: "*" - * - forceProtocol (Boolean): Force src and href to http:// if they miss a protocol. - * - * Converts a markdown text into a HTML - **/ -this.Markdown = function(text, stripUnwanted, allowedTags, allowedAttributes, forceProtocol){ - var md = Showdown.makeHtml(text); - if(stripUnwanted) - return stripUnwantedHTML(md, allowedTags, allowedAttributes, forceProtocol); - else - return md; -} - -/** - * stripUnwantedHTML(html, allowedtags, allowedAttribs, forceProtocol) -> String - * - html (String): HTML code to be parsed - * - allowedTags (String): allowed HTML tags in the form of "tag1|tag2|tag3" - * - allowedAttributes (Object): allowed attributes for specific tags - * format: {"tag1":"attrib1|attrib2|attrib3", "tag2":...} - * wildcard for all tags: "*" - * - forceProtocol (Boolean): Force src and href to http:// if they miss a protocol. - * - * Removes unwanted tags and attributes from HTML string - **/ -var stripUnwantedHTML = function(html /*, allowedTags, allowedAttributes, forceProtocol */){ - var allowedTags = arguments[1] || - 'a|b|blockquote|code|del|dd|dl|dt|em|h1|h2|h3|'+ - 'i|img|li|ol|p|pre|sup|sub|strong|strike|ul|br|hr', - allowedAttributes = arguments[2] || { - 'img': 'src|width|height|alt', - 'a': 'href', - '*': 'title' - }, forceProtocol = arguments[3] || false, - - testAllowed = new RegExp('^('+allowedTags.toLowerCase()+')$'), - findTags = /<(\/?)\s*([\w:\-]+)([^>]*)>/g, - findAttribs = /(\s*)([\w:-]+)\s*=\s*(?:(?:(["'])([^\3]+?)(?:\3))|([^\s]+))/g; - - // convert all strings patterns into regexp objects (if not already converted) - for(var i in allowedAttributes){ - if(allowedAttributes.hasOwnProperty(i) && typeof allowedAttributes[i] === 'string'){ - allowedAttributes[i] = new RegExp('^('+ - allowedAttributes[i].toLowerCase()+')$'); - } - } - - // find and match html tags - return html.replace(findTags, function(original, lslash, tag, params){ - var tagAttr, wildcardAttr, - rslash = params.substr(-1)=="/" && "/" || ""; - - tag = tag.toLowerCase(); - - // tag is not allowed, return empty string - if(!tag.match(testAllowed)) - return ""; - - // tag is allowed - else{ - // regexp objects for a particular tag - tagAttr = tag in allowedAttributes && allowedAttributes[tag]; - wildcardAttr = "*" in allowedAttributes && allowedAttributes["*"]; - - // if no attribs are allowed - if(!tagAttr && !wildcardAttr) - return "<"+lslash+tag+rslash+">"; - - // remove trailing slash if any - params = params.trim(); - if(rslash){ - params = params.substr(0, params.length-1); - } - - // find and remove unwanted attributes - params = params.replace(findAttribs, function(original, space, - name, quot, value){ - name = name.toLowerCase(); - - if (!value && !quot) { - value = ""; - quot = '"'; - } else if (!value) { - value = quot; - quot = '"'; - } - - // force data: and javascript: links and images to # - if((name=="href" || name=="src") && - (value.trim().substr(0, "javascript:".length)=="javascript:" - || value.trim().substr(0, "data:".length)=="data:")) { - value = "#"; - } - - // scope links and sources to http protocol - if (forceProtocol && - (name=="href" || name=="src") && - !/^[a-zA-Z]{3,5}:\/\//.test(value)) { - value = "http://" + value; - } - - if((wildcardAttr && name.match(wildcardAttr)) || - (tagAttr && name.match(tagAttr))){ - return space+name+"="+quot+value+quot; - }else - return ""; - }); - - return "<"+lslash+tag+(params?" "+params:"")+rslash+">"; - } - - }); -} diff --git a/node_modules/node-markdown/lib/vendor/showdown/compressed/showdown.js b/node_modules/node-markdown/lib/vendor/showdown/compressed/showdown.js deleted file mode 100644 index e43e2863e5..0000000000 --- a/node_modules/node-markdown/lib/vendor/showdown/compressed/showdown.js +++ /dev/null @@ -1,419 +0,0 @@ -/* - A A L Source code at: - T C A - T K B -*/ - -this.Showdown={}; -this.Showdown.converter=function(){ -var _1; -var _2; -var _3; -var _4=0; -this.makeHtml=function(_5){ -_1=new Array(); -_2=new Array(); -_3=new Array(); -_5=_5.replace(/~/g,"~T"); -_5=_5.replace(/\$/g,"~D"); -_5=_5.replace(/\r\n/g,"\n"); -_5=_5.replace(/\r/g,"\n"); -_5="\n\n"+_5+"\n\n"; -_5=_6(_5); -_5=_5.replace(/^[ \t]+$/mg,""); -_5=_7(_5); -_5=_8(_5); -_5=_9(_5); -_5=_a(_5); -_5=_5.replace(/~D/g,"$$"); -_5=_5.replace(/~T/g,"~"); -return _5; -}; -var _8=function(_b){ -var _b=_b.replace(/^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*?[ \t]*\n?[ \t]*(?:(\n*)["(](.+?)[")][ \t]*)?(?:\n+|\Z)/gm,function(_c,m1,m2,m3,m4){ -m1=m1.toLowerCase(); -_1[m1]=_11(m2); -if(m3){ -return m3+m4; -}else{ -if(m4){ -_2[m1]=m4.replace(/"/g,"""); -} -} -return ""; -}); -return _b; -}; -var _7=function(_12){ -_12=_12.replace(/\n/g,"\n\n"); -var _13="p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del"; -var _14="p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math"; -_12=_12.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del)\b[^\r]*?\n<\/\2>[ \t]*(?=\n+))/gm,_15); -_12=_12.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math)\b[^\r]*?.*<\/\2>[ \t]*(?=\n+)\n)/gm,_15); -_12=_12.replace(/(\n[ ]{0,3}(<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g,_15); -_12=_12.replace(/(\n\n[ ]{0,3}[ \t]*(?=\n{2,}))/g,_15); -_12=_12.replace(/(?:\n\n)([ ]{0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g,_15); -_12=_12.replace(/\n\n/g,"\n"); -return _12; -}; -var _15=function(_16,m1){ -var _18=m1; -_18=_18.replace(/\n\n/g,"\n"); -_18=_18.replace(/^\n/,""); -_18=_18.replace(/\n+$/g,""); -_18="\n\n~K"+(_3.push(_18)-1)+"K\n\n"; -return _18; -}; -var _9=function(_19){ -_19=_1a(_19); -var key=_1c("
"); -_19=_19.replace(/^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$/gm,key); -_19=_19.replace(/^[ ]{0,2}([ ]?\-[ ]?){3,}[ \t]*$/gm,key); -_19=_19.replace(/^[ ]{0,2}([ ]?\_[ ]?){3,}[ \t]*$/gm,key); -_19=_1d(_19); -_19=_1e(_19); -_19=_1f(_19); -_19=_7(_19); -_19=_20(_19); -return _19; -}; -var _21=function(_22){ -_22=_23(_22); -_22=_24(_22); -_22=_25(_22); -_22=_26(_22); -_22=_27(_22); -_22=_28(_22); -_22=_11(_22); -_22=_29(_22); -_22=_22.replace(/ +\n/g,"
\n"); -return _22; -}; -var _24=function(_2a){ -var _2b=/(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|)/gi; -_2a=_2a.replace(_2b,function(_2c){ -var tag=_2c.replace(/(.)<\/?code>(?=.)/g,"$1`"); -tag=_2e(tag,"\\`*_"); -return tag; -}); -return _2a; -}; -var _27=function(_2f){ -_2f=_2f.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,_30); -_2f=_2f.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\]\([ \t]*()?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,_30); -_2f=_2f.replace(/(\[([^\[\]]+)\])()()()()()/g,_30); -return _2f; -}; -var _30=function(_31,m1,m2,m3,m4,m5,m6,m7){ -if(m7==undefined){ -m7=""; -} -var _39=m1; -var _3a=m2; -var _3b=m3.toLowerCase(); -var url=m4; -var _3d=m7; -if(url==""){ -if(_3b==""){ -_3b=_3a.toLowerCase().replace(/ ?\n/g," "); -} -url="#"+_3b; -if(_1[_3b]!=undefined){ -url=_1[_3b]; -if(_2[_3b]!=undefined){ -_3d=_2[_3b]; -} -}else{ -if(_39.search(/\(\s*\)$/m)>-1){ -url=""; -}else{ -return _39; -} -} -} -url=_2e(url,"*_"); -var _3e="
"; -return _3e; -}; -var _26=function(_3f){ -_3f=_3f.replace(/(!\[(.*?)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,_40); -_3f=_3f.replace(/(!\[(.*?)\]\s?\([ \t]*()?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,_40); -return _3f; -}; -var _40=function(_41,m1,m2,m3,m4,m5,m6,m7){ -var _49=m1; -var _4a=m2; -var _4b=m3.toLowerCase(); -var url=m4; -var _4d=m7; -if(!_4d){ -_4d=""; -} -if(url==""){ -if(_4b==""){ -_4b=_4a.toLowerCase().replace(/ ?\n/g," "); -} -url="#"+_4b; -if(_1[_4b]!=undefined){ -url=_1[_4b]; -if(_2[_4b]!=undefined){ -_4d=_2[_4b]; -} -}else{ -return _49; -} -} -_4a=_4a.replace(/"/g,"""); -url=_2e(url,"*_"); -var _4e="\""+_4a+"\"";"+_21(m1)+""); -}); -_4f=_4f.replace(/^(.+)[ \t]*\n-+[ \t]*\n+/gm,function(_52,m1){ -return _1c("

"+_21(m1)+"

"); -}); -_4f=_4f.replace(/^(\#{1,6})[ \t]*(.+?)[ \t]*\#*\n+/gm,function(_54,m1,m2){ -var _57=m1.length; -return _1c(""+_21(m2)+""); -}); -return _4f; -}; -var _58; -var _1d=function(_59){ -_59+="~0"; -var _5a=/^(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm; -if(_4){ -_59=_59.replace(_5a,function(_5b,m1,m2){ -var _5e=m1; -var _5f=(m2.search(/[*+-]/g)>-1)?"ul":"ol"; -_5e=_5e.replace(/\n{2,}/g,"\n\n\n"); -var _60=_58(_5e); -_60=_60.replace(/\s+$/,""); -_60="<"+_5f+">"+_60+"\n"; -return _60; -}); -}else{ -_5a=/(\n\n|^\n?)(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/g; -_59=_59.replace(_5a,function(_61,m1,m2,m3){ -var _65=m1; -var _66=m2; -var _67=(m3.search(/[*+-]/g)>-1)?"ul":"ol"; -var _66=_66.replace(/\n{2,}/g,"\n\n\n"); -var _68=_58(_66); -_68=_65+"<"+_67+">\n"+_68+"\n"; -return _68; -}); -} -_59=_59.replace(/~0/,""); -return _59; -}; -_58=function(_69){ -_4++; -_69=_69.replace(/\n{2,}$/,"\n"); -_69+="~0"; -_69=_69.replace(/(\n)?(^[ \t]*)([*+-]|\d+[.])[ \t]+([^\r]+?(\n{1,2}))(?=\n*(~0|\2([*+-]|\d+[.])[ \t]+))/gm,function(_6a,m1,m2,m3,m4){ -var _6f=m4; -var _70=m1; -var _71=m2; -if(_70||(_6f.search(/\n{2,}/)>-1)){ -_6f=_9(_72(_6f)); -}else{ -_6f=_1d(_72(_6f)); -_6f=_6f.replace(/\n$/,""); -_6f=_21(_6f); -} -return "
  • "+_6f+"
  • \n"; -}); -_69=_69.replace(/~0/g,""); -_4--; -return _69; -}; -var _1e=function(_73){ -_73+="~0"; -_73=_73.replace(/(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=~0))/g,function(_74,m1,m2){ -var _77=m1; -var _78=m2; -_77=_79(_72(_77)); -_77=_6(_77); -_77=_77.replace(/^\n+/g,""); -_77=_77.replace(/\n+$/g,""); -_77="
    "+_77+"\n
    "; -return _1c(_77)+_78; -}); -_73=_73.replace(/~0/,""); -return _73; -}; -var _1c=function(_7a){ -_7a=_7a.replace(/(^\n+|\n+$)/g,""); -return "\n\n~K"+(_3.push(_7a)-1)+"K\n\n"; -}; -var _23=function(_7b){ -_7b=_7b.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm,function(_7c,m1,m2,m3,m4){ -var c=m3; -c=c.replace(/^([ \t]*)/g,""); -c=c.replace(/[ \t]*$/g,""); -c=_79(c); -return m1+""+c+""; -}); -return _7b; -}; -var _79=function(_82){ -_82=_82.replace(/&/g,"&"); -_82=_82.replace(//g,">"); -_82=_2e(_82,"*_{}[]\\",false); -return _82; -}; -var _29=function(_83){ -_83=_83.replace(/(\*\*|__)(?=\S)([^\r]*?\S[*_]*)\1/g,"$2"); -_83=_83.replace(/(\*|_)(?=\S)([^\r]*?\S)\1/g,"$2"); -return _83; -}; -var _1f=function(_84){ -_84=_84.replace(/((^[ \t]*>[ \t]?.+\n(.+\n)*\n*)+)/gm,function(_85,m1){ -var bq=m1; -bq=bq.replace(/^[ \t]*>[ \t]?/gm,"~0"); -bq=bq.replace(/~0/g,""); -bq=bq.replace(/^[ \t]+$/gm,""); -bq=_9(bq); -bq=bq.replace(/(^|\n)/g,"$1 "); -bq=bq.replace(/(\s*
    [^\r]+?<\/pre>)/gm,function(_88,m1){
    -var pre=m1;
    -pre=pre.replace(/^  /mg,"~0");
    -pre=pre.replace(/~0/g,"");
    -return pre;
    -});
    -return _1c("
    \n"+bq+"\n
    "); -}); -return _84; -}; -var _20=function(_8b){ -_8b=_8b.replace(/^\n+/g,""); -_8b=_8b.replace(/\n+$/g,""); -var _8c=_8b.split(/\n{2,}/g); -var _8d=new Array(); -var end=_8c.length; -for(var i=0;i=0){ -_8d.push(str); -}else{ -if(str.search(/\S/)>=0){ -str=_21(str); -str=str.replace(/^([ \t]*)/g,"

    "); -str+="

    "; -_8d.push(str); -} -} -} -end=_8d.length; -for(var i=0;i=0){ -var _91=_3[RegExp.$1]; -_91=_91.replace(/\$/g,"$$$$"); -_8d[i]=_8d[i].replace(/~K\d+K/,_91); -} -} -return _8d.join("\n\n"); -}; -var _11=function(_92){ -_92=_92.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g,"&"); -_92=_92.replace(/<(?![a-z\/?\$!])/gi,"<"); -return _92; -}; -var _25=function(_93){ -_93=_93.replace(/\\(\\)/g,_94); -_93=_93.replace(/\\([`*_{}\[\]()>#+-.!])/g,_94); -return _93; -}; -var _28=function(_95){ -_95=_95.replace(/<((https?|ftp|dict):[^'">\s]+)>/gi,"
    $1"); -_95=_95.replace(/<(?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi,function(_96,m1){ -return _98(_a(m1)); -}); -return _95; -}; -var _98=function(_99){ -function char2hex(ch){ -var _9b="0123456789ABCDEF"; -var dec=ch.charCodeAt(0); -return (_9b.charAt(dec>>4)+_9b.charAt(dec&15)); -} -var _9d=[function(ch){ -return "&#"+ch.charCodeAt(0)+";"; -},function(ch){ -return "&#x"+char2hex(ch)+";"; -},function(ch){ -return ch; -}]; -_99="mailto:"+_99; -_99=_99.replace(/./g,function(ch){ -if(ch=="@"){ -ch=_9d[Math.floor(Math.random()*2)](ch); -}else{ -if(ch!=":"){ -var r=Math.random(); -ch=(r>0.9?_9d[2](ch):r>0.45?_9d[1](ch):_9d[0](ch)); -} -} -return ch; -}); -_99=""+_99+""; -_99=_99.replace(/">.+:/g,"\">"); -return _99; -}; -var _a=function(_a3){ -_a3=_a3.replace(/~E(\d+)E/g,function(_a4,m1){ -var _a6=parseInt(m1); -return String.fromCharCode(_a6); -}); -return _a3; -}; -var _72=function(_a7){ -_a7=_a7.replace(/^(\t|[ ]{1,4})/gm,"~0"); -_a7=_a7.replace(/~0/g,""); -return _a7; -}; -var _6=function(_a8){ -_a8=_a8.replace(/\t(?=\t)/g," "); -_a8=_a8.replace(/\t/g,"~A~B"); -_a8=_a8.replace(/~B(.+?)~A/g,function(_a9,m1,m2){ -var _ac=m1; -var _ad=4-_ac.length%4; -for(var i=0;i<_ad;i++){ -_ac+=" "; -} -return _ac; -}); -_a8=_a8.replace(/~A/g," "); -_a8=_a8.replace(/~B/g,""); -return _a8; -}; -var _2e=function(_af,_b0,_b1){ -var _b2="(["+_b0.replace(/([\[\]\\])/g,"\\$1")+"])"; -if(_b1){ -_b2="\\\\"+_b2; -} -var _b3=new RegExp(_b2,"g"); -_af=_af.replace(_b3,_94); -return _af; -}; -var _94=function(_b4,m1){ -var _b6=m1.charCodeAt(0); -return "~E"+_b6+"E"; -}; -}; - diff --git a/node_modules/node-markdown/lib/vendor/showdown/example/showdown-gui.html b/node_modules/node-markdown/lib/vendor/showdown/example/showdown-gui.html deleted file mode 100644 index c447221ee8..0000000000 --- a/node_modules/node-markdown/lib/vendor/showdown/example/showdown-gui.html +++ /dev/null @@ -1,720 +0,0 @@ - - - - - Showdown - Markdown in Javascript - - - - - - - - - -
    -
    - Input -
    - -
    - -
    -
    - -
    - - - -
    - - - - - - - - - - - - - - -
    - - - - - - \ No newline at end of file diff --git a/node_modules/node-markdown/lib/vendor/showdown/example/showdown-gui.js b/node_modules/node-markdown/lib/vendor/showdown/example/showdown-gui.js deleted file mode 100644 index 5f3519f3f5..0000000000 --- a/node_modules/node-markdown/lib/vendor/showdown/example/showdown-gui.js +++ /dev/null @@ -1,349 +0,0 @@ -// -// showdown-gui.js -// -// A sample application for Showdown, a javascript port -// of Markdown. -// -// Copyright (c) 2007 John Fraser. -// -// Redistributable under a BSD-style open source license. -// See license.txt for more information. -// -// The full source distribution is at: -// -// A A L -// T C A -// T K B -// -// -// - -// -// The Showdown converter itself is in showdown.js, which must be -// included by the HTML before this file is. -// -// showdown-gui.js assumes the id and class definitions in -// showdown.html. It isn't dependent on the CSS, but it does -// manually hide, display, and resize the individual panes -- -// overriding the stylesheets. -// -// This sample application only interacts with showdown.js in -// two places: -// -// In startGui(): -// -// converter = new Showdown.converter(); -// -// In convertText(): -// -// text = converter.makeHtml(text); -// -// The rest of this file is user interface stuff. -// - - -// -// Register for onload -// -window.onload = startGui; - - -// -// Globals -// - -var converter; -var convertTextTimer,processingTime; -var lastText,lastOutput,lastRoomLeft; -var convertTextSetting, convertTextButton, paneSetting; -var inputPane,previewPane,outputPane,syntaxPane; -var maxDelay = 3000; // longest update pause (in ms) - - -// -// Initialization -// - -function startGui() { - // find elements - convertTextSetting = document.getElementById("convertTextSetting"); - convertTextButton = document.getElementById("convertTextButton"); - paneSetting = document.getElementById("paneSetting"); - - inputPane = document.getElementById("inputPane"); - previewPane = document.getElementById("previewPane"); - outputPane = document.getElementById("outputPane"); - syntaxPane = document.getElementById("syntaxPane"); - - // set event handlers - convertTextSetting.onchange = onConvertTextSettingChanged; - convertTextButton.onclick = onConvertTextButtonClicked; - paneSetting.onchange = onPaneSettingChanged; - window.onresize = setPaneHeights; - - // First, try registering for keyup events - // (There's no harm in calling onInput() repeatedly) - window.onkeyup = inputPane.onkeyup = onInput; - - // In case we can't capture paste events, poll for them - var pollingFallback = window.setInterval(function(){ - if(inputPane.value != lastText) - onInput(); - },1000); - - // Try registering for paste events - inputPane.onpaste = function() { - // It worked! Cancel paste polling. - if (pollingFallback!=undefined) { - window.clearInterval(pollingFallback); - pollingFallback = undefined; - } - onInput(); - } - - // Try registering for input events (the best solution) - if (inputPane.addEventListener) { - // Let's assume input also fires on paste. - // No need to cancel our keyup handlers; - // they're basically free. - inputPane.addEventListener("input",inputPane.onpaste,false); - } - - // poll for changes in font size - // this is cheap; do it often - window.setInterval(setPaneHeights,250); - - // start with blank page? - if (top.document.location.href.match(/\?blank=1$/)) - inputPane.value = ""; - - // refresh panes to avoid a hiccup - onPaneSettingChanged(); - - // build the converter - converter = new Showdown.converter(); - - // do an initial conversion to avoid a hiccup - convertText(); - - // give the input pane focus - inputPane.focus(); - - // start the other panes at the top - // (our smart scrolling moved them to the bottom) - previewPane.scrollTop = 0; - outputPane.scrollTop = 0; -} - - -// -// Conversion -// - -function convertText() { - // get input text - var text = inputPane.value; - - // if there's no change to input, cancel conversion - if (text && text == lastText) { - return; - } else { - lastText = text; - } - - var startTime = new Date().getTime(); - - // Do the conversion - text = converter.makeHtml(text); - - // display processing time - var endTime = new Date().getTime(); - processingTime = endTime - startTime; - document.getElementById("processingTime").innerHTML = processingTime+" ms"; - - // save proportional scroll positions - saveScrollPositions(); - - // update right pane - if (paneSetting.value == "outputPane") { - // the output pane is selected - outputPane.value = text; - } else if (paneSetting.value == "previewPane") { - // the preview pane is selected - previewPane.innerHTML = text; - } - - lastOutput = text; - - // restore proportional scroll positions - restoreScrollPositions(); -}; - - -// -// Event handlers -// - -function onConvertTextSettingChanged() { - // If the user just enabled automatic - // updates, we'll do one now. - onInput(); -} - -function onConvertTextButtonClicked() { - // hack: force the converter to run - lastText = ""; - - convertText(); - inputPane.focus(); -} - -function onPaneSettingChanged() { - previewPane.style.display = "none"; - outputPane.style.display = "none"; - syntaxPane.style.display = "none"; - - // now make the selected one visible - top[paneSetting.value].style.display = "block"; - - lastRoomLeft = 0; // hack: force resize of new pane - setPaneHeights(); - - if (paneSetting.value == "outputPane") { - // Update output pane - outputPane.value = lastOutput; - } else if (paneSetting.value == "previewPane") { - // Update preview pane - previewPane.innerHTML = lastOutput; - } -} - -function onInput() { -// In "delayed" mode, we do the conversion at pauses in input. -// The pause is equal to the last runtime, so that slow -// updates happen less frequently. -// -// Use a timer to schedule updates. Each keystroke -// resets the timer. - - // if we already have convertText scheduled, cancel it - if (convertTextTimer) { - window.clearTimeout(convertTextTimer); - convertTextTimer = undefined; - } - - if (convertTextSetting.value != "manual") { - var timeUntilConvertText = 0; - if (convertTextSetting.value == "delayed") { - // make timer adaptive - timeUntilConvertText = processingTime; - } - - if (timeUntilConvertText > maxDelay) - timeUntilConvertText = maxDelay; - - // Schedule convertText(). - // Even if we're updating every keystroke, use a timer at 0. - // This gives the browser time to handle other events. - convertTextTimer = window.setTimeout(convertText,timeUntilConvertText); - } -} - - -// -// Smart scrollbar adjustment -// -// We need to make sure the user can't type off the bottom -// of the preview and output pages. We'll do this by saving -// the proportional scroll positions before the update, and -// restoring them afterwards. -// - -var previewScrollPos; -var outputScrollPos; - -function getScrollPos(element) { - // favor the bottom when the text first overflows the window - if (element.scrollHeight <= element.clientHeight) - return 1.0; - return element.scrollTop/(element.scrollHeight-element.clientHeight); -} - -function setScrollPos(element,pos) { - element.scrollTop = (element.scrollHeight - element.clientHeight) * pos; -} - -function saveScrollPositions() { - previewScrollPos = getScrollPos(previewPane); - outputScrollPos = getScrollPos(outputPane); -} - -function restoreScrollPositions() { - // hack for IE: setting scrollTop ensures scrollHeight - // has been updated after a change in contents - previewPane.scrollTop = previewPane.scrollTop; - - setScrollPos(previewPane,previewScrollPos); - setScrollPos(outputPane,outputScrollPos); -} - -// -// Textarea resizing -// -// Some browsers (i.e. IE) refuse to set textarea -// percentage heights in standards mode. (But other units? -// No problem. Percentage widths? No problem.) -// -// So we'll do it in javascript. If IE's behavior ever -// changes, we should remove this crap and do 100% textarea -// heights in CSS, because it makes resizing much smoother -// on other browsers. -// - -function getTop(element) { - var sum = element.offsetTop; - while(element = element.offsetParent) - sum += element.offsetTop; - return sum; -} - -function getElementHeight(element) { - var height = element.clientHeight; - if (!height) height = element.scrollHeight; - return height; -} - -function getWindowHeight(element) { - if (window.innerHeight) - return window.innerHeight; - else if (document.documentElement && document.documentElement.clientHeight) - return document.documentElement.clientHeight; - else if (document.body) - return document.body.clientHeight; -} - -function setPaneHeights() { - var textarea = inputPane; - var footer = document.getElementById("footer"); - - var windowHeight = getWindowHeight(); - var footerHeight = getElementHeight(footer); - var textareaTop = getTop(textarea); - - // figure out how much room the panes should fill - var roomLeft = windowHeight - footerHeight - textareaTop; - - if (roomLeft < 0) roomLeft = 0; - - // if it hasn't changed, return - if (roomLeft == lastRoomLeft) { - return; - } - lastRoomLeft = roomLeft; - - // resize all panes - inputPane.style.height = roomLeft + "px"; - previewPane.style.height = roomLeft + "px"; - outputPane.style.height = roomLeft + "px"; - syntaxPane.style.height = roomLeft + "px"; -} \ No newline at end of file diff --git a/node_modules/node-markdown/lib/vendor/showdown/example/showdown.js b/node_modules/node-markdown/lib/vendor/showdown/example/showdown.js deleted file mode 100644 index a960309c11..0000000000 --- a/node_modules/node-markdown/lib/vendor/showdown/example/showdown.js +++ /dev/null @@ -1,1296 +0,0 @@ -// -// showdown.js -- A javascript port of Markdown. -// -// Copyright (c) 2007 John Fraser. -// -// Original Markdown Copyright (c) 2004-2005 John Gruber -// -// -// Redistributable under a BSD-style open source license. -// See license.txt for more information. -// -// The full source distribution is at: -// -// A A L -// T C A -// T K B -// -// -// - -// -// Wherever possible, Showdown is a straight, line-by-line port -// of the Perl version of Markdown. -// -// This is not a normal parser design; it's basically just a -// series of string substitutions. It's hard to read and -// maintain this way, but keeping Showdown close to the original -// design makes it easier to port new features. -// -// More importantly, Showdown behaves like markdown.pl in most -// edge cases. So web applications can do client-side preview -// in Javascript, and then build identical HTML on the server. -// -// This port needs the new RegExp functionality of ECMA 262, -// 3rd Edition (i.e. Javascript 1.5). Most modern web browsers -// should do fine. Even with the new regular expression features, -// We do a lot of work to emulate Perl's regex functionality. -// The tricky changes in this file mostly have the "attacklab:" -// label. Major or self-explanatory changes don't. -// -// Smart diff tools like Araxis Merge will be able to match up -// this file with markdown.pl in a useful way. A little tweaking -// helps: in a copy of markdown.pl, replace "#" with "//" and -// replace "$text" with "text". Be sure to ignore whitespace -// and line endings. -// - - -// -// Showdown usage: -// -// var text = "Markdown *rocks*."; -// -// var converter = new Showdown.converter(); -// var html = converter.makeHtml(text); -// -// alert(html); -// -// Note: move the sample code to the bottom of this -// file before uncommenting it. -// - - -// -// Showdown namespace -// -var Showdown = {}; - -// -// converter -// -// Wraps all "globals" so that the only thing -// exposed is makeHtml(). -// -Showdown.converter = function() { - -// -// Globals: -// - -// Global hashes, used by various utility routines -var g_urls; -var g_titles; -var g_html_blocks; - -// Used to track when we're inside an ordered or unordered list -// (see _ProcessListItems() for details): -var g_list_level = 0; - - -this.makeHtml = function(text) { -// -// Main function. The order in which other subs are called here is -// essential. Link and image substitutions need to happen before -// _EscapeSpecialCharsWithinTagAttributes(), so that any *'s or _'s in the -// and tags get encoded. -// - - // Clear the global hashes. If we don't clear these, you get conflicts - // from other articles when generating a page which contains more than - // one article (e.g. an index page that shows the N most recent - // articles): - g_urls = new Array(); - g_titles = new Array(); - g_html_blocks = new Array(); - - // attacklab: Replace ~ with ~T - // This lets us use tilde as an escape char to avoid md5 hashes - // The choice of character is arbitray; anything that isn't - // magic in Markdown will work. - text = text.replace(/~/g,"~T"); - - // attacklab: Replace $ with ~D - // RegExp interprets $ as a special character - // when it's in a replacement string - text = text.replace(/\$/g,"~D"); - - // Standardize line endings - text = text.replace(/\r\n/g,"\n"); // DOS to Unix - text = text.replace(/\r/g,"\n"); // Mac to Unix - - // Make sure text begins and ends with a couple of newlines: - text = "\n\n" + text + "\n\n"; - - // Convert all tabs to spaces. - text = _Detab(text); - - // Strip any lines consisting only of spaces and tabs. - // This makes subsequent regexen easier to write, because we can - // match consecutive blank lines with /\n+/ instead of something - // contorted like /[ \t]*\n+/ . - text = text.replace(/^[ \t]+$/mg,""); - - // Turn block-level HTML blocks into hash entries - text = _HashHTMLBlocks(text); - - // Strip link definitions, store in hashes. - text = _StripLinkDefinitions(text); - - text = _RunBlockGamut(text); - - text = _UnescapeSpecialChars(text); - - // attacklab: Restore dollar signs - text = text.replace(/~D/g,"$$"); - - // attacklab: Restore tildes - text = text.replace(/~T/g,"~"); - - return text; -} - - -var _StripLinkDefinitions = function(text) { -// -// Strips link definitions from text, stores the URLs and titles in -// hash references. -// - - // Link defs are in the form: ^[id]: url "optional title" - - /* - var text = text.replace(/ - ^[ ]{0,3}\[(.+)\]: // id = $1 attacklab: g_tab_width - 1 - [ \t]* - \n? // maybe *one* newline - [ \t]* - ? // url = $2 - [ \t]* - \n? // maybe one newline - [ \t]* - (?: - (\n*) // any lines skipped = $3 attacklab: lookbehind removed - ["(] - (.+?) // title = $4 - [")] - [ \t]* - )? // title is optional - (?:\n+|$) - /gm, - function(){...}); - */ - var text = text.replace(/^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*?[ \t]*\n?[ \t]*(?:(\n*)["(](.+?)[")][ \t]*)?(?:\n+|\Z)/gm, - function (wholeMatch,m1,m2,m3,m4) { - m1 = m1.toLowerCase(); - g_urls[m1] = _EncodeAmpsAndAngles(m2); // Link IDs are case-insensitive - if (m3) { - // Oops, found blank lines, so it's not a title. - // Put back the parenthetical statement we stole. - return m3+m4; - } else if (m4) { - g_titles[m1] = m4.replace(/"/g,"""); - } - - // Completely remove the definition from the text - return ""; - } - ); - - return text; -} - - -var _HashHTMLBlocks = function(text) { - // attacklab: Double up blank lines to reduce lookaround - text = text.replace(/\n/g,"\n\n"); - - // Hashify HTML blocks: - // We only want to do this for block-level HTML tags, such as headers, - // lists, and tables. That's because we still want to wrap

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

    - //
    - // tags for inner block must be indented. - //
    - //
    - // - // The outermost tags must start at the left margin for this to match, and - // the inner nested divs must be indented. - // We need to do this before the next, more liberal match, because the next - // match will start at the first `
    ` and stop at the first `
    `. - - // attacklab: This regex can be expensive when it fails. - /* - var text = text.replace(/ - ( // save in $1 - ^ // start of line (with /m) - <($block_tags_a) // start tag = $2 - \b // word break - // attacklab: hack around khtml/pcre bug... - [^\r]*?\n // any number of lines, minimally matching - // the matching end tag - [ \t]* // trailing spaces/tabs - (?=\n+) // followed by a newline - ) // attacklab: there are sentinel newlines at end of document - /gm,function(){...}}; - */ - text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del)\b[^\r]*?\n<\/\2>[ \t]*(?=\n+))/gm,hashElement); - - // - // Now match more liberally, simply from `\n` to `\n` - // - - /* - var text = text.replace(/ - ( // save in $1 - ^ // start of line (with /m) - <($block_tags_b) // start tag = $2 - \b // word break - // attacklab: hack around khtml/pcre bug... - [^\r]*? // any number of lines, minimally matching - .* // the matching end tag - [ \t]* // trailing spaces/tabs - (?=\n+) // followed by a newline - ) // attacklab: there are sentinel newlines at end of document - /gm,function(){...}}; - */ - text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math)\b[^\r]*?.*<\/\2>[ \t]*(?=\n+)\n)/gm,hashElement); - - // Special case just for
    . It was easier to make a special case than - // to make the other regex more complicated. - - /* - text = text.replace(/ - ( // save in $1 - \n\n // Starting after a blank line - [ ]{0,3} - (<(hr) // start tag = $2 - \b // word break - ([^<>])*? // - \/?>) // the matching end tag - [ \t]* - (?=\n{2,}) // followed by a blank line - ) - /g,hashElement); - */ - text = text.replace(/(\n[ ]{0,3}(<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g,hashElement); - - // Special case for standalone HTML comments: - - /* - text = text.replace(/ - ( // save in $1 - \n\n // Starting after a blank line - [ ]{0,3} // attacklab: g_tab_width - 1 - - [ \t]* - (?=\n{2,}) // followed by a blank line - ) - /g,hashElement); - */ - text = text.replace(/(\n\n[ ]{0,3}[ \t]*(?=\n{2,}))/g,hashElement); - - // PHP and ASP-style processor instructions ( and <%...%>) - - /* - text = text.replace(/ - (?: - \n\n // Starting after a blank line - ) - ( // save in $1 - [ ]{0,3} // attacklab: g_tab_width - 1 - (?: - <([?%]) // $2 - [^\r]*? - \2> - ) - [ \t]* - (?=\n{2,}) // followed by a blank line - ) - /g,hashElement); - */ - text = text.replace(/(?:\n\n)([ ]{0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g,hashElement); - - // attacklab: Undo double lines (see comment at top of this function) - text = text.replace(/\n\n/g,"\n"); - return text; -} - -var hashElement = function(wholeMatch,m1) { - var blockText = m1; - - // Undo double lines - blockText = blockText.replace(/\n\n/g,"\n"); - blockText = blockText.replace(/^\n/,""); - - // strip trailing blank lines - blockText = blockText.replace(/\n+$/g,""); - - // Replace the element text with a marker ("~KxK" where x is its key) - blockText = "\n\n~K" + (g_html_blocks.push(blockText)-1) + "K\n\n"; - - return blockText; -}; - -var _RunBlockGamut = function(text) { -// -// These are all the transformations that form block-level -// tags like paragraphs, headers, and list items. -// - text = _DoHeaders(text); - - // Do Horizontal Rules: - var key = hashBlock("
    "); - text = text.replace(/^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$/gm,key); - text = text.replace(/^[ ]{0,2}([ ]?\-[ ]?){3,}[ \t]*$/gm,key); - text = text.replace(/^[ ]{0,2}([ ]?\_[ ]?){3,}[ \t]*$/gm,key); - - text = _DoLists(text); - text = _DoCodeBlocks(text); - text = _DoBlockQuotes(text); - - // We already ran _HashHTMLBlocks() before, in Markdown(), but that - // was to escape raw HTML in the original Markdown source. This time, - // we're escaping the markup we've just created, so that we don't wrap - //

    tags around block-level tags. - text = _HashHTMLBlocks(text); - text = _FormParagraphs(text); - - return text; -} - - -var _RunSpanGamut = function(text) { -// -// These are all the transformations that occur *within* block-level -// tags like paragraphs, headers, and list items. -// - - text = _DoCodeSpans(text); - text = _EscapeSpecialCharsWithinTagAttributes(text); - text = _EncodeBackslashEscapes(text); - - // Process anchor and image tags. Images must come first, - // because ![foo][f] looks like an anchor. - text = _DoImages(text); - text = _DoAnchors(text); - - // Make links out of things like `` - // Must come after _DoAnchors(), because you can use < and > - // delimiters in inline links like [this](). - text = _DoAutoLinks(text); - text = _EncodeAmpsAndAngles(text); - text = _DoItalicsAndBold(text); - - // Do hard breaks: - text = text.replace(/ +\n/g,"
    \n"); - - return text; -} - -var _EscapeSpecialCharsWithinTagAttributes = function(text) { -// -// Within tags -- meaning between < and > -- encode [\ ` * _] so they -// don't conflict with their use in Markdown for code, italics and strong. -// - - // Build a regex to find HTML tags and comments. See Friedl's - // "Mastering Regular Expressions", 2nd Ed., pp. 200-201. - var regex = /(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|)/gi; - - text = text.replace(regex, function(wholeMatch) { - var tag = wholeMatch.replace(/(.)<\/?code>(?=.)/g,"$1`"); - tag = escapeCharacters(tag,"\\`*_"); - return tag; - }); - - return text; -} - -var _DoAnchors = function(text) { -// -// Turn Markdown link shortcuts into XHTML
    tags. -// - // - // First, handle reference-style links: [link text] [id] - // - - /* - text = text.replace(/ - ( // wrap whole match in $1 - \[ - ( - (?: - \[[^\]]*\] // allow brackets nested one level - | - [^\[] // or anything else - )* - ) - \] - - [ ]? // one optional space - (?:\n[ ]*)? // one optional newline followed by spaces - - \[ - (.*?) // id = $3 - \] - )()()()() // pad remaining backreferences - /g,_DoAnchors_callback); - */ - text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,writeAnchorTag); - - // - // Next, inline-style links: [link text](url "optional title") - // - - /* - text = text.replace(/ - ( // wrap whole match in $1 - \[ - ( - (?: - \[[^\]]*\] // allow brackets nested one level - | - [^\[\]] // or anything else - ) - ) - \] - \( // literal paren - [ \t]* - () // no id, so leave $3 empty - ? // href = $4 - [ \t]* - ( // $5 - (['"]) // quote char = $6 - (.*?) // Title = $7 - \6 // matching quote - [ \t]* // ignore any spaces/tabs between closing quote and ) - )? // title is optional - \) - ) - /g,writeAnchorTag); - */ - text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\]\([ \t]*()?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,writeAnchorTag); - - // - // Last, handle reference-style shortcuts: [link text] - // These must come last in case you've also got [link test][1] - // or [link test](/foo) - // - - /* - text = text.replace(/ - ( // wrap whole match in $1 - \[ - ([^\[\]]+) // link text = $2; can't contain '[' or ']' - \] - )()()()()() // pad rest of backreferences - /g, writeAnchorTag); - */ - text = text.replace(/(\[([^\[\]]+)\])()()()()()/g, writeAnchorTag); - - return text; -} - -var writeAnchorTag = function(wholeMatch,m1,m2,m3,m4,m5,m6,m7) { - if (m7 == undefined) m7 = ""; - var whole_match = m1; - var link_text = m2; - var link_id = m3.toLowerCase(); - var url = m4; - var title = m7; - - if (url == "") { - if (link_id == "") { - // lower-case and turn embedded newlines into spaces - link_id = link_text.toLowerCase().replace(/ ?\n/g," "); - } - url = "#"+link_id; - - if (g_urls[link_id] != undefined) { - url = g_urls[link_id]; - if (g_titles[link_id] != undefined) { - title = g_titles[link_id]; - } - } - else { - if (whole_match.search(/\(\s*\)$/m)>-1) { - // Special case for explicit empty url - url = ""; - } else { - return whole_match; - } - } - } - - url = escapeCharacters(url,"*_"); - var result = ""; - - return result; -} - - -var _DoImages = function(text) { -// -// Turn Markdown image shortcuts into tags. -// - - // - // First, handle reference-style labeled images: ![alt text][id] - // - - /* - text = text.replace(/ - ( // wrap whole match in $1 - !\[ - (.*?) // alt text = $2 - \] - - [ ]? // one optional space - (?:\n[ ]*)? // one optional newline followed by spaces - - \[ - (.*?) // id = $3 - \] - )()()()() // pad rest of backreferences - /g,writeImageTag); - */ - text = text.replace(/(!\[(.*?)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,writeImageTag); - - // - // Next, handle inline images: ![alt text](url "optional title") - // Don't forget: encode * and _ - - /* - text = text.replace(/ - ( // wrap whole match in $1 - !\[ - (.*?) // alt text = $2 - \] - \s? // One optional whitespace character - \( // literal paren - [ \t]* - () // no id, so leave $3 empty - ? // src url = $4 - [ \t]* - ( // $5 - (['"]) // quote char = $6 - (.*?) // title = $7 - \6 // matching quote - [ \t]* - )? // title is optional - \) - ) - /g,writeImageTag); - */ - text = text.replace(/(!\[(.*?)\]\s?\([ \t]*()?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,writeImageTag); - - return text; -} - -var writeImageTag = function(wholeMatch,m1,m2,m3,m4,m5,m6,m7) { - var whole_match = m1; - var alt_text = m2; - var link_id = m3.toLowerCase(); - var url = m4; - var title = m7; - - if (!title) title = ""; - - if (url == "") { - if (link_id == "") { - // lower-case and turn embedded newlines into spaces - link_id = alt_text.toLowerCase().replace(/ ?\n/g," "); - } - url = "#"+link_id; - - if (g_urls[link_id] != undefined) { - url = g_urls[link_id]; - if (g_titles[link_id] != undefined) { - title = g_titles[link_id]; - } - } - else { - return whole_match; - } - } - - alt_text = alt_text.replace(/"/g,"""); - url = escapeCharacters(url,"*_"); - var result = "\""" + _RunSpanGamut(m1) + "");}); - - text = text.replace(/^(.+)[ \t]*\n-+[ \t]*\n+/gm, - function(matchFound,m1){return hashBlock("

    " + _RunSpanGamut(m1) + "

    ");}); - - // atx-style headers: - // # Header 1 - // ## Header 2 - // ## Header 2 with closing hashes ## - // ... - // ###### Header 6 - // - - /* - text = text.replace(/ - ^(\#{1,6}) // $1 = string of #'s - [ \t]* - (.+?) // $2 = Header text - [ \t]* - \#* // optional closing #'s (not counted) - \n+ - /gm, function() {...}); - */ - - text = text.replace(/^(\#{1,6})[ \t]*(.+?)[ \t]*\#*\n+/gm, - function(wholeMatch,m1,m2) { - var h_level = m1.length; - return hashBlock("" + _RunSpanGamut(m2) + ""); - }); - - return text; -} - -// This declaration keeps Dojo compressor from outputting garbage: -var _ProcessListItems; - -var _DoLists = function(text) { -// -// Form HTML ordered (numbered) and unordered (bulleted) lists. -// - - // attacklab: add sentinel to hack around khtml/safari bug: - // http://bugs.webkit.org/show_bug.cgi?id=11231 - text += "~0"; - - // Re-usable pattern to match any entirel ul or ol list: - - /* - var whole_list = / - ( // $1 = whole list - ( // $2 - [ ]{0,3} // attacklab: g_tab_width - 1 - ([*+-]|\d+[.]) // $3 = first list item marker - [ \t]+ - ) - [^\r]+? - ( // $4 - ~0 // sentinel for workaround; should be $ - | - \n{2,} - (?=\S) - (?! // Negative lookahead for another list item marker - [ \t]* - (?:[*+-]|\d+[.])[ \t]+ - ) - ) - )/g - */ - var whole_list = /^(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm; - - if (g_list_level) { - text = text.replace(whole_list,function(wholeMatch,m1,m2) { - var list = m1; - var list_type = (m2.search(/[*+-]/g)>-1) ? "ul" : "ol"; - - // Turn double returns into triple returns, so that we can make a - // paragraph for the last item in a list, if necessary: - list = list.replace(/\n{2,}/g,"\n\n\n");; - var result = _ProcessListItems(list); - - // Trim any trailing whitespace, to put the closing `` - // up on the preceding line, to get it past the current stupid - // HTML block parser. This is a hack to work around the terrible - // hack that is the HTML block parser. - result = result.replace(/\s+$/,""); - result = "<"+list_type+">" + result + "\n"; - return result; - }); - } else { - whole_list = /(\n\n|^\n?)(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/g; - text = text.replace(whole_list,function(wholeMatch,m1,m2,m3) { - var runup = m1; - var list = m2; - - var list_type = (m3.search(/[*+-]/g)>-1) ? "ul" : "ol"; - // Turn double returns into triple returns, so that we can make a - // paragraph for the last item in a list, if necessary: - var list = list.replace(/\n{2,}/g,"\n\n\n");; - var result = _ProcessListItems(list); - result = runup + "<"+list_type+">\n" + result + "\n"; - return result; - }); - } - - // attacklab: strip sentinel - text = text.replace(/~0/,""); - - return text; -} - -_ProcessListItems = function(list_str) { -// -// Process the contents of a single ordered or unordered list, splitting it -// into individual list items. -// - // The $g_list_level global keeps track of when we're inside a list. - // Each time we enter a list, we increment it; when we leave a list, - // we decrement. If it's zero, we're not in a list anymore. - // - // We do this because when we're not inside a list, we want to treat - // something like this: - // - // I recommend upgrading to version - // 8. Oops, now this line is treated - // as a sub-list. - // - // As a single paragraph, despite the fact that the second line starts - // with a digit-period-space sequence. - // - // Whereas when we're inside a list (or sub-list), that line will be - // treated as the start of a sub-list. What a kludge, huh? This is - // an aspect of Markdown's syntax that's hard to parse perfectly - // without resorting to mind-reading. Perhaps the solution is to - // change the syntax rules such that sub-lists must start with a - // starting cardinal number; e.g. "1." or "a.". - - g_list_level++; - - // trim trailing blank lines: - list_str = list_str.replace(/\n{2,}$/,"\n"); - - // attacklab: add sentinel to emulate \z - list_str += "~0"; - - /* - list_str = list_str.replace(/ - (\n)? // leading line = $1 - (^[ \t]*) // leading whitespace = $2 - ([*+-]|\d+[.]) [ \t]+ // list marker = $3 - ([^\r]+? // list item text = $4 - (\n{1,2})) - (?= \n* (~0 | \2 ([*+-]|\d+[.]) [ \t]+)) - /gm, function(){...}); - */ - list_str = list_str.replace(/(\n)?(^[ \t]*)([*+-]|\d+[.])[ \t]+([^\r]+?(\n{1,2}))(?=\n*(~0|\2([*+-]|\d+[.])[ \t]+))/gm, - function(wholeMatch,m1,m2,m3,m4){ - var item = m4; - var leading_line = m1; - var leading_space = m2; - - if (leading_line || (item.search(/\n{2,}/)>-1)) { - item = _RunBlockGamut(_Outdent(item)); - } - else { - // Recursion for sub-lists: - item = _DoLists(_Outdent(item)); - item = item.replace(/\n$/,""); // chomp(item) - item = _RunSpanGamut(item); - } - - return "
  • " + item + "
  • \n"; - } - ); - - // attacklab: strip sentinel - list_str = list_str.replace(/~0/g,""); - - g_list_level--; - return list_str; -} - - -var _DoCodeBlocks = function(text) { -// -// Process Markdown `
    ` blocks.
    -//  
    -
    -	/*
    -		text = text.replace(text,
    -			/(?:\n\n|^)
    -			(								// $1 = the code block -- one or more lines, starting with a space/tab
    -				(?:
    -					(?:[ ]{4}|\t)			// Lines must start with a tab or a tab-width of spaces - attacklab: g_tab_width
    -					.*\n+
    -				)+
    -			)
    -			(\n*[ ]{0,3}[^ \t\n]|(?=~0))	// attacklab: g_tab_width
    -		/g,function(){...});
    -	*/
    -
    -	// attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug
    -	text += "~0";
    -	
    -	text = text.replace(/(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=~0))/g,
    -		function(wholeMatch,m1,m2) {
    -			var codeblock = m1;
    -			var nextChar = m2;
    -		
    -			codeblock = _EncodeCode( _Outdent(codeblock));
    -			codeblock = _Detab(codeblock);
    -			codeblock = codeblock.replace(/^\n+/g,""); // trim leading newlines
    -			codeblock = codeblock.replace(/\n+$/g,""); // trim trailing whitespace
    -
    -			codeblock = "
    " + codeblock + "\n
    "; - - return hashBlock(codeblock) + nextChar; - } - ); - - // attacklab: strip sentinel - text = text.replace(/~0/,""); - - return text; -} - -var hashBlock = function(text) { - text = text.replace(/(^\n+|\n+$)/g,""); - return "\n\n~K" + (g_html_blocks.push(text)-1) + "K\n\n"; -} - - -var _DoCodeSpans = function(text) { -// -// * Backtick quotes are used for spans. -// -// * You can use multiple backticks as the delimiters if you want to -// include literal backticks in the code span. So, this input: -// -// Just type ``foo `bar` baz`` at the prompt. -// -// Will translate to: -// -//

    Just type foo `bar` baz at the prompt.

    -// -// There's no arbitrary limit to the number of backticks you -// can use as delimters. If you need three consecutive backticks -// in your code, use four for delimiters, etc. -// -// * You can use spaces to get literal backticks at the edges: -// -// ... type `` `bar` `` ... -// -// Turns to: -// -// ... type `bar` ... -// - - /* - text = text.replace(/ - (^|[^\\]) // Character before opening ` can't be a backslash - (`+) // $2 = Opening run of ` - ( // $3 = The code block - [^\r]*? - [^`] // attacklab: work around lack of lookbehind - ) - \2 // Matching closer - (?!`) - /gm, function(){...}); - */ - - text = text.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm, - function(wholeMatch,m1,m2,m3,m4) { - var c = m3; - c = c.replace(/^([ \t]*)/g,""); // leading whitespace - c = c.replace(/[ \t]*$/g,""); // trailing whitespace - c = _EncodeCode(c); - return m1+""+c+""; - }); - - return text; -} - - -var _EncodeCode = function(text) { -// -// Encode/escape certain characters inside Markdown code runs. -// The point is that in code, these characters are literals, -// and lose their special Markdown meanings. -// - // Encode all ampersands; HTML entities are not - // entities within a Markdown code span. - text = text.replace(/&/g,"&"); - - // Do the angle bracket song and dance: - text = text.replace(//g,">"); - - // Now, escape characters that are magic in Markdown: - text = escapeCharacters(text,"\*_{}[]\\",false); - -// jj the line above breaks this: -//--- - -//* Item - -// 1. Subitem - -// special char: * -//--- - - return text; -} - - -var _DoItalicsAndBold = function(text) { - - // must go first: - text = text.replace(/(\*\*|__)(?=\S)([^\r]*?\S[*_]*)\1/g, - "$2"); - - text = text.replace(/(\*|_)(?=\S)([^\r]*?\S)\1/g, - "$2"); - - return text; -} - - -var _DoBlockQuotes = function(text) { - - /* - text = text.replace(/ - ( // Wrap whole match in $1 - ( - ^[ \t]*>[ \t]? // '>' at the start of a line - .+\n // rest of the first line - (.+\n)* // subsequent consecutive lines - \n* // blanks - )+ - ) - /gm, function(){...}); - */ - - text = text.replace(/((^[ \t]*>[ \t]?.+\n(.+\n)*\n*)+)/gm, - function(wholeMatch,m1) { - var bq = m1; - - // attacklab: hack around Konqueror 3.5.4 bug: - // "----------bug".replace(/^-/g,"") == "bug" - - bq = bq.replace(/^[ \t]*>[ \t]?/gm,"~0"); // trim one level of quoting - - // attacklab: clean up hack - bq = bq.replace(/~0/g,""); - - bq = bq.replace(/^[ \t]+$/gm,""); // trim whitespace-only lines - bq = _RunBlockGamut(bq); // recurse - - bq = bq.replace(/(^|\n)/g,"$1 "); - // These leading spaces screw with
     content, so we need to fix that:
    -			bq = bq.replace(
    -					/(\s*
    [^\r]+?<\/pre>)/gm,
    -				function(wholeMatch,m1) {
    -					var pre = m1;
    -					// attacklab: hack around Konqueror 3.5.4 bug:
    -					pre = pre.replace(/^  /mg,"~0");
    -					pre = pre.replace(/~0/g,"");
    -					return pre;
    -				});
    -			
    -			return hashBlock("
    \n" + bq + "\n
    "); - }); - return text; -} - - -var _FormParagraphs = function(text) { -// -// Params: -// $text - string to process with html

    tags -// - - // Strip leading and trailing lines: - text = text.replace(/^\n+/g,""); - text = text.replace(/\n+$/g,""); - - var grafs = text.split(/\n{2,}/g); - var grafsOut = new Array(); - - // - // Wrap

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

    "); - str += "

    " - grafsOut.push(str); - } - - } - - // - // Unhashify HTML blocks - // - end = grafsOut.length; - for (var i=0; i= 0) { - var blockText = g_html_blocks[RegExp.$1]; - blockText = blockText.replace(/\$/g,"$$$$"); // Escape any dollar signs - grafsOut[i] = grafsOut[i].replace(/~K\d+K/,blockText); - } - } - - return grafsOut.join("\n\n"); -} - - -var _EncodeAmpsAndAngles = function(text) { -// Smart processing for ampersands and angle brackets that need to be encoded. - - // Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin: - // http://bumppo.net/projects/amputator/ - text = text.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g,"&"); - - // Encode naked <'s - text = text.replace(/<(?![a-z\/?\$!])/gi,"<"); - - return text; -} - - -var _EncodeBackslashEscapes = function(text) { -// -// Parameter: String. -// Returns: The string, with after processing the following backslash -// escape sequences. -// - - // attacklab: The polite way to do this is with the new - // escapeCharacters() function: - // - // text = escapeCharacters(text,"\\",true); - // text = escapeCharacters(text,"`*_{}[]()>#+-.!",true); - // - // ...but we're sidestepping its use of the (slow) RegExp constructor - // as an optimization for Firefox. This function gets called a LOT. - - text = text.replace(/\\(\\)/g,escapeCharacters_callback); - text = text.replace(/\\([`*_{}\[\]()>#+-.!])/g,escapeCharacters_callback); - return text; -} - - -var _DoAutoLinks = function(text) { - - text = text.replace(/<((https?|ftp|dict):[^'">\s]+)>/gi,"
    $1"); - - // Email addresses: - - /* - text = text.replace(/ - < - (?:mailto:)? - ( - [-.\w]+ - \@ - [-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+ - ) - > - /gi, _DoAutoLinks_callback()); - */ - text = text.replace(/<(?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi, - function(wholeMatch,m1) { - return _EncodeEmailAddress( _UnescapeSpecialChars(m1) ); - } - ); - - return text; -} - - -var _EncodeEmailAddress = function(addr) { -// -// Input: an email address, e.g. "foo@example.com" -// -// Output: the email address as a mailto link, with each character -// of the address encoded as either a decimal or hex entity, in -// the hopes of foiling most address harvesting spam bots. E.g.: -// -// foo -// @example.com -// -// Based on a filter by Matthew Wickline, posted to the BBEdit-Talk -// mailing list: -// - - // attacklab: why can't javascript speak hex? - function char2hex(ch) { - var hexDigits = '0123456789ABCDEF'; - var dec = ch.charCodeAt(0); - return(hexDigits.charAt(dec>>4) + hexDigits.charAt(dec&15)); - } - - var encode = [ - function(ch){return "&#"+ch.charCodeAt(0)+";";}, - function(ch){return "&#x"+char2hex(ch)+";";}, - function(ch){return ch;} - ]; - - addr = "mailto:" + addr; - - addr = addr.replace(/./g, function(ch) { - if (ch == "@") { - // this *must* be encoded. I insist. - ch = encode[Math.floor(Math.random()*2)](ch); - } else if (ch !=":") { - // leave ':' alone (to spot mailto: later) - var r = Math.random(); - // roughly 10% raw, 45% hex, 45% dec - ch = ( - r > .9 ? encode[2](ch) : - r > .45 ? encode[1](ch) : - encode[0](ch) - ); - } - return ch; - }); - - addr = "" + addr + ""; - addr = addr.replace(/">.+:/g,"\">"); // strip the mailto: from the visible part - - return addr; -} - - -var _UnescapeSpecialChars = function(text) { -// -// Swap back in all the special characters we've hidden. -// - text = text.replace(/~E(\d+)E/g, - function(wholeMatch,m1) { - var charCodeToReplace = parseInt(m1); - return String.fromCharCode(charCodeToReplace); - } - ); - return text; -} - - -var _Outdent = function(text) { -// -// Remove one level of line-leading tabs or spaces -// - - // attacklab: hack around Konqueror 3.5.4 bug: - // "----------bug".replace(/^-/g,"") == "bug" - - text = text.replace(/^(\t|[ ]{1,4})/gm,"~0"); // attacklab: g_tab_width - - // attacklab: clean up hack - text = text.replace(/~0/g,"") - - return text; -} - -var _Detab = function(text) { -// attacklab: Detab's completely rewritten for speed. -// In perl we could fix it by anchoring the regexp with \G. -// In javascript we're less fortunate. - - // expand first n-1 tabs - text = text.replace(/\t(?=\t)/g," "); // attacklab: g_tab_width - - // replace the nth with two sentinels - text = text.replace(/\t/g,"~A~B"); - - // use the sentinel to anchor our regex so it doesn't explode - text = text.replace(/~B(.+?)~A/g, - function(wholeMatch,m1,m2) { - var leadingText = m1; - var numSpaces = 4 - leadingText.length % 4; // attacklab: g_tab_width - - // there *must* be a better way to do this: - for (var i=0; i -All rights reserved. - -Original Markdown copyright (c) 2004, John Gruber - -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - -* Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -* Neither the name "Markdown" nor the names of its contributors may - be used to endorse or promote products derived from this software - without specific prior written permission. - -This software is provided by the copyright holders and contributors "as -is" and any express or implied warranties, including, but not limited -to, the implied warranties of merchantability and fitness for a -particular purpose are disclaimed. In no event shall the copyright owner -or contributors be liable for any direct, indirect, incidental, special, -exemplary, or consequential damages (including, but not limited to, -procurement of substitute goods or services; loss of use, data, or -profits; or business interruption) however caused and on any theory of -liability, whether in contract, strict liability, or tort (including -negligence or otherwise) arising in any way out of the use of this -software, even if advised of the possibility of such damage. diff --git a/node_modules/node-markdown/lib/vendor/showdown/perlMarkdown/Markdown License.txt b/node_modules/node-markdown/lib/vendor/showdown/perlMarkdown/Markdown License.txt deleted file mode 100644 index 6d76506505..0000000000 --- a/node_modules/node-markdown/lib/vendor/showdown/perlMarkdown/Markdown License.txt +++ /dev/null @@ -1,30 +0,0 @@ -Copyright (c) 2004, John Gruber - -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - -* Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -* Neither the name "Markdown" nor the names of its contributors may - be used to endorse or promote products derived from this software - without specific prior written permission. - -This software is provided by the copyright holders and contributors "as -is" and any express or implied warranties, including, but not limited -to, the implied warranties of merchantability and fitness for a -particular purpose are disclaimed. In no event shall the copyright owner -or contributors be liable for any direct, indirect, incidental, special, -exemplary, or consequential damages (including, but not limited to, -procurement of substitute goods or services; loss of use, data, or -profits; or business interruption) however caused and on any theory of -liability, whether in contract, strict liability, or tort (including -negligence or otherwise) arising in any way out of the use of this -software, even if advised of the possibility of such damage. diff --git a/node_modules/node-markdown/lib/vendor/showdown/perlMarkdown/Markdown-1.0.2b2.pl b/node_modules/node-markdown/lib/vendor/showdown/perlMarkdown/Markdown-1.0.2b2.pl deleted file mode 100644 index 5c78edd47e..0000000000 --- a/node_modules/node-markdown/lib/vendor/showdown/perlMarkdown/Markdown-1.0.2b2.pl +++ /dev/null @@ -1,1509 +0,0 @@ -#!/usr/bin/perl - -# -# Markdown -- A text-to-HTML conversion tool for web writers -# -# Copyright (c) 2004-2005 John Gruber -# -# - - -package Markdown; -require 5.006_000; -use strict; -use warnings; - -use Digest::MD5 qw(md5_hex); -use vars qw($VERSION); -$VERSION = '1.0.2b2'; -# Sat 26 Mar 2005 - -## Disabled; causes problems under Perl 5.6.1: -# use utf8; -# binmode( STDOUT, ":utf8" ); # c.f.: http://acis.openlib.org/dev/perl-unicode-struggle.html - - -# -# Global default settings: -# -my $g_empty_element_suffix = " />"; # Change to ">" for HTML output -my $g_tab_width = 4; - - -# -# Globals: -# - -# Regex to match balanced [brackets]. See Friedl's -# "Mastering Regular Expressions", 2nd Ed., pp. 328-331. -my $g_nested_brackets; -$g_nested_brackets = qr{ - (?> # Atomic matching - [^\[\]]+ # Anything other than brackets - | - \[ - (??{ $g_nested_brackets }) # Recursive set of nested brackets - \] - )* -}x; - - -# Table of hash values for escaped characters: -my %g_escape_table; -foreach my $char (split //, '\\`*_{}[]()>#+-.!') { - $g_escape_table{$char} = md5_hex($char); -} - - -# Global hashes, used by various utility routines -my %g_urls; -my %g_titles; -my %g_html_blocks; - -# Used to track when we're inside an ordered or unordered list -# (see _ProcessListItems() for details): -my $g_list_level = 0; - - -#### Blosxom plug-in interface ########################################## - -# Set $g_blosxom_use_meta to 1 to use Blosxom's meta plug-in to determine -# which posts Markdown should process, using a "meta-markup: markdown" -# header. If it's set to 0 (the default), Markdown will process all -# entries. -my $g_blosxom_use_meta = 0; - -sub start { 1; } -sub story { - my($pkg, $path, $filename, $story_ref, $title_ref, $body_ref) = @_; - - if ( (! $g_blosxom_use_meta) or - (defined($meta::markup) and ($meta::markup =~ /^\s*markdown\s*$/i)) - ){ - $$body_ref = Markdown($$body_ref); - } - 1; -} - - -#### Movable Type plug-in interface ##################################### -eval {require MT}; # Test to see if we're running in MT. -unless ($@) { - require MT; - import MT; - require MT::Template::Context; - import MT::Template::Context; - - eval {require MT::Plugin}; # Test to see if we're running >= MT 3.0. - unless ($@) { - require MT::Plugin; - import MT::Plugin; - my $plugin = new MT::Plugin({ - name => "Markdown", - description => "A plain-text-to-HTML formatting plugin. (Version: $VERSION)", - doc_link => 'http://daringfireball.net/projects/markdown/' - }); - MT->add_plugin( $plugin ); - } - - MT::Template::Context->add_container_tag(MarkdownOptions => sub { - my $ctx = shift; - my $args = shift; - my $builder = $ctx->stash('builder'); - my $tokens = $ctx->stash('tokens'); - - if (defined ($args->{'output'}) ) { - $ctx->stash('markdown_output', lc $args->{'output'}); - } - - defined (my $str = $builder->build($ctx, $tokens) ) - or return $ctx->error($builder->errstr); - $str; # return value - }); - - MT->add_text_filter('markdown' => { - label => 'Markdown', - docs => 'http://daringfireball.net/projects/markdown/', - on_format => sub { - my $text = shift; - my $ctx = shift; - my $raw = 0; - if (defined $ctx) { - my $output = $ctx->stash('markdown_output'); - if (defined $output && $output =~ m/^html/i) { - $g_empty_element_suffix = ">"; - $ctx->stash('markdown_output', ''); - } - elsif (defined $output && $output eq 'raw') { - $raw = 1; - $ctx->stash('markdown_output', ''); - } - else { - $raw = 0; - $g_empty_element_suffix = " />"; - } - } - $text = $raw ? $text : Markdown($text); - $text; - }, - }); - - # If SmartyPants is loaded, add a combo Markdown/SmartyPants text filter: - my $smartypants; - - { - no warnings "once"; - $smartypants = $MT::Template::Context::Global_filters{'smarty_pants'}; - } - - if ($smartypants) { - MT->add_text_filter('markdown_with_smartypants' => { - label => 'Markdown With SmartyPants', - docs => 'http://daringfireball.net/projects/markdown/', - on_format => sub { - my $text = shift; - my $ctx = shift; - if (defined $ctx) { - my $output = $ctx->stash('markdown_output'); - if (defined $output && $output eq 'html') { - $g_empty_element_suffix = ">"; - } - else { - $g_empty_element_suffix = " />"; - } - } - $text = Markdown($text); - $text = $smartypants->($text, '1'); - }, - }); - } -} -else { -#### BBEdit/command-line text filter interface ########################## -# Needs to be hidden from MT (and Blosxom when running in static mode). - - # We're only using $blosxom::version once; tell Perl not to warn us: - no warnings 'once'; - unless ( defined($blosxom::version) ) { - use warnings; - - #### Check for command-line switches: ################# - my %cli_opts; - use Getopt::Long; - Getopt::Long::Configure('pass_through'); - GetOptions(\%cli_opts, - 'version', - 'shortversion', - 'html4tags', - ); - if ($cli_opts{'version'}) { # Version info - print "\nThis is Markdown, version $VERSION.\n"; - print "Copyright 2004 John Gruber\n"; - print "http://daringfireball.net/projects/markdown/\n\n"; - exit 0; - } - if ($cli_opts{'shortversion'}) { # Just the version number string. - print $VERSION; - exit 0; - } - if ($cli_opts{'html4tags'}) { # Use HTML tag style instead of XHTML - $g_empty_element_suffix = ">"; - } - - - #### Process incoming text: ########################### - my $text; - { - local $/; # Slurp the whole file - $text = <>; - } - print Markdown($text); - } -} - - - -sub Markdown { -# -# Main function. The order in which other subs are called here is -# essential. Link and image substitutions need to happen before -# _EscapeSpecialCharsWithinTagAttributes(), so that any *'s or _'s in the -# and tags get encoded. -# - my $text = shift; - - # Clear the global hashes. If we don't clear these, you get conflicts - # from other articles when generating a page which contains more than - # one article (e.g. an index page that shows the N most recent - # articles): - %g_urls = (); - %g_titles = (); - %g_html_blocks = (); - - - # Standardize line endings: - $text =~ s{\r\n}{\n}g; # DOS to Unix - $text =~ s{\r}{\n}g; # Mac to Unix - - # Make sure $text ends with a couple of newlines: - $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 =~ s/^[ \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); - - return $text . "\n"; -} - - -sub _StripLinkDefinitions { -# -# Strips link definitions from text, stores the URLs and titles in -# hash references. -# - my $text = shift; - my $less_than_tab = $g_tab_width - 1; - - # Link defs are in the form: ^[id]: url "optional title" - while ($text =~ s{ - ^[ ]{0,$less_than_tab}\[(.+)\]: # id = $1 - [ \t]* - \n? # maybe *one* newline - [ \t]* - ? # url = $2 - [ \t]* - \n? # maybe one newline - [ \t]* - (?: - (?<=\s) # lookbehind for whitespace - ["(] - (.+?) # title = $3 - [")] - [ \t]* - )? # title is optional - (?:\n+|\Z) - } - {}mx) { - $g_urls{lc $1} = _EncodeAmpsAndAngles( $2 ); # Link IDs are case-insensitive - if ($3) { - $g_titles{lc $1} = $3; - $g_titles{lc $1} =~ s/"/"/g; - } - } - - return $text; -} - - -sub _HashHTMLBlocks { - my $text = shift; - my $less_than_tab = $g_tab_width - 1; - - # Hashify HTML blocks: - # We only want to do this for block-level HTML tags, such as headers, - # lists, and tables. That's because we still want to wrap

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

    - #
    - # tags for inner block must be indented. - #
    - #
    - # - # The outermost tags must start at the left margin for this to match, and - # the inner nested divs must be indented. - # We need to do this before the next, more liberal match, because the next - # match will start at the first `
    ` and stop at the first `
    `. - $text =~ s{ - ( # save in $1 - ^ # start of line (with /m) - <($block_tags_a) # start tag = $2 - \b # word break - (.*\n)*? # any number of lines, minimally matching - # the matching end tag - [ \t]* # trailing spaces/tabs - (?=\n+|\Z) # followed by a newline or end of document - ) - }{ - my $key = md5_hex($1); - $g_html_blocks{$key} = $1; - "\n\n" . $key . "\n\n"; - }egmx; - - - # - # Now match more liberally, simply from `\n` to `\n` - # - $text =~ s{ - ( # save in $1 - ^ # start of line (with /m) - <($block_tags_b) # start tag = $2 - \b # word break - (.*\n)*? # any number of lines, minimally matching - .* # the matching end tag - [ \t]* # trailing spaces/tabs - (?=\n+|\Z) # followed by a newline or end of document - ) - }{ - my $key = md5_hex($1); - $g_html_blocks{$key} = $1; - "\n\n" . $key . "\n\n"; - }egmx; - # Special case just for
    . It was easier to make a special case than - # to make the other regex more complicated. - $text =~ s{ - (?: - (?<=\n\n) # Starting after a blank line - | # or - \A\n? # the beginning of the doc - ) - ( # save in $1 - [ ]{0,$less_than_tab} - <(hr) # start tag = $2 - \b # word break - ([^<>])*? # - /?> # the matching end tag - [ \t]* - (?=\n{2,}|\Z) # followed by a blank line or end of document - ) - }{ - my $key = md5_hex($1); - $g_html_blocks{$key} = $1; - "\n\n" . $key . "\n\n"; - }egx; - - # Special case for standalone HTML comments: - $text =~ s{ - (?: - (?<=\n\n) # Starting after a blank line - | # or - \A\n? # the beginning of the doc - ) - ( # save in $1 - [ ]{0,$less_than_tab} - (?s: - - ) - [ \t]* - (?=\n{2,}|\Z) # followed by a blank line or end of document - ) - }{ - my $key = md5_hex($1); - $g_html_blocks{$key} = $1; - "\n\n" . $key . "\n\n"; - }egx; - - - return $text; -} - - -sub _RunBlockGamut { -# -# These are all the transformations that form block-level -# tags like paragraphs, headers, and list items. -# - my $text = shift; - - $text = _DoHeaders($text); - - # Do Horizontal Rules: - $text =~ s{^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$}{\n tags around block-level tags. - $text = _HashHTMLBlocks($text); - $text = _FormParagraphs($text); - - return $text; -} - - -sub _RunSpanGamut { -# -# These are all the transformations that occur *within* block-level -# tags like paragraphs, headers, and list items. -# - my $text = shift; - - $text = _EscapeSpecialCharsWithinTagAttributes($text); - $text = _DoCodeSpans($text); - $text = _EncodeBackslashEscapes($text); - - # Process anchor and image tags. Images must come first, - # because ![foo][f] looks like an anchor. - $text = _DoImages($text); - $text = _DoAnchors($text); - - # Make links out of things like `` - # Must come after _DoAnchors(), because you can use < and > - # delimiters in inline links like [this](). - $text = _DoAutoLinks($text); - $text = _EncodeAmpsAndAngles($text); - $text = _DoItalicsAndBold($text); - - # Do hard breaks: - $text =~ s/ {2,}\n/ -- encode [\ ` * _] so they -# don't conflict with their use in Markdown for code, italics and strong. -# We're replacing each such character with its corresponding MD5 checksum -# value; this is likely overkill, but it should prevent us from colliding -# with the escape values by accident. -# - my $text = shift; - my $tokens ||= _TokenizeHTML($text); - $text = ''; # rebuild $text from the tokens - - foreach my $cur_token (@$tokens) { - if ($cur_token->[0] eq "tag") { - $cur_token->[1] =~ s! \\ !$g_escape_table{'\\'}!gx; - $cur_token->[1] =~ s! ` !$g_escape_table{'`'}!gx; - $cur_token->[1] =~ s! \* !$g_escape_table{'*'}!gx; - $cur_token->[1] =~ s! _ !$g_escape_table{'_'}!gx; - } - $text .= $cur_token->[1]; - } - return $text; -} - - -sub _DoAnchors { -# -# Turn Markdown link shortcuts into XHTML
    tags. -# - my $text = shift; - - # - # First, handle reference-style links: [link text] [id] - # - $text =~ s{ - ( # wrap whole match in $1 - \[ - ($g_nested_brackets) # link text = $2 - \] - - [ ]? # one optional space - (?:\n[ ]*)? # one optional newline followed by spaces - - \[ - (.*?) # id = $3 - \] - ) - }{ - my $result; - my $whole_match = $1; - my $link_text = $2; - my $link_id = lc $3; - - if ($link_id eq "") { - $link_id = lc $link_text; # for shortcut links like [this][]. - } - - if (defined $g_urls{$link_id}) { - my $url = $g_urls{$link_id}; - $url =~ s! \* !$g_escape_table{'*'}!gx; # We've got to encode these to avoid - $url =~ s! _ !$g_escape_table{'_'}!gx; # conflicting with italics/bold. - $result = "? # href = $3 - [ \t]* - ( # $4 - (['"]) # quote char = $5 - (.*?) # Title = $6 - \5 # matching quote - )? # title is optional - \) - ) - }{ - my $result; - my $whole_match = $1; - my $link_text = $2; - my $url = $3; - my $title = $6; - - $url =~ s! \* !$g_escape_table{'*'}!gx; # We've got to encode these to avoid - $url =~ s! _ !$g_escape_table{'_'}!gx; # conflicting with italics/bold. - $result = " tags. -# - my $text = shift; - - # - # First, handle reference-style labeled images: ![alt text][id] - # - $text =~ s{ - ( # wrap whole match in $1 - !\[ - (.*?) # alt text = $2 - \] - - [ ]? # one optional space - (?:\n[ ]*)? # one optional newline followed by spaces - - \[ - (.*?) # id = $3 - \] - - ) - }{ - my $result; - my $whole_match = $1; - my $alt_text = $2; - my $link_id = lc $3; - - if ($link_id eq "") { - $link_id = lc $alt_text; # for shortcut links like ![this][]. - } - - $alt_text =~ s/"/"/g; - if (defined $g_urls{$link_id}) { - my $url = $g_urls{$link_id}; - $url =~ s! \* !$g_escape_table{'*'}!gx; # We've got to encode these to avoid - $url =~ s! _ !$g_escape_table{'_'}!gx; # conflicting with italics/bold. - $result = "\"$alt_text\"";? # src url = $3 - [ \t]* - ( # $4 - (['"]) # quote char = $5 - (.*?) # title = $6 - \5 # matching quote - [ \t]* - )? # title is optional - \) - ) - }{ - my $result; - my $whole_match = $1; - my $alt_text = $2; - my $url = $3; - my $title = ''; - if (defined($6)) { - $title = $6; - } - - $alt_text =~ s/"/"/g; - $title =~ s/"/"/g; - $url =~ s! \* !$g_escape_table{'*'}!gx; # We've got to encode these to avoid - $url =~ s! _ !$g_escape_table{'_'}!gx; # conflicting with italics/bold. - $result = "\"$alt_text\"";" . _RunSpanGamut($1) . "\n\n"; - }egmx; - - $text =~ s{ ^(.+)[ \t]*\n-+[ \t]*\n+ }{ - "

    " . _RunSpanGamut($1) . "

    \n\n"; - }egmx; - - - # atx-style headers: - # # Header 1 - # ## Header 2 - # ## Header 2 with closing hashes ## - # ... - # ###### Header 6 - # - $text =~ s{ - ^(\#{1,6}) # $1 = string of #'s - [ \t]* - (.+?) # $2 = Header text - [ \t]* - \#* # optional closing #'s (not counted) - \n+ - }{ - my $h_level = length($1); - "" . _RunSpanGamut($2) . "\n\n"; - }egmx; - - return $text; -} - - -sub _DoLists { -# -# Form HTML ordered (numbered) and unordered (bulleted) lists. -# - my $text = shift; - my $less_than_tab = $g_tab_width - 1; - - # Re-usable patterns to match list item bullets and number markers: - my $marker_ul = qr/[*+-]/; - my $marker_ol = qr/\d+[.]/; - my $marker_any = qr/(?:$marker_ul|$marker_ol)/; - - # Re-usable pattern to match any entirel ul or ol list: - my $whole_list = qr{ - ( # $1 = whole list - ( # $2 - [ ]{0,$less_than_tab} - (${marker_any}) # $3 = first list item marker - [ \t]+ - ) - (?s:.+?) - ( # $4 - \z - | - \n{2,} - (?=\S) - (?! # Negative lookahead for another list item marker - [ \t]* - ${marker_any}[ \t]+ - ) - ) - ) - }mx; - - # We use a different prefix before nested lists than top-level lists. - # See extended comment in _ProcessListItems(). - # - # Note: There's a bit of duplication here. My original implementation - # created a scalar regex pattern as the conditional result of the test on - # $g_list_level, and then only ran the $text =~ s{...}{...}egmx - # substitution once, using the scalar as the pattern. This worked, - # everywhere except when running under MT on my hosting account at Pair - # Networks. There, this caused all rebuilds to be killed by the reaper (or - # perhaps they crashed, but that seems incredibly unlikely given that the - # same script on the same server ran fine *except* under MT. I've spent - # more time trying to figure out why this is happening than I'd like to - # admit. My only guess, backed up by the fact that this workaround works, - # is that Perl optimizes the substition when it can figure out that the - # pattern will never change, and when this optimization isn't on, we run - # afoul of the reaper. Thus, the slightly redundant code that uses two - # static s/// patterns rather than one conditional pattern. - - if ($g_list_level) { - $text =~ s{ - ^ - $whole_list - }{ - my $list = $1; - my $list_type = ($3 =~ m/$marker_ul/) ? "ul" : "ol"; - - # Turn double returns into triple returns, so that we can make a - # paragraph for the last item in a list, if necessary: - $list =~ s/\n{2,}/\n\n\n/g; - my $result = _ProcessListItems($list, $marker_any); - - # Trim any trailing whitespace, to put the closing `` - # up on the preceding line, to get it past the current stupid - # HTML block parser. This is a hack to work around the terrible - # hack that is the HTML block parser. - $result =~ s{\s+$}{}; - $result = "<$list_type>" . $result . "\n"; - $result; - }egmx; - } - else { - $text =~ s{ - (?:(?<=\n\n)|\A\n?) - $whole_list - }{ - my $list = $1; - my $list_type = ($3 =~ m/$marker_ul/) ? "ul" : "ol"; - # Turn double returns into triple returns, so that we can make a - # paragraph for the last item in a list, if necessary: - $list =~ s/\n{2,}/\n\n\n/g; - my $result = _ProcessListItems($list, $marker_any); - $result = "<$list_type>\n" . $result . "\n"; - $result; - }egmx; - } - - - return $text; -} - - -sub _ProcessListItems { -# -# Process the contents of a single ordered or unordered list, splitting it -# into individual list items. -# - - my $list_str = shift; - my $marker_any = shift; - - - # 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 =~ s/\n{2,}\z/\n/; - - - $list_str =~ s{ - (\n)? # leading line = $1 - (^[ \t]*) # leading whitespace = $2 - ($marker_any) [ \t]+ # list marker = $3 - ((?s:.+?) # list item text = $4 - (\n{1,2})) - (?= \n* (\z | \2 ($marker_any) [ \t]+)) - }{ - my $item = $4; - my $leading_line = $1; - my $leading_space = $2; - - if ($leading_line or ($item =~ m/\n{2,}/)) { - $item = _RunBlockGamut(_Outdent($item)); - } - else { - # Recursion for sub-lists: - $item = _DoLists(_Outdent($item)); - chomp $item; - $item = _RunSpanGamut($item); - } - - "
  • " . $item . "
  • \n"; - }egmx; - - $g_list_level--; - return $list_str; -} - - - -sub _DoCodeBlocks { -# -# Process Markdown `
    ` blocks.
    -#  
    -
    -  my $text = shift;
    -
    -  $text =~ s{
    -      (?:\n\n|\A)
    -      (              # $1 = the code block -- one or more lines, starting with a space/tab
    -        (?:
    -          (?:[ ]{$g_tab_width} | \t)  # Lines must start with a tab or a tab-width of spaces
    -          .*\n+
    -        )+
    -      )
    -      ((?=^[ ]{0,$g_tab_width}\S)|\Z)  # Lookahead for non-space at line-start, or end of doc
    -    }{
    -      my $codeblock = $1;
    -      my $result; # return value
    -
    -      $codeblock = _EncodeCode(_Outdent($codeblock));
    -      $codeblock = _Detab($codeblock);
    -      $codeblock =~ s/\A\n+//; # trim leading newlines
    -      $codeblock =~ s/\s+\z//; # trim trailing whitespace
    -
    -      $result = "\n\n
    " . $codeblock . "\n
    \n\n"; - - $result; - }egmx; - - return $text; -} - - -sub _DoCodeSpans { -# -# * Backtick quotes are used for spans. -# -# * You can use multiple backticks as the delimiters if you want to -# include literal backticks in the code span. So, this input: -# -# Just type ``foo `bar` baz`` at the prompt. -# -# Will translate to: -# -#

    Just type foo `bar` baz at the prompt.

    -# -# There's no arbitrary limit to the number of backticks you -# can use as delimters. If you need three consecutive backticks -# in your code, use four for delimiters, etc. -# -# * You can use spaces to get literal backticks at the edges: -# -# ... type `` `bar` `` ... -# -# Turns to: -# -# ... type `bar` ... -# - - my $text = shift; - - $text =~ s@ - (?$c
    "; - @egsx; - - return $text; -} - - -sub _EncodeCode { -# -# Encode/escape certain characters inside Markdown code runs. -# The point is that in code, these characters are literals, -# and lose their special Markdown meanings. -# - local $_ = shift; - - # Encode all ampersands; HTML entities are not - # entities within a Markdown code span. - s/&/&/g; - - # Encode $'s, but only if we're running under Blosxom. - # (Blosxom interpolates Perl variables in article bodies.) - { - no warnings 'once'; - if (defined($blosxom::version)) { - s/\$/$/g; - } - } - - - # Do the angle bracket song and dance: - s! < !<!gx; - s! > !>!gx; - - # Now, escape characters that are magic in Markdown: - s! \* !$g_escape_table{'*'}!gx; - s! _ !$g_escape_table{'_'}!gx; - s! { !$g_escape_table{'{'}!gx; - s! } !$g_escape_table{'}'}!gx; - s! \[ !$g_escape_table{'['}!gx; - s! \] !$g_escape_table{']'}!gx; - s! \\ !$g_escape_table{'\\'}!gx; - - return $_; -} - - -sub _DoItalicsAndBold { - my $text = shift; - - # must go first: - $text =~ s{ (\*\*|__) (?=\S) (.+?[*_]*) (?<=\S) \1 } - {$2}gsx; - - $text =~ s{ (\*|_) (?=\S) (.+?) (?<=\S) \1 } - {$2}gsx; - - return $text; -} - - -sub _DoBlockQuotes { - my $text = shift; - - $text =~ s{ - ( # 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 - )+ - ) - }{ - my $bq = $1; - $bq =~ s/^[ \t]*>[ \t]?//gm; # trim one level of quoting - $bq =~ s/^[ \t]+$//mg; # trim whitespace-only lines - $bq = _RunBlockGamut($bq); # recurse - - $bq =~ s/^/ /g; - # These leading spaces screw with
     content, so we need to fix that:
    -      $bq =~ s{
    -          (\s*
    .+?
    ) - }{ - my $pre = $1; - $pre =~ s/^ //mg; - $pre; - }egsx; - - "
    \n$bq\n
    \n\n"; - }egmx; - - - return $text; -} - - -sub _FormParagraphs { -# -# Params: -# $text - string to process with html

    tags -# - my $text = shift; - - # Strip leading and trailing lines: - $text =~ s/\A\n+//; - $text =~ s/\n+\z//; - - my @grafs = split(/\n{2,}/, $text); - - # - # Wrap

    tags. - # - foreach (@grafs) { - unless (defined( $g_html_blocks{$_} )) { - $_ = _RunSpanGamut($_); - s/^([ \t]*)/

    /; - $_ .= "

    "; - } - } - - # - # Unhashify HTML blocks - # - foreach (@grafs) { - if (defined( $g_html_blocks{$_} )) { - $_ = $g_html_blocks{$_}; - } - } - - return join "\n\n", @grafs; -} - - -sub _EncodeAmpsAndAngles { -# Smart processing for ampersands and angle brackets that need to be encoded. - - my $text = shift; - - # Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin: - # http://bumppo.net/projects/amputator/ - $text =~ s/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/&/g; - - # Encode naked <'s - $text =~ s{<(?![a-z/?\$!])}{<}gi; - - return $text; -} - - -sub _EncodeBackslashEscapes { -# -# Parameter: String. -# Returns: The string, with after processing the following backslash -# escape sequences. -# - local $_ = shift; - - s! \\\\ !$g_escape_table{'\\'}!gx; # Must process escaped backslashes first. - s! \\` !$g_escape_table{'`'}!gx; - s! \\\* !$g_escape_table{'*'}!gx; - s! \\_ !$g_escape_table{'_'}!gx; - s! \\\{ !$g_escape_table{'{'}!gx; - s! \\\} !$g_escape_table{'}'}!gx; - s! \\\[ !$g_escape_table{'['}!gx; - s! \\\] !$g_escape_table{']'}!gx; - s! \\\( !$g_escape_table{'('}!gx; - s! \\\) !$g_escape_table{')'}!gx; - s! \\> !$g_escape_table{'>'}!gx; - s! \\\# !$g_escape_table{'#'}!gx; - s! \\\+ !$g_escape_table{'+'}!gx; - s! \\\- !$g_escape_table{'-'}!gx; - s! \\\. !$g_escape_table{'.'}!gx; - s{ \\! }{$g_escape_table{'!'}}gx; - - return $_; -} - - -sub _DoAutoLinks { - my $text = shift; - - $text =~ s{<((https?|ftp):[^'">\s]+)>}{
    $1}gi; - - # Email addresses: - $text =~ s{ - < - (?:mailto:)? - ( - [-.\w]+ - \@ - [-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+ - ) - > - }{ - _EncodeEmailAddress( _UnescapeSpecialChars($1) ); - }egix; - - return $text; -} - - -sub _EncodeEmailAddress { -# -# Input: an email address, e.g. "foo@example.com" -# -# Output: the email address as a mailto link, with each character -# of the address encoded as either a decimal or hex entity, in -# the hopes of foiling most address harvesting spam bots. E.g.: -# -# foo -# @example.com -# -# Based on a filter by Matthew Wickline, posted to the BBEdit-Talk -# mailing list: -# - - my $addr = shift; - - srand; - my @encode = ( - sub { '&#' . ord(shift) . ';' }, - sub { '&#x' . sprintf( "%X", ord(shift) ) . ';' }, - sub { shift }, - ); - - $addr = "mailto:" . $addr; - - $addr =~ s{(.)}{ - my $char = $1; - if ( $char eq '@' ) { - # this *must* be encoded. I insist. - $char = $encode[int rand 1]->($char); - } elsif ( $char ne ':' ) { - # leave ':' alone (to spot mailto: later) - my $r = rand; - # roughly 10% raw, 45% hex, 45% dec - $char = ( - $r > .9 ? $encode[2]->($char) : - $r < .45 ? $encode[1]->($char) : - $encode[0]->($char) - ); - } - $char; - }gex; - - $addr = qq{$addr}; - $addr =~ s{">.+?:}{">}; # strip the mailto: from the visible part - - return $addr; -} - - -sub _UnescapeSpecialChars { -# -# Swap back in all the special characters we've hidden. -# - my $text = shift; - - while( my($char, $hash) = each(%g_escape_table) ) { - $text =~ s/$hash/$char/g; - } - return $text; -} - - -sub _TokenizeHTML { -# -# Parameter: String containing HTML markup. -# Returns: Reference to an array of the tokens comprising the input -# string. Each token is either a tag (possibly with nested, -# tags contained therein, such as , or a -# run of text between tags. Each element of the array is a -# two-element array; the first is either 'tag' or 'text'; -# the second is the actual value. -# -# -# Derived from the _tokenize() subroutine from Brad Choate's MTRegex plugin. -# -# - - my $str = shift; - my $pos = 0; - my $len = length $str; - my @tokens; - - my $depth = 6; - my $nested_tags = join('|', ('(?:<[a-z/!$](?:[^<>]') x $depth) . (')*>)' x $depth); - my $match = qr/(?s: ) | # comment - (?s: <\? .*? \?> ) | # processing instruction - $nested_tags/ix; # nested tags - - while ($str =~ m/($match)/g) { - my $whole_tag = $1; - my $sec_start = pos $str; - my $tag_start = $sec_start - length $whole_tag; - if ($pos < $tag_start) { - push @tokens, ['text', substr($str, $pos, $tag_start - $pos)]; - } - push @tokens, ['tag', $whole_tag]; - $pos = pos $str; - } - push @tokens, ['text', substr($str, $pos, $len - $pos)] if $pos < $len; - \@tokens; -} - - -sub _Outdent { -# -# Remove one level of line-leading tabs or spaces -# - my $text = shift; - - $text =~ s/^(\t|[ ]{1,$g_tab_width})//gm; - return $text; -} - - -sub _Detab { -# -# Cribbed from a post by Bart Lateur: -# -# - my $text = shift; - - $text =~ s{(.*?)\t}{$1.(' ' x ($g_tab_width - length($1) % $g_tab_width))}ge; - return $text; -} - - -1; - -__END__ - - -=pod - -=head1 NAME - -B - - -=head1 SYNOPSIS - -B [ B<--html4tags> ] [ B<--version> ] [ B<-shortversion> ] - [ I ... ] - - -=head1 DESCRIPTION - -Markdown is a text-to-HTML filter; it translates an easy-to-read / -easy-to-write structured text format into HTML. Markdown's text format -is most similar to that of plain text email, and supports features such -as headers, *emphasis*, code blocks, blockquotes, and links. - -Markdown's syntax is designed not as a generic markup language, but -specifically to serve as a front-end to (X)HTML. You can use span-level -HTML tags anywhere in a Markdown document, and you can use block level -HTML tags (like
    and as well). - -For more information about Markdown's syntax, see: - - http://daringfireball.net/projects/markdown/ - - -=head1 OPTIONS - -Use "--" to end switch parsing. For example, to open a file named "-z", use: - - Markdown.pl -- -z - -=over 4 - - -=item B<--html4tags> - -Use HTML 4 style for empty element tags, e.g.: - -
    - -instead of Markdown's default XHTML style tags, e.g.: - -
    - - -=item B<-v>, B<--version> - -Display Markdown's version number and copyright information. - - -=item B<-s>, B<--shortversion> - -Display the short-form version number. - - -=back - - - -=head1 BUGS - -To file bug reports or feature requests (other than topics listed in the -Caveats section above) please send email to: - - support@daringfireball.net - -Please include with your report: (1) the example input; (2) the output -you expected; (3) the output Markdown actually produced. - - -=head1 VERSION HISTORY - -See the readme file for detailed release notes for this version. - -1.0.2b2 - 20 Mar 2005 - - + Fix for nested sub-lists in list-paragraph mode. Previously we got - a spurious extra level of `

    ` tags for something like this: - - * this - - * sub - - that - - + Experimental support for [this] as a synonym for [this][]. - (Note to self: No test yet for this.) - Be sure to test, e.g.: [permutations of this sort of [thing][].] - - -1.0.2b1 - 28 Feb 2005 - - + Fix for backticks within HTML tag: like this - - + Fix for escaped backticks still triggering code spans: - - There are two raw backticks here: \` and here: \`, not a code span - -1.0.1 - 14 Dec 2004 - -1.0 - 28 Aug 2004 - - -=head1 AUTHOR - - John Gruber - http://daringfireball.net - - PHP port and other contributions by Michel Fortin - http://michelf.com - - -=head1 COPYRIGHT AND LICENSE - -Copyright (c) 2003-2005 John Gruber - -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - -* Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -* Neither the name "Markdown" nor the names of its contributors may - be used to endorse or promote products derived from this software - without specific prior written permission. - -This software is provided by the copyright holders and contributors "as -is" and any express or implied warranties, including, but not limited -to, the implied warranties of merchantability and fitness for a -particular purpose are disclaimed. In no event shall the copyright owner -or contributors be liable for any direct, indirect, incidental, special, -exemplary, or consequential damages (including, but not limited to, -procurement of substitute goods or services; loss of use, data, or -profits; or business interruption) however caused and on any theory of -liability, whether in contract, strict liability, or tort (including -negligence or otherwise) arising in any way out of the use of this -software, even if advised of the possibility of such damage. - -=cut diff --git a/node_modules/node-markdown/lib/vendor/showdown/perlMarkdown/Markdown-1.0.2b7.pl b/node_modules/node-markdown/lib/vendor/showdown/perlMarkdown/Markdown-1.0.2b7.pl deleted file mode 100644 index c3b351fed2..0000000000 --- a/node_modules/node-markdown/lib/vendor/showdown/perlMarkdown/Markdown-1.0.2b7.pl +++ /dev/null @@ -1,1642 +0,0 @@ -#!/usr/bin/env perl - -# -# Markdown -- A text-to-HTML conversion tool for web writers -# -# Copyright (c) 2004-2005 John Gruber -# -# - - -package Markdown; -require 5.006_000; -use strict; -use warnings; - -use Digest::MD5 qw(md5_hex); -use vars qw($VERSION); -$VERSION = '1.0.2b7'; -# Tue 29 Aug 2006 - -## Disabled; causes problems under Perl 5.6.1: -# use utf8; -# binmode( STDOUT, ":utf8" ); # c.f.: http://acis.openlib.org/dev/perl-unicode-struggle.html - -# -# Global default settings: -# -my $g_empty_element_suffix = " />"; # Change to ">" for HTML output -my $g_tab_width = 4; - - -# -# Globals: -# - -# Regex to match balanced [brackets]. See Friedl's -# "Mastering Regular Expressions", 2nd Ed., pp. 328-331. -my $g_nested_brackets; -$g_nested_brackets = qr{ - (?> # Atomic matching - [^\[\]]+ # Anything other than brackets - | - \[ - (??{ $g_nested_brackets }) # Recursive set of nested brackets - \] - )* -}x; - - -# Table of hash values for escaped characters: -my %g_escape_table; -foreach my $char (split //, '\\`*_{}[]()>#+-.!') { - $g_escape_table{$char} = md5_hex($char); -} - - -# Global hashes, used by various utility routines -my %g_urls; -my %g_titles; -my %g_html_blocks; - -# Used to track when we're inside an ordered or unordered list -# (see _ProcessListItems() for details): -my $g_list_level = 0; - - -#### Blosxom plug-in interface ########################################## - -# Set $g_blosxom_use_meta to 1 to use Blosxom's meta plug-in to determine -# which posts Markdown should process, using a "meta-markup: markdown" -# header. If it's set to 0 (the default), Markdown will process all -# entries. -my $g_blosxom_use_meta = 0; - -sub start { 1; } -sub story { - my($pkg, $path, $filename, $story_ref, $title_ref, $body_ref) = @_; - - if ( (! $g_blosxom_use_meta) or - (defined($meta::markup) and ($meta::markup =~ /^\s*markdown\s*$/i)) - ){ - $$body_ref = Markdown($$body_ref); - } - 1; -} - - -#### Movable Type plug-in interface ##################################### -eval {require MT}; # Test to see if we're running in MT. -unless ($@) { - require MT; - import MT; - require MT::Template::Context; - import MT::Template::Context; - - eval {require MT::Plugin}; # Test to see if we're running >= MT 3.0. - unless ($@) { - require MT::Plugin; - import MT::Plugin; - my $plugin = new MT::Plugin({ - name => "Markdown", - description => "A plain-text-to-HTML formatting plugin. (Version: $VERSION)", - doc_link => 'http://daringfireball.net/projects/markdown/' - }); - MT->add_plugin( $plugin ); - } - - MT::Template::Context->add_container_tag(MarkdownOptions => sub { - my $ctx = shift; - my $args = shift; - my $builder = $ctx->stash('builder'); - my $tokens = $ctx->stash('tokens'); - - if (defined ($args->{'output'}) ) { - $ctx->stash('markdown_output', lc $args->{'output'}); - } - - defined (my $str = $builder->build($ctx, $tokens) ) - or return $ctx->error($builder->errstr); - $str; # return value - }); - - MT->add_text_filter('markdown' => { - label => 'Markdown', - docs => 'http://daringfireball.net/projects/markdown/', - on_format => sub { - my $text = shift; - my $ctx = shift; - my $raw = 0; - if (defined $ctx) { - my $output = $ctx->stash('markdown_output'); - if (defined $output && $output =~ m/^html/i) { - $g_empty_element_suffix = ">"; - $ctx->stash('markdown_output', ''); - } - elsif (defined $output && $output eq 'raw') { - $raw = 1; - $ctx->stash('markdown_output', ''); - } - else { - $raw = 0; - $g_empty_element_suffix = " />"; - } - } - $text = $raw ? $text : Markdown($text); - $text; - }, - }); - - # If SmartyPants is loaded, add a combo Markdown/SmartyPants text filter: - my $smartypants; - - { - no warnings "once"; - $smartypants = $MT::Template::Context::Global_filters{'smarty_pants'}; - } - - if ($smartypants) { - MT->add_text_filter('markdown_with_smartypants' => { - label => 'Markdown With SmartyPants', - docs => 'http://daringfireball.net/projects/markdown/', - on_format => sub { - my $text = shift; - my $ctx = shift; - if (defined $ctx) { - my $output = $ctx->stash('markdown_output'); - if (defined $output && $output eq 'html') { - $g_empty_element_suffix = ">"; - } - else { - $g_empty_element_suffix = " />"; - } - } - $text = Markdown($text); - $text = $smartypants->($text, '1'); - }, - }); - } -} -else { -#### BBEdit/command-line text filter interface ########################## -# Needs to be hidden from MT (and Blosxom when running in static mode). - - # We're only using $blosxom::version once; tell Perl not to warn us: - no warnings 'once'; - unless ( defined($blosxom::version) ) { - use warnings; - - #### Check for command-line switches: ################# - my %cli_opts; - use Getopt::Long; - Getopt::Long::Configure('pass_through'); - GetOptions(\%cli_opts, - 'version', - 'shortversion', - 'html4tags', - ); - if ($cli_opts{'version'}) { # Version info - print "\nThis is Markdown, version $VERSION.\n"; - print "Copyright 2004 John Gruber\n"; - print "http://daringfireball.net/projects/markdown/\n\n"; - exit 0; - } - if ($cli_opts{'shortversion'}) { # Just the version number string. - print $VERSION; - exit 0; - } - if ($cli_opts{'html4tags'}) { # Use HTML tag style instead of XHTML - $g_empty_element_suffix = ">"; - } - - - #### Process incoming text: ########################### - my $text; - { - local $/; # Slurp the whole file - $text = <>; - } - print Markdown($text); - } -} - - - -sub Markdown { -# -# Main function. The order in which other subs are called here is -# essential. Link and image substitutions need to happen before -# _EscapeSpecialCharsWithinTagAttributes(), so that any *'s or _'s in the -# and tags get encoded. -# - my $text = shift; - - # Clear the global hashes. If we don't clear these, you get conflicts - # from other articles when generating a page which contains more than - # one article (e.g. an index page that shows the N most recent - # articles): - %g_urls = (); - %g_titles = (); - %g_html_blocks = (); - - - # Standardize line endings: - $text =~ s{\r\n}{\n}g; # DOS to Unix - $text =~ s{\r}{\n}g; # Mac to Unix - - # Make sure $text ends with a couple of newlines: - $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 =~ s/^[ \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); - - return $text . "\n"; -} - - -sub _StripLinkDefinitions { -# -# Strips link definitions from text, stores the URLs and titles in -# hash references. -# - my $text = shift; - my $less_than_tab = $g_tab_width - 1; - - # Link defs are in the form: ^[id]: url "optional title" - while ($text =~ s{ - ^[ ]{0,$less_than_tab}\[(.+)\]: # id = $1 - [ \t]* - \n? # maybe *one* newline - [ \t]* - ? # url = $2 - [ \t]* - \n? # maybe one newline - [ \t]* - (?: - (?<=\s) # lookbehind for whitespace - ["(] - (.+?) # title = $3 - [")] - [ \t]* - )? # title is optional - (?:\n+|\Z) - } - {}mx) { - $g_urls{lc $1} = _EncodeAmpsAndAngles( $2 ); # Link IDs are case-insensitive - if ($3) { - $g_titles{lc $1} = $3; - $g_titles{lc $1} =~ s/"/"/g; - } - } - - return $text; -} - - -sub _HashHTMLBlocks { - my $text = shift; - my $less_than_tab = $g_tab_width - 1; - - # Hashify HTML blocks: - # We only want to do this for block-level HTML tags, such as headers, - # lists, and tables. That's because we still want to wrap

    s around - # "paragraphs" that are wrapped in non-block-level tags, such as anchors, - # phrase emphasis, and spans. The list of tags we're looking for is - # hard-coded: - my $block_tags = qr{ - (?: - p | div | h[1-6] | blockquote | pre | table | - dl | ol | ul | script | noscript | form | - fieldset | iframe | math | ins | del - ) - }x; - - my $tag_attrs = qr{ - (?: # Match one attr name/value pair - \s+ # There needs to be at least some whitespace - # before each attribute name. - [\w.:_-]+ # Attribute name - \s*=\s* - (["']) # Attribute quoter - .+? # Attribute value - \1 # Closing quoter - )* # Zero or more - }x; - - my $empty_tag = qr{< \w+ $tag_attrs \s* />}xms; - my $open_tag = qr{< $block_tags $tag_attrs \s* >}xms; - my $close_tag = undef; # let Text::Balanced handle this - - use Text::Balanced qw(gen_extract_tagged); - my $extract_block = gen_extract_tagged($open_tag, $close_tag, undef, { ignore => [$empty_tag] }); - - my @chunks; - ## TO-DO: the 0,3 on the next line ought to respect the - ## tabwidth, or else, we should mandate 4-space tabwidth and - ## be done with it: - while ($text =~ s{^(([ ]{0,3}<)?.*\n)}{}m) { - my $cur_line = $1; - if (defined $2) { - # current line could be start of code block - - my ($tag, $remainder) = $extract_block->($cur_line . $text); - if ($tag) { - my $key = md5_hex($tag); - $g_html_blocks{$key} = $tag; - push @chunks, "\n\n" . $key . "\n\n"; - $text = $remainder; - } - else { - # No tag match, so toss $cur_line into @chunks - push @chunks, $cur_line; - } - } - else { - # current line could NOT be start of code block - push @chunks, $cur_line; - } - - } - push @chunks, $text; # Whatever is left. - - $text = join '', @chunks; - - - - # Special case just for


    . It was easier to make a special case than - # to make the other regex more complicated. - $text =~ s{ - (?: - (?<=\n\n) # Starting after a blank line - | # or - \A\n? # the beginning of the doc - ) - ( # save in $1 - [ ]{0,$less_than_tab} - <(hr) # start tag = $2 - \b # word break - ([^<>])*? # - /?> # the matching end tag - [ \t]* - (?=\n{2,}|\Z) # followed by a blank line or end of document - ) - }{ - my $key = md5_hex($1); - $g_html_blocks{$key} = $1; - "\n\n" . $key . "\n\n"; - }egx; - - # Special case for standalone HTML comments: - $text =~ s{ - (?: - (?<=\n\n) # Starting after a blank line - | # or - \A\n? # the beginning of the doc - ) - ( # save in $1 - [ ]{0,$less_than_tab} - (?s: - - ) - [ \t]* - (?=\n{2,}|\Z) # followed by a blank line or end of document - ) - }{ - my $key = md5_hex($1); - $g_html_blocks{$key} = $1; - "\n\n" . $key . "\n\n"; - }egx; - - # PHP and ASP-style processor instructions ( and <%…%>) - $text =~ s{ - (?: - (?<=\n\n) # Starting after a blank line - | # or - \A\n? # the beginning of the doc - ) - ( # save in $1 - [ ]{0,$less_than_tab} - (?s: - <([?%]) # $2 - .*? - \2> - ) - [ \t]* - (?=\n{2,}|\Z) # followed by a blank line or end of document - ) - }{ - my $key = md5_hex($1); - $g_html_blocks{$key} = $1; - "\n\n" . $key . "\n\n"; - }egx; - - - return $text; -} - - -sub _RunBlockGamut { -# -# These are all the transformations that form block-level -# tags like paragraphs, headers, and list items. -# - my $text = shift; - - $text = _DoHeaders($text); - - # Do Horizontal Rules: - $text =~ s{^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$}{\n tags around block-level tags. - $text = _HashHTMLBlocks($text); - $text = _FormParagraphs($text); - - return $text; -} - - -sub _RunSpanGamut { -# -# These are all the transformations that occur *within* block-level -# tags like paragraphs, headers, and list items. -# - my $text = shift; - - $text = _DoCodeSpans($text); - $text = _EscapeSpecialCharsWithinTagAttributes($text); - $text = _EncodeBackslashEscapes($text); - - # Process anchor and image tags. Images must come first, - # because ![foo][f] looks like an anchor. - $text = _DoImages($text); - $text = _DoAnchors($text); - - # Make links out of things like `` - # Must come after _DoAnchors(), because you can use < and > - # delimiters in inline links like [this](). - $text = _DoAutoLinks($text); - $text = _EncodeAmpsAndAngles($text); - $text = _DoItalicsAndBold($text); - - # Do hard breaks: - $text =~ s/ {2,}\n/ -- encode [\ ` * _] so they -# don't conflict with their use in Markdown for code, italics and strong. -# We're replacing each such character with its corresponding MD5 checksum -# value; this is likely overkill, but it should prevent us from colliding -# with the escape values by accident. -# - my $text = shift; - my $tokens ||= _TokenizeHTML($text); - $text = ''; # rebuild $text from the tokens - - foreach my $cur_token (@$tokens) { - if ($cur_token->[0] eq "tag") { - $cur_token->[1] =~ s! \\ !$g_escape_table{'\\'}!gx; - $cur_token->[1] =~ s{ (?<=.)(?=.) }{$g_escape_table{'`'}}gx; - $cur_token->[1] =~ s! \* !$g_escape_table{'*'}!gx; - $cur_token->[1] =~ s! _ !$g_escape_table{'_'}!gx; - } - $text .= $cur_token->[1]; - } - return $text; -} - - -sub _DoAnchors { -# -# Turn Markdown link shortcuts into XHTML tags. -# - my $text = shift; - - # - # First, handle reference-style links: [link text] [id] - # - $text =~ s{ - ( # wrap whole match in $1 - \[ - ($g_nested_brackets) # link text = $2 - \] - - [ ]? # one optional space - (?:\n[ ]*)? # one optional newline followed by spaces - - \[ - (.*?) # id = $3 - \] - ) - }{ - my $result; - my $whole_match = $1; - my $link_text = $2; - my $link_id = lc $3; - - if ($link_id eq "") { - $link_id = lc $link_text; # for shortcut links like [this][]. - } - - if (defined $g_urls{$link_id}) { - my $url = $g_urls{$link_id}; - $url =~ s! \* !$g_escape_table{'*'}!gx; # We've got to encode these to avoid - $url =~ s! _ !$g_escape_table{'_'}!gx; # conflicting with italics/bold. - $result = "? # href = $3 - [ \t]* - ( # $4 - (['"]) # quote char = $5 - (.*?) # Title = $6 - \5 # matching quote - [ \t]* # ignore any spaces/tabs between closing quote and ) - )? # title is optional - \) - ) - }{ - my $result; - my $whole_match = $1; - my $link_text = $2; - my $url = $3; - my $title = $6; - - $url =~ s! \* !$g_escape_table{'*'}!gx; # We've got to encode these to avoid - $url =~ s! _ !$g_escape_table{'_'}!gx; # conflicting with italics/bold. - $result = " tags. -# - my $text = shift; - - # - # First, handle reference-style labeled images: ![alt text][id] - # - $text =~ s{ - ( # wrap whole match in $1 - !\[ - (.*?) # alt text = $2 - \] - - [ ]? # one optional space - (?:\n[ ]*)? # one optional newline followed by spaces - - \[ - (.*?) # id = $3 - \] - - ) - }{ - my $result; - my $whole_match = $1; - my $alt_text = $2; - my $link_id = lc $3; - - if ($link_id eq "") { - $link_id = lc $alt_text; # for shortcut links like ![this][]. - } - - $alt_text =~ s/"/"/g; - if (defined $g_urls{$link_id}) { - my $url = $g_urls{$link_id}; - $url =~ s! \* !$g_escape_table{'*'}!gx; # We've got to encode these to avoid - $url =~ s! _ !$g_escape_table{'_'}!gx; # conflicting with italics/bold. - $result = "\"$alt_text\"";? # src url = $3 - [ \t]* - ( # $4 - (['"]) # quote char = $5 - (.*?) # title = $6 - \5 # matching quote - [ \t]* - )? # title is optional - \) - ) - }{ - my $result; - my $whole_match = $1; - my $alt_text = $2; - my $url = $3; - my $title = ''; - if (defined($6)) { - $title = $6; - } - - $alt_text =~ s/"/"/g; - $title =~ s/"/"/g; - $url =~ s! \* !$g_escape_table{'*'}!gx; # We've got to encode these to avoid - $url =~ s! _ !$g_escape_table{'_'}!gx; # conflicting with italics/bold. - $result = "\"$alt_text\"";" . _RunSpanGamut($1) . "\n\n"; - }egmx; - - $text =~ s{ ^(.+)[ \t]*\n-+[ \t]*\n+ }{ - "

    " . _RunSpanGamut($1) . "

    \n\n"; - }egmx; - - - # atx-style headers: - # # Header 1 - # ## Header 2 - # ## Header 2 with closing hashes ## - # ... - # ###### Header 6 - # - $text =~ s{ - ^(\#{1,6}) # $1 = string of #'s - [ \t]* - (.+?) # $2 = Header text - [ \t]* - \#* # optional closing #'s (not counted) - \n+ - }{ - my $h_level = length($1); - "" . _RunSpanGamut($2) . "\n\n"; - }egmx; - - return $text; -} - - -sub _DoLists { -# -# Form HTML ordered (numbered) and unordered (bulleted) lists. -# - my $text = shift; - my $less_than_tab = $g_tab_width - 1; - - # Re-usable patterns to match list item bullets and number markers: - my $marker_ul = qr/[*+-]/; - my $marker_ol = qr/\d+[.]/; - my $marker_any = qr/(?:$marker_ul|$marker_ol)/; - - # Re-usable pattern to match any entirel ul or ol list: - my $whole_list = qr{ - ( # $1 = whole list - ( # $2 - [ ]{0,$less_than_tab} - (${marker_any}) # $3 = first list item marker - [ \t]+ - ) - (?s:.+?) - ( # $4 - \z - | - \n{2,} - (?=\S) - (?! # Negative lookahead for another list item marker - [ \t]* - ${marker_any}[ \t]+ - ) - ) - ) - }mx; - - # We use a different prefix before nested lists than top-level lists. - # See extended comment in _ProcessListItems(). - # - # Note: There's a bit of duplication here. My original implementation - # created a scalar regex pattern as the conditional result of the test on - # $g_list_level, and then only ran the $text =~ s{...}{...}egmx - # substitution once, using the scalar as the pattern. This worked, - # everywhere except when running under MT on my hosting account at Pair - # Networks. There, this caused all rebuilds to be killed by the reaper (or - # perhaps they crashed, but that seems incredibly unlikely given that the - # same script on the same server ran fine *except* under MT. I've spent - # more time trying to figure out why this is happening than I'd like to - # admit. My only guess, backed up by the fact that this workaround works, - # is that Perl optimizes the substition when it can figure out that the - # pattern will never change, and when this optimization isn't on, we run - # afoul of the reaper. Thus, the slightly redundant code that uses two - # static s/// patterns rather than one conditional pattern. - - if ($g_list_level) { - $text =~ s{ - ^ - $whole_list - }{ - my $list = $1; - my $list_type = ($3 =~ m/$marker_ul/) ? "ul" : "ol"; - - # Turn double returns into triple returns, so that we can make a - # paragraph for the last item in a list, if necessary: - $list =~ s/\n{2,}/\n\n\n/g; - my $result = _ProcessListItems($list, $marker_any); - - # Trim any trailing whitespace, to put the closing `` - # up on the preceding line, to get it past the current stupid - # HTML block parser. This is a hack to work around the terrible - # hack that is the HTML block parser. - $result =~ s{\s+$}{}; - $result = "<$list_type>" . $result . "\n"; - $result; - }egmx; - } - else { - $text =~ s{ - (?:(?<=\n\n)|\A\n?) - $whole_list - }{ - my $list = $1; - my $list_type = ($3 =~ m/$marker_ul/) ? "ul" : "ol"; - # Turn double returns into triple returns, so that we can make a - # paragraph for the last item in a list, if necessary: - $list =~ s/\n{2,}/\n\n\n/g; - my $result = _ProcessListItems($list, $marker_any); - $result = "<$list_type>\n" . $result . "\n"; - $result; - }egmx; - } - - - return $text; -} - - -sub _ProcessListItems { -# -# Process the contents of a single ordered or unordered list, splitting it -# into individual list items. -# - - my $list_str = shift; - my $marker_any = shift; - - - # 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 =~ s/\n{2,}\z/\n/; - - - $list_str =~ s{ - (\n)? # leading line = $1 - (^[ \t]*) # leading whitespace = $2 - ($marker_any) [ \t]+ # list marker = $3 - ((?s:.+?) # list item text = $4 - (\n{1,2})) - (?= \n* (\z | \2 ($marker_any) [ \t]+)) - }{ - my $item = $4; - my $leading_line = $1; - my $leading_space = $2; - - if ($leading_line or ($item =~ m/\n{2,}/)) { - $item = _RunBlockGamut(_Outdent($item)); - } - else { - # Recursion for sub-lists: - $item = _DoLists(_Outdent($item)); - chomp $item; - $item = _RunSpanGamut($item); - } - - "
  • " . $item . "
  • \n"; - }egmx; - - $g_list_level--; - return $list_str; -} - - - -sub _DoCodeBlocks { -# -# Process Markdown `
    ` blocks.
    -#	
    -
    -	my $text = shift;
    -
    -	$text =~ s{
    -			(?:\n\n|\A)
    -			(	            # $1 = the code block -- one or more lines, starting with a space/tab
    -			  (?:
    -			    (?:[ ]{$g_tab_width} | \t)  # Lines must start with a tab or a tab-width of spaces
    -			    .*\n+
    -			  )+
    -			)
    -			((?=^[ ]{0,$g_tab_width}\S)|\Z)	# Lookahead for non-space at line-start, or end of doc
    -		}{
    -			my $codeblock = $1;
    -			my $result; # return value
    -
    -			$codeblock = _EncodeCode(_Outdent($codeblock));
    -			$codeblock = _Detab($codeblock);
    -			$codeblock =~ s/\A\n+//; # trim leading newlines
    -			$codeblock =~ s/\n+\z//; # trim trailing newlines
    -
    -			$result = "\n\n
    " . $codeblock . "\n
    \n\n"; - - $result; - }egmx; - - return $text; -} - - -sub _DoCodeSpans { -# -# * Backtick quotes are used for spans. -# -# * You can use multiple backticks as the delimiters if you want to -# include literal backticks in the code span. So, this input: -# -# Just type ``foo `bar` baz`` at the prompt. -# -# Will translate to: -# -#

    Just type foo `bar` baz at the prompt.

    -# -# There's no arbitrary limit to the number of backticks you -# can use as delimters. If you need three consecutive backticks -# in your code, use four for delimiters, etc. -# -# * You can use spaces to get literal backticks at the edges: -# -# ... type `` `bar` `` ... -# -# Turns to: -# -# ... type `bar` ... -# - - my $text = shift; - - $text =~ s@ - (?$c
    "; - @egsx; - - return $text; -} - - -sub _EncodeCode { -# -# Encode/escape certain characters inside Markdown code runs. -# The point is that in code, these characters are literals, -# and lose their special Markdown meanings. -# - local $_ = shift; - - # Encode all ampersands; HTML entities are not - # entities within a Markdown code span. - s/&/&/g; - - # Encode $'s, but only if we're running under Blosxom. - # (Blosxom interpolates Perl variables in article bodies.) - { - no warnings 'once'; - if (defined($blosxom::version)) { - s/\$/$/g; - } - } - - - # Do the angle bracket song and dance: - s! < !<!gx; - s! > !>!gx; - - # Now, escape characters that are magic in Markdown: - s! \* !$g_escape_table{'*'}!gx; - s! _ !$g_escape_table{'_'}!gx; - s! { !$g_escape_table{'{'}!gx; - s! } !$g_escape_table{'}'}!gx; - s! \[ !$g_escape_table{'['}!gx; - s! \] !$g_escape_table{']'}!gx; - s! \\ !$g_escape_table{'\\'}!gx; - - return $_; -} - - -sub _DoItalicsAndBold { - my $text = shift; - - # must go first: - $text =~ s{ (\*\*|__) (?=\S) (.+?[*_]*) (?<=\S) \1 } - {$2}gsx; - - $text =~ s{ (\*|_) (?=\S) (.+?) (?<=\S) \1 } - {$2}gsx; - - return $text; -} - - -sub _DoBlockQuotes { - my $text = shift; - - $text =~ s{ - ( # 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 - )+ - ) - }{ - my $bq = $1; - $bq =~ s/^[ \t]*>[ \t]?//gm; # trim one level of quoting - $bq =~ s/^[ \t]+$//mg; # trim whitespace-only lines - $bq = _RunBlockGamut($bq); # recurse - - $bq =~ s/^/ /g; - # These leading spaces screw with
     content, so we need to fix that:
    -			$bq =~ s{
    -					(\s*
    .+?
    ) - }{ - my $pre = $1; - $pre =~ s/^ //mg; - $pre; - }egsx; - - "
    \n$bq\n
    \n\n"; - }egmx; - - - return $text; -} - - -sub _FormParagraphs { -# -# Params: -# $text - string to process with html

    tags -# - my $text = shift; - - # Strip leading and trailing lines: - $text =~ s/\A\n+//; - $text =~ s/\n+\z//; - - my @grafs = split(/\n{2,}/, $text); - - # - # Wrap

    tags. - # - foreach (@grafs) { - unless (defined( $g_html_blocks{$_} )) { - $_ = _RunSpanGamut($_); - s/^([ \t]*)/

    /; - $_ .= "

    "; - } - } - - # - # Unhashify HTML blocks - # -# foreach my $graf (@grafs) { -# my $block = $g_html_blocks{$graf}; -# if (defined $block) { -# $graf = $block; -# } -# } - - foreach my $graf (@grafs) { - # Modify elements of @grafs in-place... - my $block = $g_html_blocks{$graf}; - if (defined $block) { - $graf = $block; - if ($block =~ m{ - \A - ( # $1 =
    tag -
    ]* - \b - markdown\s*=\s* (['"]) # $2 = attr quote char - 1 - \2 - [^>]* - > - ) - ( # $3 = contents - .* - ) - (
    ) # $4 = closing tag - \z - - }xms - ) { - my ($div_open, $div_content, $div_close) = ($1, $3, $4); - - # We can't call Markdown(), because that resets the hash; - # that initialization code should be pulled into its own sub, though. - $div_content = _HashHTMLBlocks($div_content); - $div_content = _StripLinkDefinitions($div_content); - $div_content = _RunBlockGamut($div_content); - $div_content = _UnescapeSpecialChars($div_content); - - $div_open =~ s{\smarkdown\s*=\s*(['"]).+?\1}{}ms; - - $graf = $div_open . "\n" . $div_content . "\n" . $div_close; - } - } - } - - - return join "\n\n", @grafs; -} - - -sub _EncodeAmpsAndAngles { -# Smart processing for ampersands and angle brackets that need to be encoded. - - my $text = shift; - - # Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin: - # http://bumppo.net/projects/amputator/ - $text =~ s/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/&/g; - - # Encode naked <'s - $text =~ s{<(?![a-z/?\$!])}{<}gi; - - return $text; -} - - -sub _EncodeBackslashEscapes { -# -# Parameter: String. -# Returns: The string, with after processing the following backslash -# escape sequences. -# - local $_ = shift; - - s! \\\\ !$g_escape_table{'\\'}!gx; # Must process escaped backslashes first. - s! \\` !$g_escape_table{'`'}!gx; - s! \\\* !$g_escape_table{'*'}!gx; - s! \\_ !$g_escape_table{'_'}!gx; - s! \\\{ !$g_escape_table{'{'}!gx; - s! \\\} !$g_escape_table{'}'}!gx; - s! \\\[ !$g_escape_table{'['}!gx; - s! \\\] !$g_escape_table{']'}!gx; - s! \\\( !$g_escape_table{'('}!gx; - s! \\\) !$g_escape_table{')'}!gx; - s! \\> !$g_escape_table{'>'}!gx; - s! \\\# !$g_escape_table{'#'}!gx; - s! \\\+ !$g_escape_table{'+'}!gx; - s! \\\- !$g_escape_table{'-'}!gx; - s! \\\. !$g_escape_table{'.'}!gx; - s{ \\! }{$g_escape_table{'!'}}gx; - - return $_; -} - - -sub _DoAutoLinks { - my $text = shift; - - $text =~ s{<((https?|ftp|dict):[^'">\s]+)>}{
    $1}gi; - - # Email addresses: - $text =~ s{ - < - (?:mailto:)? - ( - [-.\w]+ - \@ - [-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+ - ) - > - }{ - _EncodeEmailAddress( _UnescapeSpecialChars($1) ); - }egix; - - return $text; -} - - -sub _EncodeEmailAddress { -# -# Input: an email address, e.g. "foo@example.com" -# -# Output: the email address as a mailto link, with each character -# of the address encoded as either a decimal or hex entity, in -# the hopes of foiling most address harvesting spam bots. E.g.: -# -# foo -# @example.com -# -# Based on a filter by Matthew Wickline, posted to the BBEdit-Talk -# mailing list: -# - - my $addr = shift; - - srand; - my @encode = ( - sub { '&#' . ord(shift) . ';' }, - sub { '&#x' . sprintf( "%X", ord(shift) ) . ';' }, - sub { shift }, - ); - - $addr = "mailto:" . $addr; - - $addr =~ s{(.)}{ - my $char = $1; - if ( $char eq '@' ) { - # this *must* be encoded. I insist. - $char = $encode[int rand 1]->($char); - } elsif ( $char ne ':' ) { - # leave ':' alone (to spot mailto: later) - my $r = rand; - # roughly 10% raw, 45% hex, 45% dec - $char = ( - $r > .9 ? $encode[2]->($char) : - $r < .45 ? $encode[1]->($char) : - $encode[0]->($char) - ); - } - $char; - }gex; - - $addr = qq{$addr}; - $addr =~ s{">.+?:}{">}; # strip the mailto: from the visible part - - return $addr; -} - - -sub _UnescapeSpecialChars { -# -# Swap back in all the special characters we've hidden. -# - my $text = shift; - - while( my($char, $hash) = each(%g_escape_table) ) { - $text =~ s/$hash/$char/g; - } - return $text; -} - - -sub _TokenizeHTML { -# -# Parameter: String containing HTML markup. -# Returns: Reference to an array of the tokens comprising the input -# string. Each token is either a tag (possibly with nested, -# tags contained therein, such as , or a -# run of text between tags. Each element of the array is a -# two-element array; the first is either 'tag' or 'text'; -# the second is the actual value. -# -# -# Derived from the _tokenize() subroutine from Brad Choate's MTRegex plugin. -# -# - - my $str = shift; - my $pos = 0; - my $len = length $str; - my @tokens; - - my $depth = 6; - my $nested_tags = join('|', ('(?:<[a-z/!$](?:[^<>]') x $depth) . (')*>)' x $depth); - my $match = qr/(?s: ) | # comment - (?s: <\? .*? \?> ) | # processing instruction - $nested_tags/ix; # nested tags - - while ($str =~ m/($match)/g) { - my $whole_tag = $1; - my $sec_start = pos $str; - my $tag_start = $sec_start - length $whole_tag; - if ($pos < $tag_start) { - push @tokens, ['text', substr($str, $pos, $tag_start - $pos)]; - } - push @tokens, ['tag', $whole_tag]; - $pos = pos $str; - } - push @tokens, ['text', substr($str, $pos, $len - $pos)] if $pos < $len; - - return \@tokens; -} - - -sub _Outdent { -# -# Remove one level of line-leading tabs or spaces -# - my $text = shift; - - $text =~ s/^(\t|[ ]{1,$g_tab_width})//gm; - return $text; -} - - -sub _Detab { -# -# Cribbed from a post by Bart Lateur: -# -# - my $text = shift; - - $text =~ s{(.*?)\t}{$1.(' ' x ($g_tab_width - length($1) % $g_tab_width))}ge; - return $text; -} - - -1; - -__END__ - - -=pod - -=head1 NAME - -B - - -=head1 SYNOPSIS - -B [ B<--html4tags> ] [ B<--version> ] [ B<-shortversion> ] - [ I ... ] - - -=head1 DESCRIPTION - -Markdown is a text-to-HTML filter; it translates an easy-to-read / -easy-to-write structured text format into HTML. Markdown's text format -is most similar to that of plain text email, and supports features such -as headers, *emphasis*, code blocks, blockquotes, and links. - -Markdown's syntax is designed not as a generic markup language, but -specifically to serve as a front-end to (X)HTML. You can use span-level -HTML tags anywhere in a Markdown document, and you can use block level -HTML tags (like
    and
    as well). - -For more information about Markdown's syntax, see: - - http://daringfireball.net/projects/markdown/ - - -=head1 OPTIONS - -Use "--" to end switch parsing. For example, to open a file named "-z", use: - - Markdown.pl -- -z - -=over 4 - - -=item B<--html4tags> - -Use HTML 4 style for empty element tags, e.g.: - -
    - -instead of Markdown's default XHTML style tags, e.g.: - -
    - - -=item B<-v>, B<--version> - -Display Markdown's version number and copyright information. - - -=item B<-s>, B<--shortversion> - -Display the short-form version number. - - -=back - - - -=head1 BUGS - -To file bug reports or feature requests (other than topics listed in the -Caveats section above) please send email to: - - support@daringfireball.net - -Please include with your report: (1) the example input; (2) the output -you expected; (3) the output Markdown actually produced. - - -=head1 VERSION HISTORY - -See the readme file for detailed release notes for this version. - -1.0.2b7 - - + Changed shebang line from "/usr/bin/perl" to "/usr/bin/env perl" - - + Now only trim trailing newlines from code blocks, instead of trimming - all trailing whitespace characters. - - -1.0.2b6 - Mon 03 Apr 2006 - - + Fixed bad performance bug in new `Text::Balanced`-based block-level parser. - - -1.0.2b5 - Thu 08 Dec 2005 - - + Fixed bug where this: - - [text](http://m.com "title" ) - - wasn't working as expected, because the parser wasn't allowing for spaces - before the closing paren. - - -1.0.2b4 - Thu 08 Sep 2005 - - + Filthy hack to support markdown='1' in div tags, because I need it - to write today's fireball. - - + First crack at a new, smarter, block-level HTML parser. - -1.0.2b3 - Thu 28 Apr 2005 - - + _DoAutoLinks() now supports the 'dict://' URL scheme. - - + PHP- and ASP-style processor instructions are now protected as - raw HTML blocks. - - - <% ... %> - - + Workarounds for regressions introduced with fix for "backticks within - tags" bug in 1.0.2b1. The fix is to allow `...` to be turned into - ... within an HTML tag attribute, and then to turn - these spurious `` tags back into literal backtick characters - in _EscapeSpecialCharsWithinTagAttributes(). - - The regression was caused because in the fix, we moved - _EscapeSpecialCharsWithinTagAttributes() ahead of _DoCodeSpans() - in _RunSpanGamut(), but that's no good. We need to process code - spans first, otherwise we can get tripped up by something like this: - - `` - - -1.0.2b2 - 20 Mar 2005 - - + Fix for nested sub-lists in list-paragraph mode. Previously we got - a spurious extra level of `

    ` tags for something like this: - - * this - - * sub - - that - - + Experimental support for [this] as a synonym for [this][]. - (Note to self: No test yet for this.) - Be sure to test, e.g.: [permutations of this sort of [thing][].] - - -1.0.2b1 - 28 Feb 2005 - - + Fix for backticks within HTML tag: like this - - + Fix for escaped backticks still triggering code spans: - - There are two raw backticks here: \` and here: \`, not a code span - -1.0.1 - 14 Dec 2004 - -1.0 - 28 Aug 2004 - - -=head1 AUTHOR - - John Gruber - http://daringfireball.net - - PHP port and other contributions by Michel Fortin - http://michelf.com - - -=head1 COPYRIGHT AND LICENSE - -Copyright (c) 2003-2005 John Gruber - -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - -* Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -* Neither the name "Markdown" nor the names of its contributors may - be used to endorse or promote products derived from this software - without specific prior written permission. - -This software is provided by the copyright holders and contributors "as -is" and any express or implied warranties, including, but not limited -to, the implied warranties of merchantability and fitness for a -particular purpose are disclaimed. In no event shall the copyright owner -or contributors be liable for any direct, indirect, incidental, special, -exemplary, or consequential damages (including, but not limited to, -procurement of substitute goods or services; loss of use, data, or -profits; or business interruption) however caused and on any theory of -liability, whether in contract, strict liability, or tort (including -negligence or otherwise) arising in any way out of the use of this -software, even if advised of the possibility of such damage. - -=cut diff --git a/node_modules/node-markdown/lib/vendor/showdown/perlMarkdown/readme.txt b/node_modules/node-markdown/lib/vendor/showdown/perlMarkdown/readme.txt deleted file mode 100644 index 9e73e56c4d..0000000000 --- a/node_modules/node-markdown/lib/vendor/showdown/perlMarkdown/readme.txt +++ /dev/null @@ -1,13 +0,0 @@ -Reference Implementation ------------------------- - -This directory contains John Gruber's original Perl implementation of Markdown. Smart diff programs like Araxis Merge will be able to match up this file with markdown.pl. - -A little tweaking helps. In markdown.pl: - - - replace `#` with `//` - - replace `$text` with `text` - -Be sure to ignore whitespace and line endings. - -Note: This release of Showdown is based on `markdown1.0.2b7.pl`, but uses the HTML parser from `markdown1.0.2b2.pl`. diff --git a/node_modules/node-markdown/lib/vendor/showdown/readme.txt b/node_modules/node-markdown/lib/vendor/showdown/readme.txt deleted file mode 100644 index 4b0d96107f..0000000000 --- a/node_modules/node-markdown/lib/vendor/showdown/readme.txt +++ /dev/null @@ -1,156 +0,0 @@ - -Showdown -- A JavaScript port of Markdown -========================================= - -Showdown Copyright (c) 2007 John Fraser. - - -Original Markdown Copyright (c) 2004-2005 John Gruber - - -Redistributable under a BSD-style open source license. -See license.txt for more information. - - -What's it for? --------------- - -Developers can use Showdown to: - - * Add in-browser preview to existing Markdown apps - - Showdown's output is (almost always) identical to - markdown.pl's, so the server can reproduce exactly - the output that the user saw. (See below for - exceptions.) - - * Add Markdown input to programs that don't support it - - Any app that accepts HTML input can now be made to speak - Markdown by modifying the input pages's HTML. If your - application lets users edit documents again later, - then they won't have access to the original Markdown - text. But this should be good enough for many - uses -- and you can do it with just a two-line - `onsubmit` function! - - * Add Markdown input to closed-source web apps - - You can write bookmarklets or userscripts to extend - any standard textarea on the web so that it accepts - Markdown instead of HTML. With a little more hacking, - the same can probably be done with many rich edit - controls. - - * Build new web apps from scratch - - A Showdown front-end can send back text in Markdown, - HTML or both, so you can trade bandwidth for server - load to reduce your cost of operation. If your app - requires JavaScript, you won't need to do any - Markdown processing on the server at all. (For most - uses, you'll still need to sanitize the HTML before - showing it to other users -- but you'd need to do - that anyway if you're allowing raw HTML in your - Markdown.) - - -Browser Compatibility ---------------------- - -Showdown has been tested successfully with: - - - Firefox 1.5 and 2.0 - - Internet Explorer 6 and 7 - - Safari 2.0.4 - - Opera 8.54 and 9.10 - - Netscape 8.1.2 - - Konqueror 3.5.4 - -In theory, Showdown will work in any browser that supports ECMA 262 3rd Edition (JavaScript 1.5). The converter itself might even work in things that aren't web browsers, like Acrobat. No promises. - - -Known Differences in Output ---------------------------- - -In most cases, Showdown's output is identical to that of Perl Markdown v1.0.2b7. What follows is a list of all known deviations. Please email me if you find more. - - - * This release uses the HTML parser from Markdown 1.0.2b2, - which means it fails `Inline HTML (Advanced).text` from - the Markdown test suite: - -

    -
    - unindented == broken -
    -
    - - - * Showdown doesn't support the markdown="1" attribute: - -
    - Markdown does *not* work in here. -
    - - This is half laziness on my part and half stubbornness. - Markdown is smart enough to process the contents of span- - level tags without screwing things up; shouldn't it be - able to do the same inside block elements? Let's find a - way to make markdown="1" the default. - - - * You can only nest square brackets in link titles to a - depth of two levels: - - [[fine]](http://www.attacklab.net/) - [[[broken]]](http://www.attacklab.net/) - - If you need more, you can escape them with backslashes. - - - * When sublists have paragraphs, Showdown produces equivalent - HTML with a slightly different arrangement of newlines: - - + item - - - subitem - - The HTML has a superfluous newline before this - paragraph. - - - subitem - - The HTML here is unchanged. - - - subitem - - The HTML is missing a newline after this - list subitem. - - - - * Markdown.pl creates empty title attributes for - inline-style images: - - Here's an empty title on an inline-style - ![image](http://w3.org/Icons/valid-xhtml10). - - I tried to replicate this to clean up my diffs during - testing, but I went too far: now Showdown also makes - empty titles for reference-style images: - - Showdown makes an empty title for - reference-style ![images][] too. - - [images]: http://w3.org/Icons/valid-xhtml10 - - - * With crazy input, Markdown will mistakenly put - `` or `` tags in URLs: - -
    - improbable URL - - - Showdown won't. But still, don't do that. diff --git a/node_modules/node-markdown/lib/vendor/showdown/src/showdown.js b/node_modules/node-markdown/lib/vendor/showdown/src/showdown.js deleted file mode 100644 index 35c6fb00d5..0000000000 --- a/node_modules/node-markdown/lib/vendor/showdown/src/showdown.js +++ /dev/null @@ -1,1299 +0,0 @@ -// Line 70, 78 updated to be compatible with node.js module system -// 2010 Andris Reinman - -// -// showdown.js -- A javascript port of Markdown. -// -// Copyright (c) 2007 John Fraser. -// -// Original Markdown Copyright (c) 2004-2005 John Gruber -// -// -// Redistributable under a BSD-style open source license. -// See license.txt for more information. -// -// The full source distribution is at: -// -// A A L -// T C A -// T K B -// -// -// - -// -// Wherever possible, Showdown is a straight, line-by-line port -// of the Perl version of Markdown. -// -// This is not a normal parser design; it's basically just a -// series of string substitutions. It's hard to read and -// maintain this way, but keeping Showdown close to the original -// design makes it easier to port new features. -// -// More importantly, Showdown behaves like markdown.pl in most -// edge cases. So web applications can do client-side preview -// in Javascript, and then build identical HTML on the server. -// -// This port needs the new RegExp functionality of ECMA 262, -// 3rd Edition (i.e. Javascript 1.5). Most modern web browsers -// should do fine. Even with the new regular expression features, -// We do a lot of work to emulate Perl's regex functionality. -// The tricky changes in this file mostly have the "attacklab:" -// label. Major or self-explanatory changes don't. -// -// Smart diff tools like Araxis Merge will be able to match up -// this file with markdown.pl in a useful way. A little tweaking -// helps: in a copy of markdown.pl, replace "#" with "//" and -// replace "$text" with "text". Be sure to ignore whitespace -// and line endings. -// - - -// -// Showdown usage: -// -// var text = "Markdown *rocks*."; -// -// var converter = new Showdown.converter(); -// var html = converter.makeHtml(text); -// -// alert(html); -// -// Note: move the sample code to the bottom of this -// file before uncommenting it. -// - - -// -// Showdown namespace -// -this.Showdown = {}; - -// -// converter -// -// Wraps all "globals" so that the only thing -// exposed is makeHtml(). -// -this.Showdown.converter = function() { - -// -// Globals: -// - -// Global hashes, used by various utility routines -var g_urls; -var g_titles; -var g_html_blocks; - -// Used to track when we're inside an ordered or unordered list -// (see _ProcessListItems() for details): -var g_list_level = 0; - - -this.makeHtml = function(text) { -// -// Main function. The order in which other subs are called here is -// essential. Link and image substitutions need to happen before -// _EscapeSpecialCharsWithinTagAttributes(), so that any *'s or _'s in the -// and tags get encoded. -// - - // Clear the global hashes. If we don't clear these, you get conflicts - // from other articles when generating a page which contains more than - // one article (e.g. an index page that shows the N most recent - // articles): - g_urls = new Array(); - g_titles = new Array(); - g_html_blocks = new Array(); - - // attacklab: Replace ~ with ~T - // This lets us use tilde as an escape char to avoid md5 hashes - // The choice of character is arbitray; anything that isn't - // magic in Markdown will work. - text = text.replace(/~/g,"~T"); - - // attacklab: Replace $ with ~D - // RegExp interprets $ as a special character - // when it's in a replacement string - text = text.replace(/\$/g,"~D"); - - // Standardize line endings - text = text.replace(/\r\n/g,"\n"); // DOS to Unix - text = text.replace(/\r/g,"\n"); // Mac to Unix - - // Make sure text begins and ends with a couple of newlines: - text = "\n\n" + text + "\n\n"; - - // Convert all tabs to spaces. - text = _Detab(text); - - // Strip any lines consisting only of spaces and tabs. - // This makes subsequent regexen easier to write, because we can - // match consecutive blank lines with /\n+/ instead of something - // contorted like /[ \t]*\n+/ . - text = text.replace(/^[ \t]+$/mg,""); - - // Turn block-level HTML blocks into hash entries - text = _HashHTMLBlocks(text); - - // Strip link definitions, store in hashes. - text = _StripLinkDefinitions(text); - - text = _RunBlockGamut(text); - - text = _UnescapeSpecialChars(text); - - // attacklab: Restore dollar signs - text = text.replace(/~D/g,"$$"); - - // attacklab: Restore tildes - text = text.replace(/~T/g,"~"); - - return text; -} - - -var _StripLinkDefinitions = function(text) { -// -// Strips link definitions from text, stores the URLs and titles in -// hash references. -// - - // Link defs are in the form: ^[id]: url "optional title" - - /* - var text = text.replace(/ - ^[ ]{0,3}\[(.+)\]: // id = $1 attacklab: g_tab_width - 1 - [ \t]* - \n? // maybe *one* newline - [ \t]* - ? // url = $2 - [ \t]* - \n? // maybe one newline - [ \t]* - (?: - (\n*) // any lines skipped = $3 attacklab: lookbehind removed - ["(] - (.+?) // title = $4 - [")] - [ \t]* - )? // title is optional - (?:\n+|$) - /gm, - function(){...}); - */ - var text = text.replace(/^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*?[ \t]*\n?[ \t]*(?:(\n*)["(](.+?)[")][ \t]*)?(?:\n+|\Z)/gm, - function (wholeMatch,m1,m2,m3,m4) { - m1 = m1.toLowerCase(); - g_urls[m1] = _EncodeAmpsAndAngles(m2); // Link IDs are case-insensitive - if (m3) { - // Oops, found blank lines, so it's not a title. - // Put back the parenthetical statement we stole. - return m3+m4; - } else if (m4) { - g_titles[m1] = m4.replace(/"/g,"""); - } - - // Completely remove the definition from the text - return ""; - } - ); - - return text; -} - - -var _HashHTMLBlocks = function(text) { - // attacklab: Double up blank lines to reduce lookaround - text = text.replace(/\n/g,"\n\n"); - - // Hashify HTML blocks: - // We only want to do this for block-level HTML tags, such as headers, - // lists, and tables. That's because we still want to wrap

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

    - //
    - // tags for inner block must be indented. - //
    - //
    - // - // The outermost tags must start at the left margin for this to match, and - // the inner nested divs must be indented. - // We need to do this before the next, more liberal match, because the next - // match will start at the first `
    ` and stop at the first `
    `. - - // attacklab: This regex can be expensive when it fails. - /* - var text = text.replace(/ - ( // save in $1 - ^ // start of line (with /m) - <($block_tags_a) // start tag = $2 - \b // word break - // attacklab: hack around khtml/pcre bug... - [^\r]*?\n // any number of lines, minimally matching - // the matching end tag - [ \t]* // trailing spaces/tabs - (?=\n+) // followed by a newline - ) // attacklab: there are sentinel newlines at end of document - /gm,function(){...}}; - */ - text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del)\b[^\r]*?\n<\/\2>[ \t]*(?=\n+))/gm,hashElement); - - // - // Now match more liberally, simply from `\n` to `\n` - // - - /* - var text = text.replace(/ - ( // save in $1 - ^ // start of line (with /m) - <($block_tags_b) // start tag = $2 - \b // word break - // attacklab: hack around khtml/pcre bug... - [^\r]*? // any number of lines, minimally matching - .* // the matching end tag - [ \t]* // trailing spaces/tabs - (?=\n+) // followed by a newline - ) // attacklab: there are sentinel newlines at end of document - /gm,function(){...}}; - */ - text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math)\b[^\r]*?.*<\/\2>[ \t]*(?=\n+)\n)/gm,hashElement); - - // Special case just for
    . It was easier to make a special case than - // to make the other regex more complicated. - - /* - text = text.replace(/ - ( // save in $1 - \n\n // Starting after a blank line - [ ]{0,3} - (<(hr) // start tag = $2 - \b // word break - ([^<>])*? // - \/?>) // the matching end tag - [ \t]* - (?=\n{2,}) // followed by a blank line - ) - /g,hashElement); - */ - text = text.replace(/(\n[ ]{0,3}(<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g,hashElement); - - // Special case for standalone HTML comments: - - /* - text = text.replace(/ - ( // save in $1 - \n\n // Starting after a blank line - [ ]{0,3} // attacklab: g_tab_width - 1 - - [ \t]* - (?=\n{2,}) // followed by a blank line - ) - /g,hashElement); - */ - text = text.replace(/(\n\n[ ]{0,3}[ \t]*(?=\n{2,}))/g,hashElement); - - // PHP and ASP-style processor instructions ( and <%...%>) - - /* - text = text.replace(/ - (?: - \n\n // Starting after a blank line - ) - ( // save in $1 - [ ]{0,3} // attacklab: g_tab_width - 1 - (?: - <([?%]) // $2 - [^\r]*? - \2> - ) - [ \t]* - (?=\n{2,}) // followed by a blank line - ) - /g,hashElement); - */ - text = text.replace(/(?:\n\n)([ ]{0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g,hashElement); - - // attacklab: Undo double lines (see comment at top of this function) - text = text.replace(/\n\n/g,"\n"); - return text; -} - -var hashElement = function(wholeMatch,m1) { - var blockText = m1; - - // Undo double lines - blockText = blockText.replace(/\n\n/g,"\n"); - blockText = blockText.replace(/^\n/,""); - - // strip trailing blank lines - blockText = blockText.replace(/\n+$/g,""); - - // Replace the element text with a marker ("~KxK" where x is its key) - blockText = "\n\n~K" + (g_html_blocks.push(blockText)-1) + "K\n\n"; - - return blockText; -}; - -var _RunBlockGamut = function(text) { -// -// These are all the transformations that form block-level -// tags like paragraphs, headers, and list items. -// - text = _DoHeaders(text); - - // Do Horizontal Rules: - var key = hashBlock("
    "); - text = text.replace(/^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$/gm,key); - text = text.replace(/^[ ]{0,2}([ ]?\-[ ]?){3,}[ \t]*$/gm,key); - text = text.replace(/^[ ]{0,2}([ ]?\_[ ]?){3,}[ \t]*$/gm,key); - - text = _DoLists(text); - text = _DoCodeBlocks(text); - text = _DoBlockQuotes(text); - - // We already ran _HashHTMLBlocks() before, in Markdown(), but that - // was to escape raw HTML in the original Markdown source. This time, - // we're escaping the markup we've just created, so that we don't wrap - //

    tags around block-level tags. - text = _HashHTMLBlocks(text); - text = _FormParagraphs(text); - - return text; -} - - -var _RunSpanGamut = function(text) { -// -// These are all the transformations that occur *within* block-level -// tags like paragraphs, headers, and list items. -// - - text = _DoCodeSpans(text); - text = _EscapeSpecialCharsWithinTagAttributes(text); - text = _EncodeBackslashEscapes(text); - - // Process anchor and image tags. Images must come first, - // because ![foo][f] looks like an anchor. - text = _DoImages(text); - text = _DoAnchors(text); - - // Make links out of things like `` - // Must come after _DoAnchors(), because you can use < and > - // delimiters in inline links like [this](). - text = _DoAutoLinks(text); - text = _EncodeAmpsAndAngles(text); - text = _DoItalicsAndBold(text); - - // Do hard breaks: - text = text.replace(/ +\n/g,"
    \n"); - - return text; -} - -var _EscapeSpecialCharsWithinTagAttributes = function(text) { -// -// Within tags -- meaning between < and > -- encode [\ ` * _] so they -// don't conflict with their use in Markdown for code, italics and strong. -// - - // Build a regex to find HTML tags and comments. See Friedl's - // "Mastering Regular Expressions", 2nd Ed., pp. 200-201. - var regex = /(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|)/gi; - - text = text.replace(regex, function(wholeMatch) { - var tag = wholeMatch.replace(/(.)<\/?code>(?=.)/g,"$1`"); - tag = escapeCharacters(tag,"\\`*_"); - return tag; - }); - - return text; -} - -var _DoAnchors = function(text) { -// -// Turn Markdown link shortcuts into XHTML
    tags. -// - // - // First, handle reference-style links: [link text] [id] - // - - /* - text = text.replace(/ - ( // wrap whole match in $1 - \[ - ( - (?: - \[[^\]]*\] // allow brackets nested one level - | - [^\[] // or anything else - )* - ) - \] - - [ ]? // one optional space - (?:\n[ ]*)? // one optional newline followed by spaces - - \[ - (.*?) // id = $3 - \] - )()()()() // pad remaining backreferences - /g,_DoAnchors_callback); - */ - text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,writeAnchorTag); - - // - // Next, inline-style links: [link text](url "optional title") - // - - /* - text = text.replace(/ - ( // wrap whole match in $1 - \[ - ( - (?: - \[[^\]]*\] // allow brackets nested one level - | - [^\[\]] // or anything else - ) - ) - \] - \( // literal paren - [ \t]* - () // no id, so leave $3 empty - ? // href = $4 - [ \t]* - ( // $5 - (['"]) // quote char = $6 - (.*?) // Title = $7 - \6 // matching quote - [ \t]* // ignore any spaces/tabs between closing quote and ) - )? // title is optional - \) - ) - /g,writeAnchorTag); - */ - text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\]\([ \t]*()?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,writeAnchorTag); - - // - // Last, handle reference-style shortcuts: [link text] - // These must come last in case you've also got [link test][1] - // or [link test](/foo) - // - - /* - text = text.replace(/ - ( // wrap whole match in $1 - \[ - ([^\[\]]+) // link text = $2; can't contain '[' or ']' - \] - )()()()()() // pad rest of backreferences - /g, writeAnchorTag); - */ - text = text.replace(/(\[([^\[\]]+)\])()()()()()/g, writeAnchorTag); - - return text; -} - -var writeAnchorTag = function(wholeMatch,m1,m2,m3,m4,m5,m6,m7) { - if (m7 == undefined) m7 = ""; - var whole_match = m1; - var link_text = m2; - var link_id = m3.toLowerCase(); - var url = m4; - var title = m7; - - if (url == "") { - if (link_id == "") { - // lower-case and turn embedded newlines into spaces - link_id = link_text.toLowerCase().replace(/ ?\n/g," "); - } - url = "#"+link_id; - - if (g_urls[link_id] != undefined) { - url = g_urls[link_id]; - if (g_titles[link_id] != undefined) { - title = g_titles[link_id]; - } - } - else { - if (whole_match.search(/\(\s*\)$/m)>-1) { - // Special case for explicit empty url - url = ""; - } else { - return whole_match; - } - } - } - - url = escapeCharacters(url,"*_"); - var result = ""; - - return result; -} - - -var _DoImages = function(text) { -// -// Turn Markdown image shortcuts into tags. -// - - // - // First, handle reference-style labeled images: ![alt text][id] - // - - /* - text = text.replace(/ - ( // wrap whole match in $1 - !\[ - (.*?) // alt text = $2 - \] - - [ ]? // one optional space - (?:\n[ ]*)? // one optional newline followed by spaces - - \[ - (.*?) // id = $3 - \] - )()()()() // pad rest of backreferences - /g,writeImageTag); - */ - text = text.replace(/(!\[(.*?)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,writeImageTag); - - // - // Next, handle inline images: ![alt text](url "optional title") - // Don't forget: encode * and _ - - /* - text = text.replace(/ - ( // wrap whole match in $1 - !\[ - (.*?) // alt text = $2 - \] - \s? // One optional whitespace character - \( // literal paren - [ \t]* - () // no id, so leave $3 empty - ? // src url = $4 - [ \t]* - ( // $5 - (['"]) // quote char = $6 - (.*?) // title = $7 - \6 // matching quote - [ \t]* - )? // title is optional - \) - ) - /g,writeImageTag); - */ - text = text.replace(/(!\[(.*?)\]\s?\([ \t]*()?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,writeImageTag); - - return text; -} - -var writeImageTag = function(wholeMatch,m1,m2,m3,m4,m5,m6,m7) { - var whole_match = m1; - var alt_text = m2; - var link_id = m3.toLowerCase(); - var url = m4; - var title = m7; - - if (!title) title = ""; - - if (url == "") { - if (link_id == "") { - // lower-case and turn embedded newlines into spaces - link_id = alt_text.toLowerCase().replace(/ ?\n/g," "); - } - url = "#"+link_id; - - if (g_urls[link_id] != undefined) { - url = g_urls[link_id]; - if (g_titles[link_id] != undefined) { - title = g_titles[link_id]; - } - } - else { - return whole_match; - } - } - - alt_text = alt_text.replace(/"/g,"""); - url = escapeCharacters(url,"*_"); - var result = "\""" + _RunSpanGamut(m1) + "");}); - - text = text.replace(/^(.+)[ \t]*\n-+[ \t]*\n+/gm, - function(matchFound,m1){return hashBlock("

    " + _RunSpanGamut(m1) + "

    ");}); - - // atx-style headers: - // # Header 1 - // ## Header 2 - // ## Header 2 with closing hashes ## - // ... - // ###### Header 6 - // - - /* - text = text.replace(/ - ^(\#{1,6}) // $1 = string of #'s - [ \t]* - (.+?) // $2 = Header text - [ \t]* - \#* // optional closing #'s (not counted) - \n+ - /gm, function() {...}); - */ - - text = text.replace(/^(\#{1,6})[ \t]*(.+?)[ \t]*\#*\n+/gm, - function(wholeMatch,m1,m2) { - var h_level = m1.length; - return hashBlock("" + _RunSpanGamut(m2) + ""); - }); - - return text; -} - -// This declaration keeps Dojo compressor from outputting garbage: -var _ProcessListItems; - -var _DoLists = function(text) { -// -// Form HTML ordered (numbered) and unordered (bulleted) lists. -// - - // attacklab: add sentinel to hack around khtml/safari bug: - // http://bugs.webkit.org/show_bug.cgi?id=11231 - text += "~0"; - - // Re-usable pattern to match any entirel ul or ol list: - - /* - var whole_list = / - ( // $1 = whole list - ( // $2 - [ ]{0,3} // attacklab: g_tab_width - 1 - ([*+-]|\d+[.]) // $3 = first list item marker - [ \t]+ - ) - [^\r]+? - ( // $4 - ~0 // sentinel for workaround; should be $ - | - \n{2,} - (?=\S) - (?! // Negative lookahead for another list item marker - [ \t]* - (?:[*+-]|\d+[.])[ \t]+ - ) - ) - )/g - */ - var whole_list = /^(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm; - - if (g_list_level) { - text = text.replace(whole_list,function(wholeMatch,m1,m2) { - var list = m1; - var list_type = (m2.search(/[*+-]/g)>-1) ? "ul" : "ol"; - - // Turn double returns into triple returns, so that we can make a - // paragraph for the last item in a list, if necessary: - list = list.replace(/\n{2,}/g,"\n\n\n");; - var result = _ProcessListItems(list); - - // Trim any trailing whitespace, to put the closing `` - // up on the preceding line, to get it past the current stupid - // HTML block parser. This is a hack to work around the terrible - // hack that is the HTML block parser. - result = result.replace(/\s+$/,""); - result = "<"+list_type+">" + result + "\n"; - return result; - }); - } else { - whole_list = /(\n\n|^\n?)(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/g; - text = text.replace(whole_list,function(wholeMatch,m1,m2,m3) { - var runup = m1; - var list = m2; - - var list_type = (m3.search(/[*+-]/g)>-1) ? "ul" : "ol"; - // Turn double returns into triple returns, so that we can make a - // paragraph for the last item in a list, if necessary: - var list = list.replace(/\n{2,}/g,"\n\n\n");; - var result = _ProcessListItems(list); - result = runup + "<"+list_type+">\n" + result + "\n"; - return result; - }); - } - - // attacklab: strip sentinel - text = text.replace(/~0/,""); - - return text; -} - -_ProcessListItems = function(list_str) { -// -// Process the contents of a single ordered or unordered list, splitting it -// into individual list items. -// - // The $g_list_level global keeps track of when we're inside a list. - // Each time we enter a list, we increment it; when we leave a list, - // we decrement. If it's zero, we're not in a list anymore. - // - // We do this because when we're not inside a list, we want to treat - // something like this: - // - // I recommend upgrading to version - // 8. Oops, now this line is treated - // as a sub-list. - // - // As a single paragraph, despite the fact that the second line starts - // with a digit-period-space sequence. - // - // Whereas when we're inside a list (or sub-list), that line will be - // treated as the start of a sub-list. What a kludge, huh? This is - // an aspect of Markdown's syntax that's hard to parse perfectly - // without resorting to mind-reading. Perhaps the solution is to - // change the syntax rules such that sub-lists must start with a - // starting cardinal number; e.g. "1." or "a.". - - g_list_level++; - - // trim trailing blank lines: - list_str = list_str.replace(/\n{2,}$/,"\n"); - - // attacklab: add sentinel to emulate \z - list_str += "~0"; - - /* - list_str = list_str.replace(/ - (\n)? // leading line = $1 - (^[ \t]*) // leading whitespace = $2 - ([*+-]|\d+[.]) [ \t]+ // list marker = $3 - ([^\r]+? // list item text = $4 - (\n{1,2})) - (?= \n* (~0 | \2 ([*+-]|\d+[.]) [ \t]+)) - /gm, function(){...}); - */ - list_str = list_str.replace(/(\n)?(^[ \t]*)([*+-]|\d+[.])[ \t]+([^\r]+?(\n{1,2}))(?=\n*(~0|\2([*+-]|\d+[.])[ \t]+))/gm, - function(wholeMatch,m1,m2,m3,m4){ - var item = m4; - var leading_line = m1; - var leading_space = m2; - - if (leading_line || (item.search(/\n{2,}/)>-1)) { - item = _RunBlockGamut(_Outdent(item)); - } - else { - // Recursion for sub-lists: - item = _DoLists(_Outdent(item)); - item = item.replace(/\n$/,""); // chomp(item) - item = _RunSpanGamut(item); - } - - return "
  • " + item + "
  • \n"; - } - ); - - // attacklab: strip sentinel - list_str = list_str.replace(/~0/g,""); - - g_list_level--; - return list_str; -} - - -var _DoCodeBlocks = function(text) { -// -// Process Markdown `
    ` blocks.
    -//  
    -
    -	/*
    -		text = text.replace(text,
    -			/(?:\n\n|^)
    -			(								// $1 = the code block -- one or more lines, starting with a space/tab
    -				(?:
    -					(?:[ ]{4}|\t)			// Lines must start with a tab or a tab-width of spaces - attacklab: g_tab_width
    -					.*\n+
    -				)+
    -			)
    -			(\n*[ ]{0,3}[^ \t\n]|(?=~0))	// attacklab: g_tab_width
    -		/g,function(){...});
    -	*/
    -
    -	// attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug
    -	text += "~0";
    -	
    -	text = text.replace(/(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=~0))/g,
    -		function(wholeMatch,m1,m2) {
    -			var codeblock = m1;
    -			var nextChar = m2;
    -		
    -			codeblock = _EncodeCode( _Outdent(codeblock));
    -			codeblock = _Detab(codeblock);
    -			codeblock = codeblock.replace(/^\n+/g,""); // trim leading newlines
    -			codeblock = codeblock.replace(/\n+$/g,""); // trim trailing whitespace
    -
    -			codeblock = "
    " + codeblock + "\n
    "; - - return hashBlock(codeblock) + nextChar; - } - ); - - // attacklab: strip sentinel - text = text.replace(/~0/,""); - - return text; -} - -var hashBlock = function(text) { - text = text.replace(/(^\n+|\n+$)/g,""); - return "\n\n~K" + (g_html_blocks.push(text)-1) + "K\n\n"; -} - - -var _DoCodeSpans = function(text) { -// -// * Backtick quotes are used for spans. -// -// * You can use multiple backticks as the delimiters if you want to -// include literal backticks in the code span. So, this input: -// -// Just type ``foo `bar` baz`` at the prompt. -// -// Will translate to: -// -//

    Just type foo `bar` baz at the prompt.

    -// -// There's no arbitrary limit to the number of backticks you -// can use as delimters. If you need three consecutive backticks -// in your code, use four for delimiters, etc. -// -// * You can use spaces to get literal backticks at the edges: -// -// ... type `` `bar` `` ... -// -// Turns to: -// -// ... type `bar` ... -// - - /* - text = text.replace(/ - (^|[^\\]) // Character before opening ` can't be a backslash - (`+) // $2 = Opening run of ` - ( // $3 = The code block - [^\r]*? - [^`] // attacklab: work around lack of lookbehind - ) - \2 // Matching closer - (?!`) - /gm, function(){...}); - */ - - text = text.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm, - function(wholeMatch,m1,m2,m3,m4) { - var c = m3; - c = c.replace(/^([ \t]*)/g,""); // leading whitespace - c = c.replace(/[ \t]*$/g,""); // trailing whitespace - c = _EncodeCode(c); - return m1+""+c+""; - }); - - return text; -} - - -var _EncodeCode = function(text) { -// -// Encode/escape certain characters inside Markdown code runs. -// The point is that in code, these characters are literals, -// and lose their special Markdown meanings. -// - // Encode all ampersands; HTML entities are not - // entities within a Markdown code span. - text = text.replace(/&/g,"&"); - - // Do the angle bracket song and dance: - text = text.replace(//g,">"); - - // Now, escape characters that are magic in Markdown: - text = escapeCharacters(text,"\*_{}[]\\",false); - -// jj the line above breaks this: -//--- - -//* Item - -// 1. Subitem - -// special char: * -//--- - - return text; -} - - -var _DoItalicsAndBold = function(text) { - - // must go first: - text = text.replace(/(\*\*|__)(?=\S)([^\r]*?\S[*_]*)\1/g, - "$2"); - - text = text.replace(/(\*|_)(?=\S)([^\r]*?\S)\1/g, - "$2"); - - return text; -} - - -var _DoBlockQuotes = function(text) { - - /* - text = text.replace(/ - ( // Wrap whole match in $1 - ( - ^[ \t]*>[ \t]? // '>' at the start of a line - .+\n // rest of the first line - (.+\n)* // subsequent consecutive lines - \n* // blanks - )+ - ) - /gm, function(){...}); - */ - - text = text.replace(/((^[ \t]*>[ \t]?.+\n(.+\n)*\n*)+)/gm, - function(wholeMatch,m1) { - var bq = m1; - - // attacklab: hack around Konqueror 3.5.4 bug: - // "----------bug".replace(/^-/g,"") == "bug" - - bq = bq.replace(/^[ \t]*>[ \t]?/gm,"~0"); // trim one level of quoting - - // attacklab: clean up hack - bq = bq.replace(/~0/g,""); - - bq = bq.replace(/^[ \t]+$/gm,""); // trim whitespace-only lines - bq = _RunBlockGamut(bq); // recurse - - bq = bq.replace(/(^|\n)/g,"$1 "); - // These leading spaces screw with
     content, so we need to fix that:
    -			bq = bq.replace(
    -					/(\s*
    [^\r]+?<\/pre>)/gm,
    -				function(wholeMatch,m1) {
    -					var pre = m1;
    -					// attacklab: hack around Konqueror 3.5.4 bug:
    -					pre = pre.replace(/^  /mg,"~0");
    -					pre = pre.replace(/~0/g,"");
    -					return pre;
    -				});
    -			
    -			return hashBlock("
    \n" + bq + "\n
    "); - }); - return text; -} - - -var _FormParagraphs = function(text) { -// -// Params: -// $text - string to process with html

    tags -// - - // Strip leading and trailing lines: - text = text.replace(/^\n+/g,""); - text = text.replace(/\n+$/g,""); - - var grafs = text.split(/\n{2,}/g); - var grafsOut = new Array(); - - // - // Wrap

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

    "); - str += "

    " - grafsOut.push(str); - } - - } - - // - // Unhashify HTML blocks - // - end = grafsOut.length; - for (var i=0; i= 0) { - var blockText = g_html_blocks[RegExp.$1]; - blockText = blockText.replace(/\$/g,"$$$$"); // Escape any dollar signs - grafsOut[i] = grafsOut[i].replace(/~K\d+K/,blockText); - } - } - - return grafsOut.join("\n\n"); -} - - -var _EncodeAmpsAndAngles = function(text) { -// Smart processing for ampersands and angle brackets that need to be encoded. - - // Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin: - // http://bumppo.net/projects/amputator/ - text = text.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g,"&"); - - // Encode naked <'s - text = text.replace(/<(?![a-z\/?\$!])/gi,"<"); - - return text; -} - - -var _EncodeBackslashEscapes = function(text) { -// -// Parameter: String. -// Returns: The string, with after processing the following backslash -// escape sequences. -// - - // attacklab: The polite way to do this is with the new - // escapeCharacters() function: - // - // text = escapeCharacters(text,"\\",true); - // text = escapeCharacters(text,"`*_{}[]()>#+-.!",true); - // - // ...but we're sidestepping its use of the (slow) RegExp constructor - // as an optimization for Firefox. This function gets called a LOT. - - text = text.replace(/\\(\\)/g,escapeCharacters_callback); - text = text.replace(/\\([`*_{}\[\]()>#+-.!])/g,escapeCharacters_callback); - return text; -} - - -var _DoAutoLinks = function(text) { - - text = text.replace(/<((https?|ftp|dict):[^'">\s]+)>/gi,"
    $1"); - - // Email addresses: - - /* - text = text.replace(/ - < - (?:mailto:)? - ( - [-.\w]+ - \@ - [-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+ - ) - > - /gi, _DoAutoLinks_callback()); - */ - text = text.replace(/<(?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi, - function(wholeMatch,m1) { - return _EncodeEmailAddress( _UnescapeSpecialChars(m1) ); - } - ); - - return text; -} - - -var _EncodeEmailAddress = function(addr) { -// -// Input: an email address, e.g. "foo@example.com" -// -// Output: the email address as a mailto link, with each character -// of the address encoded as either a decimal or hex entity, in -// the hopes of foiling most address harvesting spam bots. E.g.: -// -// foo -// @example.com -// -// Based on a filter by Matthew Wickline, posted to the BBEdit-Talk -// mailing list: -// - - // attacklab: why can't javascript speak hex? - function char2hex(ch) { - var hexDigits = '0123456789ABCDEF'; - var dec = ch.charCodeAt(0); - return(hexDigits.charAt(dec>>4) + hexDigits.charAt(dec&15)); - } - - var encode = [ - function(ch){return "&#"+ch.charCodeAt(0)+";";}, - function(ch){return "&#x"+char2hex(ch)+";";}, - function(ch){return ch;} - ]; - - addr = "mailto:" + addr; - - addr = addr.replace(/./g, function(ch) { - if (ch == "@") { - // this *must* be encoded. I insist. - ch = encode[Math.floor(Math.random()*2)](ch); - } else if (ch !=":") { - // leave ':' alone (to spot mailto: later) - var r = Math.random(); - // roughly 10% raw, 45% hex, 45% dec - ch = ( - r > .9 ? encode[2](ch) : - r > .45 ? encode[1](ch) : - encode[0](ch) - ); - } - return ch; - }); - - addr = "" + addr + ""; - addr = addr.replace(/">.+:/g,"\">"); // strip the mailto: from the visible part - - return addr; -} - - -var _UnescapeSpecialChars = function(text) { -// -// Swap back in all the special characters we've hidden. -// - text = text.replace(/~E(\d+)E/g, - function(wholeMatch,m1) { - var charCodeToReplace = parseInt(m1); - return String.fromCharCode(charCodeToReplace); - } - ); - return text; -} - - -var _Outdent = function(text) { -// -// Remove one level of line-leading tabs or spaces -// - - // attacklab: hack around Konqueror 3.5.4 bug: - // "----------bug".replace(/^-/g,"") == "bug" - - text = text.replace(/^(\t|[ ]{1,4})/gm,"~0"); // attacklab: g_tab_width - - // attacklab: clean up hack - text = text.replace(/~0/g,"") - - return text; -} - -var _Detab = function(text) { -// attacklab: Detab's completely rewritten for speed. -// In perl we could fix it by anchoring the regexp with \G. -// In javascript we're less fortunate. - - // expand first n-1 tabs - text = text.replace(/\t(?=\t)/g," "); // attacklab: g_tab_width - - // replace the nth with two sentinels - text = text.replace(/\t/g,"~A~B"); - - // use the sentinel to anchor our regex so it doesn't explode - text = text.replace(/~B(.+?)~A/g, - function(wholeMatch,m1,m2) { - var leadingText = m1; - var numSpaces = 4 - leadingText.length % 4; // attacklab: g_tab_width - - // there *must* be a better way to do this: - for (var i=0; i Date: Sun, 10 Mar 2013 22:31:42 -0700 Subject: [PATCH 05/54] feat(tooltip,popover): logic moved to $tooltip svc Popover and tooltip directive creation is now performed through the help of the `$tooltip` service that manages all aspects of directive creation and management based on only the name of the component and their default triggers. The tooltip and popover sub-directives and their templates were refactored to use common scope variables that the new service can provide, yielding even greater flexibility. Animation is now enabled by default. Lastly, the `$tooltipProvider` has been established to allow setting default global options for the tooltip and popover, but these features are not yet public as the API is sure to change drastically as we flesh out which global options to allow. But the framework is in place as this was a logical time to incorporate it. --- src/popover/docs/readme.md | 3 + src/popover/popover.js | 144 +------------ src/popover/test/popoverSpec.js | 12 +- src/tooltip/docs/demo.html | 2 +- src/tooltip/test/tooltip.spec.js | 4 +- src/tooltip/tooltip.js | 321 ++++++++++++++++------------ template/popover/popover.html | 4 +- template/tooltip/tooltip-popup.html | 2 +- 8 files changed, 209 insertions(+), 283 deletions(-) diff --git a/src/popover/docs/readme.md b/src/popover/docs/readme.md index eb5f53428a..87316ddeee 100644 --- a/src/popover/docs/readme.md +++ b/src/popover/docs/readme.md @@ -1,2 +1,5 @@ A lightweight, extensible directive for fancy popover creation. The popover directive supports multiple placements, optional transition animation, and more. + +Like the Twitter Bootstrap jQuery plugin, the popover **requires** the tooltip +module. diff --git a/src/popover/popover.js b/src/popover/popover.js index d64e729eff..1cee58f851 100644 --- a/src/popover/popover.js +++ b/src/popover/popover.js @@ -3,152 +3,16 @@ * function, placement as a function, inside, support for more triggers than * just mouse enter/leave, html popovers, and selector delegatation. */ -angular.module( 'ui.bootstrap.popover', [] ) +angular.module( 'ui.bootstrap.popover', [ 'ui.bootstrap.tooltip' ] ) .directive( 'popoverPopup', function () { return { restrict: 'EA', replace: true, - scope: { popoverTitle: '@', popoverContent: '@', placement: '@', animation: '&', isOpen: '&' }, + scope: { title: '@', content: '@', placement: '@', animation: '&', isOpen: '&' }, templateUrl: 'template/popover/popover.html' }; }) -.directive( 'popover', [ '$compile', '$timeout', '$parse', '$window', function ( $compile, $timeout, $parse, $window ) { - - var template = - ''+ - ''; - - return { - scope: true, - link: function ( scope, element, attr ) { - var popover = $compile( template )( scope ), - transitionTimeout; - - attr.$observe( 'popover', function ( val ) { - scope.tt_popover = val; - }); - - attr.$observe( 'popoverTitle', function ( val ) { - scope.tt_title = val; - }); - - attr.$observe( 'popoverPlacement', function ( val ) { - // If no placement was provided, default to 'top'. - scope.tt_placement = val || 'top'; - }); - - attr.$observe( 'popoverAnimation', function ( val ) { - scope.tt_animation = $parse( val ); - }); - - // By default, the popover is not open. - scope.tt_isOpen = false; - - // Calculate the current position and size of the directive element. - function getPosition() { - var boundingClientRect = element[0].getBoundingClientRect(); - return { - width: element.prop( 'offsetWidth' ), - height: element.prop( 'offsetHeight' ), - top: boundingClientRect.top + $window.pageYOffset, - left: boundingClientRect.left + $window.pageXOffset - }; - } - - function show() { - var position, - ttWidth, - ttHeight, - ttPosition; - - // If there is a pending remove transition, we must cancel it, lest the - // toolip be mysteriously removed. - if ( transitionTimeout ) { - $timeout.cancel( transitionTimeout ); - } - - // Set the initial positioning. - popover.css({ top: 0, left: 0, display: 'block' }); - - // Now we add it to the DOM because need some info about it. But it's not - // visible yet anyway. - element.after( popover ); - - // Get the position of the directive element. - position = getPosition(); - - // Get the height and width of the popover so we can center it. - ttWidth = popover.prop( 'offsetWidth' ); - ttHeight = popover.prop( 'offsetHeight' ); - - // Calculate the popover's top and left coordinates to center it with - // this directive. - switch ( scope.tt_placement ) { - case 'right': - ttPosition = { - top: (position.top + position.height / 2 - ttHeight / 2) + 'px', - left: (position.left + position.width) + 'px' - }; - break; - case 'bottom': - ttPosition = { - top: (position.top + position.height) + 'px', - left: (position.left + position.width / 2 - ttWidth / 2) + 'px' - }; - break; - case 'left': - ttPosition = { - top: (position.top + position.height / 2 - ttHeight / 2) + 'px', - left: (position.left - ttWidth) + 'px' - }; - break; - default: - ttPosition = { - top: (position.top - ttHeight) + 'px', - left: (position.left + position.width / 2 - ttWidth / 2) + 'px' - }; - break; - } - - // Now set the calculated positioning. - popover.css( ttPosition ); - - // And show the popover. - scope.tt_isOpen = true; - } - - // Hide the popover popup element. - function hide() { - // First things first: we don't show it anymore. - //popover.removeClass( 'in' ); - scope.tt_isOpen = false; - - // And now we remove it from the DOM. However, if we have animation, we - // need to wait for it to expire beforehand. - // FIXME: this is a placeholder for a port of the transitions library. - if ( angular.isDefined( scope.tt_animation ) && scope.tt_animation() ) { - transitionTimeout = $timeout( function () { popover.remove(); }, 500 ); - } else { - popover.remove(); - } - } - - // Register the event listeners. - element.bind( 'click', function() { - if(scope.tt_isOpen){ - scope.$apply( hide ); - } else { - scope.$apply( show ); - } - - }); - } - }; +.directive( 'popover', [ '$compile', '$timeout', '$parse', '$window', '$tooltip', function ( $compile, $timeout, $parse, $window, $tooltip ) { + return $tooltip( 'popover', 'click' ); }]); diff --git a/src/popover/test/popoverSpec.js b/src/popover/test/popoverSpec.js index f98d292b21..633aeb8b2c 100644 --- a/src/popover/test/popoverSpec.js +++ b/src/popover/test/popoverSpec.js @@ -81,7 +81,7 @@ describe('popover', function() { tt.trigger( 'click' ); expect( tt.text() ).toBe( scope.items[0].name ); - expect( tt.scope().tt_popover ).toBe( scope.items[0].popover ); + expect( tt.scope().tt_content ).toBe( scope.items[0].popover ); tt.trigger( 'click' ); })); @@ -89,12 +89,12 @@ describe('popover', function() { it('should only have an isolate scope on the popup', inject( function ( $compile ) { var ttScope; - scope.popoverMsg = "popover Text"; - scope.popoverTitle = "popover Text"; + scope.popoverContent = "Popover Content"; + scope.popoverTitle = "Popover Title"; scope.alt = "Alt Message"; elmBody = $compile( angular.element( - '
    Selector Text
    ' + '
    Selector Text
    ' ) )( scope ); $compile( elmBody )( scope ); @@ -107,8 +107,8 @@ describe('popover', function() { ttScope = angular.element( elmBody.children()[1] ).scope(); expect( ttScope.placement ).toBe( 'top' ); - expect( ttScope.popoverTitle ).toBe( scope.popoverTitle ); - expect( ttScope.popoverContent ).toBe( scope.popoverMsg ); + expect( ttScope.title ).toBe( scope.popoverTitle ); + expect( ttScope.content ).toBe( scope.popoverContent ); elm.trigger( 'click' ); })); diff --git a/src/tooltip/docs/demo.html b/src/tooltip/docs/demo.html index 815c540e2e..954c70e57e 100644 --- a/src/tooltip/docs/demo.html +++ b/src/tooltip/docs/demo.html @@ -12,7 +12,7 @@ nunc sed velit dignissim sodales ut eu sem integer vitae. Turpis egestas bottom pharetra convallis posuere morbi leo urna, - fading + fading at elementum eu, facilisis sed odio morbi quis commodo odio. In cursus turpis massa tincidunt dui ut.

    diff --git a/src/tooltip/test/tooltip.spec.js b/src/tooltip/test/tooltip.spec.js index 1eb537a7e8..b9849df338 100644 --- a/src/tooltip/test/tooltip.spec.js +++ b/src/tooltip/test/tooltip.spec.js @@ -81,7 +81,7 @@ describe('tooltip', function() { tt.trigger( 'mouseenter' ); expect( tt.text() ).toBe( scope.items[0].name ); - expect( tt.scope().tt_tooltip ).toBe( scope.items[0].tooltip ); + expect( tt.scope().tt_content ).toBe( scope.items[0].tooltip ); tt.trigger( 'mouseleave' ); })); @@ -106,7 +106,7 @@ describe('tooltip', function() { ttScope = angular.element( elmBody.children()[1] ).scope(); expect( ttScope.placement ).toBe( 'top' ); - expect( ttScope.tooltipTitle ).toBe( scope.tooltipMsg ); + expect( ttScope.content ).toBe( scope.tooltipMsg ); elm.trigger( 'mouseleave' ); })); diff --git a/src/tooltip/tooltip.js b/src/tooltip/tooltip.js index 7af22a45b3..15617274a8 100644 --- a/src/tooltip/tooltip.js +++ b/src/tooltip/tooltip.js @@ -1,52 +1,57 @@ /** * The following features are still outstanding: popup delay, animation as a * function, placement as a function, inside, support for more triggers than - * just mouse enter/leave, html tooltips, and selector delegatation. + * just mouse enter/leave, html tooltips, and selector delegation. */ angular.module( 'ui.bootstrap.tooltip', [] ) -.directive( 'tooltipPopup', function () { - return { - restrict: 'EA', - replace: true, - scope: { tooltipTitle: '@', placement: '@', animation: '&', isOpen: '&' }, - templateUrl: 'template/tooltip/tooltip-popup.html' + +/** + * The $tooltip service creates tooltip- and popover-like directives as well as + * houses global options for them. + */ +.provider( '$tooltip', function () { + // The default options tooltip and popover. + var defaultOptions = { + placement: 'top', + animation: true }; -}) -.directive( 'tooltip', [ '$compile', '$timeout', '$parse', '$window', function ( $compile, $timeout, $parse, $window) { - - var template = - ''+ - ''; + + // The options specified to the provider globally. + var globalOptions = {}; - return { - scope: true, - link: function ( scope, element, attr ) { - var tooltip = $compile( template )( scope ), - transitionTimeout; - - attr.$observe( 'tooltip', function ( val ) { - scope.tt_tooltip = val; - }); - - attr.$observe( 'tooltipPlacement', function ( val ) { - // If no placement was provided, default to 'top'. - scope.tt_placement = val || 'top'; - }); - - attr.$observe( 'tooltipAnimation', function ( val ) { - scope.tt_animation = $parse( val ); - }); - - // By default, the tooltip is not open. - scope.tt_isOpen = false; - + /** + * The `options({})` allows global configuration of all dialogs in the + * application. + * + * var app = angular.module( 'App', ['ui.bootstrap.tooltip'], function( $tooltipProvider ) { + * // place tooltips left instead of top by default + * $tooltipProvider.options( { placement: 'left' } ); + * }); + */ + this.options = function( value ) { + angular.extend( globalOptions, value ); + }; + + /** + * Returns the actual instance of the $tooltip service. + * TODO support multiple triggers + */ + this.$get = [ '$window', '$compile', '$timeout', '$parse', function ( $window, $compile, $timeout, $parse ) { + return function $tooltip ( type, defaultTriggerShow, defaultTriggerHide ) { + var options = angular.extend( {}, defaultOptions, globalOptions ); + + var template = + '<'+ type +'-popup '+ + 'title="{{tt_title}}" '+ + 'content="{{tt_content}}" '+ + 'placement="{{tt_placement}}" '+ + 'animation="tt_animation()" '+ + 'is-open="tt_isOpen"'+ + '>'+ + ''; + // Calculate the current position and size of the directive element. - function getPosition() { + function getPosition( element ) { var boundingClientRect = element[0].getBoundingClientRect(); return { width: element.prop( 'offsetWidth' ), @@ -55,99 +60,153 @@ angular.module( 'ui.bootstrap.tooltip', [] ) left: boundingClientRect.left + $window.pageXOffset }; } - - // Show the tooltip popup element. - function show() { - var position, - ttWidth, - ttHeight, - ttPosition; - - //don't show empty tooltips - if (!scope.tt_tooltip) { - return; - } + + return { + restrict: 'EA', + scope: true, + link: function link ( scope, element, attrs ) { + var tooltip = $compile( template )( scope ), + transitionTimeout; - // If there is a pending remove transition, we must cancel it, lest the - // toolip be mysteriously removed. - if ( transitionTimeout ) { - $timeout.cancel( transitionTimeout ); - } - - // Set the initial positioning. - tooltip.css({ top: 0, left: 0, display: 'block' }); - - // Now we add it to the DOM because need some info about it. But it's not - // visible yet anyway. - element.after( tooltip ); - - // Get the position of the directive element. - position = getPosition(); - - // Get the height and width of the tooltip so we can center it. - ttWidth = tooltip.prop( 'offsetWidth' ); - ttHeight = tooltip.prop( 'offsetHeight' ); - - // Calculate the tooltip's top and left coordinates to center it with - // this directive. - switch ( scope.tt_placement ) { - case 'right': - ttPosition = { - top: (position.top + position.height / 2 - ttHeight / 2) + 'px', - left: (position.left + position.width) + 'px' - }; - break; - case 'bottom': - ttPosition = { - top: (position.top + position.height) + 'px', - left: (position.left + position.width / 2 - ttWidth / 2) + 'px' - }; - break; - case 'left': - ttPosition = { - top: (position.top + position.height / 2 - ttHeight / 2) + 'px', - left: (position.left - ttWidth) + 'px' - }; - break; - default: - ttPosition = { - top: (position.top - ttHeight) + 'px', - left: (position.left + position.width / 2 - ttWidth / 2) + 'px' - }; - break; - } - - // Now set the calculated positioning. - tooltip.css( ttPosition ); + attrs.$observe( type, function ( val ) { + scope.tt_content = val; + }); + + attrs.$observe( type+'Title', function ( val ) { + scope.tt_title = val; + }); + + attrs.$observe( type+'Placement', function ( val ) { + scope.tt_placement = angular.isDefined( val ) ? val : options.placement; + }); + + attrs.$observe( type+'Animation', function ( val ) { + scope.tt_animation = angular.isDefined( val ) ? $parse( val ) : function(){ return options.animation; }; + }); + + // By default, the tooltip is not open. + // TODO add ability to start tooltip opened + scope.tt_isOpen = false; - // And show the tooltip. - scope.tt_isOpen = true; - } - - // Hide the tooltip popup element. - function hide() { - // First things first: we don't show it anymore. - //tooltip.removeClass( 'in' ); - scope.tt_isOpen = false; - - // And now we remove it from the DOM. However, if we have animation, we - // need to wait for it to expire beforehand. - // FIXME: this is a placeholder for a port of the transitions library. - if ( angular.isDefined( scope.tt_animation ) && scope.tt_animation() ) { - transitionTimeout = $timeout( function () { tooltip.remove(); }, 500 ); - } else { - tooltip.remove(); + // Show the tooltip popup element. + function show() { + var position, + ttWidth, + ttHeight, + ttPosition; + + // Don't show empty tooltips. + if ( ! scope.tt_content ) { + return; + } + + // If there is a pending remove transition, we must cancel it, lest the + // toolip be mysteriously removed. + if ( transitionTimeout ) { + $timeout.cancel( transitionTimeout ); + } + + // Set the initial positioning. + tooltip.css({ top: 0, left: 0, display: 'block' }); + + // Now we add it to the DOM because need some info about it. But it's not + // visible yet anyway. + element.after( tooltip ); + + // Get the position of the directive element. + position = getPosition( element ); + + // Get the height and width of the tooltip so we can center it. + ttWidth = tooltip.prop( 'offsetWidth' ); + ttHeight = tooltip.prop( 'offsetHeight' ); + + // Calculate the tooltip's top and left coordinates to center it with + // this directive. + switch ( scope.tt_placement ) { + case 'right': + ttPosition = { + top: (position.top + position.height / 2 - ttHeight / 2) + 'px', + left: (position.left + position.width) + 'px' + }; + break; + case 'bottom': + ttPosition = { + top: (position.top + position.height) + 'px', + left: (position.left + position.width / 2 - ttWidth / 2) + 'px' + }; + break; + case 'left': + ttPosition = { + top: (position.top + position.height / 2 - ttHeight / 2) + 'px', + left: (position.left - ttWidth) + 'px' + }; + break; + default: + ttPosition = { + top: (position.top - ttHeight) + 'px', + left: (position.left + position.width / 2 - ttWidth / 2) + 'px' + }; + break; + } + + // Now set the calculated positioning. + tooltip.css( ttPosition ); + + // And show the tooltip. + scope.tt_isOpen = true; + } + + // Hide the tooltip popup element. + function hide() { + // First things first: we don't show it anymore. + //tooltip.removeClass( 'in' ); + scope.tt_isOpen = false; + + // And now we remove it from the DOM. However, if we have animation, we + // need to wait for it to expire beforehand. + // FIXME: this is a placeholder for a port of the transitions library. + if ( angular.isDefined( scope.tt_animation ) && scope.tt_animation() ) { + transitionTimeout = $timeout( function () { tooltip.remove(); }, 500 ); + } else { + tooltip.remove(); + } + } + + // Register the event listeners. If only one event listener was + // supplied, we use the same event listener for showing and hiding. + // TODO add ability to customize event triggers + if ( ! angular.isDefined( defaultTriggerHide ) ) { + element.bind( defaultTriggerShow, function toggleTooltipBind () { + if ( ! scope.tt_isOpen ) { + scope.$apply( show ); + } else { + scope.$apply( hide ); + } + }); + } else { + element.bind( defaultTriggerShow, function showTooltipBind() { + scope.$apply( show ); + }); + element.bind( defaultTriggerHide, function hideTooltipBind() { + scope.$apply( hide ); + }); + } } - } - - // Register the event listeners. - element.bind( 'mouseenter', function() { - scope.$apply( show ); - }); - element.bind( 'mouseleave', function() { - scope.$apply( hide ); - }); - } + }; + }; + }]; +}) + +.directive( 'tooltipPopup', function () { + return { + restrict: 'E', + replace: true, + scope: { content: '@', placement: '@', animation: '&', isOpen: '&' }, + templateUrl: 'template/tooltip/tooltip-popup.html' }; +}) + +.directive( 'tooltip', [ '$tooltip', function ( $tooltip ) { + return $tooltip( 'tooltip', 'mouseenter', 'mouseleave' ); }]); diff --git a/template/popover/popover.html b/template/popover/popover.html index 1d1104dd2b..5929ee6e6a 100644 --- a/template/popover/popover.html +++ b/template/popover/popover.html @@ -2,7 +2,7 @@
    -

    -
    +

    +
    diff --git a/template/tooltip/tooltip-popup.html b/template/tooltip/tooltip-popup.html index 77d35de8e2..fd51120774 100644 --- a/template/tooltip/tooltip-popup.html +++ b/template/tooltip/tooltip-popup.html @@ -1,4 +1,4 @@
    -
    +
    From 261f2072284a832bc9d1d3bf859ce84203cf5a7f Mon Sep 17 00:00:00 2001 From: tbekos Date: Tue, 5 Mar 2013 12:13:59 +0200 Subject: [PATCH 06/54] feat(progressbar): add progressbar directive Closes #187 --- src/progressbar/docs/demo.html | 27 ++ src/progressbar/docs/demo.js | 44 +++ src/progressbar/docs/readme.md | 3 + src/progressbar/progressbar.js | 106 ++++++++ src/progressbar/test/progressbar.spec.js | 326 +++++++++++++++++++++++ template/progressbar/bar.html | 1 + template/progressbar/progress.html | 1 + 7 files changed, 508 insertions(+) create mode 100644 src/progressbar/docs/demo.html create mode 100644 src/progressbar/docs/demo.js create mode 100644 src/progressbar/docs/readme.md create mode 100644 src/progressbar/progressbar.js create mode 100644 src/progressbar/test/progressbar.spec.js create mode 100644 template/progressbar/bar.html create mode 100644 template/progressbar/progress.html diff --git a/src/progressbar/docs/demo.html b/src/progressbar/docs/demo.html new file mode 100644 index 0000000000..8fbfb082f8 --- /dev/null +++ b/src/progressbar/docs/demo.html @@ -0,0 +1,27 @@ +
    +

    Static

    +
    +
    +
    +
    +
    + +

    Dynamic

    +
    Value: {{dynamic}}
    + + + No animation + + + Object (changes type based on value) + + +

    Stacked

    + Array values with automatic types +
    Value: {{stackedArray}}
    + + + Objects +
    Value: {{stacked}}
    + +
    \ No newline at end of file diff --git a/src/progressbar/docs/demo.js b/src/progressbar/docs/demo.js new file mode 100644 index 0000000000..57676e8af6 --- /dev/null +++ b/src/progressbar/docs/demo.js @@ -0,0 +1,44 @@ +var ProgressDemoCtrl = function ($scope) { + + $scope.random = function() { + var value = Math.floor((Math.random()*100)+1); + var type; + + if (value < 25) { + type = 'success'; + } else if (value < 50) { + type = 'info'; + } else if (value < 75) { + type = 'warning'; + } else { + type = 'danger'; + } + + $scope.dynamic = value; + $scope.dynamicObject = { + value: value, + type: type + }; + }; + $scope.random(); + + var types = ['success', 'info', 'warning', 'danger']; + $scope.randomStacked = function() { + $scope.stackedArray = []; + $scope.stacked = []; + + var n = Math.floor((Math.random()*4)+1); + + for (var i=0; i < n; i++) { + var value = Math.floor((Math.random()*30)+1); + $scope.stackedArray.push(value); + + var index = Math.floor((Math.random()*4)); + $scope.stacked.push({ + value: value, + type: types[index] + }); + } + }; + $scope.randomStacked(); +}; diff --git a/src/progressbar/docs/readme.md b/src/progressbar/docs/readme.md new file mode 100644 index 0000000000..ebc9cb9580 --- /dev/null +++ b/src/progressbar/docs/readme.md @@ -0,0 +1,3 @@ +A lightweight progress bar directive that is focused on providing progress visualization! + +The progress bar directive supports multiple (stacked) bars into the same element, optional transition animation, event handler for full & empty state and many more. diff --git a/src/progressbar/progressbar.js b/src/progressbar/progressbar.js new file mode 100644 index 0000000000..ced3b88833 --- /dev/null +++ b/src/progressbar/progressbar.js @@ -0,0 +1,106 @@ +angular.module('ui.bootstrap.progressbar', ['ui.bootstrap.transition']) + +.constant('progressConfig', { + animate: true, + autoType: false, + stackedTypes: ['success', 'info', 'warning', 'danger'] +}) + +.controller('ProgressBarController', ['$scope', '$attrs', 'progressConfig', function($scope, $attrs, progressConfig) { + + // Whether bar transitions should be animated + var animate = angular.isDefined($attrs.animate) ? $scope.$eval($attrs.animate) : progressConfig.animate; + var autoType = angular.isDefined($attrs.autoType) ? $scope.$eval($attrs.autoType) : progressConfig.autoType; + var stackedTypes = angular.isDefined($attrs.stackedTypes) ? $scope.$eval('[' + $attrs.stackedTypes + ']') : progressConfig.stackedTypes; + + // Create bar object + this.makeBar = function(newBar, oldBar, index) { + var newValue = (angular.isObject(newBar)) ? newBar.value : (newBar || 0); + var oldValue = (angular.isObject(oldBar)) ? oldBar.value : (oldBar || 0); + var type = (angular.isObject(newBar) && angular.isDefined(newBar.type)) ? newBar.type : (autoType) ? getStackedType(index || 0) : null; + + return { + from: oldValue, + to: newValue, + type: type, + animate: animate + }; + }; + + function getStackedType(index) { + return stackedTypes[index]; + } + + this.addBar = function(bar) { + $scope.bars.push(bar); + $scope.totalPercent += bar.to; + }; + + this.clearBars = function() { + $scope.bars = []; + $scope.totalPercent = 0; + }; + this.clearBars(); +}]) + +.directive('progress', function() { + return { + restrict: 'EA', + replace: true, + controller: 'ProgressBarController', + scope: { + value: '=', + onFull: '&', + onEmpty: '&' + }, + templateUrl: 'template/progressbar/progress.html', + link: function(scope, element, attrs, controller) { + scope.$watch('value', function(newValue, oldValue) { + controller.clearBars(); + + if (angular.isArray(newValue)) { + // Stacked progress bar + for (var i=0, n=newValue.length; i < n; i++) { + controller.addBar(controller.makeBar(newValue[i], oldValue[i], i)); + } + } else { + // Simple bar + controller.addBar(controller.makeBar(newValue, oldValue)); + } + }, true); + + // Total percent listeners + scope.$watch('totalPercent', function(value) { + if (value >= 100) { + scope.onFull(); + } else if (value <= 0) { + scope.onEmpty(); + } + }, true); + } + }; +}) + +.directive('progressbar', ['$transition', function($transition) { + return { + restrict: 'EA', + replace: true, + scope: { + width: '=', + old: '=', + type: '=', + animate: '=' + }, + templateUrl: 'template/progressbar/bar.html', + link: function(scope, element) { + scope.$watch('width', function(value) { + if (scope.animate) { + element.css('width', scope.old + '%'); + $transition(element, {width: value + '%'}); + } else { + element.css('width', value + '%'); + } + }); + } + }; +}]); \ No newline at end of file diff --git a/src/progressbar/test/progressbar.spec.js b/src/progressbar/test/progressbar.spec.js new file mode 100644 index 0000000000..24889e26e7 --- /dev/null +++ b/src/progressbar/test/progressbar.spec.js @@ -0,0 +1,326 @@ +describe('progressbar directive with no binding', function () { + var $rootScope, element; + beforeEach(module('ui.bootstrap.progressbar')); + beforeEach(module('template/progressbar/progress.html', 'template/progressbar/bar.html')); + beforeEach(inject(function(_$compile_, _$rootScope_) { + $compile = _$compile_; + $rootScope = _$rootScope_; + element = $compile('')($rootScope); + $rootScope.$digest(); + })); + + it('has a "progress" css class', function() { + expect(element.hasClass('progress')).toBe(true); + }); + + it('contains one child element with "bar" css class', function() { + expect(element.children().length).toBe(1); + expect(element.children().eq(0).hasClass('bar')).toBe(true); + }); + + it('has a "bar" element with expected width', function() { + expect(element.children().eq(0).css('width')).toBe('22%'); + }); +}); + +describe('progressbar directive with data-binding', function () { + var $rootScope, element; + beforeEach(module('ui.bootstrap.progressbar')); + beforeEach(module('template/progressbar/progress.html', 'template/progressbar/bar.html')); + beforeEach(inject(function(_$compile_, _$rootScope_) { + $compile = _$compile_; + $rootScope = _$rootScope_; + $rootScope.percent = 33; + element = $compile('')($rootScope); + $rootScope.$digest(); + })); + + it('has a "progress" css class', function() { + expect(element.hasClass('progress')).toBe(true); + }); + + it('contains one child element with "bar" css class', function() { + expect(element.children().length).toBe(1); + expect(element.children().eq(0).hasClass('bar')).toBe(true); + }); + + it('has a "bar" element with expected width', function() { + expect(element.children().eq(0).css('width')).toBe('33%'); + }); + + it('changes width when bind value changes', function() { + $rootScope.percent = 55; + $rootScope.$digest(); + expect(element.children().length).toBe(1); + expect(element.children().eq(0).css('width')).toBe('55%'); + expect(element.children().eq(0).hasClass('bar')).toBe(true); + + $rootScope.percent += 11; + $rootScope.$digest(); + expect(element.children().eq(0).css('width')).toBe('66%'); + + $rootScope.percent = 0; + $rootScope.$digest(); + expect(element.children().eq(0).css('width')).toBe('0%'); + }); + + it('can handle correctly objects value && class', function() { + $rootScope.percent = { + value: 45, + type: 'warning' + }; + $rootScope.$digest(); + + expect(element.children().length).toBe(1); + expect(element.hasClass('progress')).toBe(true); + + var barElement = element.children().eq(0); + expect(barElement.css('width')).toBe('45%'); + expect(barElement.hasClass('bar')).toBe(true); + expect(barElement.hasClass('bar-warning')).toBe(true); + }); + +}); + +describe('stacked progressbar directive', function () { + var $rootScope, element; + beforeEach(module('ui.bootstrap.progressbar')); + beforeEach(module('template/progressbar/progress.html', 'template/progressbar/bar.html')); + beforeEach(inject(function(_$compile_, _$rootScope_) { + $compile = _$compile_; + $rootScope = _$rootScope_; + $rootScope.stacked = [12, 22, 33]; + element = $compile('')($rootScope); + $rootScope.$digest(); + })); + + it('has a "progress" css class', function() { + expect(element.hasClass('progress')).toBe(true); + }); + + it('contains tree child elements with "bar" css class each', function() { + expect(element.children().length).toBe(3); + expect(element.children().eq(0).hasClass('bar')).toBe(true); + expect(element.children().eq(1).hasClass('bar')).toBe(true); + expect(element.children().eq(2).hasClass('bar')).toBe(true); + }); + + it('has a elements with expected width', function() { + expect(element.children().eq(0).css('width')).toBe('12%'); + expect(element.children().eq(1).css('width')).toBe('22%'); + expect(element.children().eq(2).css('width')).toBe('33%'); + }); + + it('changes width when bind value changes', function() { + $rootScope.stacked[1] = 35; + $rootScope.$digest(); + + expect(element.children().length).toBe(3); + expect(element.children().eq(0).css('width')).toBe('12%'); + expect(element.children().eq(1).css('width')).toBe('35%'); + expect(element.children().eq(2).css('width')).toBe('33%'); + }); + + it('can remove bars', function() { + $rootScope.stacked.pop(); + $rootScope.$digest(); + + expect(element.children().length).toBe(2); + + expect(element.children().eq(0).css('width')).toBe('12%'); + expect(element.children().eq(1).css('width')).toBe('22%'); + }); + + it('can handle correctly object changes', function() { + $rootScope.stacked[1] = { + value: 29, + type: 'danger' + }; + $rootScope.$digest(); + + expect(element.children().length).toBe(3); + + var barElement; + + barElement = element.children().eq(0); + expect(barElement.css('width')).toBe('12%'); + expect(barElement.hasClass('bar')).toBe(true); + expect(barElement.hasClass('bar-danger')).toBe(false); + + barElement = element.children().eq(1); + expect(barElement.css('width')).toBe('29%'); + expect(barElement.hasClass('bar')).toBe(true); + expect(barElement.hasClass('bar-danger')).toBe(true); + + barElement = element.children().eq(2); + expect(barElement.css('width')).toBe('33%'); + expect(barElement.hasClass('bar')).toBe(true); + expect(barElement.hasClass('bar-danger')).toBe(false); + }); + + it('can handle mixed objects with custom classes', function() { + $rootScope.stacked = [ + { value: 15, type: 'info' }, + 11, + { value: 9, type: 'danger' }, + { value: 22, type: 'warning' }, + 5 + ]; + $rootScope.$digest(); + + expect(element.children().length).toBe(5); + + var barElement; + + barElement = element.children().eq(0); + expect(barElement.css('width')).toBe('15%'); + expect(barElement.hasClass('bar-info')).toBe(true); + + barElement = element.children().eq(1); + expect(barElement.css('width')).toBe('11%'); + expect(barElement.hasClass('bar-info')).toBe(false); + + barElement = element.children().eq(2); + expect(barElement.css('width')).toBe('9%'); + expect(barElement.hasClass('bar-danger')).toBe(true); + + barElement = element.children().eq(3); + expect(barElement.css('width')).toBe('22%'); + expect(barElement.hasClass('bar-warning')).toBe(true); + + barElement = element.children().eq(4); + expect(barElement.css('width')).toBe('5%'); + expect(barElement.hasClass('bar-warning')).toBe(false); + }); + +}); + +describe('stacked progressbar directive handlers', function () { + var $rootScope, element; + beforeEach(module('ui.bootstrap.progressbar')); + beforeEach(module('template/progressbar/progress.html', 'template/progressbar/bar.html')); + beforeEach(inject(function(_$compile_, _$rootScope_) { + $compile = _$compile_; + $rootScope = _$rootScope_; + $rootScope.stacked = [20, 30, 40]; // total: 90 + $rootScope.fullHandler = jasmine.createSpy('fullHandler'); + $rootScope.emptyHandler = jasmine.createSpy('emptyHandler'); + element = $compile('')($rootScope); + $rootScope.$digest(); + })); + + + it("should not fire at start", function () { + expect($rootScope.fullHandler).not.toHaveBeenCalled(); + expect($rootScope.emptyHandler).not.toHaveBeenCalled(); + }); + + it("should fire callback when full", function () { + $rootScope.stacked.push(10); // total: 100 + $rootScope.$digest(); + + expect($rootScope.fullHandler).toHaveBeenCalled(); + expect($rootScope.emptyHandler).not.toHaveBeenCalled(); + }); + + it("should fire callback when empties", function () { + $rootScope.stacked = 0; + $rootScope.$digest(); + + expect($rootScope.fullHandler).not.toHaveBeenCalled(); + expect($rootScope.emptyHandler).toHaveBeenCalled(); + }); + +}); + +describe('stacked progressbar directive with auto-types', function () { + var $rootScope, element; + var config = {}; + beforeEach(module('ui.bootstrap.progressbar')); + beforeEach(module('template/progressbar/progress.html', 'template/progressbar/bar.html')); + beforeEach(inject(function(_$compile_, _$rootScope_, progressConfig) { + $compile = _$compile_; + $rootScope = _$rootScope_; + $rootScope.stacked = [12, 22, {value: 33}, {value: 5}, 11]; + element = $compile('')($rootScope); + $rootScope.$digest(); + + angular.extend(config, progressConfig); + })); + afterEach(inject(function(progressConfig) { + // return it to the original state + angular.extend(progressConfig, config); + })); + + it('has a "progress" css class', function() { + expect(element.hasClass('progress')).toBe(true); + }); + + it('contains tree child elements with "bar" css class each', function() { + expect(element.children().length).toBe(5); + for (var i = 0; i < 5; i++) { + expect(element.children().eq(i).hasClass('bar')).toBe(true); + } + }); + + it('has elements with expected width', function() { + expect(element.children().eq(0).css('width')).toBe('12%'); + expect(element.children().eq(1).css('width')).toBe('22%'); + expect(element.children().eq(2).css('width')).toBe('33%'); + expect(element.children().eq(3).css('width')).toBe('5%'); + expect(element.children().eq(4).css('width')).toBe('11%'); + }); + + it('has elements with automatic types', function() { + var stackedTypes = config.stackedTypes; + + for (var i = 0; i < stackedTypes.length; i++) { + expect(element.children().eq(i).hasClass('bar-' + stackedTypes[i])).toBe(true); + } + }); + + it('ignore automatic type if one is specified', function() { + $rootScope.stacked[1] = { + value: 18, + type: 'something' + }; + $rootScope.$digest(); + + var stackedTypes = config.stackedTypes; + + var bar = element.children().eq(1); + expect(bar.css('width')).toBe('18%'); + expect(bar.hasClass('bar-' + stackedTypes[1])).toBe(false); + expect(bar.hasClass('bar-something')).toBe(true); + }); + + + it('can provide automatic classes to be applied', function() { + $rootScope.stacked[1] = { + value: 18, + type: 'something' + }; + $rootScope.$digest(); + + var stackedTypes = config.stackedTypes; + + var bar = element.children().eq(1); + expect(bar.css('width')).toBe('18%'); + expect(bar.hasClass('bar-' + stackedTypes[1])).toBe(false); + expect(bar.hasClass('bar-something')).toBe(true); + }); + + it('can bypass default configuration for stacked classes from attribute', function() { + element = $compile('')($rootScope); + $rootScope.$digest(); + + var stackedTypes = config.stackedTypes; + + expect(element.children().eq(0).hasClass('bar-danger')).toBe(true); + expect(element.children().eq(0).hasClass('bar-' + stackedTypes[0])).toBe(false); + + expect(element.children().eq(1).hasClass('bar-warning')).toBe(true); + expect(element.children().eq(2).hasClass('bar-success')).toBe(true); + }); + +}); \ No newline at end of file diff --git a/template/progressbar/bar.html b/template/progressbar/bar.html new file mode 100644 index 0000000000..09a5a6b010 --- /dev/null +++ b/template/progressbar/bar.html @@ -0,0 +1 @@ +
    \ No newline at end of file diff --git a/template/progressbar/progress.html b/template/progressbar/progress.html new file mode 100644 index 0000000000..d390e79f7d --- /dev/null +++ b/template/progressbar/progress.html @@ -0,0 +1 @@ +
    \ No newline at end of file From 61f34d33c44bcb71cadd2dfaa0253ce7b6853aa9 Mon Sep 17 00:00:00 2001 From: antonellopasella Date: Thu, 14 Mar 2013 11:55:02 +0100 Subject: [PATCH 07/54] docs(dialog): update README.md Updated due to https://github.com/angular-ui/bootstrap/pull/118 --- src/dialog/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dialog/README.md b/src/dialog/README.md index b6c97bdc3f..aaefad07e3 100644 --- a/src/dialog/README.md +++ b/src/dialog/README.md @@ -45,7 +45,7 @@ Example: app.controller('MainCtrl', function($dialog, $scope) { $scope.openItemEditor = function(item){ - var d = $dialog.dialog({modalFade: false, resolve: {item: angular.copy(item) }}); + var d = $dialog.dialog({modalFade: false, resolve: {item: function(){ angular.copy(item)} }}); d.open('dialogs/item-editor.html', 'EditItemController'); }; }); From 0fb08e882a4bde2ad33955d27848186051a4da99 Mon Sep 17 00:00:00 2001 From: Pawel Kozlowski Date: Fri, 15 Mar 2013 21:21:23 +0100 Subject: [PATCH 08/54] refactor(modal): remove unused variables --- src/modal/modal.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/modal/modal.js b/src/modal/modal.js index 0d2cebc114..7f8a828a6d 100644 --- a/src/modal/modal.js +++ b/src/modal/modal.js @@ -1,7 +1,5 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.dialog']) .directive('modal', ['$parse', '$dialog', function($parse, $dialog) { - var backdropEl; - var body = angular.element(document.getElementsByTagName('body')[0]); return { restrict: 'EA', terminal: true, From 0228b06e58c3c5dc981253cad6d7d6e78e4e8ddb Mon Sep 17 00:00:00 2001 From: Pawel Kozlowski Date: Sat, 16 Mar 2013 23:13:33 +0100 Subject: [PATCH 09/54] docs(README): minor updates --- README.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 7b8e35e6b0..4d48b4513b 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,10 @@ [![Build Status](https://secure.travis-ci.org/angular-ui/bootstrap.png)](http://travis-ci.org/angular-ui/bootstrap) +## Demo + +What to see directives in action? Visit https://github.com/angular-ui/bootstrap! + ## Project philosophy ### Native, lightweight directives @@ -34,13 +38,15 @@ We are always looking for the quality contributions! Please check the [CONTRIBUT * Install global dev dependencies: `npm install -g grunt-cli testacular` * Instal local dev dependencies: `npm install` while current directory is bootstrap repo -#### Run unit tests +#### Build +* Build the whole project: `grunt` - this will run `lint`, `test`, and `concat` targets + +Check the Grunt build file for other tasks that are defined for this project + +#### TDD * Start testacular server: `grunt server` * Run test: `grunt test-run` -#### Before commit -* Build the whole project: `grunt` - this will run `lint`, `test`, and `concat` targets - ### Release * Bump up version number in `package.json` * Commit the version change with the following message: `chore(release): [versio number]` From acca7dcd15b5bada2ceb247684890406d134dc13 Mon Sep 17 00:00:00 2001 From: Pawel Kozlowski Date: Sat, 16 Mar 2013 23:39:09 +0100 Subject: [PATCH 10/54] fix(typeahead): close matches popup on click outside typeahead Closes #231 --- src/typeahead/test/typeahead.spec.js | 22 +++++++++++++++++++--- src/typeahead/typeahead.js | 7 ++++++- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/typeahead/test/typeahead.spec.js b/src/typeahead/test/typeahead.spec.js index 6e9339cb5a..29b293f901 100644 --- a/src/typeahead/test/typeahead.spec.js +++ b/src/typeahead/test/typeahead.spec.js @@ -144,14 +144,14 @@ describe('typeahead tests', function () { describe('typeahead', function () { - var $scope, $compile; + var $scope, $compile, $document; var changeInputValueTo; - beforeEach(inject(function (_$rootScope_, _$compile_, $sniffer) { + beforeEach(inject(function (_$rootScope_, _$compile_, _$document_, $sniffer) { $scope = _$rootScope_; $scope.source = ['foo', 'bar', 'baz']; $compile = _$compile_; - + $document = _$document_; changeInputValueTo = function (element, value) { var inputEl = findInput(element); inputEl.val(value); @@ -318,5 +318,21 @@ describe('typeahead tests', function () { }); }); + describe('regressions tests', function () { + + it('issue 231 - closes matches popup on click outside typeahead', function () { + var element = prepareInputEl("
    "); + var inputEl = findInput(element); + + changeInputValueTo(element, 'b'); + var dropdown = findDropDown(element); + + $document.find('body').click(); + $scope.$digest(); + + expect(dropdown).not.toHaveClass('open'); + }); + }); + }); }); \ No newline at end of file diff --git a/src/typeahead/typeahead.js b/src/typeahead/typeahead.js index 521d7b05bd..29af035946 100644 --- a/src/typeahead/typeahead.js +++ b/src/typeahead/typeahead.js @@ -30,7 +30,7 @@ angular.module('ui.bootstrap.typeahead', []) }]) //options - min length - .directive('typeahead', ['$compile', '$q', 'typeaheadParser', function ($compile, $q, typeaheadParser) { + .directive('typeahead', ['$compile', '$q', '$document', 'typeaheadParser', function ($compile, $q, $document, typeaheadParser) { var HOT_KEYS = [9, 13, 27, 38, 40]; @@ -155,6 +155,11 @@ angular.module('ui.bootstrap.typeahead', []) } }); + $document.find('body').bind('click', function(){ + scope.matches = []; + scope.$digest(); + }); + var tplElCompiled = $compile("")(scope); element.after(tplElCompiled); From ca1d1e505be0da4aac7dc423e43cc31f0f0d9f9f Mon Sep 17 00:00:00 2001 From: Pawel Kozlowski Date: Sat, 16 Mar 2013 23:53:46 +0100 Subject: [PATCH 11/54] docs(README): minor updates --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4d48b4513b..3841de2a7e 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ ## Demo -What to see directives in action? Visit https://github.com/angular-ui/bootstrap! +Do you want to see directives in action? Visit http://angular-ui.github.com/bootstrap/! ## Project philosophy From aedc05654b19342d96eabc4fc08d6a090765a48b Mon Sep 17 00:00:00 2001 From: Matt Raible Date: Tue, 12 Mar 2013 11:59:21 -0600 Subject: [PATCH 12/54] feat(carousel): Hide navigation indicators if only one slide --- src/carousel/test/carousel.spec.js | 31 ++++++++++++++++++++++++++++++ template/carousel/carousel.html | 6 +++--- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/carousel/test/carousel.spec.js b/src/carousel/test/carousel.spec.js index 6749c84b9b..a962bc15dc 100644 --- a/src/carousel/test/carousel.spec.js +++ b/src/carousel/test/carousel.spec.js @@ -63,6 +63,37 @@ describe('carousel', function() { var indicators = elm.find('ol.carousel-indicators > li'); expect(indicators.length).toBe(3); }); + + it('should hide navigation when only one slide', function () { + scope.slides=[{active:false,content:'one'}]; + scope.$apply(); + elm = $compile( + '' + + '' + + '{{slide.content}}' + + '' + + '' + )(scope); + var indicators = elm.find('ol.carousel-indicators > li'); + expect(indicators.length).toBe(0); + + var navNext = elm.find('a.right'); + expect(navNext.length).toBe(0); + + var navPrev = elm.find('a.left'); + expect(navPrev.length).toBe(0); + }); + + it('should show navigation when there are 3 slides', function () { + var indicators = elm.find('ol.carousel-indicators > li'); + expect(indicators.length).not.toBe(0); + + var navNext = elm.find('a.right'); + expect(navNext.length).not.toBe(0); + + var navPrev = elm.find('a.left'); + expect(navPrev.length).not.toBe(0); + }); it('should go to next when clicking next button', function() { var navNext = elm.find('a.right'); diff --git a/template/carousel/carousel.html b/template/carousel/carousel.html index aede73959e..97fad9f3f1 100644 --- a/template/carousel/carousel.html +++ b/template/carousel/carousel.html @@ -1,8 +1,8 @@ From 52ea3414d60101b2b6f8e4c262345e43f0a2c5c8 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Mon, 18 Mar 2013 14:23:49 +0000 Subject: [PATCH 13/54] chore(grunt): update to latest grunt to fix grunt bug when running on node 0.10.0 Changes to the path API in nodeJS causes the build to fail with grunt 0.4.0. This patch fixes it in two ways: - Update to grunt 0.4.1 - this version fixes the bug - Add cwd option to build - this also fixes the issue but from our side and works with grunt 0.4.0 --- Gruntfile.js | 4 +++- package.json | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 06acbce84e..beeff606ea 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -149,7 +149,9 @@ module.exports = function(grunt) { srcFiles = ['src/*/*.js']; tplFiles = ['template/*/*.html.js']; - grunt.file.expand({filter: 'isDirectory'}, 'src/*').forEach(function(dir) { + var folders = grunt.file.expand({filter: 'isDirectory', cwd: '.'}, 'src/*'); + + folders.forEach(function(dir) { findModule(dir.split('/')[1]); }); } diff --git a/package.json b/package.json index b8f0a9095e..40cd14fb44 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "dependencies": {}, "devDependencies": { "node-markdown": "0.1.1", - "grunt": "0.4.0", + "grunt": "~0.4.1", "grunt-contrib-jshint": "~0.2.0", "grunt-contrib-concat": "~0.1.3", "grunt-contrib-uglify": "~0.1.1" From 2c9dc0556de82a1ff6657a996fded62ab5ac5549 Mon Sep 17 00:00:00 2001 From: Pawel Kozlowski Date: Mon, 18 Mar 2013 22:29:54 +0100 Subject: [PATCH 14/54] docs(README): add installation instructions --- README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/README.md b/README.md index 3841de2a7e..3d63a8e61b 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,23 @@ Do you want to see directives in action? Visit http://angular-ui.github.com/bootstrap/! +## Installation + +Installation is easy as angular-ui-bootstrap has minimal dependencies - only the AngularJS and Bootstrap's CSS are required. +After downloading dependencies (or better yet, referencing them from your favourite CDN) you need to download build version of this project. All the files and their purposes are described here: +https://github.com/angular-ui/bootstrap/tree/gh-pages#build-files +Don't worry, if you are not sure which file to take, opt for `ui-bootstrap-tpls-[version].min.js`. + +When you are done downloading all the dependencies and project files the only remaining part is to add dependencies on the `ui.bootstrap` AngularJS module: + +```javascript +angular.module('myModule', ['ui.bootstrap']); +``` + +Project files are also available through your favourite package manager: +* **Bower**: `bower install angular-bootstrap` +* **NuGet**: https://nuget.org/packages/Angular.UI.Bootstrap/ + ## Project philosophy ### Native, lightweight directives @@ -58,5 +75,6 @@ Check the Grunt build file for other tasks that are defined for this project * push changes * switch back to the `main branch` and modify `package.json` to bump up version for the next iteration * commit (`chore(release): starting [versio number]`) and push +* publish Bower and NuGet packages Well done! (If you don't like repeating yourself open a PR with a grunt task taking care of the above!) From 22a00cd08e38b16bb47bd9191a32c4f90eeb7f41 Mon Sep 17 00:00:00 2001 From: Pawel Kozlowski Date: Tue, 19 Mar 2013 20:31:20 +0100 Subject: [PATCH 15/54] fix(typeahead): stop keydown event propagation when ESC pressed to discard matches Closes #243 --- src/typeahead/typeahead.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/typeahead/typeahead.js b/src/typeahead/typeahead.js index 29af035946..6317c6bb2e 100644 --- a/src/typeahead/typeahead.js +++ b/src/typeahead/typeahead.js @@ -126,7 +126,7 @@ angular.module('ui.bootstrap.typeahead', []) modelCtrl.$render(); }; - //bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(9) + //bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(27) element.bind('keydown', function (evt) { //typeahead is open and an "interesting" key was pressed @@ -150,6 +150,7 @@ angular.module('ui.bootstrap.typeahead', []) }); } else if (evt.which === 27) { + evt.stopPropagation(); scope.matches = []; scope.$digest(); } From 38c1badd54849a2be249cec17ea11f199c5e6844 Mon Sep 17 00:00:00 2001 From: Pawel Kozlowski Date: Tue, 19 Mar 2013 20:49:39 +0100 Subject: [PATCH 16/54] fix(tabs): remove superfluous href from tabs template --- template/tabs/tabs.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/template/tabs/tabs.html b/template/tabs/tabs.html index 06e92f3750..27bec3274e 100644 --- a/template/tabs/tabs.html +++ b/template/tabs/tabs.html @@ -1,7 +1,7 @@
    From beb257fe78df955c532faeb30f7bb3ee459d0972 Mon Sep 17 00:00:00 2001 From: Andy Joslin Date: Thu, 14 Mar 2013 19:16:06 -0400 Subject: [PATCH 17/54] chore(build): Add grunt-contrib-watch task for grunt v0.4 --- Gruntfile.js | 3 ++- package.json | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index beeff606ea..85b3b06949 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -2,6 +2,7 @@ var markdown = require('node-markdown').Markdown; module.exports = function(grunt) { + grunt.loadNpmTasks('grunt-contrib-watch'); grunt.loadNpmTasks('grunt-contrib-concat'); grunt.loadNpmTasks('grunt-contrib-jshint'); grunt.loadNpmTasks('grunt-contrib-uglify'); @@ -22,7 +23,7 @@ module.exports = function(grunt) { }, watch: { files: ['<%= jshint.files %>', 'template/**/*.html'], - tasks: 'before-test test-run' + tasks: ['before-test', 'test-run'] }, concat: { dist: { diff --git a/package.json b/package.json index 40cd14fb44..edb327d8d2 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "grunt": "~0.4.1", "grunt-contrib-jshint": "~0.2.0", "grunt-contrib-concat": "~0.1.3", - "grunt-contrib-uglify": "~0.1.1" + "grunt-contrib-uglify": "~0.1.1", + "grunt-contrib-watch": "~0.3.1" } } From e5d593b866cb6a198a1e2fc528fc4db6e8e880c7 Mon Sep 17 00:00:00 2001 From: Josh David Miller Date: Wed, 20 Mar 2013 17:11:28 -0700 Subject: [PATCH 18/54] feat(tooltipProvider): added appendToBody option for $tooltip --- src/tooltip/test/tooltip.spec.js | 41 +++++++++++++++++++++++++++++++- src/tooltip/tooltip.js | 16 +++++++++---- 2 files changed, 51 insertions(+), 6 deletions(-) diff --git a/src/tooltip/test/tooltip.spec.js b/src/tooltip/test/tooltip.spec.js index b9849df338..d23ffda2cc 100644 --- a/src/tooltip/test/tooltip.spec.js +++ b/src/tooltip/test/tooltip.spec.js @@ -123,4 +123,43 @@ describe('tooltip', function() { })); }); - +describe('$tooltipProvider', function() { + var elm, + elmBody, + scope, + elmScope, + body; + + // load the tooltip code + beforeEach(module('ui.bootstrap.tooltip')); + + // load the template + beforeEach(module('template/tooltip/tooltip-popup.html')); + + it( 'should not be open initially', function() { + module( function ( $tooltipProvider ) { + $tooltipProvider.options({ appendToBody: true }); + }); + + inject( function( $rootScope, $compile, $document ) { + $body = $document.find( 'body' ); + elmBody = angular.element( + '
    Selector Text
    ' + ); + + scope = $rootScope; + $compile(elmBody)(scope); + scope.$digest(); + elm = elmBody.find('span'); + elmScope = elm.scope(); + + var bodyLength = $body.children().length; + elm.trigger( 'mouseenter' ); + + expect( elmScope.tt_isOpen ).toBe( true ); + expect( elmBody.children().length ).toBe( 1 ); + expect( $body.children().length ).toEqual( bodyLength + 1 ); + }); + }); +}); + diff --git a/src/tooltip/tooltip.js b/src/tooltip/tooltip.js index 15617274a8..9da3f51676 100644 --- a/src/tooltip/tooltip.js +++ b/src/tooltip/tooltip.js @@ -20,7 +20,7 @@ angular.module( 'ui.bootstrap.tooltip', [] ) var globalOptions = {}; /** - * The `options({})` allows global configuration of all dialogs in the + * `options({})` allows global configuration of all tooltips in the * application. * * var app = angular.module( 'App', ['ui.bootstrap.tooltip'], function( $tooltipProvider ) { @@ -36,7 +36,7 @@ angular.module( 'ui.bootstrap.tooltip', [] ) * Returns the actual instance of the $tooltip service. * TODO support multiple triggers */ - this.$get = [ '$window', '$compile', '$timeout', '$parse', function ( $window, $compile, $timeout, $parse ) { + this.$get = [ '$window', '$compile', '$timeout', '$parse', '$document', function ( $window, $compile, $timeout, $parse, $document ) { return function $tooltip ( type, defaultTriggerShow, defaultTriggerHide ) { var options = angular.extend( {}, defaultOptions, globalOptions ); @@ -65,8 +65,9 @@ angular.module( 'ui.bootstrap.tooltip', [] ) restrict: 'EA', scope: true, link: function link ( scope, element, attrs ) { - var tooltip = $compile( template )( scope ), - transitionTimeout; + var tooltip = $compile( template )( scope ); + var transitionTimeout; + var $body; attrs.$observe( type, function ( val ) { scope.tt_content = val; @@ -111,7 +112,12 @@ angular.module( 'ui.bootstrap.tooltip', [] ) // Now we add it to the DOM because need some info about it. But it's not // visible yet anyway. - element.after( tooltip ); + if ( options.appendToBody ) { + $body = $body || $document.find( 'body' ); + $body.append( tooltip ); + } else { + element.after( tooltip ); + } // Get the position of the directive element. position = getPosition( element ); From a79a2ba8b73b903275f13e18588543aafad53414 Mon Sep 17 00:00:00 2001 From: Anton Date: Wed, 20 Mar 2013 22:02:54 -0700 Subject: [PATCH 19/54] feat(tooltip): added popup-delay option --- src/popover/test/popoverSpec.js | 43 ++++++++++++------ src/tooltip/docs/demo.html | 4 +- src/tooltip/test/tooltip.spec.js | 78 ++++++++++++++++++++++++++++++++ src/tooltip/tooltip.js | 31 ++++++++++--- 4 files changed, 135 insertions(+), 21 deletions(-) diff --git a/src/popover/test/popoverSpec.js b/src/popover/test/popoverSpec.js index 633aeb8b2c..81e37d4734 100644 --- a/src/popover/test/popoverSpec.js +++ b/src/popover/test/popoverSpec.js @@ -1,7 +1,7 @@ describe('popover', function() { - var elm, + var elm, elmBody, - scope, + scope, elmScope; // load the popover code @@ -11,8 +11,8 @@ describe('popover', function() { beforeEach(module('template/popover/popover.html')); beforeEach(inject(function($rootScope, $compile) { - elmBody = angular.element( - '
    Selector Text
    ' + elmBody = angular.element( + '
    Selector Text
    ' ); scope = $rootScope; @@ -24,7 +24,7 @@ describe('popover', function() { it('should not be open initially', inject(function() { expect( elmScope.tt_isOpen ).toBe( false ); - + // We can only test *that* the popover-popup element wasn't created as the // implementation is templated and replaced. expect( elmBody.children().length ).toBe( 1 ); @@ -51,8 +51,8 @@ describe('popover', function() { })); it('should allow specification of placement', inject( function( $compile ) { - elm = $compile( angular.element( - 'Selector Text' + elm = $compile( angular.element( + 'Selector Text' ) )( scope ); elmScope = elm.scope(); @@ -62,7 +62,7 @@ describe('popover', function() { it('should work inside an ngRepeat', inject( function( $compile ) { - elm = $compile( angular.element( + elm = $compile( angular.element( '
      '+ '
    • '+ '{{item.name}}'+ @@ -73,11 +73,11 @@ describe('popover', function() { scope.items = [ { name: "One", popover: "First popover" } ]; - + scope.$digest(); - + var tt = angular.element( elm.find("li > span")[0] ); - + tt.trigger( 'click' ); expect( tt.text() ).toBe( scope.items[0].name ); @@ -93,7 +93,7 @@ describe('popover', function() { scope.popoverTitle = "Popover Title"; scope.alt = "Alt Message"; - elmBody = $compile( angular.element( + elmBody = $compile( angular.element( '
      Selector Text
      ' ) )( scope ); @@ -101,7 +101,7 @@ describe('popover', function() { scope.$digest(); elm = elmBody.find( 'span' ); elmScope = elm.scope(); - + elm.trigger( 'click' ); expect( elm.attr( 'alt' ) ).toBe( scope.alt ); @@ -113,6 +113,23 @@ describe('popover', function() { elm.trigger( 'click' ); })); + + it( 'should allow specification of delay', inject( function ($timeout, $compile) { + + elm = $compile( angular.element( + 'Selector Text' + ) )( scope ); + elmScope = elm.scope(); + scope.$digest(); + + elm.trigger( 'click' ); + expect( elmScope.tt_isOpen ).toBe( false ); + + $timeout.flush(); + expect( elmScope.tt_isOpen ).toBe( true ); + + } ) ); + }); diff --git a/src/tooltip/docs/demo.html b/src/tooltip/docs/demo.html index 954c70e57e..737d8d5f28 100644 --- a/src/tooltip/docs/demo.html +++ b/src/tooltip/docs/demo.html @@ -13,8 +13,8 @@ bottom pharetra convallis posuere morbi leo urna, fading - at elementum eu, facilisis sed odio morbi quis commodo odio. In cursus - turpis massa tincidunt dui ut. + at elementum eu, facilisis sed odio morbi quis commodo odio. In cursus + delayed turpis massa tincidunt dui ut.

    diff --git a/src/tooltip/test/tooltip.spec.js b/src/tooltip/test/tooltip.spec.js index b9849df338..524de5ea4b 100644 --- a/src/tooltip/test/tooltip.spec.js +++ b/src/tooltip/test/tooltip.spec.js @@ -121,6 +121,84 @@ describe('tooltip', function() { expect(elmBody.children().length).toBe(1); })); + + describe('with specified popup delay', function () { + + beforeEach(inject(function ($compile) { + scope.delay='1000'; + elm = $compile(angular.element( + 'Selector Text' + ))(scope); + elmScope = elm.scope(); + scope.$digest(); + })); + + it('should open after timeout', inject(function ($timeout) { + + elm.trigger('mouseenter'); + expect(elmScope.tt_isOpen).toBe(false); + + $timeout.flush(); + expect(elmScope.tt_isOpen).toBe(true); + + })); + + it('should not open if mouseleave before timeout', inject(function ($timeout) { + elm.trigger('mouseenter'); + expect(elmScope.tt_isOpen).toBe(false); + + elm.trigger('mouseleave'); + $timeout.flush(); + expect(elmScope.tt_isOpen).toBe(false); + })); + + it('should use default popup delay if specified delay is not a number', function(){ + scope.delay='text1000'; + scope.$digest(); + elm.trigger('mouseenter'); + expect(elmScope.tt_isOpen).toBe(true); + }); + + }); + +}); + +describe('tooltip with popup delay configured through provider', function(){ + + var elm, + elmBody, + scope, + elmScope; + + beforeEach(module('ui.bootstrap.tooltip', function($tooltipProvider){ + $tooltipProvider.options({popupDelay: 1000}); + })); + + // load the template + beforeEach(module('template/tooltip/tooltip-popup.html')); + + beforeEach(inject(function($rootScope, $compile) { + elmBody = angular.element( + '
    Selector Text
    ' + ); + + scope = $rootScope; + $compile(elmBody)(scope); + scope.$digest(); + elm = elmBody.find('span'); + elmScope = elm.scope(); + })); + + it('should open after timeout', inject(function($timeout) { + + elm.trigger( 'mouseenter' ); + expect( elmScope.tt_isOpen ).toBe( false ); + + $timeout.flush(); + expect( elmScope.tt_isOpen ).toBe( true ); + + })); + }); diff --git a/src/tooltip/tooltip.js b/src/tooltip/tooltip.js index 15617274a8..affe7f9f0a 100644 --- a/src/tooltip/tooltip.js +++ b/src/tooltip/tooltip.js @@ -1,5 +1,5 @@ /** - * The following features are still outstanding: popup delay, animation as a + * The following features are still outstanding: animation as a * function, placement as a function, inside, support for more triggers than * just mouse enter/leave, html tooltips, and selector delegation. */ @@ -13,7 +13,8 @@ angular.module( 'ui.bootstrap.tooltip', [] ) // The default options tooltip and popover. var defaultOptions = { placement: 'top', - animation: true + animation: true, + popupDelay: 0 }; // The options specified to the provider globally. @@ -66,7 +67,8 @@ angular.module( 'ui.bootstrap.tooltip', [] ) scope: true, link: function link ( scope, element, attrs ) { var tooltip = $compile( template )( scope ), - transitionTimeout; + transitionTimeout, + popupTimeout; attrs.$observe( type, function ( val ) { scope.tt_content = val; @@ -84,9 +86,23 @@ angular.module( 'ui.bootstrap.tooltip', [] ) scope.tt_animation = angular.isDefined( val ) ? $parse( val ) : function(){ return options.animation; }; }); + attrs.$observe( type+'PopupDelay', function ( val ) { + var delay = parseInt( val, 10 ); + scope.tt_popupDelay = ! isNaN(delay) ? delay : options.popupDelay; + }); + // By default, the tooltip is not open. // TODO add ability to start tooltip opened scope.tt_isOpen = false; + + //show the tooltip with delay if specified, otherwise show it immediately + function showWithDelay() { + if( scope.tt_popupDelay ){ + popupTimeout = $timeout( show, scope.tt_popupDelay ); + }else { + scope.$apply( show ); + } + } // Show the tooltip popup element. function show() { @@ -101,7 +117,7 @@ angular.module( 'ui.bootstrap.tooltip', [] ) } // If there is a pending remove transition, we must cancel it, lest the - // toolip be mysteriously removed. + // tooltip be mysteriously removed. if ( transitionTimeout ) { $timeout.cancel( transitionTimeout ); } @@ -161,6 +177,9 @@ angular.module( 'ui.bootstrap.tooltip', [] ) // First things first: we don't show it anymore. //tooltip.removeClass( 'in' ); scope.tt_isOpen = false; + + //if tooltip is going to be shown after delay, we must cancel this + $timeout.cancel( popupTimeout ); // And now we remove it from the DOM. However, if we have animation, we // need to wait for it to expire beforehand. @@ -178,14 +197,14 @@ angular.module( 'ui.bootstrap.tooltip', [] ) if ( ! angular.isDefined( defaultTriggerHide ) ) { element.bind( defaultTriggerShow, function toggleTooltipBind () { if ( ! scope.tt_isOpen ) { - scope.$apply( show ); + showWithDelay(); } else { scope.$apply( hide ); } }); } else { element.bind( defaultTriggerShow, function showTooltipBind() { - scope.$apply( show ); + showWithDelay(); }); element.bind( defaultTriggerHide, function hideTooltipBind() { scope.$apply( hide ); From 929a46faa3a3eea3dec7ae8c1387f332eee9a9bc Mon Sep 17 00:00:00 2001 From: Pawel Kozlowski Date: Sun, 24 Mar 2013 21:22:07 +0100 Subject: [PATCH 20/54] fix(typeahead): correctly render initial model value Closes #203 --- src/typeahead/test/typeahead.spec.js | 11 +++++++++++ src/typeahead/typeahead.js | 6 +++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/typeahead/test/typeahead.spec.js b/src/typeahead/test/typeahead.spec.js index 29b293f901..e444cf5790 100644 --- a/src/typeahead/test/typeahead.spec.js +++ b/src/typeahead/test/typeahead.spec.js @@ -217,6 +217,17 @@ describe('typeahead tests', function () { expect(element).toBeClosed(); }); + it('should correctly render initial state if the "as" keyword is used', function () { + + $scope.states = [{code: 'AL', name: 'Alaska'}, {code: 'CL', name: 'California'}]; + $scope.result = $scope.states[0]; + + var element = prepareInputEl("
    "); + var inputEl = findInput(element); + + expect(inputEl.val()).toEqual('Alaska'); + }); + it('should not get open on model change', function () { var element = prepareInputEl("
    "); $scope.$apply(function(){ diff --git a/src/typeahead/typeahead.js b/src/typeahead/typeahead.js index 6317c6bb2e..3614f1b239 100644 --- a/src/typeahead/typeahead.js +++ b/src/typeahead/typeahead.js @@ -38,7 +38,7 @@ angular.module('ui.bootstrap.typeahead', []) require:'ngModel', link:function (originalScope, element, attrs, modelCtrl) { - var selected = modelCtrl.$modelValue; + var selected; //minimal no of characters that needs to be entered before typeahead kicks-in var minSearch = originalScope.$eval(attrs.typeaheadMinLength) || 1; @@ -95,7 +95,7 @@ angular.module('ui.bootstrap.typeahead', []) scope.query = undefined; //plug into $parsers pipeline to open a typeahead on view changes initiated from DOM - //$parsers kick-in on all the changes coming from the vview as well as manually triggered by $setViewValue + //$parsers kick-in on all the changes coming from the view as well as manually triggered by $setViewValue modelCtrl.$parsers.push(function (inputValue) { resetMatches(); @@ -112,7 +112,7 @@ angular.module('ui.bootstrap.typeahead', []) modelCtrl.$render = function () { var locals = {}; - locals[parserResult.itemName] = selected; + locals[parserResult.itemName] = selected || modelCtrl.$viewValue; element.val(parserResult.viewMapper(scope, locals) || modelCtrl.$viewValue); selected = undefined; }; From a40c3fbe08fb774ef1e2e6bbe0c00b797f1c71c2 Mon Sep 17 00:00:00 2001 From: Pawel Kozlowski Date: Tue, 26 Mar 2013 21:09:57 +0100 Subject: [PATCH 21/54] feat(typeahead): support the editable property Closes #269 --- src/typeahead/test/typeahead.spec.js | 12 ++++++++++++ src/typeahead/typeahead.js | 11 ++++++++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/typeahead/test/typeahead.spec.js b/src/typeahead/test/typeahead.spec.js index e444cf5790..8daeee9c75 100644 --- a/src/typeahead/test/typeahead.spec.js +++ b/src/typeahead/test/typeahead.spec.js @@ -271,6 +271,18 @@ describe('typeahead tests', function () { var matchHighlight = findMatches(element).find('a').html(); expect(matchHighlight).toEqual('prefixfoo'); }); + + it('should by default bind view value to model even if not part of matches', function () { + var element = prepareInputEl("
    "); + changeInputValueTo(element, 'not in matches'); + expect($scope.result).toEqual('not in matches'); + }); + + it('should support the editable property to limit model bindings to matches only', function () { + var element = prepareInputEl("
    "); + changeInputValueTo(element, 'not in matches'); + expect($scope.result).toEqual(undefined); + }); }); describe('selecting a match', function () { diff --git a/src/typeahead/typeahead.js b/src/typeahead/typeahead.js index 3614f1b239..93e4ca7914 100644 --- a/src/typeahead/typeahead.js +++ b/src/typeahead/typeahead.js @@ -46,6 +46,9 @@ angular.module('ui.bootstrap.typeahead', []) //expressions used by typeahead var parserResult = typeaheadParser.parse(attrs.typeahead); + //should it restrict model values to the ones selected from the popup only? + var isEditable = originalScope.$eval(attrs.typeaheadEditable) !== false; + //create a child scope for the typeahead directive so we are not polluting original scope //with typeahead-specific data (matches, query etc.) var scope = originalScope.$new(); @@ -107,7 +110,7 @@ angular.module('ui.bootstrap.typeahead', []) } } - return undefined; + return isEditable ? inputValue : undefined; }); modelCtrl.$render = function () { @@ -151,13 +154,15 @@ angular.module('ui.bootstrap.typeahead', []) } else if (evt.which === 27) { evt.stopPropagation(); - scope.matches = []; + + resetMatches(); scope.$digest(); } }); $document.find('body').bind('click', function(){ - scope.matches = []; + + resetMatches(); scope.$digest(); }); From dcc9ef31db5fc33dadd8ab29193a9bf01a201c54 Mon Sep 17 00:00:00 2001 From: Guy Nesher Date: Tue, 26 Mar 2013 18:26:52 +0000 Subject: [PATCH 22/54] fix(dialog): set _open to false on init --- src/dialog/dialog.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/dialog/dialog.js b/src/dialog/dialog.js index 13a578482d..67a192b2ce 100644 --- a/src/dialog/dialog.js +++ b/src/dialog/dialog.js @@ -66,6 +66,7 @@ dialogModule.provider("$dialog", function(){ function Dialog(opts) { var self = this, options = this.options = angular.extend({}, defaults, globalOptions, opts); + this._open = false; this.backdropEl = createElement(options.backdropClass); if(options.backdropFade){ From 194c848b3e6007eb3404785766458dfbaf6eeab8 Mon Sep 17 00:00:00 2001 From: Shaun Grady Date: Thu, 28 Mar 2013 11:34:51 -0700 Subject: [PATCH 23/54] refactor(dropdownToggle): cleanup code and increase legibility --- src/dropdownToggle/dropdownToggle.js | 42 ++++++++++------------------ 1 file changed, 15 insertions(+), 27 deletions(-) diff --git a/src/dropdownToggle/dropdownToggle.js b/src/dropdownToggle/dropdownToggle.js index e07a0a600e..89a181c128 100644 --- a/src/dropdownToggle/dropdownToggle.js +++ b/src/dropdownToggle/dropdownToggle.js @@ -12,49 +12,37 @@ */ -angular.module('ui.bootstrap.dropdownToggle', []).directive('dropdownToggle', -['$document', '$location', '$window', function ($document, $location, $window) { - var openElement = null, close; +angular.module('ui.bootstrap.dropdownToggle', []).directive('dropdownToggle', + ['$document', '$location', '$window', function ($document, $location, $window) { + var openElement = null, + closeMenu = angular.noop; return { restrict: 'CA', link: function(scope, element, attrs) { - scope.$watch(function dropdownTogglePathWatch(){return $location.path();}, function dropdownTogglePathWatchAction() { - if (close) { close(); } - }); - - element.parent().bind('click', function(event) { - if (close) { close(); } - }); - + scope.$watch('$location.path', function() { closeMenu(); }); + element.parent().bind('click', function() { closeMenu(); }); element.bind('click', function(event) { event.preventDefault(); event.stopPropagation(); - - var iWasOpen = false; - - if (openElement) { - iWasOpen = openElement === element; - close(); - } - - if (!iWasOpen){ + var elementWasOpen = (element === openElement); + if (!!openElement) { + closeMenu(); } + if (!elementWasOpen){ element.parent().addClass('open'); openElement = element; - - close = function (event) { + closeMenu = function (event) { if (event) { event.preventDefault(); event.stopPropagation(); } - $document.unbind('click', close); + $document.unbind('click', closeMenu); element.parent().removeClass('open'); - close = null; + closeMenu = angular.noop; openElement = null; }; - - $document.bind('click', close); + $document.bind('click', closeMenu); } }); } }; -}]); +}]); \ No newline at end of file From 556a37ea65e0266185c3c8baf34ea1dad521a855 Mon Sep 17 00:00:00 2001 From: Andy Joslin Date: Sat, 30 Mar 2013 21:15:39 -0400 Subject: [PATCH 24/54] chore(build): Make test-config not autoWatch, speed up grunt watch task --- Gruntfile.js | 13 +++++++++++-- testacular.conf.js | 2 +- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 85b3b06949..3b75335c67 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -22,8 +22,17 @@ module.exports = function(grunt) { all: 'angular.module("ui.bootstrap", ["ui.bootstrap.tpls", <%= srcModules %>]);' }, watch: { - files: ['<%= jshint.files %>', 'template/**/*.html'], - tasks: ['before-test', 'test-run'] + html: { + files: ['template/**/*.html'], + tasks: ['html2js'] + }, + js: { + //nospawn makes the tests start faster + nospawn: true, + files: ['src/**/*.js'], + //we don't need to jshint here, it slows down everything else + tasks: ['test-run'] + } }, concat: { dist: { diff --git a/testacular.conf.js b/testacular.conf.js index b7c299b755..15d329e4ec 100644 --- a/testacular.conf.js +++ b/testacular.conf.js @@ -47,7 +47,7 @@ colors = true; logLevel = LOG_INFO; // enable / disable watching file and executing tests whenever any file changes -autoWatch = true; +autoWatch = false; // Continuous Integration mode // if true, it capture browsers, run tests and exit From c2645f4a6129d68fb965d401e9de2f55ffd43c14 Mon Sep 17 00:00:00 2001 From: Joe Vanderstelt Date: Thu, 28 Mar 2013 17:59:05 -0400 Subject: [PATCH 25/54] fix(alert): don't show close button if no close callback specified --- src/alert/alert.js | 11 +++++++---- src/alert/docs/demo.html | 2 +- src/alert/docs/readme.md | 4 +++- src/alert/test/alert.spec.js | 31 ++++++++++++++++++++----------- template/alert/alert.html | 4 ++-- 5 files changed, 33 insertions(+), 19 deletions(-) diff --git a/src/alert/alert.js b/src/alert/alert.js index bec5a2904b..07f68dde5b 100644 --- a/src/alert/alert.js +++ b/src/alert/alert.js @@ -4,9 +4,12 @@ angular.module("ui.bootstrap.alert", []).directive('alert', function () { templateUrl:'template/alert/alert.html', transclude:true, replace:true, - scope:{ - type:'=', - close:'&' + scope: { + type: '=', + close: '&' + }, + link: function(scope, iElement, iAttrs, controller) { + scope.closeable = "close" in iAttrs; } }; -}); \ No newline at end of file +}); diff --git a/src/alert/docs/demo.html b/src/alert/docs/demo.html index d91517526e..a750374f37 100644 --- a/src/alert/docs/demo.html +++ b/src/alert/docs/demo.html @@ -1,4 +1,4 @@
    {{alert.msg}} -
    \ No newline at end of file + diff --git a/src/alert/docs/readme.md b/src/alert/docs/readme.md index 1fafb54af4..fb8029a5a3 100644 --- a/src/alert/docs/readme.md +++ b/src/alert/docs/readme.md @@ -1,3 +1,5 @@ Alert is an AngularJS-version of bootstrap's alert. -This directive can be used to generate alerts from the dynamic model data (using the ng-repeat directive); \ No newline at end of file +This directive can be used to generate alerts from the dynamic model data (using the ng-repeat directive); + +The presence of the "close" attribute determines if a close button is displayed diff --git a/src/alert/test/alert.spec.js b/src/alert/test/alert.spec.js index 5fa21daeb5..0ffbc241a7 100644 --- a/src/alert/test/alert.spec.js +++ b/src/alert/test/alert.spec.js @@ -1,4 +1,5 @@ describe("alert", function () { + var scope, ctrl, model, $compile; var element; @@ -11,14 +12,16 @@ describe("alert", function () { $compile = _$compile_; element = angular.element( - "
    {{alert.msg}}" + - "
    "); + "
    " + + "{{alert.msg}}" + + "" + + "
    "); scope.alerts = [ { msg:'foo', type:'success'}, { msg:'bar', type:'error'}, - { msg:'baz' } + { msg:'baz'} ]; })); @@ -48,13 +51,6 @@ describe("alert", function () { expect(alerts.eq(2)).not.toHaveClass('alert-block'); }); - it('it should be possible to add additional classes for alert', function () { - var element = $compile('Default alert!')(scope); - scope.$digest(); - expect(element).toHaveClass('alert-block'); - expect(element).toHaveClass('alert-info'); - }); - it("should fire callback when closed", function () { var alerts = createAlerts(); @@ -67,4 +63,17 @@ describe("alert", function () { expect(scope.removeAlert).toHaveBeenCalledWith(1); }); + it('should not show close buttons if no close callback specified', function () { + var element = $compile('No close')(scope); + scope.$digest(); + expect(findCloseButton(0).length).toEqual(0); + }); + + it('it should be possible to add additional classes for alert', function () { + var element = $compile('Default alert!')(scope); + scope.$digest(); + expect(element).toHaveClass('alert-block'); + expect(element).toHaveClass('alert-info'); + }); + }); diff --git a/template/alert/alert.html b/template/alert/alert.html index d060a1779c..26f65b3c75 100644 --- a/template/alert/alert.html +++ b/template/alert/alert.html @@ -1,4 +1,4 @@
    - +
    -
    \ No newline at end of file + From 55437b163f17caf8437c9ff66cf3193050f0d352 Mon Sep 17 00:00:00 2001 From: themikelee Date: Fri, 22 Mar 2013 21:58:42 -0300 Subject: [PATCH 26/54] fix(collapse): remove reference to msTransition for IE10 In IE10 msTransition exists but msTransitionEnd never fires. IE10 does however support standard transitionend. Since IE<10 does not support transitions this line can be removed entirely. This has already been done in Twitter Bootstrap. Reference here: https://github.com/twitter/bootstrap/pull/4166 --- src/transition/transition.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/transition/transition.js b/src/transition/transition.js index 84f382b2a0..c23d3f76f7 100644 --- a/src/transition/transition.js +++ b/src/transition/transition.js @@ -61,14 +61,12 @@ angular.module('ui.bootstrap.transition', []) 'WebkitTransition': 'webkitTransitionEnd', 'MozTransition': 'transitionend', 'OTransition': 'oTransitionEnd', - 'msTransition': 'MSTransitionEnd', 'transition': 'transitionend' }; var animationEndEventNames = { 'WebkitTransition': 'webkitAnimationEnd', 'MozTransition': 'animationend', 'OTransition': 'oAnimationEnd', - 'msTransition': 'MSAnimationEnd', 'transition': 'animationend' }; function findEndEventName(endEventNames) { From ce81d772ccb4f49ef4d1377fe687f171a9805d53 Mon Sep 17 00:00:00 2001 From: Eric Date: Tue, 2 Apr 2013 15:00:27 -0500 Subject: [PATCH 27/54] docs(dialog): corrected resolve example --- src/dialog/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dialog/README.md b/src/dialog/README.md index aaefad07e3..8e28ab0308 100644 --- a/src/dialog/README.md +++ b/src/dialog/README.md @@ -45,7 +45,7 @@ Example: app.controller('MainCtrl', function($dialog, $scope) { $scope.openItemEditor = function(item){ - var d = $dialog.dialog({modalFade: false, resolve: {item: function(){ angular.copy(item)} }}); + var d = $dialog.dialog({modalFade: false, resolve: {item: function(){ return angular.copy(item); } }}); d.open('dialogs/item-editor.html', 'EditItemController'); }; }); From 4cbe252bad91969431a37bf6e72585cee7e6c1d2 Mon Sep 17 00:00:00 2001 From: Pawel Kozlowski Date: Tue, 2 Apr 2013 22:43:37 +0200 Subject: [PATCH 28/54] demo(accordion): minor demo changes --- src/accordion/docs/readme.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/accordion/docs/readme.md b/src/accordion/docs/readme.md index d80b297612..4ec3e25705 100644 --- a/src/accordion/docs/readme.md +++ b/src/accordion/docs/readme.md @@ -1,6 +1,4 @@ -### Description - -The **accordion directive** builds on top of the collapse directive to provide a list of items, with collapsible bodies that are collapsed or expanded by clicking on the item's header. +sThe **accordion directive** builds on top of the collapse directive to provide a list of items, with collapsible bodies that are collapsed or expanded by clicking on the item's header. We can control whether expanding an item will cause the other items to close, using the `close-others` attribute on accordion. From 78da771a673253603b025f3c9a849c9ea803bd4a Mon Sep 17 00:00:00 2001 From: Pawel Kozlowski Date: Wed, 3 Apr 2013 20:18:49 +0200 Subject: [PATCH 29/54] demo(accordion): fix typo in demo's readme --- src/accordion/docs/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/accordion/docs/readme.md b/src/accordion/docs/readme.md index 4ec3e25705..5b3ae93b39 100644 --- a/src/accordion/docs/readme.md +++ b/src/accordion/docs/readme.md @@ -1,4 +1,4 @@ -sThe **accordion directive** builds on top of the collapse directive to provide a list of items, with collapsible bodies that are collapsed or expanded by clicking on the item's header. +The **accordion directive** builds on top of the collapse directive to provide a list of items, with collapsible bodies that are collapsed or expanded by clicking on the item's header. We can control whether expanding an item will cause the other items to close, using the `close-others` attribute on accordion. From 45ed280512c0f0da1022ca74df8ece89edb7b549 Mon Sep 17 00:00:00 2001 From: Josh David Miller Date: Wed, 3 Apr 2013 16:46:43 -0700 Subject: [PATCH 30/54] feat(tooltip): added tooltip-html-unsafe directive The directive displays the unsanitized HTML in the tooltip instead of the escaped text. The $tooltip service has been modified to allow a little more flexibility in terms of the prefix used on the $observe'd attributes. For example, the `tooltip-html-unsafe` directive needs to be called as written, but it would be nonsensical to require all other attributes (like animation or placement) to also use that verbose prefix as opposed to the simpler and more familiar `tooltip-` prefix. The service now allows independent specification of the name and its prefix. Lastly, the docs for the tooltip and popover have been updated to show their available optional attributes. Closes #246 --- src/popover/docs/readme.md | 11 +++++ src/popover/popover.js | 2 +- src/tooltip/docs/demo.html | 4 ++ src/tooltip/docs/demo.js | 1 + src/tooltip/docs/readme.md | 16 +++++++ src/tooltip/test/tooltip.spec.js | 38 ++++++++++++++++ src/tooltip/tooltip.js | 45 +++++++++++++++---- .../tooltip/tooltip-html-unsafe-popup.html | 4 ++ 8 files changed, 111 insertions(+), 10 deletions(-) create mode 100644 template/tooltip/tooltip-html-unsafe-popup.html diff --git a/src/popover/docs/readme.md b/src/popover/docs/readme.md index 87316ddeee..05b1ecb89d 100644 --- a/src/popover/docs/readme.md +++ b/src/popover/docs/readme.md @@ -3,3 +3,14 @@ directive supports multiple placements, optional transition animation, and more. Like the Twitter Bootstrap jQuery plugin, the popover **requires** the tooltip module. + +The popover directives provides several optional attributes to control how it +will display: + +- `popover-title`: A string to display as a fancy title. +- `popover-placement`: Where to place it? Defaults to "top", but also accepts + "bottom", "left", or "right". +- `popover-animation`: Should it fade in and out? Defaults to "true". +- `popover-popup-delay`: For how long should the user have to have the mouse + over the element before the popover shows (in milliseconds)? Defaults to 0. + diff --git a/src/popover/popover.js b/src/popover/popover.js index 1cee58f851..c38ff9461e 100644 --- a/src/popover/popover.js +++ b/src/popover/popover.js @@ -13,6 +13,6 @@ angular.module( 'ui.bootstrap.popover', [ 'ui.bootstrap.tooltip' ] ) }; }) .directive( 'popover', [ '$compile', '$timeout', '$parse', '$window', '$tooltip', function ( $compile, $timeout, $parse, $window, $tooltip ) { - return $tooltip( 'popover', 'click' ); + return $tooltip( 'popover', 'popover', 'click' ); }]); diff --git a/src/tooltip/docs/demo.html b/src/tooltip/docs/demo.html index 737d8d5f28..48047c559c 100644 --- a/src/tooltip/docs/demo.html +++ b/src/tooltip/docs/demo.html @@ -16,5 +16,9 @@ at elementum eu, facilisis sed odio morbi quis commodo odio. In cursus delayed turpis massa tincidunt dui ut.

    + +

    + I can even contain HTML. Check me out! +

    diff --git a/src/tooltip/docs/demo.js b/src/tooltip/docs/demo.js index e31c64df07..f3a2c73d51 100644 --- a/src/tooltip/docs/demo.js +++ b/src/tooltip/docs/demo.js @@ -1,4 +1,5 @@ var TooltipDemoCtrl = function ($scope) { $scope.dynamicTooltip = "Hello, World!"; $scope.dynamicTooltipText = "dynamic"; + $scope.htmlTooltip = "I've been made bold!"; }; diff --git a/src/tooltip/docs/readme.md b/src/tooltip/docs/readme.md index abc53df636..306bd1f61f 100644 --- a/src/tooltip/docs/readme.md +++ b/src/tooltip/docs/readme.md @@ -1,2 +1,18 @@ A lightweight, extensible directive for fancy tooltip creation. The tooltip directive supports multiple placements, optional transition animation, and more. + +There are two versions of the tooltip: `tooltip` and `tooltip-html-unsafe`. The +former takes text only and will escape any HTML provided. The latter takes +whatever HTML is provided and displays it in a tooltip; it called "unsafe" +because the HTML is not sanitized. *The user is responsible for ensuring the +content is safe to put into the DOM!* + +The tooltip directives provide several optional attributes to control how they +will display: + +- `tooltip-placement`: Where to place it? Defaults to "top", but also accepts + "bottom", "left", or "right". +- `tooltip-animation`: Should it fade in and out? Defaults to "true". +- `tooltip-popup-delay`: For how long should the user have to have the mouse + over the element before the tooltip shows (in milliseconds)? Defaults to 0. + diff --git a/src/tooltip/test/tooltip.spec.js b/src/tooltip/test/tooltip.spec.js index 7539c717fa..fcda8b6997 100644 --- a/src/tooltip/test/tooltip.spec.js +++ b/src/tooltip/test/tooltip.spec.js @@ -163,6 +163,44 @@ describe('tooltip', function() { }); +describe( 'tooltipHtmlUnsafe', function() { + var elm, elmBody, scope; + + // load the tooltip code + beforeEach(module('ui.bootstrap.tooltip', function ( $tooltipProvider ) { + $tooltipProvider.options({ animation: false }); + })); + + // load the template + beforeEach(module('template/tooltip/tooltip-html-unsafe-popup.html')); + + beforeEach(inject(function($rootScope, $compile) { + scope = $rootScope; + scope.html = 'I say: Hello!'; + + elmBody = $compile( angular.element( + '
    Selector Text
    ' + ))( scope ); + scope.$digest(); + elm = elmBody.find('span'); + elmScope = elm.scope(); + })); + + it( 'should show on mouseenter and hide on mouseleave', inject( function () { + expect( elmScope.tt_isOpen ).toBe( false ); + + elm.trigger( 'mouseenter' ); + expect( elmScope.tt_isOpen ).toBe( true ); + expect( elmBody.children().length ).toBe( 2 ); + + expect( elmScope.tt_content ).toEqual( scope.html ); + + elm.trigger( 'mouseleave' ); + expect( elmScope.tt_isOpen ).toBe( false ); + expect( elmBody.children().length ).toBe( 1 ); + })); +}); + describe( '$tooltipProvider', function() { describe( 'popupDelay', function() { diff --git a/src/tooltip/tooltip.js b/src/tooltip/tooltip.js index 4f36430e3f..1db5654fb7 100644 --- a/src/tooltip/tooltip.js +++ b/src/tooltip/tooltip.js @@ -33,23 +33,35 @@ angular.module( 'ui.bootstrap.tooltip', [] ) angular.extend( globalOptions, value ); }; + /** + * This is a helper function for translating camel-case to snake-case. + */ + function snake_case(name){ + var regexp = /[A-Z]/g; + var separator = '-'; + return name.replace(regexp, function(letter, pos) { + return (pos ? separator : '') + letter.toLowerCase(); + }); + } + /** * Returns the actual instance of the $tooltip service. * TODO support multiple triggers */ this.$get = [ '$window', '$compile', '$timeout', '$parse', '$document', function ( $window, $compile, $timeout, $parse, $document ) { - return function $tooltip ( type, defaultTriggerShow, defaultTriggerHide ) { + return function $tooltip ( type, prefix, defaultTriggerShow, defaultTriggerHide ) { var options = angular.extend( {}, defaultOptions, globalOptions ); + var directiveName = snake_case( type ); var template = - '<'+ type +'-popup '+ + '<'+ directiveName +'-popup '+ 'title="{{tt_title}}" '+ 'content="{{tt_content}}" '+ 'placement="{{tt_placement}}" '+ 'animation="tt_animation()" '+ 'is-open="tt_isOpen"'+ '>'+ - ''; + ''; // Calculate the current position and size of the directive element. function getPosition( element ) { @@ -75,19 +87,19 @@ angular.module( 'ui.bootstrap.tooltip', [] ) scope.tt_content = val; }); - attrs.$observe( type+'Title', function ( val ) { + attrs.$observe( prefix+'Title', function ( val ) { scope.tt_title = val; }); - attrs.$observe( type+'Placement', function ( val ) { + attrs.$observe( prefix+'Placement', function ( val ) { scope.tt_placement = angular.isDefined( val ) ? val : options.placement; }); - attrs.$observe( type+'Animation', function ( val ) { + attrs.$observe( prefix+'Animation', function ( val ) { scope.tt_animation = angular.isDefined( val ) ? $parse( val ) : function(){ return options.animation; }; }); - attrs.$observe( type+'PopupDelay', function ( val ) { + attrs.$observe( prefix+'PopupDelay', function ( val ) { var delay = parseInt( val, 10 ); scope.tt_popupDelay = ! isNaN(delay) ? delay : options.popupDelay; }); @@ -232,6 +244,21 @@ angular.module( 'ui.bootstrap.tooltip', [] ) }) .directive( 'tooltip', [ '$tooltip', function ( $tooltip ) { - return $tooltip( 'tooltip', 'mouseenter', 'mouseleave' ); -}]); + return $tooltip( 'tooltip', 'tooltip', 'mouseenter', 'mouseleave' ); +}]) + +.directive( 'tooltipHtmlUnsafePopup', function () { + return { + restrict: 'E', + replace: true, + scope: { content: '@', placement: '@', animation: '&', isOpen: '&' }, + templateUrl: 'template/tooltip/tooltip-html-unsafe-popup.html' + }; +}) + +.directive( 'tooltipHtmlUnsafe', [ '$tooltip', function ( $tooltip ) { + return $tooltip( 'tooltipHtmlUnsafe', 'tooltip', 'mouseenter', 'mouseleave' ); +}]) + +; diff --git a/template/tooltip/tooltip-html-unsafe-popup.html b/template/tooltip/tooltip-html-unsafe-popup.html new file mode 100644 index 0000000000..09a5bd506a --- /dev/null +++ b/template/tooltip/tooltip-html-unsafe-popup.html @@ -0,0 +1,4 @@ +
    +
    +
    +
    From ba33f63a5b3fc4ef8ed2e4da7cb713902e823548 Mon Sep 17 00:00:00 2001 From: tbekos Date: Fri, 5 Apr 2013 13:35:22 +0300 Subject: [PATCH 31/54] refactor(pagination): simplify math --- src/pagination/pagination.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pagination/pagination.js b/src/pagination/pagination.js index bba5f11ed8..1af16bd898 100644 --- a/src/pagination/pagination.js +++ b/src/pagination/pagination.js @@ -52,7 +52,7 @@ angular.module('ui.bootstrap.pagination', []) startPage = 1; } if ((startPage + maxSize - 1) > scope.numPages) { - startPage = startPage - ((startPage + maxSize - 1) - scope.numPages ); + startPage = scope.numPages - maxSize + 1; } // Add page number links From d8cc019bab7d30a9299822cb8aaebe68f22f2e61 Mon Sep 17 00:00:00 2001 From: Pawel Kozlowski Date: Sat, 6 Apr 2013 14:36:08 +0300 Subject: [PATCH 32/54] chore(changelog): update changelog to the new syntax --- CHANGELOG.md | 76 +++++++++++++++++++--------------------------------- 1 file changed, 27 insertions(+), 49 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 55e48eeb40..d0d901e39b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,57 +2,35 @@ ## Features -### dialog - -* Make $dialog 'resolve' property to work the same way of $routeProvider.when (739f86f) - -### modal - -* allow global override of modal options (acaf72b) - -### buttons - -* add checkbox and radio buttons (571ccf4) - -### carousel - -* add slide indicators (3b677ee) - -### typeahead - -* add typeahead directive (6a97da2) - -### accordion - -* enable HTML in accordion headings (3afcaa4) - -### pagination - -* add first/last link & constant congif options (0ff0454) +- **dialog:** + - Make $dialog 'resolve' property to work the same way of $routeProvider.when ([739f86f](https://github.com/angular-ui/bootstrap/commit/739f86f)) +- **modal:** + - allow global override of modal options ([acaf72b](https://github.com/angular-ui/bootstrap/commit/acaf72b)) +- **buttons:** + - add checkbox and radio buttons ([571ccf4](https://github.com/angular-ui/bootstrap/commit/571ccf4)) +- **carousel:** + - add slide indicators ([3b677ee](https://github.com/angular-ui/bootstrap/commit/3b677ee)) +- **typeahead:** + - add typeahead directive ([6a97da2](https://github.com/angular-ui/bootstrap/commit/6a97da2)) +- **accordion:** + - enable HTML in accordion headings ([3afcaa4](https://github.com/angular-ui/bootstrap/commit/3afcaa4)) +- **pagination:** + - add first/last link & constant congif options ([0ff0454](https://github.com/angular-ui/bootstrap/commit/0ff0454)) ## Bug fixes -### dialog - -* update resolve section to new syntax (1f87486) -* $compile entire modal (7575b3c) - -### tooltip - -* don't show tooltips if there is no content to show (030901e) -* fix placement issues (a2bbf4d) - -### collapse - -* Avoids fixed height on collapse (ff5d119) - -### accordion - -* fix minification issues (f4da4d6) - -### typeahead - -* update inputs value on mapping where label is not derived from the model (a5f64de) +- **dialog:** + - update resolve section to new syntax ([1f87486](https://github.com/angular-ui/bootstrap/commit/1f87486)) + - $compile entire modal ([7575b3c](https://github.com/angular-ui/bootstrap/commit/7575b3c)) +- **tooltip:** + - don't show tooltips if there is no content to show ([030901e](https://github.com/angular-ui/bootstrap/commit/030901e)) + - fix placement issues ([a2bbf4d](https://github.com/angular-ui/bootstrap/commit/a2bbf4d)) +- **collapse:** + - Avoids fixed height on collapse ([ff5d119](https://github.com/angular-ui/bootstrap/commit/ff5d119)) +- **accordion:** + - fix minification issues ([f4da4d6](https://github.com/angular-ui/bootstrap/commit/f4da4d6)) +- **typeahead:** + - update inputs value on mapping where label is not derived from the model ([a5f64de](https://github.com/angular-ui/bootstrap/commit/a5f64de)) # 0.1.0 (2013-02-02) @@ -71,4 +49,4 @@ Version `0.1.0` was released with the following directives: * pagination * popover * tabs -* tooltip \ No newline at end of file +* tooltip From f698717ca0eac540e50a5e72d18975c53b41a502 Mon Sep 17 00:00:00 2001 From: Pawel Kozlowski Date: Fri, 5 Apr 2013 20:53:41 +0200 Subject: [PATCH 33/54] chore(build): add changelog task for grunt --- Gruntfile.js | 49 +++++++++++++++++++++++++++++++++++++++++++ misc/changelog.tpl.md | 12 +++++++++++ 2 files changed, 61 insertions(+) create mode 100644 misc/changelog.tpl.md diff --git a/Gruntfile.js b/Gruntfile.js index 3b75335c67..e04293a452 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -287,6 +287,55 @@ module.exports = function(grunt) { var options = ['--no-single-run', '--auto-watch'].concat(this.args); runTestacular('start', options); }); + + //changelog generation + grunt.registerTask('changelog', 'generates changelog markdown from git commits', function () { + + var changeFrom = this.args[0], changeTo = this.args[1] || 'HEAD'; + + var done = grunt.task.current.async(); + var child = grunt.util.spawn({ + cmd:process.platform === 'win32' ? 'git.cmd' : 'git', + args:['log', changeFrom + '..' + changeTo, '--oneline'] + }, function (err, result, code) { + + var changelog = { + chore: {}, demo: {}, docs: {}, feat: {}, fix: {}, refactor: {}, style: {}, test: {} + }; + + var COMMIT_MSG_REGEXP = /^(chore|demo|docs|feat|fix|refactor|style|test)\((.+)\):? (.+)$/; + var gitlog = ('' + result).split('\n').reverse(); + + if (code) { + grunt.log.error(err); + done(false); + } else { + + gitlog.forEach(function (logItem) { + var sha1 = logItem.slice(0, 7); + var fullMsg = logItem.slice(8); + + var msgMatches = fullMsg.match(COMMIT_MSG_REGEXP); + var changeType = msgMatches[1]; + var directive = msgMatches[2]; + var directiveMsg = msgMatches[3]; + + if (!changelog[changeType][directive]) { + changelog[changeType][directive] = []; + } + changelog[changeType][directive].push({sha1:sha1, msg:directiveMsg}); + }); + + console.log(grunt.template.process(grunt.file.read('misc/changelog.tpl.md'), {data: { + changelog: changelog, + today: grunt.template.today('yyyy-mm-dd'), + version : grunt.config('pkg.version') + }})); + + done(); + } + }); + }); return grunt; }; diff --git a/misc/changelog.tpl.md b/misc/changelog.tpl.md new file mode 100644 index 0000000000..6656b61d15 --- /dev/null +++ b/misc/changelog.tpl.md @@ -0,0 +1,12 @@ +# <%= version%> (<%= today%>) + +## Features + +<% _(changelog.feat).forEach(function(changes, directive) { %>- **<%= directive%>:** +<% changes.forEach(function(change) { %> - <%= change.msg%> ([<%= change.sha1%>](https://github.com/angular-ui/bootstrap/commit/<%= change.sha1%>)) +<% }) %><% }) %> +## Bug fixes + +<% _(changelog.fix).forEach(function(changes, directive) { %>- **<%= directive%>:** +<% changes.forEach(function(change) { %> - <%= change.msg%> ([<%= change.sha1%>](https://github.com/angular-ui/bootstrap/commit/<%= change.sha1%>)) +<% }) %><% }) %> \ No newline at end of file From 38d8c383f3d7adbf9dfd84891b820e961be4bc9d Mon Sep 17 00:00:00 2001 From: L42y <423300@gmail.com> Date: Sun, 7 Apr 2013 00:49:30 +0800 Subject: [PATCH 34/54] chore(build): rename testacular to karma --- .travis.yml | 2 +- Gruntfile.js | 22 +++++++++++----------- README.md | 4 ++-- testacular.conf.js => karma.conf.js | 0 4 files changed, 14 insertions(+), 14 deletions(-) rename testacular.conf.js => karma.conf.js (100%) diff --git a/.travis.yml b/.travis.yml index 7509934f30..f4f24e0e28 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ before_script: - export DISPLAY=:99.0 - sh -e /etc/init.d/xvfb start - - npm install --quiet -g grunt-cli testacular + - npm install --quiet -g grunt-cli karma - npm install script: grunt \ No newline at end of file diff --git a/Gruntfile.js b/Gruntfile.js index e04293a452..9e9a250042 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -243,13 +243,13 @@ module.exports = function(grunt) { files.forEach(html2js); }); - // Testacular configuration - function runTestacular(command, options) { - var testacularCmd = process.platform === 'win32' ? 'testacular.cmd' : 'testacular'; + // Karma configuration + function runKarma(command, options) { + var karmaCmd = process.platform === 'win32' ? 'karma.cmd' : 'karma'; var args = [command].concat(options); var done = grunt.task.current.async(); var child = grunt.util.spawn({ - cmd: testacularCmd, + cmd: karmaCmd, args: args }, function(err, result, code) { if (code) { @@ -270,22 +270,22 @@ module.exports = function(grunt) { //Can augment options with command line arguments options = options.concat(this.args); } - runTestacular('start', options); + runKarma('start', options); }); - grunt.registerTask('server', 'start testacular server', function() { + grunt.registerTask('server', 'start karma server', function() { var options = ['--no-single-run', '--no-auto-watch'].concat(this.args); - runTestacular('start', options); + runKarma('start', options); }); - grunt.registerTask('test-run', 'run tests against continuous testacular server', function() { + grunt.registerTask('test-run', 'run tests against continuous karma server', function() { var options = ['--single-run', '--no-auto-watch'].concat(this.args); - runTestacular('run', options); + runKarma('run', options); }); - grunt.registerTask('test-watch', 'start testacular server, watch & execute tests', function() { + grunt.registerTask('test-watch', 'start karma server, watch & execute tests', function() { var options = ['--no-single-run', '--auto-watch'].concat(this.args); - runTestacular('start', options); + runKarma('start', options); }); //changelog generation diff --git a/README.md b/README.md index 3d63a8e61b..da354c8974 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ We are always looking for the quality contributions! Please check the [CONTRIBUT ### Development #### Prepare your environment * Install [Node.js](http://nodejs.org/) and NPM (should come with) -* Install global dev dependencies: `npm install -g grunt-cli testacular` +* Install global dev dependencies: `npm install -g grunt-cli karma` * Instal local dev dependencies: `npm install` while current directory is bootstrap repo #### Build @@ -61,7 +61,7 @@ We are always looking for the quality contributions! Please check the [CONTRIBUT Check the Grunt build file for other tasks that are defined for this project #### TDD -* Start testacular server: `grunt server` +* Start karma server: `grunt server` * Run test: `grunt test-run` ### Release diff --git a/testacular.conf.js b/karma.conf.js similarity index 100% rename from testacular.conf.js rename to karma.conf.js From 0618ce4af005b5533ba359d7dbff64e2548b4bcc Mon Sep 17 00:00:00 2001 From: Andy Joslin Date: Sun, 7 Apr 2013 21:18:55 -0400 Subject: [PATCH 35/54] demo(*): Update github pages links to *.github.io (Closes #303) --- README.md | 4 ++-- misc/demo-assets/plunker.js | 4 ++-- misc/demo-template.html | 10 +++++----- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index da354c8974..3805069752 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# bootstrap - [AngularJS](http://angularjs.org/) directives specific to [twitter bootstrap](http://twitter.github.com/bootstrap/) +# bootstrap - [AngularJS](http://angularjs.org/) directives specific to [twitter bootstrap](http://twitter.github.io/bootstrap/) *** @@ -6,7 +6,7 @@ ## Demo -Do you want to see directives in action? Visit http://angular-ui.github.com/bootstrap/! +Do you want to see directives in action? Visit http://angular-ui.github.io/bootstrap/! ## Installation diff --git a/misc/demo-assets/plunker.js b/misc/demo-assets/plunker.js index 3eda263ebd..32f1cff222 100644 --- a/misc/demo-assets/plunker.js +++ b/misc/demo-assets/plunker.js @@ -16,7 +16,7 @@ angular.module('plunker', []) '\n' + ' \n' + ' \n' + - ' \n' + + ' \n' + ' \n' + ' \n' + ' \n' + @@ -30,7 +30,7 @@ angular.module('plunker', []) return "angular.module('plunker', ['ui.bootstrap']);" + "\n" + content; }; - addField('description', 'http://angular-ui.github.com/bootstrap/'); + addField('description', 'http://angular-ui.github.io/bootstrap/'); addField('files[index.html]', indexContent(content.markup, version)); addField('files[example.js]', scriptContent(content.javascript)); diff --git a/misc/demo-template.html b/misc/demo-template.html index a84e431bab..8a85750292 100644 --- a/misc/demo-template.html +++ b/misc/demo-template.html @@ -32,11 +32,11 @@
    - <% modules.forEach(function(module) { %> + <% demoModules.forEach(function(module) { %>
    - <%= module.html %> + <%= module.docs.html %>
    - <%= module.description %> + <%= module.docs.md %>

    - +
    -
    <%- module.html %>
    -
    <%- module.js %>
    +
    <%- module.docs.html %>
    +
    <%- module.docs.js %>
    - + <% }); %> diff --git a/package.json b/package.json index edb327d8d2..7199257ed9 100644 --- a/package.json +++ b/package.json @@ -4,11 +4,13 @@ "version": "0.3.0-SNAPSHOT", "dependencies": {}, "devDependencies": { - "node-markdown": "0.1.1", "grunt": "~0.4.1", - "grunt-contrib-jshint": "~0.2.0", + "node-markdown": "0.1.1", "grunt-contrib-concat": "~0.1.3", - "grunt-contrib-uglify": "~0.1.1", - "grunt-contrib-watch": "~0.3.1" + "grunt-contrib-copy": "~0.4.1", + "grunt-contrib-uglify": "~0.2.0", + "grunt-contrib-watch": "~0.3.1", + "grunt-contrib-jshint": "~0.4.0", + "grunt-html2js": "~0.1.3" } } From 5744a17c57674cef98fd780262ede5f9d56eed59 Mon Sep 17 00:00:00 2001 From: tbekos Date: Mon, 8 Apr 2013 11:20:52 +0300 Subject: [PATCH 41/54] refactor(pagination): More efficient when no maxSize set (Closes #305) --- src/pagination/pagination.js | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/pagination/pagination.js b/src/pagination/pagination.js index 1af16bd898..d042958282 100644 --- a/src/pagination/pagination.js +++ b/src/pagination/pagination.js @@ -43,20 +43,23 @@ angular.module('ui.bootstrap.pagination', []) scope.$watch('numPages + currentPage + maxSize', function() { scope.pages = []; - //set the default maxSize to numPages - var maxSize = ( scope.maxSize && scope.maxSize < scope.numPages ) ? scope.maxSize : scope.numPages; - var startPage = scope.currentPage - Math.floor(maxSize/2); - - //adjust the startPage within boundary - if(startPage < 1) { - startPage = 1; - } - if ((startPage + maxSize - 1) > scope.numPages) { - startPage = scope.numPages - maxSize + 1; + // Default page limits + var startPage = 1, endPage = scope.numPages; + + // recompute if maxSize + if ( scope.maxSize && scope.maxSize < scope.numPages ) { + startPage = Math.max(scope.currentPage - Math.floor(scope.maxSize/2), 1); + endPage = startPage + scope.maxSize - 1; + + // Adjust if limit is exceeded + if (endPage > scope.numPages) { + endPage = scope.numPages; + startPage = endPage - scope.maxSize + 1; + } } // Add page number links - for (var number = startPage, max = startPage + maxSize; number < max; number++) { + for (var number = startPage; number <= endPage; number++) { var page = makePage(number, number, scope.isActive(number), false); scope.pages.push(page); } From e75cea99d72c288c5e81f27596ba64524477ccc0 Mon Sep 17 00:00:00 2001 From: Andy Joslin Date: Tue, 16 Apr 2013 12:35:51 -0400 Subject: [PATCH 42/54] chore(build): Fix dependencies causing duplicate modules, fix markdown --- Gruntfile.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Gruntfile.js b/Gruntfile.js index c64acea909..0ffc80055e 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -124,7 +124,11 @@ module.exports = function(grunt) { //Common ui.bootstrap module containing all modules for src and templates //findModule: Adds a given module to config + var foundModules = {}; function findModule(name) { + if (foundModules[name]) { return; } + foundModules[name] = true; + function breakup(text, separator) { return text.replace(/[A-Z]/g, function (match) { return separator + match; @@ -150,7 +154,7 @@ module.exports = function(grunt) { dependencies: dependenciesForModule(name), docs: { md: grunt.file.expand("src/"+name+"/docs/*.md") - .map(grunt.file.read).join("\n"), + .map(grunt.file.read).map(markdown).join("\n"), js: grunt.file.expand("src/"+name+"/docs/*.js") .map(grunt.file.read).join("\n"), html: grunt.file.expand("src/"+name+"/docs/*.html") From 6b5e6369cdf4ceee6a53aedcdf0cdda36a2d3dd6 Mon Sep 17 00:00:00 2001 From: tbekos Date: Fri, 19 Apr 2013 17:04:22 +0300 Subject: [PATCH 43/54] feat(rating): add rating directive Closes #337 --- src/rating/docs/demo.html | 10 +++ src/rating/docs/demo.js | 4 ++ src/rating/docs/readme.md | 3 + src/rating/rating.js | 53 ++++++++++++++ src/rating/test/rating.spec.js | 128 +++++++++++++++++++++++++++++++++ template/rating/rating.html | 3 + 6 files changed, 201 insertions(+) create mode 100644 src/rating/docs/demo.html create mode 100644 src/rating/docs/demo.js create mode 100644 src/rating/docs/readme.md create mode 100644 src/rating/rating.js create mode 100644 src/rating/test/rating.spec.js create mode 100644 template/rating/rating.html diff --git a/src/rating/docs/demo.html b/src/rating/docs/demo.html new file mode 100644 index 0000000000..24c229d4d5 --- /dev/null +++ b/src/rating/docs/demo.html @@ -0,0 +1,10 @@ +
    + + +
    +
    Rate: {{rate}}  - Readonly is: {{isReadonly}}
    + +
    + + +
    \ No newline at end of file diff --git a/src/rating/docs/demo.js b/src/rating/docs/demo.js new file mode 100644 index 0000000000..efb7a88f43 --- /dev/null +++ b/src/rating/docs/demo.js @@ -0,0 +1,4 @@ +var RatingDemoCtrl = function ($scope) { + $scope.rate = 7; + $scope.isReadonly = false; +}; diff --git a/src/rating/docs/readme.md b/src/rating/docs/readme.md new file mode 100644 index 0000000000..fbcdfb268c --- /dev/null +++ b/src/rating/docs/readme.md @@ -0,0 +1,3 @@ +Rating directive that will take care of visualising a star rating bar. + +It also provides optional attribute `max` to vary the number of stars and `readonly` attribute to diasble user's interaction. \ No newline at end of file diff --git a/src/rating/rating.js b/src/rating/rating.js new file mode 100644 index 0000000000..7d623e1782 --- /dev/null +++ b/src/rating/rating.js @@ -0,0 +1,53 @@ +angular.module('ui.bootstrap.rating', []) + +.constant('ratingConfig', { + max: 5 +}) + +.directive('rating', ['ratingConfig', '$parse', function(ratingConfig, $parse) { + return { + restrict: 'EA', + scope: { + value: '=' + }, + templateUrl: 'template/rating/rating.html', + replace: true, + link: function(scope, element, attrs) { + + var maxRange = angular.isDefined(attrs.max) ? scope.$eval(attrs.max) : ratingConfig.max; + + scope.range = []; + for (var i = 1; i <= maxRange; i++) { + scope.range.push(i); + } + + scope.rate = function(value) { + if ( ! scope.readonly ) { + scope.value = value; + } + }; + + scope.enter = function(value) { + if ( ! scope.readonly ) { + scope.val = value; + } + }; + + scope.reset = function() { + scope.val = angular.copy(scope.value); + }; + scope.reset(); + + scope.$watch('value', function(value) { + scope.val = value; + }); + + scope.readonly = false; + if (attrs.readonly) { + scope.$parent.$watch($parse(attrs.readonly), function(value) { + scope.readonly = !!value; + }); + } + } + }; +}]); \ No newline at end of file diff --git a/src/rating/test/rating.spec.js b/src/rating/test/rating.spec.js new file mode 100644 index 0000000000..4d289c04ef --- /dev/null +++ b/src/rating/test/rating.spec.js @@ -0,0 +1,128 @@ +describe('rating directive', function () { + var $rootScope, element; + beforeEach(module('ui.bootstrap.rating')); + beforeEach(module('template/rating/rating.html')); + beforeEach(inject(function(_$compile_, _$rootScope_) { + $compile = _$compile_; + $rootScope = _$rootScope_; + $rootScope.rate = 3; + element = $compile('')($rootScope); + $rootScope.$digest(); + })); + + function getState(stars) { + var state = []; + for (var i = 0, n = stars.length; i < n; i++) { + state.push( (stars.eq(i).hasClass('icon-star') && ! stars.eq(i).hasClass('icon-star-empty')) ); + } + return state; + } + + it('contains the default number of icons', function() { + expect(element.find('i').length).toBe(5); + }); + + it('initializes the default star icons as selected', function() { + var stars = element.find('i'); + expect(getState(stars)).toEqual([true, true, true, false, false]); + }); + + it('handles correcty the click event', function() { + var stars = element.find('i'); + + var star2 = stars.eq(1); + star2.click(); + $rootScope.$digest(); + expect(getState(stars)).toEqual([true, true, false, false, false]); + expect($rootScope.rate).toBe(2); + + var star5 = stars.eq(4); + star5.click(); + $rootScope.$digest(); + expect(getState(stars)).toEqual([true, true, true, true, true]); + expect($rootScope.rate).toBe(5); + }); + + it('handles correcty the hover event', function() { + var stars = element.find('i'); + + var star2 = stars.eq(1); + star2.trigger('mouseover'); + $rootScope.$digest(); + expect(getState(stars)).toEqual([true, true, false, false, false]); + expect($rootScope.rate).toBe(3); + + var star5 = stars.eq(4); + star5.trigger('mouseover'); + $rootScope.$digest(); + expect(getState(stars)).toEqual([true, true, true, true, true]); + expect($rootScope.rate).toBe(3); + + element.trigger('mouseout'); + expect(getState(stars)).toEqual([true, true, true, false, false]); + expect($rootScope.rate).toBe(3); + }); + + it('changes the number of selected icons when value changes', function() { + $rootScope.rate = 2; + $rootScope.$digest(); + + var stars = element.find('i'); + expect(getState(stars)).toEqual([true, true, false, false, false]); + }); + + it('shows different number of icons when `max` attribute is set', function() { + element = $compile('')($rootScope); + $rootScope.$digest(); + + expect(element.find('i').length).toBe(7); + }); + + it('handles readonly attribute', function() { + $rootScope.isReadonly = true; + element = $compile('')($rootScope); + $rootScope.$digest(); + + var stars = element.find('i'); + expect(getState(stars)).toEqual([true, true, true, false, false]); + + var star5 = stars.eq(4); + star5.trigger('mouseover'); + $rootScope.$digest(); + expect(getState(stars)).toEqual([true, true, true, false, false]); + + $rootScope.isReadonly = false; + $rootScope.$digest(); + + star5.trigger('mouseover'); + $rootScope.$digest(); + expect(getState(stars)).toEqual([true, true, true, true, true]); + }); + +}); + +describe('setting ratingConfig', function() { + var $rootScope, element; + var originalConfig = {}; + beforeEach(module('ui.bootstrap.rating')); + beforeEach(module('template/rating/rating.html')); + beforeEach(inject(function(_$compile_, _$rootScope_, ratingConfig) { + $compile = _$compile_; + $rootScope = _$rootScope_; + $rootScope.rate = 5; + angular.extend(originalConfig, ratingConfig); + ratingConfig.max = 10; + element = $compile('')($rootScope); + $rootScope.$digest(); + })); + afterEach(inject(function(ratingConfig) { + // return it to the original state + angular.extend(ratingConfig, originalConfig); + })); + + it('should change number of icon elements', function () { + expect(element.find('i').length).toBe(10); + }); + +}); + diff --git a/template/rating/rating.html b/template/rating/rating.html new file mode 100644 index 0000000000..df902ca3c1 --- /dev/null +++ b/template/rating/rating.html @@ -0,0 +1,3 @@ + + + From 661c303c348e00d8088e9b089be63d99c24ce9ba Mon Sep 17 00:00:00 2001 From: Pawel Kozlowski Date: Mon, 22 Apr 2013 22:22:12 +0200 Subject: [PATCH 44/54] demo(typeahead): fix a link to the select directive --- src/typeahead/docs/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/typeahead/docs/readme.md b/src/typeahead/docs/readme.md index e88cef964f..efb8a1f930 100644 --- a/src/typeahead/docs/readme.md +++ b/src/typeahead/docs/readme.md @@ -4,5 +4,5 @@ This directive can be used to quickly create elegant typeheads with any form tex It is very well integrated into the AngularJS as: -* it uses the same, flexible syntax as the `select` directive (http://docs.angularjs.org/api/ng.directive:select) +* it uses the same, flexible syntax as the [select directive](http://docs.angularjs.org/api/ng.directive:select) * works with promises and it means that you can retrieve matches using the `$http` service with minimal effort \ No newline at end of file From a6c540e5627c384e276341605134f1370dad404c Mon Sep 17 00:00:00 2001 From: Jon Card Date: Thu, 18 Apr 2013 16:55:07 -0600 Subject: [PATCH 45/54] fix(dialog): IE8 fix to not set data() against text nodes Closes #328 --- src/dialog/dialog.js | 2 +- src/dialog/test/dialog.spec.js | 20 +++++++++++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/dialog/dialog.js b/src/dialog/dialog.js index 67a192b2ce..92ef40730b 100644 --- a/src/dialog/dialog.js +++ b/src/dialog/dialog.js @@ -123,7 +123,7 @@ dialogModule.provider("$dialog", function(){ if (self.options.controller) { var ctrl = $controller(self.options.controller, locals); - self.modalEl.contents().data('ngControllerController', ctrl); + self.modalEl.children().data('ngControllerController', ctrl); } $compile(self.modalEl)($scope); diff --git a/src/dialog/test/dialog.spec.js b/src/dialog/test/dialog.spec.js index 8cbdcbf618..79d368462c 100644 --- a/src/dialog/test/dialog.spec.js +++ b/src/dialog/test/dialog.spec.js @@ -1,7 +1,7 @@ describe('Given ui.bootstrap.dialog', function(){ var $document, $compile, $scope, $rootScope, $dialog, q, provider; - var template = '
    I\'m a template
    '; + var template = '
    I\'m a template
    '; beforeEach(module('ui.bootstrap.dialog')); beforeEach(module('template/dialog/message.html')); @@ -287,4 +287,22 @@ describe('Given ui.bootstrap.dialog', function(){ expect($document.find('body > div.modal > div.modal-header').length).toBe(1); }); }); + + describe('when opening it with a template containing white-space', function(){ + + var controllerIsCreated; + function Controller($scope, dialog){ + controllerIsCreated = true; + } + + beforeEach(function(){ + createDialog({ + template:'
    Has whitespace that IE8 does not like assigning data() to
    ', + controller: Controller + }); + openDialog(); + }); + + dialogShouldBeOpen(); + }); }); From 77d9fa7e139ac1eb64a2da2794ea6cf7be616057 Mon Sep 17 00:00:00 2001 From: Pawel Kozlowski Date: Mon, 15 Apr 2013 21:09:39 +0200 Subject: [PATCH 46/54] feat(position): add new positioning service --- src/position/position.js | 79 ++++++++++++++++++++++++ src/position/test/test.html | 118 ++++++++++++++++++++++++++++++++++++ 2 files changed, 197 insertions(+) create mode 100644 src/position/position.js create mode 100644 src/position/test/test.html diff --git a/src/position/position.js b/src/position/position.js new file mode 100644 index 0000000000..c62c12b210 --- /dev/null +++ b/src/position/position.js @@ -0,0 +1,79 @@ +angular.module('ui.bootstrap.position', []) + +/** + * A set of utility methods that can be use to retrieve position of DOM elements. + * It is meant to be used where we need to absolute-position DOM elements in + * relation to other, existing elements (this is the case for tooltips, popovers, + * typeahead suggestions etc.). + */ + .factory('$position', ['$document', '$window', function ($document, $window) { + + function getStyle(el, cssprop) { + if (el.currentStyle) { //IE + return el.currentStyle[cssprop]; + } else if ($window.getComputedStyle) { + return $window.getComputedStyle(el)[cssprop]; + } + // finally try and get inline style + return el.style[cssprop]; + } + + /** + * Checks if a given element is statically positioned + * @param element - raw DOM element + */ + function isStaticPositioned(element) { + return (getStyle(element, "position") || 'static' ) === 'static'; + } + + /** + * returns the closest, non-statically positioned parentOffset of a given element + * @param element + */ + var parentOffsetEl = function (element) { + var docDomEl = $document[0]; + var offsetParent = element.offsetParent || docDomEl; + while (offsetParent && offsetParent !== docDomEl && isStaticPositioned(offsetParent) ) { + offsetParent = offsetParent.offsetParent; + } + return offsetParent || docDomEl; + }; + + return { + /** + * Provides read-only equivalent of jQuery's position function: + * http://api.jquery.com/position/ + */ + position: function (element) { + var elBCR = this.offset(element); + var offsetParentBCR = { top: 0, left: 0 }; + var offsetParentEl = parentOffsetEl(element[0]); + if (offsetParentEl != $document[0]) { + offsetParentBCR = this.offset(angular.element(offsetParentEl)); + offsetParentBCR.top += offsetParentEl.clientTop; + offsetParentBCR.left += offsetParentEl.clientLeft; + } + + return { + width: element.prop('offsetWidth'), + height: element.prop('offsetHeight'), + top: elBCR.top - offsetParentBCR.top, + left: elBCR.left - offsetParentBCR.left + }; + }, + + /** + * Provides read-only equivalent of jQuery's offset function: + * http://api.jquery.com/offset/ + */ + offset: function (element) { + var boundingClientRect = element[0].getBoundingClientRect(); + return { + width: element.prop('offsetWidth'), + height: element.prop('offsetHeight'), + top: boundingClientRect.top + ($window.pageYOffset || $document[0].body.scrollTop), + left: boundingClientRect.left + ($window.pageXOffset || $document[0].body.scrollLeft) + }; + } + }; + }]); diff --git a/src/position/test/test.html b/src/position/test/test.html new file mode 100644 index 0000000000..4bdaca490e --- /dev/null +++ b/src/position/test/test.html @@ -0,0 +1,118 @@ + + + + + + + + + + +

    Within body

    + +
    Content
    + +

    Within statically positioned DIV

    + +
    +
    Content
    +
    + +

    Within relative-positioned DIV - position specified in CSS

    + +
    +
    Content
    +
    + +

    Within relative-positioned DIV

    + +
    +
    Content
    +
    + +

    Within absolute-positioned DIV

    + +
    +
    Content
    +
    + +

    Next to a float element

    + +
    +
    Content
    +
    + +

    Within a table

    +
    + + + + +
    Some other content +
    Content
    +
    + +

    Within a table that is inside a relative-positioned DIV

    + +
    + + + + + +
    Some other content +
    Content
    +
    +
    + +

    Inside looong text

    +

    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur non velit nulla. Suspendisse sit amet tempus diam. Sed at ultricies neque. Suspendisse id felis a sem placerat ornare. Donec auctor, purus at molestie tempor, arcu enim molestie lacus, ac imperdiet massa urna eu massa. Praesent velit tellus, scelerisque a fermentum ut, ornare in diam. Phasellus egestas molestie feugiat. Vivamus sit amet viverra metus.

    +

    Etiam ultricies odio commodo erat ullamcorper sodales. Nullam ac dui ac libero dictum mollis. Quisque convallis adipiscing facilisis. In nec nisi velit, id auctor lectus. Cras interdum urna non felis lacinia vulputate. Integer dignissim, mi aliquam gravida auctor, massa odio cursus lorem, eu ultrices eros nisl tempus diam. Maecenas tristique pellentesque nisi sed adipiscing. Aenean hendrerit sapien quis arcu lobortis vitae pulvinar ante volutpat. Morbi consectetur erat eu lacus facilisis eu ullamcorper orci euismod. Quisque diam dui, interdum in suscipit et, fringilla non justo. Pellentesque non nibh odio. Proin sit amet massa sem.

    +

    Nam in urna erat, at congue nisi. Donec eu tellus lorem, sed facilisis tellus. Aliquam suscipit faucibus ipsum, at hendrerit metus interdum at. Integer et eros ac lacus vulputate sagittis quis quis erat. Suspendisse consectetur vehicula purus vitae imperdiet. Suspendisse in augue magna, quis imperdiet enim. Nullam non diam ac erat auctor bibendum. Praesent ante mauris, egestas sit amet molestie sed, tristique at lorem. Nam at mi ac nisl venenatis semper nec eget mi. Pellentesque a lectus ac leo feugiat suscipit. Quisque tristique dui nec urna placerat a viverra mi iaculis. Ut et tellus et turpis sagittis iaculis nec eu magna. Sed quis nunc non arcu tincidunt ultricies viverra id mauris.

    +

    Curabitur luctus rutrum ultricies. Aenean ut rutrum orci. Sed molestie lorem in leo cursus id feugiat nisi scelerisque. Maecenas pulvinar neque nec lacus feugiat dictum. Donec viverra felis nec nisi mollis feugiat. Phasellus vehicula, ligula at mattis porttitor, sapien urna hendrerit quam, at fringilla nisl quam vel elit. In eu lacus ligula. Praesent eget gravida nisl. Suspendisse velit diam, pellentesque a tempus quis, vestibulum vel leo.

    +

    Maecenas feugiat ultrices laoreet. Sed congue posuere diam ac faucibus. Pellentesque eget leo ligula. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed nec quam eu tellus sagittis cursus a sit amet eros. Mauris sit amet orci at orci vulputate commodo ut ut nunc. Etiam sagittis erat ut nisi ultricies feugiat. Morbi sed eros nisi. Cras vitae augue in risus aliquet commodo non id est.

    +
    HERE
    +

    Maecenas laoreet nisi pretium elit bibendum eget tempor nunc aliquet. Vivamus interdum nisi sit amet tortor fermentum congue. Suspendisse at posuere erat. Aliquam hendrerit ultricies nunc non adipiscing. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Duis molestie viverra nulla a aliquet. Nullam non eros vel sem vehicula suscipit. Ut sit amet arcu ac tortor dignissim viverra in a ligula.

    + +
    +

    Within fixed div

    +
    Content
    +
    + + + From 74beecdb8f445f2d2d00a6d098b2872fada424ce Mon Sep 17 00:00:00 2001 From: Pawel Kozlowski Date: Mon, 15 Apr 2013 21:09:51 +0200 Subject: [PATCH 47/54] fix(typeahead): fix matches pop-up positioning issues Closes #262, #282 --- src/typeahead/test/typeahead.spec.js | 9 ++++----- src/typeahead/typeahead.js | 28 ++++++++++++++++++++-------- template/typeahead/typeahead.html | 12 +++++------- 3 files changed, 29 insertions(+), 20 deletions(-) diff --git a/src/typeahead/test/typeahead.spec.js b/src/typeahead/test/typeahead.spec.js index e2c3eebf64..746a5a2854 100644 --- a/src/typeahead/test/typeahead.spec.js +++ b/src/typeahead/test/typeahead.spec.js @@ -200,7 +200,7 @@ describe('typeahead tests', function () { }; var findDropDown = function(element) { - return element.find('div.dropdown'); + return element.find('ul.typeahead'); }; var findMatches = function(element) { @@ -222,7 +222,7 @@ describe('typeahead tests', function () { this.message = function() { return "Expected '" + angular.mock.dump(this.actual) + "' to be closed."; }; - return !typeaheadEl.hasClass('open') && findMatches(this.actual).length === 0; + return typeaheadEl.css('display')==='none' && findMatches(this.actual).length === 0; }, toBeOpenWithActive: function(noOfMatches, activeIdx) { @@ -232,7 +232,7 @@ describe('typeahead tests', function () { this.message = function() { return "Expected '" + angular.mock.dump(this.actual) + "' to be opened."; }; - return typeaheadEl.hasClass('open') && liEls.length === noOfMatches && $(liEls[activeIdx]).hasClass('active'); + return typeaheadEl.css('display')==='block' && liEls.length === noOfMatches && $(liEls[activeIdx]).hasClass('active'); } }); }); @@ -391,12 +391,11 @@ describe('typeahead tests', function () { var inputEl = findInput(element); changeInputValueTo(element, 'b'); - var dropdown = findDropDown(element); $document.find('body').click(); $scope.$digest(); - expect(dropdown).not.toHaveClass('open'); + expect(element).toBeClosed(); }); }); diff --git a/src/typeahead/typeahead.js b/src/typeahead/typeahead.js index 901f4ccbaa..6bb4ad3eaf 100644 --- a/src/typeahead/typeahead.js +++ b/src/typeahead/typeahead.js @@ -1,4 +1,4 @@ -angular.module('ui.bootstrap.typeahead', []) +angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position']) /** * A helper service that can parse typeahead's syntax (string provided by users) @@ -29,8 +29,7 @@ angular.module('ui.bootstrap.typeahead', []) }; }]) - //options - min length - .directive('typeahead', ['$compile', '$parse', '$q', '$document', 'typeaheadParser', function ($compile, $parse, $q, $document, typeaheadParser) { + .directive('typeahead', ['$compile', '$parse', '$q', '$document', '$position', 'typeaheadParser', function ($compile, $parse, $q, $document, $position, typeaheadParser) { var HOT_KEYS = [9, 13, 27, 38, 40]; @@ -51,6 +50,16 @@ angular.module('ui.bootstrap.typeahead', []) var isLoadingSetter = $parse(attrs.typeaheadLoading).assign || angular.noop; + //pop-up element used to display matches + var popUpEl = angular.element( + ""+ + ""); + //create a child scope for the typeahead directive so we are not polluting original scope //with typeahead-specific data (matches, query etc.) var scope = originalScope.$new(); @@ -87,6 +96,11 @@ angular.module('ui.bootstrap.typeahead', []) } scope.query = inputValue; + //position pop-up with matches - we need to re-calculate its position each time we are opening a window + //with matches as a pop-up might be absolute-positioned and position of an input might have changed on a page + //due to other elements being rendered + scope.position = $position.position(element); + scope.position.top = scope.position.top + element.prop('offsetHeight'); } else { resetMatches(); @@ -167,15 +181,12 @@ angular.module('ui.bootstrap.typeahead', []) } }); - $document.find('body').bind('click', function(){ - + $document.bind('click', function(){ resetMatches(); scope.$digest(); }); - var tplElCompiled = $compile("")(scope); - element.after(tplElCompiled); + element.after($compile(popUpEl)(scope)); } }; @@ -188,6 +199,7 @@ angular.module('ui.bootstrap.typeahead', []) matches:'=', query:'=', active:'=', + position:'=', select:'&' }, replace:true, diff --git a/template/typeahead/typeahead.html b/template/typeahead/typeahead.html index 8d4a6908b5..d535f0e438 100644 --- a/template/typeahead/typeahead.html +++ b/template/typeahead/typeahead.html @@ -1,7 +1,5 @@ - \ No newline at end of file + \ No newline at end of file From 474ce52e8eadee763987ebd930cf790d4bcd6d97 Mon Sep 17 00:00:00 2001 From: Monish Parajuli Date: Wed, 24 Apr 2013 21:17:00 +1000 Subject: [PATCH 48/54] fix(dialog): close dialog on location change Closes #335 --- src/dialog/dialog.js | 7 +++++++ src/dialog/test/dialog.spec.js | 15 +++++++++++++++ src/modal/test/modal.spec.js | 6 ++++++ 3 files changed, 28 insertions(+) diff --git a/src/dialog/dialog.js b/src/dialog/dialog.js index 92ef40730b..13277671cc 100644 --- a/src/dialog/dialog.js +++ b/src/dialog/dialog.js @@ -93,6 +93,11 @@ dialogModule.provider("$dialog", function(){ e.preventDefault(); self.$scope.$apply(); }; + + this.handleLocationChange = function() { + self.close(); + self.$scope.$apply(); + }; } // The `isOpen()` method returns wether the dialog is currently visible. @@ -184,6 +189,8 @@ dialogModule.provider("$dialog", function(){ Dialog.prototype._bindEvents = function() { if(this.options.keyboard){ body.bind('keydown', this.handledEscapeKey); } if(this.options.backdrop && this.options.backdropClick){ this.backdropEl.bind('click', this.handleBackDropClick); } + + this.$scope.$on('$locationChangeSuccess', this.handleLocationChange); }; Dialog.prototype._unbindEvents = function() { diff --git a/src/dialog/test/dialog.spec.js b/src/dialog/test/dialog.spec.js index 79d368462c..f913451751 100644 --- a/src/dialog/test/dialog.spec.js +++ b/src/dialog/test/dialog.spec.js @@ -59,6 +59,11 @@ describe('Given ui.bootstrap.dialog', function(){ var clearGlobalOptions = function(){ provider.options({}); }; + + var changeLocation = function() { + $rootScope.$broadcast('$locationChangeSuccess'); + $rootScope.$apply(); + }; var dialogShouldBeClosed = function(){ it('should not include a backdrop in the DOM', function(){ @@ -287,6 +292,16 @@ describe('Given ui.bootstrap.dialog', function(){ expect($document.find('body > div.modal > div.modal-header').length).toBe(1); }); }); + + describe('When dialog is open and location changes', function(){ + beforeEach(function(){ + createDialog({template:template}); + openDialog(); + changeLocation(); + }); + + dialogShouldBeClosed(); + }); describe('when opening it with a template containing white-space', function(){ diff --git a/src/modal/test/modal.spec.js b/src/modal/test/modal.spec.js index 2a5319a690..b69f3e8e6a 100644 --- a/src/modal/test/modal.spec.js +++ b/src/modal/test/modal.spec.js @@ -169,5 +169,11 @@ describe('Give ui.boostrap.modal', function() { $scope.$digest(); expect($scope.modalShown).not.toBeTruthy(); }); + + it('should update the model if the location change is successful', function() { + $rootScope.$broadcast('$locationChangeSuccess'); + $scope.$digest(); + expect($scope.modalShown).not.toBeTruthy(); + }); }); }); \ No newline at end of file From 77e6acb9bb7e906ba08d105ccc4fa0f0bb5b6ebf Mon Sep 17 00:00:00 2001 From: Pawel Kozlowski Date: Thu, 25 Apr 2013 20:00:07 +0200 Subject: [PATCH 49/54] fix($dialog): fix $apply in progres on $location change --- src/dialog/dialog.js | 1 - src/dialog/test/dialog.spec.js | 31 +++++++++++++++++-------------- src/modal/test/modal.spec.js | 11 ++++++----- 3 files changed, 23 insertions(+), 20 deletions(-) diff --git a/src/dialog/dialog.js b/src/dialog/dialog.js index 13277671cc..4a992a1a2f 100644 --- a/src/dialog/dialog.js +++ b/src/dialog/dialog.js @@ -96,7 +96,6 @@ dialogModule.provider("$dialog", function(){ this.handleLocationChange = function() { self.close(); - self.$scope.$apply(); }; } diff --git a/src/dialog/test/dialog.spec.js b/src/dialog/test/dialog.spec.js index f913451751..79e7b87685 100644 --- a/src/dialog/test/dialog.spec.js +++ b/src/dialog/test/dialog.spec.js @@ -60,10 +60,6 @@ describe('Given ui.bootstrap.dialog', function(){ provider.options({}); }; - var changeLocation = function() { - $rootScope.$broadcast('$locationChangeSuccess'); - $rootScope.$apply(); - }; var dialogShouldBeClosed = function(){ it('should not include a backdrop in the DOM', function(){ @@ -75,7 +71,7 @@ describe('Given ui.bootstrap.dialog', function(){ }); it('should return false for isOpen()', function(){ - expect(dialog.isOpen()).toBe(false); + expect(dialog.isOpen()).toBeFalsy(); }); }; @@ -292,16 +288,23 @@ describe('Given ui.bootstrap.dialog', function(){ expect($document.find('body > div.modal > div.modal-header').length).toBe(1); }); }); - - describe('When dialog is open and location changes', function(){ - beforeEach(function(){ - createDialog({template:template}); - openDialog(); - changeLocation(); - }); - dialogShouldBeClosed(); - }); + describe('When dialog is open and location changes', function () { + + var changeLocation = function () { + $rootScope.$apply(function(){ + $rootScope.$broadcast('$locationChangeSuccess'); + }); + }; + + beforeEach(function () { + createDialog({template: template}); + openDialog(); + changeLocation(); + }); + + dialogShouldBeClosed(); + }); describe('when opening it with a template containing white-space', function(){ diff --git a/src/modal/test/modal.spec.js b/src/modal/test/modal.spec.js index b69f3e8e6a..35cee5b9da 100644 --- a/src/modal/test/modal.spec.js +++ b/src/modal/test/modal.spec.js @@ -170,10 +170,11 @@ describe('Give ui.boostrap.modal', function() { expect($scope.modalShown).not.toBeTruthy(); }); - it('should update the model if the location change is successful', function() { - $rootScope.$broadcast('$locationChangeSuccess'); - $scope.$digest(); - expect($scope.modalShown).not.toBeTruthy(); - }); + it('should update the model if the location change is successful', function () { + $rootScope.$apply(function(){ + $rootScope.$broadcast('$locationChangeSuccess'); + }); + expect($scope.modalShown).toBeFalsy(); + }); }); }); \ No newline at end of file From 7bd1a9178c516e3cf6786f78dd60b77eb940bde7 Mon Sep 17 00:00:00 2001 From: lastnico Date: Thu, 25 Apr 2013 10:47:57 +0300 Subject: [PATCH 50/54] docs($dialog): add missing method isOpen in doc --- src/dialog/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/dialog/README.md b/src/dialog/README.md index 845322f89d..eea4b44583 100644 --- a/src/dialog/README.md +++ b/src/dialog/README.md @@ -91,3 +91,7 @@ The dialog object returned by the `$dialog` service methods `open` and `message` Closes the dialog. Optionally a result can be specified. The result is used to resolve the promise returned by the `open` method. +#### `isOpen` + +Returns true if the dialog is shown, else returns false. + From 6458f4873d20e611d5a3510a8b15e9935e138458 Mon Sep 17 00:00:00 2001 From: Josh David Miller Date: Fri, 26 Apr 2013 12:41:01 -0700 Subject: [PATCH 51/54] fix($tooltip): fix positioning issues in tooltips and popovers To calculate the relative position of the tooltip or popover, the $tooltip service now uses the $position service, which resolves many positioning bugs. Closes #265, #115 --- src/popover/docs/readme.md | 2 ++ src/tooltip/docs/readme.md | 2 ++ src/tooltip/tooltip.js | 17 +++-------------- 3 files changed, 7 insertions(+), 14 deletions(-) diff --git a/src/popover/docs/readme.md b/src/popover/docs/readme.md index 05b1ecb89d..8b43c66c62 100644 --- a/src/popover/docs/readme.md +++ b/src/popover/docs/readme.md @@ -14,3 +14,5 @@ will display: - `popover-popup-delay`: For how long should the user have to have the mouse over the element before the popover shows (in milliseconds)? Defaults to 0. +The popover directives require the `$position` service. + diff --git a/src/tooltip/docs/readme.md b/src/tooltip/docs/readme.md index 306bd1f61f..ca8f1b4a73 100644 --- a/src/tooltip/docs/readme.md +++ b/src/tooltip/docs/readme.md @@ -16,3 +16,5 @@ will display: - `tooltip-popup-delay`: For how long should the user have to have the mouse over the element before the tooltip shows (in milliseconds)? Defaults to 0. +The tooltip directives require the `$position` service. + diff --git a/src/tooltip/tooltip.js b/src/tooltip/tooltip.js index 1db5654fb7..c72484ce71 100644 --- a/src/tooltip/tooltip.js +++ b/src/tooltip/tooltip.js @@ -3,7 +3,7 @@ * function, placement as a function, inside, support for more triggers than * just mouse enter/leave, html tooltips, and selector delegation. */ -angular.module( 'ui.bootstrap.tooltip', [] ) +angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position' ] ) /** * The $tooltip service creates tooltip- and popover-like directives as well as @@ -48,7 +48,7 @@ angular.module( 'ui.bootstrap.tooltip', [] ) * Returns the actual instance of the $tooltip service. * TODO support multiple triggers */ - this.$get = [ '$window', '$compile', '$timeout', '$parse', '$document', function ( $window, $compile, $timeout, $parse, $document ) { + this.$get = [ '$window', '$compile', '$timeout', '$parse', '$document', '$position', function ( $window, $compile, $timeout, $parse, $document, $position ) { return function $tooltip ( type, prefix, defaultTriggerShow, defaultTriggerHide ) { var options = angular.extend( {}, defaultOptions, globalOptions ); var directiveName = snake_case( type ); @@ -63,17 +63,6 @@ angular.module( 'ui.bootstrap.tooltip', [] ) '>'+ ''; - // Calculate the current position and size of the directive element. - function getPosition( element ) { - var boundingClientRect = element[0].getBoundingClientRect(); - return { - width: element.prop( 'offsetWidth' ), - height: element.prop( 'offsetHeight' ), - top: boundingClientRect.top + $window.pageYOffset, - left: boundingClientRect.left + $window.pageXOffset - }; - } - return { restrict: 'EA', scope: true, @@ -148,7 +137,7 @@ angular.module( 'ui.bootstrap.tooltip', [] ) } // Get the position of the directive element. - position = getPosition( element ); + position = $position.position( element ); // Get the height and width of the tooltip so we can center it. ttWidth = tooltip.prop( 'offsetWidth' ); From b1ba821b3f49f1fb5e844ce90cdca9f097733855 Mon Sep 17 00:00:00 2001 From: Josh David Miller Date: Fri, 26 Apr 2013 17:04:17 -0700 Subject: [PATCH 52/54] feat($tooltip): support for custom triggers The `$tooltip` service now has two ways to customize the default triggers. The `$tooltipProvider` takes a `trigger` option and the `*-trigger` attribute can be applied to a single element. The `$tooltipProvider`'s `trigger` option overwrites the default value but the attribute will overwrite both. A few logical default triggers are supported out of the box and have an associated map to determine which hide trigger to use. `mouseenter` -> `mouseleave`, `focus` -> `blur`, and `click` -> `click`. If any other trigger is provided, it will be used to both show and hide the tooltip. Custom hide triggers are not yet supported as they would require some code trickery due to the way `$observe` works. Closes #131 --- src/popover/docs/demo.html | 7 ++ src/popover/docs/readme.md | 2 + src/popover/test/popoverSpec.js | 86 ------------------ src/tooltip/docs/demo.html | 7 ++ src/tooltip/docs/readme.md | 13 +++ src/tooltip/test/tooltip.spec.js | 113 +++++++++++++++++++++--- src/tooltip/tooltip.js | 145 +++++++++++++++++++++---------- 7 files changed, 228 insertions(+), 145 deletions(-) diff --git a/src/popover/docs/demo.html b/src/popover/docs/demo.html index d8bbf5b10c..2a9bdfb1b4 100644 --- a/src/popover/docs/demo.html +++ b/src/popover/docs/demo.html @@ -14,6 +14,13 @@

    Positional

    +
    +

    Triggers

    + + +

    Other

    diff --git a/src/popover/docs/readme.md b/src/popover/docs/readme.md index 8b43c66c62..e94651ccb3 100644 --- a/src/popover/docs/readme.md +++ b/src/popover/docs/readme.md @@ -13,6 +13,8 @@ will display: - `popover-animation`: Should it fade in and out? Defaults to "true". - `popover-popup-delay`: For how long should the user have to have the mouse over the element before the popover shows (in milliseconds)? Defaults to 0. +- `popover-trigger`: What should trigger the show of the popover? See the + `tooltip` directive for supported values. The popover directives require the `$position` service. diff --git a/src/popover/test/popoverSpec.js b/src/popover/test/popoverSpec.js index 81e37d4734..b7c5b34ad7 100644 --- a/src/popover/test/popoverSpec.js +++ b/src/popover/test/popoverSpec.js @@ -44,92 +44,6 @@ describe('popover', function() { elm.trigger( 'click' ); expect( elmScope.tt_isOpen ).toBe( false ); })); - - it('should have default placement of "top"', inject(function() { - elm.trigger( 'click' ); - expect( elmScope.tt_placement ).toBe( "top" ); - })); - - it('should allow specification of placement', inject( function( $compile ) { - elm = $compile( angular.element( - 'Selector Text' - ) )( scope ); - elmScope = elm.scope(); - - elm.trigger( 'click' ); - expect( elmScope.tt_placement ).toBe( "bottom" ); - })); - - it('should work inside an ngRepeat', inject( function( $compile ) { - - elm = $compile( angular.element( - '
      '+ - '
    • '+ - '{{item.name}}'+ - '
    • '+ - '
    ' - ) )( scope ); - - scope.items = [ - { name: "One", popover: "First popover" } - ]; - - scope.$digest(); - - var tt = angular.element( elm.find("li > span")[0] ); - - tt.trigger( 'click' ); - - expect( tt.text() ).toBe( scope.items[0].name ); - expect( tt.scope().tt_content ).toBe( scope.items[0].popover ); - - tt.trigger( 'click' ); - })); - - it('should only have an isolate scope on the popup', inject( function ( $compile ) { - var ttScope; - - scope.popoverContent = "Popover Content"; - scope.popoverTitle = "Popover Title"; - scope.alt = "Alt Message"; - - elmBody = $compile( angular.element( - '
    Selector Text
    ' - ) )( scope ); - - $compile( elmBody )( scope ); - scope.$digest(); - elm = elmBody.find( 'span' ); - elmScope = elm.scope(); - - elm.trigger( 'click' ); - expect( elm.attr( 'alt' ) ).toBe( scope.alt ); - - ttScope = angular.element( elmBody.children()[1] ).scope(); - expect( ttScope.placement ).toBe( 'top' ); - expect( ttScope.title ).toBe( scope.popoverTitle ); - expect( ttScope.content ).toBe( scope.popoverContent ); - - elm.trigger( 'click' ); - })); - - - it( 'should allow specification of delay', inject( function ($timeout, $compile) { - - elm = $compile( angular.element( - 'Selector Text' - ) )( scope ); - elmScope = elm.scope(); - scope.$digest(); - - elm.trigger( 'click' ); - expect( elmScope.tt_isOpen ).toBe( false ); - - $timeout.flush(); - expect( elmScope.tt_isOpen ).toBe( true ); - - } ) ); - }); diff --git a/src/tooltip/docs/demo.html b/src/tooltip/docs/demo.html index 48047c559c..40c8a3c110 100644 --- a/src/tooltip/docs/demo.html +++ b/src/tooltip/docs/demo.html @@ -20,5 +20,12 @@

    I can even contain HTML. Check me out!

    +

    + Or use custom triggers, like focus: + +

    diff --git a/src/tooltip/docs/readme.md b/src/tooltip/docs/readme.md index ca8f1b4a73..5ecb79563d 100644 --- a/src/tooltip/docs/readme.md +++ b/src/tooltip/docs/readme.md @@ -15,6 +15,19 @@ will display: - `tooltip-animation`: Should it fade in and out? Defaults to "true". - `tooltip-popup-delay`: For how long should the user have to have the mouse over the element before the tooltip shows (in milliseconds)? Defaults to 0. +- `tooltip-trigger`: What should trigger a show of the tooltip? The tooltip directives require the `$position` service. +**Triggers** + +The following show triggers are supported out of the box, along with their +provided hide triggers: + +- `mouseenter`: `mouseleave` +- `click`: `click` +- `focus`: `blur` + +For any non-supported value, the trigger will be used to both show and hide the +tooltip. + diff --git a/src/tooltip/test/tooltip.spec.js b/src/tooltip/test/tooltip.spec.js index fcda8b6997..1bc4e2e69b 100644 --- a/src/tooltip/test/tooltip.spec.js +++ b/src/tooltip/test/tooltip.spec.js @@ -54,6 +54,7 @@ describe('tooltip', function() { elm = $compile( angular.element( 'Selector Text' ) )( scope ); + scope.$apply(); elmScope = elm.scope(); elm.trigger( 'mouseenter' ); @@ -161,6 +162,46 @@ describe('tooltip', function() { }); + describe( 'with a trigger attribute', function() { + var scope, elmBody, elm, elmScope; + + beforeEach( inject( function( $rootScope ) { + scope = $rootScope; + })); + + it( 'should use it to show but set the hide trigger based on the map for mapped triggers', inject( function( $compile ) { + elmBody = angular.element( + '
    ' + ); + $compile(elmBody)(scope); + scope.$apply(); + elm = elmBody.find('input'); + elmScope = elm.scope(); + + expect( elmScope.tt_isOpen ).toBeFalsy(); + elm.trigger('focus'); + expect( elmScope.tt_isOpen ).toBeTruthy(); + elm.trigger('blur'); + expect( elmScope.tt_isOpen ).toBeFalsy(); + })); + + it( 'should use it as both the show and hide triggers for unmapped triggers', inject( function( $compile ) { + elmBody = angular.element( + '
    ' + ); + $compile(elmBody)(scope); + scope.$apply(); + elm = elmBody.find('input'); + elmScope = elm.scope(); + + expect( elmScope.tt_isOpen ).toBeFalsy(); + elm.trigger('fakeTriggerAttr'); + expect( elmScope.tt_isOpen ).toBeTruthy(); + elm.trigger('fakeTriggerAttr'); + expect( elmScope.tt_isOpen ).toBeFalsy(); + })); + }); + }); describe( 'tooltipHtmlUnsafe', function() { @@ -202,13 +243,13 @@ describe( 'tooltipHtmlUnsafe', function() { }); describe( '$tooltipProvider', function() { - - describe( 'popupDelay', function() { - var elm, + var elm, elmBody, - scope, - elmScope; + scope, + elmScope, + body; + describe( 'popupDelay', function() { beforeEach(module('ui.bootstrap.tooltip', function($tooltipProvider){ $tooltipProvider.options({popupDelay: 1000}); })); @@ -241,12 +282,6 @@ describe( '$tooltipProvider', function() { }); describe('appendToBody', function() { - var elm, - elmBody, - scope, - elmScope, - body; - // load the tooltip code beforeEach(module('ui.bootstrap.tooltip', function ( $tooltipProvider ) { $tooltipProvider.options({ appendToBody: true }); @@ -275,5 +310,61 @@ describe( '$tooltipProvider', function() { expect( $body.children().length ).toEqual( bodyLength + 1 ); })); }); + + describe( 'triggers', function() { + describe( 'triggers with a mapped value', function() { + beforeEach(module('ui.bootstrap.tooltip', function($tooltipProvider){ + $tooltipProvider.options({trigger: 'focus'}); + })); + + // load the template + beforeEach(module('template/tooltip/tooltip-popup.html')); + + it( 'should use the show trigger and the mapped value for the hide trigger', inject( function ( $rootScope, $compile ) { + elmBody = angular.element( + '
    ' + ); + + scope = $rootScope; + $compile(elmBody)(scope); + scope.$digest(); + elm = elmBody.find('input'); + elmScope = elm.scope(); + + expect( elmScope.tt_isOpen ).toBeFalsy(); + elm.trigger('focus'); + expect( elmScope.tt_isOpen ).toBeTruthy(); + elm.trigger('blur'); + expect( elmScope.tt_isOpen ).toBeFalsy(); + })); + }); + + describe( 'triggers without a mapped value', function() { + beforeEach(module('ui.bootstrap.tooltip', function($tooltipProvider){ + $tooltipProvider.options({trigger: 'fakeTrigger'}); + })); + + // load the template + beforeEach(module('template/tooltip/tooltip-popup.html')); + + it( 'should use the show trigger to hide', inject( function ( $rootScope, $compile ) { + elmBody = angular.element( + '
    Selector Text
    ' + ); + + scope = $rootScope; + $compile(elmBody)(scope); + scope.$digest(); + elm = elmBody.find('span'); + elmScope = elm.scope(); + + expect( elmScope.tt_isOpen ).toBeFalsy(); + elm.trigger('fakeTrigger'); + expect( elmScope.tt_isOpen ).toBeTruthy(); + elm.trigger('fakeTrigger'); + expect( elmScope.tt_isOpen ).toBeFalsy(); + })); + }); + }); }); diff --git a/src/tooltip/tooltip.js b/src/tooltip/tooltip.js index c72484ce71..281d532a4a 100644 --- a/src/tooltip/tooltip.js +++ b/src/tooltip/tooltip.js @@ -17,6 +17,13 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position' ] ) popupDelay: 0 }; + // Default hide triggers for each show trigger + var triggerMap = { + 'mouseenter': 'mouseleave', + 'click': 'click', + 'focus': 'blur' + }; + // The options specified to the provider globally. var globalOptions = {}; @@ -49,9 +56,41 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position' ] ) * TODO support multiple triggers */ this.$get = [ '$window', '$compile', '$timeout', '$parse', '$document', '$position', function ( $window, $compile, $timeout, $parse, $document, $position ) { - return function $tooltip ( type, prefix, defaultTriggerShow, defaultTriggerHide ) { + return function $tooltip ( type, prefix, defaultTriggerShow ) { var options = angular.extend( {}, defaultOptions, globalOptions ); + + /** + * Returns an object of show and hide triggers. + * + * If a trigger is supplied, + * it is used to show the tooltip; otherwise, it will use the `trigger` + * option passed to the `$tooltipProvider.options` method; else it will + * default to the trigger supplied to this directive factory. + * + * The hide trigger is based on the show trigger. If the `trigger` option + * was passed to the `$tooltipProvider.options` method, it will use the + * mapped trigger from `triggerMap` or the passed trigger if the map is + * undefined; otherwise, it uses the `triggerMap` value of the show + * trigger; else it will just use the show trigger. + */ + function setTriggers ( trigger ) { + var show, hide; + + show = trigger || options.trigger || defaultTriggerShow; + if ( angular.isDefined ( options.trigger ) ) { + hide = triggerMap[options.trigger] || show; + } else { + hide = triggerMap[show] || show; + } + + return { + show: show, + hide: hide + }; + } + var directiveName = snake_case( type ); + var triggers = setTriggers( undefined ); var template = '<'+ directiveName +'-popup '+ @@ -72,39 +111,32 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position' ] ) var popupTimeout; var $body; - attrs.$observe( type, function ( val ) { - scope.tt_content = val; - }); - - attrs.$observe( prefix+'Title', function ( val ) { - scope.tt_title = val; - }); - - attrs.$observe( prefix+'Placement', function ( val ) { - scope.tt_placement = angular.isDefined( val ) ? val : options.placement; - }); - - attrs.$observe( prefix+'Animation', function ( val ) { - scope.tt_animation = angular.isDefined( val ) ? $parse( val ) : function(){ return options.animation; }; - }); - - attrs.$observe( prefix+'PopupDelay', function ( val ) { - var delay = parseInt( val, 10 ); - scope.tt_popupDelay = ! isNaN(delay) ? delay : options.popupDelay; - }); - // By default, the tooltip is not open. // TODO add ability to start tooltip opened scope.tt_isOpen = false; - //show the tooltip with delay if specified, otherwise show it immediately - function showWithDelay() { - if( scope.tt_popupDelay ){ + function toggleTooltipBind () { + if ( ! scope.tt_isOpen ) { + showTooltipBind(); + } else { + hideTooltipBind(); + } + } + + // Show the tooltip with delay if specified, otherwise show it immediately + function showTooltipBind() { + if ( scope.tt_popupDelay ) { popupTimeout = $timeout( show, scope.tt_popupDelay ); - }else { + } else { scope.$apply( show ); } } + + function hideTooltipBind () { + scope.$apply(function () { + hide(); + }); + } // Show the tooltip popup element. function show() { @@ -182,7 +214,6 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position' ] ) // Hide the tooltip popup element. function hide() { // First things first: we don't show it anymore. - //tooltip.removeClass( 'in' ); scope.tt_isOpen = false; //if tooltip is going to be shown after delay, we must cancel this @@ -198,25 +229,43 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position' ] ) } } - // Register the event listeners. If only one event listener was - // supplied, we use the same event listener for showing and hiding. - // TODO add ability to customize event triggers - if ( ! angular.isDefined( defaultTriggerHide ) ) { - element.bind( defaultTriggerShow, function toggleTooltipBind () { - if ( ! scope.tt_isOpen ) { - showWithDelay(); - } else { - scope.$apply( hide ); - } - }); - } else { - element.bind( defaultTriggerShow, function showTooltipBind() { - showWithDelay(); - }); - element.bind( defaultTriggerHide, function hideTooltipBind() { - scope.$apply( hide ); - }); - } + /** + * Observe the relevant attributes. + */ + attrs.$observe( type, function ( val ) { + scope.tt_content = val; + }); + + attrs.$observe( prefix+'Title', function ( val ) { + scope.tt_title = val; + }); + + attrs.$observe( prefix+'Placement', function ( val ) { + scope.tt_placement = angular.isDefined( val ) ? val : options.placement; + }); + + attrs.$observe( prefix+'Animation', function ( val ) { + scope.tt_animation = angular.isDefined( val ) ? $parse( val ) : function(){ return options.animation; }; + }); + + attrs.$observe( prefix+'PopupDelay', function ( val ) { + var delay = parseInt( val, 10 ); + scope.tt_popupDelay = ! isNaN(delay) ? delay : options.popupDelay; + }); + + attrs.$observe( prefix+'Trigger', function ( val ) { + element.unbind( triggers.show ); + element.unbind( triggers.hide ); + + triggers = setTriggers( val ); + + if ( triggers.show === triggers.hide ) { + element.bind( triggers.show, toggleTooltipBind ); + } else { + element.bind( triggers.show, showTooltipBind ); + element.bind( triggers.hide, hideTooltipBind ); + } + }); } }; }; @@ -233,7 +282,7 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position' ] ) }) .directive( 'tooltip', [ '$tooltip', function ( $tooltip ) { - return $tooltip( 'tooltip', 'tooltip', 'mouseenter', 'mouseleave' ); + return $tooltip( 'tooltip', 'tooltip', 'mouseenter' ); }]) .directive( 'tooltipHtmlUnsafePopup', function () { @@ -246,7 +295,7 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position' ] ) }) .directive( 'tooltipHtmlUnsafe', [ '$tooltip', function ( $tooltip ) { - return $tooltip( 'tooltipHtmlUnsafe', 'tooltip', 'mouseenter', 'mouseleave' ); + return $tooltip( 'tooltipHtmlUnsafe', 'tooltip', 'mouseenter' ); }]) ; From b36096f549d5a817d8dbd5dcf1458fa2aa8761e1 Mon Sep 17 00:00:00 2001 From: Pawel Kozlowski Date: Sat, 27 Apr 2013 22:08:15 +0200 Subject: [PATCH 53/54] chore(build): properly generate depndencies on tpl modules Closes #365 --- Gruntfile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gruntfile.js b/Gruntfile.js index 0ffc80055e..9f66975196 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -218,7 +218,7 @@ module.exports = function(grunt) { var modules = grunt.config('modules'); grunt.config('srcModules', pluck(modules, 'moduleName')); - grunt.config('tplModules', pluck(modules, 'tplModules')); + grunt.config('tplModules', pluck(modules, 'tplModules').filter(function(tpls) { return tpls.length > 0;} )); grunt.config('demoModules', modules.filter(function(module) { return module.docs.md && module.docs.js && module.docs.html; })); From 8ab3b40c8fa77c8ffd87b2963406ce44bcfe13e8 Mon Sep 17 00:00:00 2001 From: Pawel Kozlowski Date: Tue, 30 Apr 2013 22:24:22 +0200 Subject: [PATCH 54/54] chore(release): 0.3.0 --- CHANGELOG.md | 43 ++++++++++++++++++++++++++++++++++++++++++- package.json | 2 +- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d0d901e39b..b445dcd1dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,44 @@ +# 0.3.0 (2013-04-30) + +## Features + +- **progressbar:** + - add progressbar directive ([261f2072](https://github.com/angular-ui/bootstrap/commit/261f2072)) +- **rating:** + - add rating directive ([6b5e6369](https://github.com/angular-ui/bootstrap/commit/6b5e6369)) +- **typeahead:** + - support the editable property ([a40c3fbe](https://github.com/angular-ui/bootstrap/commit/a40c3fbe)) + - support typeahead-loading bindable expression ([b58c9c88](https://github.com/angular-ui/bootstrap/commit/b58c9c88)) +- **tooltip:** + - added popup-delay option ([a79a2ba8](https://github.com/angular-ui/bootstrap/commit/a79a2ba8)) + - added appendToBody to $tooltip ([1ee467f8](https://github.com/angular-ui/bootstrap/commit/1ee467f8)) + - added tooltip-html-unsafe directive ([45ed2805](https://github.com/angular-ui/bootstrap/commit/45ed2805)) + - support for custom triggers ([b1ba821b](https://github.com/angular-ui/bootstrap/commit/b1ba821b)) + +## Bug Fixes + +- **alert:** + - don't show close button if no close callback specified ([c2645f4a](https://github.com/angular-ui/bootstrap/commit/c2645f4a)) +- **carousel:** + - Hide navigation indicators if only one slide ([aedc0565](https://github.com/angular-ui/bootstrap/commit/aedc0565)) +- **collapse:** + - remove reference to msTransition for IE10 ([55437b16](https://github.com/angular-ui/bootstrap/commit/55437b16)) +- **dialog:** + - set _open to false on init ([dcc9ef31](https://github.com/angular-ui/bootstrap/commit/dcc9ef31)) + - close dialog on location change ([474ce52e](https://github.com/angular-ui/bootstrap/commit/474ce52e)) + - IE8 fix to not set data() against text nodes ([a6c540e5](https://github.com/angular-ui/bootstrap/commit/a6c540e5)) + - fix $apply in progres on $location change ([77e6acb9](https://github.com/angular-ui/bootstrap/commit/77e6acb9)) +- **tabs:** + - remove superfluous href from tabs template ([38c1badd](https://github.com/angular-ui/bootstrap/commit/38c1badd)) +- **tooltip:** + - fix positioning issues in tooltips and popovers ([6458f487](https://github.com/angular-ui/bootstrap/commit/6458f487)) +- **typeahead:** + - close matches popup on click outside typeahead ([acca7dcd](https://github.com/angular-ui/bootstrap/commit/acca7dcd)) + - stop keydown event propagation when ESC pressed to discard matches ([22a00cd0](https://github.com/angular-ui/bootstrap/commit/22a00cd0)) + - correctly render initial model value ([929a46fa](https://github.com/angular-ui/bootstrap/commit/929a46fa)) + - correctly higlight matches if query contains regexp-special chars ([467afcd6](https://github.com/angular-ui/bootstrap/commit/467afcd6)) + - fix matches pop-up positioning issues ([74beecdb](https://github.com/angular-ui/bootstrap/commit/74beecdb)) + # 0.2.0 (2013-03-03) ## Features @@ -49,4 +90,4 @@ Version `0.1.0` was released with the following directives: * pagination * popover * tabs -* tooltip +* tooltip diff --git a/package.json b/package.json index 7199257ed9..e6b0515a5d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "author": "https://github.com/angular-ui/bootstrap/graphs/contributors", "name": "angular-ui-bootstrap", - "version": "0.3.0-SNAPSHOT", + "version": "0.3.0", "dependencies": {}, "devDependencies": { "grunt": "~0.4.1",