19 |
├── .gitignore ├── evently ├── tasks │ ├── recent │ │ ├── path.txt │ │ ├── selectors │ │ │ └── ul │ │ │ │ └── _changes │ │ │ │ ├── render.txt │ │ │ │ ├── query.json │ │ │ │ ├── selectors │ │ │ │ ├── a[href=#reply] │ │ │ │ │ └── click.js │ │ │ │ └── a[href=#done] │ │ │ │ │ └── click.js │ │ │ │ ├── mustache.html │ │ │ │ └── data.js │ │ └── mustache.html │ ├── tags │ │ ├── path.txt │ │ └── selectors │ │ │ └── ul │ │ │ └── _changes │ │ │ └── query.js │ ├── users │ │ ├── path.txt │ │ └── selectors │ │ │ └── ul │ │ │ └── _changes │ │ │ └── query.js │ └── mentions │ │ ├── path.txt │ │ └── selectors │ │ └── ul │ │ └── _changes │ │ └── query.js ├── replies │ └── _init │ │ ├── selectors │ │ ├── ul │ │ │ └── _changes │ │ │ │ ├── render.txt │ │ │ │ ├── query.js │ │ │ │ ├── mustache.html │ │ │ │ └── data.js │ │ └── form │ │ │ └── submit.js │ │ └── mustache.html ├── profile │ ├── loggedOut │ │ └── mustache.html │ └── profileReady │ │ ├── mustache.html │ │ └── selectors │ │ └── form │ │ └── submit.js ├── tagcloud │ └── _changes │ │ ├── query.json │ │ ├── mustache.html │ │ └── data.js └── usercloud │ └── _changes │ ├── query.json │ ├── mustache.html │ └── data.js ├── views ├── tag-cloud │ ├── reduce.js │ └── map.js ├── user-cloud │ ├── reduce.js │ └── map.js ├── mentions │ └── map.js ├── userProfile │ └── map.js ├── task-replies │ └── map.js ├── recent-tasks │ └── map.js └── users-tasks │ └── map.js ├── vendor ├── couchapp │ ├── evently │ │ ├── docs │ │ │ ├── index │ │ │ │ ├── path.txt │ │ │ │ ├── mustache.html │ │ │ │ └── data.js │ │ │ └── topic │ │ │ │ ├── path.txt │ │ │ │ ├── mustache.html │ │ │ │ ├── data.js │ │ │ │ ├── edit │ │ │ │ └── _init │ │ │ │ │ ├── selectors │ │ │ │ │ ├── a.edit │ │ │ │ │ │ └── click.js │ │ │ │ │ └── a.run │ │ │ │ │ │ └── click.js │ │ │ │ │ └── fun.js │ │ │ │ └── after.js │ │ ├── account │ │ │ ├── loginForm │ │ │ │ ├── selectors │ │ │ │ │ ├── a[href=#signup].json │ │ │ │ │ └── form │ │ │ │ │ │ └── submit.js │ │ │ │ ├── after.js │ │ │ │ └── mustache.html │ │ │ ├── signupForm │ │ │ │ ├── selectors │ │ │ │ │ ├── a[href=#login].json │ │ │ │ │ └── form │ │ │ │ │ │ └── submit.js │ │ │ │ ├── after.js │ │ │ │ └── mustache.html │ │ │ ├── loggedIn │ │ │ │ ├── selectors.json │ │ │ │ ├── mustache.html │ │ │ │ └── data.js │ │ │ ├── loggedOut │ │ │ │ ├── mustache.html │ │ │ │ └── selectors.json │ │ │ ├── adminParty │ │ │ │ └── mustache.html │ │ │ ├── doLogout.js │ │ │ ├── doLogin.js │ │ │ ├── doSignup.js │ │ │ └── _init.js │ │ ├── profile │ │ │ ├── loggedOut │ │ │ │ ├── mustache.html │ │ │ │ └── after.js │ │ │ ├── profileReady │ │ │ │ ├── data.js │ │ │ │ ├── after.js │ │ │ │ └── mustache.html │ │ │ ├── noProfile │ │ │ │ ├── data.js │ │ │ │ ├── mustache.html │ │ │ │ └── selectors │ │ │ │ │ └── form │ │ │ │ │ └── submit.js │ │ │ └── loggedIn.js │ │ └── README.md │ ├── metadata.json │ ├── docs │ │ ├── profile.md │ │ ├── docs.md │ │ ├── couchapp.md │ │ ├── pathbinder.md │ │ ├── evently.md │ │ └── account.md │ ├── README.md │ ├── lib │ │ ├── redirect.js │ │ ├── list.js │ │ ├── cache.js │ │ ├── atom.js │ │ ├── path.js │ │ ├── docform.js │ │ ├── md5.js │ │ └── markdown.js │ └── _attachments │ │ ├── docs.js │ │ ├── loader.js │ │ ├── docs.css │ │ ├── docs.html │ │ ├── jquery.couch.app.util.js │ │ ├── jquery.pathbinder.js │ │ ├── jquery.mustache.js │ │ ├── jquery.couch.app.js │ │ └── jquery.evently.js └── showdown │ ├── showdown-licenese.txt │ └── _attachments │ └── showdown.js ├── _attachments ├── style │ ├── iphone.css │ └── main.css ├── images │ └── icon.png ├── index.html └── script │ ├── app.js │ └── md5.js ├── validate_doc_update.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .couchapprc -------------------------------------------------------------------------------- /evently/tasks/recent/path.txt: -------------------------------------------------------------------------------- 1 | / -------------------------------------------------------------------------------- /views/tag-cloud/reduce.js: -------------------------------------------------------------------------------- 1 | _count -------------------------------------------------------------------------------- /views/user-cloud/reduce.js: -------------------------------------------------------------------------------- 1 | _count -------------------------------------------------------------------------------- /evently/tasks/tags/path.txt: -------------------------------------------------------------------------------- 1 | /tags/:tag -------------------------------------------------------------------------------- /evently/tasks/users/path.txt: -------------------------------------------------------------------------------- 1 | /users/:name -------------------------------------------------------------------------------- /evently/tasks/mentions/path.txt: -------------------------------------------------------------------------------- 1 | /mentions/:name -------------------------------------------------------------------------------- /vendor/couchapp/evently/docs/index/path.txt: -------------------------------------------------------------------------------- 1 | / -------------------------------------------------------------------------------- /vendor/couchapp/evently/docs/topic/path.txt: -------------------------------------------------------------------------------- 1 | /topic/:id -------------------------------------------------------------------------------- /views/mentions/map.js: -------------------------------------------------------------------------------- 1 | function(doc) { 2 | 3 | } -------------------------------------------------------------------------------- /_attachments/style/iphone.css: -------------------------------------------------------------------------------- 1 | body { 2 | width:600px; 3 | } -------------------------------------------------------------------------------- /evently/replies/_init/selectors/ul/_changes/render.txt: -------------------------------------------------------------------------------- 1 | append -------------------------------------------------------------------------------- /evently/tasks/recent/selectors/ul/_changes/render.txt: -------------------------------------------------------------------------------- 1 | prepend -------------------------------------------------------------------------------- /evently/tasks/recent/mustache.html: -------------------------------------------------------------------------------- 1 |
Please log in to add tasks.
-------------------------------------------------------------------------------- /vendor/couchapp/evently/docs/topic/mustache.html: -------------------------------------------------------------------------------- 1 |Please log in to see your profile.
-------------------------------------------------------------------------------- /vendor/couchapp/evently/profile/profileReady/data.js: -------------------------------------------------------------------------------- 1 | function(e, p) { 2 | return p 3 | } 4 | -------------------------------------------------------------------------------- /evently/tagcloud/_changes/query.json: -------------------------------------------------------------------------------- 1 | { 2 | "view" : "tag-cloud", 3 | "group_level" : 1 4 | } 5 | -------------------------------------------------------------------------------- /evently/usercloud/_changes/query.json: -------------------------------------------------------------------------------- 1 | { 2 | "view" : "user-cloud", 3 | "group_level" : 1 4 | } 5 | -------------------------------------------------------------------------------- /vendor/couchapp/evently/profile/loggedOut/after.js: -------------------------------------------------------------------------------- 1 | function() { 2 | $$(this).profile = null; 3 | }; -------------------------------------------------------------------------------- /vendor/couchapp/evently/profile/noProfile/data.js: -------------------------------------------------------------------------------- 1 | function(e, userCtx) { 2 | return userCtx; 3 | } -------------------------------------------------------------------------------- /_attachments/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jchris/taskr/HEAD/_attachments/images/icon.png -------------------------------------------------------------------------------- /vendor/couchapp/evently/profile/profileReady/after.js: -------------------------------------------------------------------------------- 1 | function(e, p) { 2 | $$(this).profile = p; 3 | }; -------------------------------------------------------------------------------- /vendor/couchapp/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "couchapp", 3 | "description": "official couchapp vendor" 4 | } -------------------------------------------------------------------------------- /vendor/couchapp/evently/account/loggedIn/selectors.json: -------------------------------------------------------------------------------- 1 | { 2 | "a[href=#logout]" : {"click" : ["doLogout"]} 3 | } -------------------------------------------------------------------------------- /vendor/couchapp/evently/account/loggedOut/mustache.html: -------------------------------------------------------------------------------- 1 | Signup or Login -------------------------------------------------------------------------------- /vendor/couchapp/evently/account/loginForm/after.js: -------------------------------------------------------------------------------- 1 | function() { 2 | $("input[name=name]", this).focus(); 3 | } -------------------------------------------------------------------------------- /vendor/couchapp/evently/account/signupForm/after.js: -------------------------------------------------------------------------------- 1 | function() { 2 | $("input[name=name]", this).focus(); 3 | } -------------------------------------------------------------------------------- /views/userProfile/map.js: -------------------------------------------------------------------------------- 1 | function(doc) { 2 | if (doc.type == "userProfile") { 3 | emit(doc.name, doc); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /views/task-replies/map.js: -------------------------------------------------------------------------------- 1 | function(doc) { 2 | if (doc.type == "reply") { 3 | emit([doc.reply_to, doc.created_at], doc) 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /evently/tagcloud/_changes/mustache.html: -------------------------------------------------------------------------------- 1 | {{#tags}} 2 | #{{tag}} 3 | {{/tags}} -------------------------------------------------------------------------------- /vendor/couchapp/evently/docs/index/mustache.html: -------------------------------------------------------------------------------- 1 |Admin party, everyone is admin! Fix this in Futon before proceeding.
-------------------------------------------------------------------------------- /vendor/couchapp/lib/redirect.js: -------------------------------------------------------------------------------- 1 | exports.permanent = function(redirect) { 2 | return { 3 | code : 301, 4 | headers : { 5 | "Location" : redirect 6 | } 7 | }; 8 | }; -------------------------------------------------------------------------------- /vendor/couchapp/evently/account/doLogout.js: -------------------------------------------------------------------------------- 1 | function() { 2 | var elem = $(this); 3 | $.couch.logout({ 4 | success : function() { 5 | elem.trigger("_init"); 6 | } 7 | }); 8 | } -------------------------------------------------------------------------------- /views/users-tasks/map.js: -------------------------------------------------------------------------------- 1 | function(doc) { 2 | if (doc.type == "task" && doc.authorProfile && doc.authorProfile.name) { 3 | emit([doc.authorProfile.name, new Date(doc.created_at)], doc) 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /vendor/couchapp/evently/account/loggedIn/mustache.html: -------------------------------------------------------------------------------- 1 | Welcome 2 | {{name}}! 3 | Logout? 4 | -------------------------------------------------------------------------------- /vendor/couchapp/_attachments/docs.js: -------------------------------------------------------------------------------- 1 | $.log = function() { 2 | // console.log(arguments) 3 | }; 4 | 5 | $.couch.app(function(app) { 6 | $("#docs").evently(app.ddoc.vendor.couchapp.evently.docs, app); 7 | $.pathbinder.begin("/"); 8 | }); -------------------------------------------------------------------------------- /vendor/couchapp/evently/account/loggedIn/data.js: -------------------------------------------------------------------------------- 1 | function(e, r) { 2 | return { 3 | name : r.userCtx.name, 4 | uri_name : encodeURIComponent(r.userCtx.name), 5 | auth_db : encodeURIComponent(r.info.authentication_db) 6 | }; 7 | } -------------------------------------------------------------------------------- /evently/tasks/recent/selectors/ul/_changes/selectors/a[href=#reply]/click.js: -------------------------------------------------------------------------------- 1 | function() { 2 | var li = $(this).parents("li"); 3 | var app = $$(this).app; 4 | $("div.replies",li).evently(app.ddoc.evently.replies, app); 5 | return false; 6 | } -------------------------------------------------------------------------------- /vendor/couchapp/evently/docs/topic/data.js: -------------------------------------------------------------------------------- 1 | function(e, p) { 2 | var doc = $$(this).app.ddoc.vendor.couchapp.docs[p.id]; 3 | var converter = new Showdown.converter(); 4 | var html = converter.makeHtml(doc); 5 | return { 6 | html : html 7 | }; 8 | }; -------------------------------------------------------------------------------- /vendor/couchapp/evently/account/doLogin.js: -------------------------------------------------------------------------------- 1 | function(e, name, pass) { 2 | var elem = $(this); 3 | $.couch.login({ 4 | name : name, 5 | password : pass, 6 | success : function(r) { 7 | elem.trigger("_init") 8 | } 9 | }); 10 | } -------------------------------------------------------------------------------- /evently/replies/_init/mustache.html: -------------------------------------------------------------------------------- 1 |Leave a reply:
5 | 10 |Hello {{nickname}}!
8 | -------------------------------------------------------------------------------- /evently/tasks/users/selectors/ul/_changes/query.js: -------------------------------------------------------------------------------- 1 | function(e) { 2 | var params = e.data.args[1]; 3 | return { 4 | view : "users-tasks", 5 | limit : 25, 6 | startkey : [params.name, {}], 7 | endkey : [params.name], 8 | descending : true, 9 | type : "newRows" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /vendor/couchapp/evently/account/loginForm/mustache.html: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /vendor/couchapp/evently/account/signupForm/mustache.html: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /vendor/couchapp/evently/docs/index/data.js: -------------------------------------------------------------------------------- 1 | function() { 2 | var docs = $$(this).app.ddoc.vendor.couchapp.docs; 3 | var dnames = []; 4 | $.forIn(docs, function(d) { 5 | dnames.push({ 6 | title: d, 7 | href : "#/topic/"+encodeURIComponent(d) 8 | }); 9 | }); 10 | return {docs:dnames}; 11 | }; -------------------------------------------------------------------------------- /evently/tasks/tags/selectors/ul/_changes/query.js: -------------------------------------------------------------------------------- 1 | function(e) { 2 | var params = e.data.args[1]; 3 | return { 4 | view : "tag-cloud", 5 | limit : 25, 6 | startkey : [params.tag, {}], 7 | endkey : [params.tag], 8 | reduce : false, 9 | descending : true, 10 | type : "newRows" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /evently/tagcloud/_changes/data.js: -------------------------------------------------------------------------------- 1 | function(resp) { 2 | var tags = resp.rows.map(function(r) { 3 | return { 4 | tag : r.key, 5 | // todo use a new mustache delimiter for this 6 | tag_uri : encodeURIComponent(r.key), 7 | size : (r.value * 4) + 10 8 | }; 9 | }); 10 | return {tags:tags}; 11 | } 12 | -------------------------------------------------------------------------------- /evently/tasks/mentions/selectors/ul/_changes/query.js: -------------------------------------------------------------------------------- 1 | function(e) { 2 | var params = e.data.args[1]; 3 | return { 4 | view : "user-cloud", 5 | limit : 25, 6 | startkey : [params.name, {}], 7 | endkey : [params.name], 8 | reduce : false, 9 | descending : true, 10 | type : "newRows" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /vendor/couchapp/evently/docs/topic/edit/_init/selectors/a.edit/click.js: -------------------------------------------------------------------------------- 1 | function() { 2 | var pre = $(this).prev('pre'); 3 | var js = pre.text(); 4 | var lines = js.split('\n').length; 5 | var ta = $(''); 6 | ta.text(js); 7 | pre.replace(ta); 8 | return false; 9 | }; 10 | -------------------------------------------------------------------------------- /views/tag-cloud/map.js: -------------------------------------------------------------------------------- 1 | function(doc) { 2 | if (doc.type == "task" && doc.state != "done") { 3 | var words = {}; 4 | doc.body.replace(/\#([\w\-\.]+)/g, function(tag, word) { 5 | words[word.toLowerCase()] = true; 6 | }); 7 | for (var w in words) { 8 | emit([w, doc.created_at], doc); 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /views/user-cloud/map.js: -------------------------------------------------------------------------------- 1 | function(doc) { 2 | if (doc.type == "task" && doc.state != "done") { 3 | var words = {}; 4 | doc.body.replace(/\@([\w\-]+)/g, function(tag, word) { 5 | words[word.toLowerCase()] = true; 6 | }); 7 | for (var w in words) { 8 | emit([w, doc.created_at], doc); 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /evently/replies/_init/selectors/ul/_changes/query.js: -------------------------------------------------------------------------------- 1 | function() { 2 | var li = $(this).parents("li"); 3 | var reply_id = li.attr("data-id"); 4 | return { 5 | view : "task-replies", 6 | limit : 25, 7 | startkey : [reply_id, {}], 8 | endkey : [reply_id], 9 | descending : true, 10 | type : "newRows" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /vendor/couchapp/lib/list.js: -------------------------------------------------------------------------------- 1 | // Helpers for writing server-side _list functions in CouchDB 2 | exports.withRows = function(fun) { 3 | var f = function() { 4 | var row = getRow(); 5 | return row && fun(row); 6 | }; 7 | f.iterator = true; 8 | return f; 9 | } 10 | 11 | exports.send = function(chunk) { 12 | send(chunk + "\n") 13 | } -------------------------------------------------------------------------------- /evently/profile/profileReady/mustache.html: -------------------------------------------------------------------------------- 1 |Error running #', id, 5 | ' code block:
', 6 | (y.toSource ? y.toSource() : JSON.stringify(y)), 7 | ''].join('')); 8 | } 9 | var id = e.data.args[1]; 10 | var example = $("#code-"+id); 11 | var js = $('textarea',example).val() || $('pre',example).text(); 12 | $('#'+id).unbind(); 13 | try { 14 | eval(js); 15 | } catch (y) { 16 | err(y, id); 17 | } 18 | } catch(x) { 19 | err(x, id); 20 | } 21 | return false; 22 | } -------------------------------------------------------------------------------- /vendor/couchapp/evently/docs/topic/edit/_init/fun.js: -------------------------------------------------------------------------------- 1 | function(e, id) { 2 | var editable = $(this); 3 | if ($$(editable)._init_ran) {return false;} 4 | // add edit link 5 | var edit = $('edit code'); 6 | editable.append(edit); 7 | 8 | // add run box 9 | var example = $('
The db name is {{name}}
', 9 | data : app.db 10 | } 11 | }); 12 | }); 13 | 14 | Yay couchapp. 15 | 16 | The `$.couch.app()` function also loads the current design document so that it is available for templates etc. That is how the words you are reading were loaded. This file is included in the CouchApp application library. Let's look at the design doc: 17 | 18 | $.couch.app(function(app) { 19 | $("#ddoc").evently({ 20 | _init : { 21 | mustache : 'Click to show the full doc source:
{{ddoc}}',
22 | data : {
23 | ddoc : JSON.stringify(app.ddoc, null, 2).slice(0,100) + '...'
24 | }
25 | },
26 | click : {
27 | mustache : 'The full doc source (rerun to hide):
{{ddoc}}',
28 | data : {
29 | ddoc : JSON.stringify(app.ddoc, null, 2)
30 | }
31 | }
32 | });
33 | });
34 |
35 |
--------------------------------------------------------------------------------
/vendor/couchapp/_attachments/docs.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | you went to foo
"); 14 | }); 15 | $("#basic_path").pathbinder("foo", "/foo"); 16 | 17 | This code sets up the `#basic_path` div with some initial content, including a link to `#/foo`. If you click the link to foo, you'll see the URL change. It is the changed URL which Pathbinder sees and uses to trigger any running code. You can experiment by manually entering the `#/foo` URL hash, instead of clicking the link, and you'll see that it also triggers the `foo` event. 18 | 19 | ## Using path parameters 20 | 21 | Pathbinder was inspired by the path handling in [Sammy.js](http://github.com/aq/sammy.js). Like Sammy, you can use it to pull parameters from the URL-hash. This page can be linked [using a path that has "pathbinder" as a parameter](#/topic/pathbinder). Let's explore how you can pull parameters out of a path. 22 | 23 | $("#param_path").html(''); 24 | $("#param_path").bind("foo", function(e, params) { 25 | $(this).html("you went to foo - "+params.id+"
"); 26 | }); 27 | $("#param_path").pathbinder("foo", "/foo/:id"); 28 | 29 | When you click the link to super foo, you'll see the param is passed through the event. You can also edit the URL to see that "super" is not hard coded and can be replaced with other values. 30 | 31 | ## Pathbinder with Evently 32 | 33 | It should be no suprise that Pathbinder and Evently play well together. The gist of it is that Evently looks for a key called `path` and if it finds it, uses Pathbinder to connect that event handler to the path. Let's try it out: 34 | 35 | $("#evently_path").evently({ 36 | _init : { 37 | path : '/index', 38 | mustache : 'the index. more cowbell!
' 39 | }, 40 | cowbell : { 41 | path : '/cowbell', 42 | mustache : 'Now that is a lot of cowbell. back to the index
' 43 | } 44 | }); 45 | 46 | Note that when you use an Evently path, Evently also takes care to visit the path when the corresponding event is triggered. So running the above example code (which automatically triggers the `_init` event) will set the hash to `#/index`. If you were to trigger the `cowbell` event through non-path means, you'd see that it changes the path to `#/cowbell` anyway. 47 | 48 | ### Too many widgets 49 | 50 | One thing worth noting: there is only one URL hash for any given page, so be aware that if you have multiple widgets competing for the real-estate, they could conflict with each other. Pathbinder won't do anything when presented with a path it doesn't care about (go ahead, try out some non-sense ones on this page). 51 | 52 | This means that if you have a few widgets all using the path, the page should still behave in a useful way. However, this breaks down if you intend people to be able to use the URL hash to link to page state. Since there can be only one URL hash, whichever action they took last will be reflected in the bookmarked URL. For this reason it makes sense to limit yourself to one path-based Evently widget per page. 53 | -------------------------------------------------------------------------------- /vendor/couchapp/_attachments/jquery.pathbinder.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | // functions for handling the path 3 | // thanks sammy.js 4 | var PATH_REPLACER = "([^\/]+)", 5 | PATH_NAME_MATCHER = /:([\w\d]+)/g, 6 | QUERY_STRING_MATCHER = /\?([^#]*)$/, 7 | SPLAT_MATCHER = /(\*)/, 8 | SPLAT_REPLACER = "(.+)", 9 | _currentPath, 10 | _lastPath, 11 | _pathInterval; 12 | 13 | function hashChanged() { 14 | _currentPath = getPath(); 15 | // if path is actually changed from what we thought it was, then react 16 | if (_lastPath != _currentPath) { 17 | return triggerOnPath(_currentPath); 18 | } 19 | } 20 | 21 | $.pathbinder = { 22 | changeFuns : [], 23 | paths : [], 24 | begin : function(defaultPath) { 25 | // this should trigger the defaultPath if there's not a path in the URL 26 | // otherwise it should trigger the URL's path 27 | $(function() { 28 | var loadPath = getPath(); 29 | if (loadPath) { 30 | triggerOnPath(loadPath); 31 | } else { 32 | goPath(defaultPath); 33 | triggerOnPath(defaultPath); 34 | } 35 | }) 36 | }, 37 | go : function(path) { 38 | goPath(path); 39 | triggerOnPath(path); 40 | }, 41 | onChange : function (fun) { 42 | $.pathbinder.changeFuns.push(fun); 43 | } 44 | }; 45 | 46 | function pollPath(every) { 47 | function hashCheck() { 48 | _currentPath = getPath(); 49 | // path changed if _currentPath != _lastPath 50 | if (_lastPath != _currentPath) { 51 | setTimeout(function() { 52 | $(window).trigger('hashchange'); 53 | }, 1); 54 | } 55 | }; 56 | hashCheck(); 57 | _pathInterval = setInterval(hashCheck, every); 58 | $(window).bind('unload', function() { 59 | clearInterval(_pathInterval); 60 | }); 61 | } 62 | 63 | function triggerOnPath(path) { 64 | $.pathbinder.changeFuns.forEach(function(fun) {fun(path)}); 65 | var pathSpec, path_params, params = {}, param_name, param; 66 | for (var i=0; i < $.pathbinder.paths.length; i++) { 67 | pathSpec = $.pathbinder.paths[i]; 68 | // $.log("pathSpec", pathSpec); 69 | if ((path_params = pathSpec.matcher.exec(path)) !== null) { 70 | // $.log("path_params", path_params); 71 | path_params.shift(); 72 | for (var j=0; j < path_params.length; j++) { 73 | param_name = pathSpec.param_names[j]; 74 | param = decodeURIComponent(path_params[j]); 75 | if (param_name) { 76 | params[param_name] = param; 77 | } else { 78 | if (!params.splat) params.splat = []; 79 | params.splat.push(param); 80 | } 81 | }; 82 | pathSpec.callback(params); 83 | // return true; // removed this to allow for multi match 84 | } 85 | }; 86 | }; 87 | 88 | // bind the event 89 | $(function() { 90 | if ('onhashchange' in window) { 91 | // we have a native event 92 | } else { 93 | pollPath(10); 94 | } 95 | // setTimeout(hashChanged,50); 96 | $(window).bind('hashchange', hashChanged); 97 | }); 98 | 99 | function registerPath(pathSpec) { 100 | $.pathbinder.paths.push(pathSpec); 101 | }; 102 | 103 | function setPath(pathSpec, params) { 104 | var newPath = $.mustache(pathSpec.template, params); 105 | goPath(newPath); 106 | }; 107 | 108 | function goPath(newPath) { 109 | // $.log("goPath", newPath) 110 | window.location = '#'+newPath; 111 | _lastPath = getPath(); 112 | }; 113 | 114 | function getPath() { 115 | var matches = window.location.toString().match(/^[^#]*(#.+)$/); 116 | return matches ? matches[1] : ''; 117 | }; 118 | 119 | function makePathSpec(path, callback) { 120 | var param_names = []; 121 | var template = ""; 122 | 123 | PATH_NAME_MATCHER.lastIndex = 0; 124 | 125 | while ((path_match = PATH_NAME_MATCHER.exec(path)) !== null) { 126 | param_names.push(path_match[1]); 127 | } 128 | 129 | return { 130 | param_names : param_names, 131 | matcher : new RegExp(path.replace( 132 | PATH_NAME_MATCHER, PATH_REPLACER).replace( 133 | SPLAT_MATCHER, SPLAT_REPLACER) + "/?$"), 134 | template : path.replace(PATH_NAME_MATCHER, function(a, b) { 135 | return '{{'+b+'}}'; 136 | }).replace(SPLAT_MATCHER, '{{splat}}'), 137 | callback : callback 138 | }; 139 | }; 140 | 141 | $.fn.pathbinder = function(name, paths, options) { 142 | options = options || {}; 143 | var self = $(this), pathList = paths.split(/\n/); 144 | $.each(pathList, function() { 145 | var path = this; 146 | if (path) { 147 | // $.log("bind path", path); 148 | var pathSpec = makePathSpec(path, function(params) { 149 | // $.log("path cb", name, path, self) 150 | // $.log("trigger path: "+path+" params: ", params); 151 | self.trigger(name, [params]); 152 | }); 153 | // set the path when the event triggered through other means 154 | if (options.bindPath) { 155 | self.bind(name, function(ev, params) { 156 | params = params || {}; 157 | // $.log("set path", name, pathSpec) 158 | setPath(pathSpec, params); 159 | }); 160 | } 161 | // trigger when the path matches 162 | registerPath(pathSpec); 163 | } 164 | }); 165 | }; 166 | })(jQuery); 167 | -------------------------------------------------------------------------------- /vendor/couchapp/docs/evently.md: -------------------------------------------------------------------------------- 1 | # Evently Docs 2 | 3 | Evently is an declarative framework for evented jQuery applications. You write your code as widgets made up of templates and callbacks, while Evently handles the busywork of linking them together. 4 | 5 | Evently has special handlers for CouchDB views and `_changes` feeds, and could be easily extended for other server-side frameworks. 6 | 7 | ## Hello World 8 | 9 | At it's simplest an Evently widget is a set of events connected to a single DOM element. 10 | 11 | JavaScript: 12 | 13 | $("#hello").evently({ 14 | _init : { 15 | mustache : "Hello world
", 16 | }, 17 | click : { 18 | mustache : "What a crazy world!
", 19 | } 20 | }); 21 | 22 | You can also do some more interesting things: 23 | 24 | $("#heyjane").evently({ 25 | _init : { 26 | mustache : '', 27 | selectors : { 28 | 'a[href=#joan]' : { 29 | click : 'hiJoan' 30 | }, 31 | 'a[href=#jane]' : { 32 | click : 'hiJane' 33 | } 34 | } 35 | }, 36 | hiJoan : { 37 | mustache : 'Hello Joan!
' 38 | }, 39 | hiJane : { 40 | mustache : "Darn, it's Jane...
", 41 | after : function() { 42 | setTimeout(function() { 43 | // automatically trigger the "janeRocks" event after 2 seconds. 44 | $("#heyjane").trigger("janeRocks"); 45 | }, 2000); 46 | } 47 | }, 48 | janeRocks : { 49 | render : "append", 50 | mustache : "Actually Jane is awesome.
" 51 | } 52 | }); 53 | 54 | 55 | The imporant thing about this is that the widget is defined by an JavaScript object. This means we can save it as files on our hard drive and `couchapp` will handle saving it as a JSON object for us. 56 | 57 | [screenshot of the above code in textmate's file drawer] 58 | 59 | When we let CouchApp package our evently apps we get to work on them in individual files, instead of as a great big giant mess of JavaScript. This means HTML is HTML, JSON is JSON, and JavaScript is JavaScript. Yay! 60 | 61 | ## Ajax Hello World 62 | 63 | Let's do a little Ajax. We'll just load the version of the CouchDB instance we happen to be serving our HTML from: 64 | 65 | $("#ajax").evently({ 66 | _init : { 67 | mustache : 'Loading CouchDB server info.
', 68 | after : function() { 69 | var widget = $(this); 70 | $.ajax({ 71 | url : '/', 72 | complete : function(req) { 73 | var resp = $.httpData(req, "json"); 74 | widget.trigger("version", [resp]); 75 | } 76 | }) 77 | } 78 | }, 79 | version : { 80 | mustache : "Running CouchDB version {{version}}
", 81 | data : function(e, resp) { 82 | return resp; 83 | } 84 | } 85 | }); 86 | 87 | Explain `mustache` and `data` 88 | 89 | -- triggering other events 90 | -- selectors 91 | -- create a doc 92 | 93 | ## Evently and CouchApp together 94 | 95 | Evently makes it easy to write decoupled JavaScript code, but as the examples above show, Evently widgets can turn into a lot of JSON to look at all on one screen. Because Evently code is declarative, and each handler and callback stands on its own (instead of being wrapped in a common closure), it can be broken out into individual files. 96 | 97 | CouchApp provides a mechanism for mapping between individual files and JSON structures. In this model a directory structure is mapped to a JSON object. So if you have a directory structure like: 98 | 99 | _init/ 100 | mustache.html 101 | selectors/ 102 | form/ 103 | submit.js 104 | input.name/ 105 | change.js 106 | a.cancel/ 107 | click.txt 108 | cancelled/ 109 | mustache.html 110 | selectors/ 111 | a.continue/ 112 | click.txt 113 | 114 | It will appear within your CouchApp design document as: 115 | 116 | { 117 | _init : { 118 | mustache : "contents of mustache.html", 119 | selectors { 120 | form : { 121 | submit : "function() { ... }" 122 | }, 123 | "input.name" { 124 | change : "function() { ... }" 125 | }, 126 | "a.cancel" { 127 | click : "cancelled" 128 | } 129 | } 130 | }, 131 | cancelled : { 132 | mustache : "contents of mustache.html", 133 | selectors : { 134 | "a.continue" : { 135 | click : "_init" 136 | } 137 | } 138 | } 139 | } 140 | 141 | This makes Evently and CouchApp a natural fit for each other. I swear I didn't plan this when I started writing Evently, it just turned out to be an awesome side effect of trying to stay as close to JSON as possible. 142 | 143 | In the [account widget tutorial](#/topic/account) we see the details of the account widget. What isn't discussed much there, is how the code is edited on your filesystem. 144 | 145 | If you are writing an Evently CouchApp widget you can edit the individual pieces on your filesystem. This has the added advantage of giving you native syntax highlighting for all the code. Instead of editing everything as JSON or JavaScript, the templates can be treated as HTML, the paths as text, etc. 146 | 147 | ## Evently Queries 148 | 149 | Evently understands CouchDB in a couple of very simple ways. If you know CouchDB, you're probably familiar with its Map Reduce views. Evently lets you specify view queries in a declarative way, and even takes care of the Ajax request. All you have to do is write code to handle the returned data. 150 | 151 | -- new rows, etc 152 | 153 | -- run a query 154 | 155 | -- connect to changes 156 | 157 | -- links to example apps 158 | 159 | ## Freeform Asynchronous Actions 160 | 161 | Watch out, you're dangerous! Evently allows you to make any old asyncronous action you want, with the `widget.async` member. The callback is the first argument to the `async` function. Check it out: 162 | 163 | $("#async").evently({ 164 | _init : { 165 | mustache : "How many databases on the local host?
Answer: {{number_of_dbs}}
Other stuff: {{args}}
More: {{allArgs}}
", 166 | async : function(cb) { 167 | var ag = Array.prototype.slice.call(arguments).map(function(a){return a.toSource ? a.toSource() : a}); 168 | $.couch.allDbs({ 169 | success : function(resp) { 170 | cb(resp.length, ag); 171 | } 172 | }) 173 | }, 174 | data : function(count, args) { 175 | return { 176 | number_of_dbs : count, 177 | args : JSON.stringify(args), 178 | allArgs : JSON.stringify(Array.prototype.slice.call(arguments)) 179 | }; 180 | } 181 | }, 182 | click : { 183 | mustache : "What a crazy world!
", 184 | } 185 | }); -------------------------------------------------------------------------------- /vendor/couchapp/_attachments/jquery.mustache.js: -------------------------------------------------------------------------------- 1 | /* 2 | Shameless port of a shameless port 3 | @defunkt => @janl => @aq 4 | 5 | See http://github.com/defunkt/mustache for more info. 6 | */ 7 | 8 | ;(function($) { 9 | 10 | /* 11 | Shamless port of http://github.com/defunkt/mustache 12 | by Jan LehnardtNot much to see here
" 162 | }, 163 | loggedIn : { 164 | mustache : "loggedIn was triggered from another widget, {{name}}.
", 165 | data : function(e, r) { 166 | return { name : r.userCtx.name }; 167 | } 168 | } 169 | }); 170 | 171 | Be sure to run the above example code before the next one, otherwise there won't be anything to link to. 172 | 173 | This next block of code demonstrates how to link two widgets together. First we create a normal account widget on the `#link_source` element, then we tell Evently to connect it to the `#link_target` element. Now whenever the `loggedIn` evenr is triggered on the source, it will be triggered on the target. 174 | 175 | $.couch.app(function(app){ 176 | $("#link_source").evently(app.ddoc.vendor.couchapp.evently.account); 177 | // link the source to the target, for the loggedIn event 178 | $.evently.connect($("#link_source"), $("#link_target"), ["loggedIn"]); 179 | }); 180 | 181 | ## Conclusion 182 | 183 | If you are writing a CouchApp that will have users logging and and logging out, you'd do well to use the account widget. It's customizable and linkable. And what's more, it's code that's already written. 184 | 185 | Enjoy! 186 | 187 | -------------------------------------------------------------------------------- /_attachments/script/md5.js: -------------------------------------------------------------------------------- 1 | /* 2 | * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message 3 | * Digest Algorithm, as defined in RFC 1321. 4 | * Version 2.1 Copyright (C) Paul Johnston 1999 - 2002. 5 | * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet 6 | * Distributed under the BSD License 7 | * See http://pajhome.org.uk/crypt/md5 for more info. 8 | */ 9 | 10 | /* 11 | * Configurable variables. You may need to tweak these to be compatible with 12 | * the server-side, but the defaults work in most cases. 13 | */ 14 | var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */ 15 | var b64pad = ""; /* base-64 pad character. "=" for strict RFC compliance */ 16 | var chrsz = 8; /* bits per input character. 8 - ASCII; 16 - Unicode */ 17 | 18 | /* 19 | * These are the functions you'll usually want to call 20 | * They take string arguments and return either hex or base-64 encoded strings 21 | */ 22 | function hex_md5(s){ return binl2hex(core_md5(str2binl(s), s.length * chrsz));} 23 | function b64_md5(s){ return binl2b64(core_md5(str2binl(s), s.length * chrsz));} 24 | function str_md5(s){ return binl2str(core_md5(str2binl(s), s.length * chrsz));} 25 | function hex_hmac_md5(key, data) { return binl2hex(core_hmac_md5(key, data)); } 26 | function b64_hmac_md5(key, data) { return binl2b64(core_hmac_md5(key, data)); } 27 | function str_hmac_md5(key, data) { return binl2str(core_hmac_md5(key, data)); } 28 | 29 | /* 30 | * Perform a simple self-test to see if the VM is working 31 | */ 32 | function md5_vm_test() 33 | { 34 | return hex_md5("abc") == "900150983cd24fb0d6963f7d28e17f72"; 35 | } 36 | 37 | /* 38 | * Calculate the MD5 of an array of little-endian words, and a bit length 39 | keep 40 | */ 41 | function core_md5(x, len) 42 | { 43 | /* append padding */ 44 | x[len >> 5] |= 0x80 << ((len) % 32); 45 | x[(((len + 64) >>> 9) << 4) + 14] = len; 46 | 47 | var a = 1732584193; 48 | var b = -271733879; 49 | var c = -1732584194; 50 | var d = 271733878; 51 | 52 | for(var i = 0; i < x.length; i += 16) 53 | { 54 | var olda = a; 55 | var oldb = b; 56 | var oldc = c; 57 | var oldd = d; 58 | 59 | a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936); 60 | d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586); 61 | c = md5_ff(c, d, a, b, x[i+ 2], 17, 606105819); 62 | b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330); 63 | a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897); 64 | d = md5_ff(d, a, b, c, x[i+ 5], 12, 1200080426); 65 | c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341); 66 | b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983); 67 | a = md5_ff(a, b, c, d, x[i+ 8], 7 , 1770035416); 68 | d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417); 69 | c = md5_ff(c, d, a, b, x[i+10], 17, -42063); 70 | b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162); 71 | a = md5_ff(a, b, c, d, x[i+12], 7 , 1804603682); 72 | d = md5_ff(d, a, b, c, x[i+13], 12, -40341101); 73 | c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290); 74 | b = md5_ff(b, c, d, a, x[i+15], 22, 1236535329); 75 | 76 | a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510); 77 | d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632); 78 | c = md5_gg(c, d, a, b, x[i+11], 14, 643717713); 79 | b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302); 80 | a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691); 81 | d = md5_gg(d, a, b, c, x[i+10], 9 , 38016083); 82 | c = md5_gg(c, d, a, b, x[i+15], 14, -660478335); 83 | b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848); 84 | a = md5_gg(a, b, c, d, x[i+ 9], 5 , 568446438); 85 | d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690); 86 | c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961); 87 | b = md5_gg(b, c, d, a, x[i+ 8], 20, 1163531501); 88 | a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467); 89 | d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784); 90 | c = md5_gg(c, d, a, b, x[i+ 7], 14, 1735328473); 91 | b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734); 92 | 93 | a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558); 94 | d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463); 95 | c = md5_hh(c, d, a, b, x[i+11], 16, 1839030562); 96 | b = md5_hh(b, c, d, a, x[i+14], 23, -35309556); 97 | a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060); 98 | d = md5_hh(d, a, b, c, x[i+ 4], 11, 1272893353); 99 | c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632); 100 | b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640); 101 | a = md5_hh(a, b, c, d, x[i+13], 4 , 681279174); 102 | d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222); 103 | c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979); 104 | b = md5_hh(b, c, d, a, x[i+ 6], 23, 76029189); 105 | a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487); 106 | d = md5_hh(d, a, b, c, x[i+12], 11, -421815835); 107 | c = md5_hh(c, d, a, b, x[i+15], 16, 530742520); 108 | b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651); 109 | 110 | a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844); 111 | d = md5_ii(d, a, b, c, x[i+ 7], 10, 1126891415); 112 | c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905); 113 | b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055); 114 | a = md5_ii(a, b, c, d, x[i+12], 6 , 1700485571); 115 | d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606); 116 | c = md5_ii(c, d, a, b, x[i+10], 15, -1051523); 117 | b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799); 118 | a = md5_ii(a, b, c, d, x[i+ 8], 6 , 1873313359); 119 | d = md5_ii(d, a, b, c, x[i+15], 10, -30611744); 120 | c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380); 121 | b = md5_ii(b, c, d, a, x[i+13], 21, 1309151649); 122 | a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070); 123 | d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379); 124 | c = md5_ii(c, d, a, b, x[i+ 2], 15, 718787259); 125 | b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551); 126 | 127 | a = safe_add(a, olda); 128 | b = safe_add(b, oldb); 129 | c = safe_add(c, oldc); 130 | d = safe_add(d, oldd); 131 | } 132 | return Array(a, b, c, d); 133 | 134 | } 135 | 136 | /* 137 | * These functions implement the four basic operations the algorithm uses. 138 | */ 139 | function md5_cmn(q, a, b, x, s, t) 140 | { 141 | return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s),b); 142 | } 143 | function md5_ff(a, b, c, d, x, s, t) 144 | { 145 | return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t); 146 | } 147 | function md5_gg(a, b, c, d, x, s, t) 148 | { 149 | return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t); 150 | } 151 | function md5_hh(a, b, c, d, x, s, t) 152 | { 153 | return md5_cmn(b ^ c ^ d, a, b, x, s, t); 154 | } 155 | function md5_ii(a, b, c, d, x, s, t) 156 | { 157 | return md5_cmn(c ^ (b | (~d)), a, b, x, s, t); 158 | } 159 | 160 | /* 161 | * Calculate the HMAC-MD5, of a key and some data 162 | */ 163 | function core_hmac_md5(key, data) 164 | { 165 | var bkey = str2binl(key); 166 | if(bkey.length > 16) bkey = core_md5(bkey, key.length * chrsz); 167 | 168 | var ipad = Array(16), opad = Array(16); 169 | for(var i = 0; i < 16; i++) 170 | { 171 | ipad[i] = bkey[i] ^ 0x36363636; 172 | opad[i] = bkey[i] ^ 0x5C5C5C5C; 173 | } 174 | 175 | var hash = core_md5(ipad.concat(str2binl(data)), 512 + data.length * chrsz); 176 | return core_md5(opad.concat(hash), 512 + 128); 177 | } 178 | 179 | /* 180 | * Add integers, wrapping at 2^32. This uses 16-bit operations internally 181 | * to work around bugs in some JS interpreters. 182 | */ 183 | function safe_add(x, y) 184 | { 185 | var lsw = (x & 0xFFFF) + (y & 0xFFFF); 186 | var msw = (x >> 16) + (y >> 16) + (lsw >> 16); 187 | return (msw << 16) | (lsw & 0xFFFF); 188 | } 189 | 190 | /* 191 | * Bitwise rotate a 32-bit number to the left. 192 | */ 193 | function bit_rol(num, cnt) 194 | { 195 | return (num << cnt) | (num >>> (32 - cnt)); 196 | } 197 | 198 | /* 199 | * Convert a string to an array of little-endian words 200 | * If chrsz is ASCII, characters >255 have their hi-byte silently ignored. 201 | keep 202 | */ 203 | function str2binl(str) 204 | { 205 | var bin = Array(); 206 | var mask = (1 << chrsz) - 1; 207 | for(var i = 0; i < str.length * chrsz; i += chrsz) 208 | bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (i%32); 209 | return bin; 210 | } 211 | 212 | /* 213 | * Convert an array of little-endian words to a string 214 | */ 215 | function binl2str(bin) 216 | { 217 | var str = ""; 218 | var mask = (1 << chrsz) - 1; 219 | for(var i = 0; i < bin.length * 32; i += chrsz) 220 | str += String.fromCharCode((bin[i>>5] >>> (i % 32)) & mask); 221 | return str; 222 | } 223 | 224 | /* 225 | * Convert an array of little-endian words to a hex string. 226 | keep 227 | */ 228 | function binl2hex(binarray) 229 | { 230 | var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef"; 231 | var str = ""; 232 | for(var i = 0; i < binarray.length * 4; i++) 233 | { 234 | str += hex_tab.charAt((binarray[i>>2] >> ((i%4)*8+4)) & 0xF) + 235 | hex_tab.charAt((binarray[i>>2] >> ((i%4)*8 )) & 0xF); 236 | } 237 | return str; 238 | } 239 | 240 | /* 241 | * Convert an array of little-endian words to a base-64 string 242 | */ 243 | function binl2b64(binarray) 244 | { 245 | var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 246 | var str = ""; 247 | for(var i = 0; i < binarray.length * 4; i += 3) 248 | { 249 | var triplet = (((binarray[i >> 2] >> 8 * ( i %4)) & 0xFF) << 16) 250 | | (((binarray[i+1 >> 2] >> 8 * ((i+1)%4)) & 0xFF) << 8 ) 251 | | ((binarray[i+2 >> 2] >> 8 * ((i+2)%4)) & 0xFF); 252 | for(var j = 0; j < 4; j++) 253 | { 254 | if(i * 8 + j * 6 > binarray.length * 32) str += b64pad; 255 | else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F); 256 | } 257 | } 258 | return str; 259 | } -------------------------------------------------------------------------------- /vendor/couchapp/lib/md5.js: -------------------------------------------------------------------------------- 1 | /* 2 | * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message 3 | * Digest Algorithm, as defined in RFC 1321. 4 | * Version 2.1 Copyright (C) Paul Johnston 1999 - 2002. 5 | * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet 6 | * Distributed under the BSD License 7 | * See http://pajhome.org.uk/crypt/md5 for more info. 8 | */ 9 | 10 | /* 11 | * Configurable variables. You may need to tweak these to be compatible with 12 | * the server-side, but the defaults work in most cases. 13 | */ 14 | var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */ 15 | var b64pad = ""; /* base-64 pad character. "=" for strict RFC compliance */ 16 | var chrsz = 8; /* bits per input character. 8 - ASCII; 16 - Unicode */ 17 | 18 | /* 19 | * These are the functions you'll usually want to call 20 | * They take string arguments and return either hex or base-64 encoded strings 21 | */ 22 | function hex_md5(s){ return binl2hex(core_md5(str2binl(s), s.length * chrsz));} 23 | function b64_md5(s){ return binl2b64(core_md5(str2binl(s), s.length * chrsz));} 24 | function str_md5(s){ return binl2str(core_md5(str2binl(s), s.length * chrsz));} 25 | function hex_hmac_md5(key, data) { return binl2hex(core_hmac_md5(key, data)); } 26 | function b64_hmac_md5(key, data) { return binl2b64(core_hmac_md5(key, data)); } 27 | function str_hmac_md5(key, data) { return binl2str(core_hmac_md5(key, data)); } 28 | 29 | /* 30 | * Perform a simple self-test to see if the VM is working 31 | */ 32 | function md5_vm_test() 33 | { 34 | return hex_md5("abc") == "900150983cd24fb0d6963f7d28e17f72"; 35 | } 36 | 37 | /* 38 | * Calculate the MD5 of an array of little-endian words, and a bit length 39 | keep 40 | */ 41 | function core_md5(x, len) 42 | { 43 | /* append padding */ 44 | x[len >> 5] |= 0x80 << ((len) % 32); 45 | x[(((len + 64) >>> 9) << 4) + 14] = len; 46 | 47 | var a = 1732584193; 48 | var b = -271733879; 49 | var c = -1732584194; 50 | var d = 271733878; 51 | 52 | for(var i = 0; i < x.length; i += 16) 53 | { 54 | var olda = a; 55 | var oldb = b; 56 | var oldc = c; 57 | var oldd = d; 58 | 59 | a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936); 60 | d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586); 61 | c = md5_ff(c, d, a, b, x[i+ 2], 17, 606105819); 62 | b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330); 63 | a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897); 64 | d = md5_ff(d, a, b, c, x[i+ 5], 12, 1200080426); 65 | c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341); 66 | b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983); 67 | a = md5_ff(a, b, c, d, x[i+ 8], 7 , 1770035416); 68 | d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417); 69 | c = md5_ff(c, d, a, b, x[i+10], 17, -42063); 70 | b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162); 71 | a = md5_ff(a, b, c, d, x[i+12], 7 , 1804603682); 72 | d = md5_ff(d, a, b, c, x[i+13], 12, -40341101); 73 | c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290); 74 | b = md5_ff(b, c, d, a, x[i+15], 22, 1236535329); 75 | 76 | a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510); 77 | d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632); 78 | c = md5_gg(c, d, a, b, x[i+11], 14, 643717713); 79 | b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302); 80 | a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691); 81 | d = md5_gg(d, a, b, c, x[i+10], 9 , 38016083); 82 | c = md5_gg(c, d, a, b, x[i+15], 14, -660478335); 83 | b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848); 84 | a = md5_gg(a, b, c, d, x[i+ 9], 5 , 568446438); 85 | d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690); 86 | c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961); 87 | b = md5_gg(b, c, d, a, x[i+ 8], 20, 1163531501); 88 | a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467); 89 | d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784); 90 | c = md5_gg(c, d, a, b, x[i+ 7], 14, 1735328473); 91 | b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734); 92 | 93 | a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558); 94 | d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463); 95 | c = md5_hh(c, d, a, b, x[i+11], 16, 1839030562); 96 | b = md5_hh(b, c, d, a, x[i+14], 23, -35309556); 97 | a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060); 98 | d = md5_hh(d, a, b, c, x[i+ 4], 11, 1272893353); 99 | c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632); 100 | b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640); 101 | a = md5_hh(a, b, c, d, x[i+13], 4 , 681279174); 102 | d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222); 103 | c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979); 104 | b = md5_hh(b, c, d, a, x[i+ 6], 23, 76029189); 105 | a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487); 106 | d = md5_hh(d, a, b, c, x[i+12], 11, -421815835); 107 | c = md5_hh(c, d, a, b, x[i+15], 16, 530742520); 108 | b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651); 109 | 110 | a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844); 111 | d = md5_ii(d, a, b, c, x[i+ 7], 10, 1126891415); 112 | c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905); 113 | b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055); 114 | a = md5_ii(a, b, c, d, x[i+12], 6 , 1700485571); 115 | d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606); 116 | c = md5_ii(c, d, a, b, x[i+10], 15, -1051523); 117 | b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799); 118 | a = md5_ii(a, b, c, d, x[i+ 8], 6 , 1873313359); 119 | d = md5_ii(d, a, b, c, x[i+15], 10, -30611744); 120 | c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380); 121 | b = md5_ii(b, c, d, a, x[i+13], 21, 1309151649); 122 | a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070); 123 | d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379); 124 | c = md5_ii(c, d, a, b, x[i+ 2], 15, 718787259); 125 | b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551); 126 | 127 | a = safe_add(a, olda); 128 | b = safe_add(b, oldb); 129 | c = safe_add(c, oldc); 130 | d = safe_add(d, oldd); 131 | } 132 | return Array(a, b, c, d); 133 | 134 | } 135 | 136 | /* 137 | * These functions implement the four basic operations the algorithm uses. 138 | */ 139 | function md5_cmn(q, a, b, x, s, t) 140 | { 141 | return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s),b); 142 | } 143 | function md5_ff(a, b, c, d, x, s, t) 144 | { 145 | return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t); 146 | } 147 | function md5_gg(a, b, c, d, x, s, t) 148 | { 149 | return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t); 150 | } 151 | function md5_hh(a, b, c, d, x, s, t) 152 | { 153 | return md5_cmn(b ^ c ^ d, a, b, x, s, t); 154 | } 155 | function md5_ii(a, b, c, d, x, s, t) 156 | { 157 | return md5_cmn(c ^ (b | (~d)), a, b, x, s, t); 158 | } 159 | 160 | /* 161 | * Calculate the HMAC-MD5, of a key and some data 162 | */ 163 | function core_hmac_md5(key, data) 164 | { 165 | var bkey = str2binl(key); 166 | if(bkey.length > 16) bkey = core_md5(bkey, key.length * chrsz); 167 | 168 | var ipad = Array(16), opad = Array(16); 169 | for(var i = 0; i < 16; i++) 170 | { 171 | ipad[i] = bkey[i] ^ 0x36363636; 172 | opad[i] = bkey[i] ^ 0x5C5C5C5C; 173 | } 174 | 175 | var hash = core_md5(ipad.concat(str2binl(data)), 512 + data.length * chrsz); 176 | return core_md5(opad.concat(hash), 512 + 128); 177 | } 178 | 179 | /* 180 | * Add integers, wrapping at 2^32. This uses 16-bit operations internally 181 | * to work around bugs in some JS interpreters. 182 | */ 183 | function safe_add(x, y) 184 | { 185 | var lsw = (x & 0xFFFF) + (y & 0xFFFF); 186 | var msw = (x >> 16) + (y >> 16) + (lsw >> 16); 187 | return (msw << 16) | (lsw & 0xFFFF); 188 | } 189 | 190 | /* 191 | * Bitwise rotate a 32-bit number to the left. 192 | */ 193 | function bit_rol(num, cnt) 194 | { 195 | return (num << cnt) | (num >>> (32 - cnt)); 196 | } 197 | 198 | /* 199 | * Convert a string to an array of little-endian words 200 | * If chrsz is ASCII, characters >255 have their hi-byte silently ignored. 201 | keep 202 | */ 203 | function str2binl(str) 204 | { 205 | var bin = Array(); 206 | var mask = (1 << chrsz) - 1; 207 | for(var i = 0; i < str.length * chrsz; i += chrsz) 208 | bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (i%32); 209 | return bin; 210 | } 211 | 212 | /* 213 | * Convert an array of little-endian words to a string 214 | */ 215 | function binl2str(bin) 216 | { 217 | var str = ""; 218 | var mask = (1 << chrsz) - 1; 219 | for(var i = 0; i < bin.length * 32; i += chrsz) 220 | str += String.fromCharCode((bin[i>>5] >>> (i % 32)) & mask); 221 | return str; 222 | } 223 | 224 | /* 225 | * Convert an array of little-endian words to a hex string. 226 | keep 227 | */ 228 | function binl2hex(binarray) 229 | { 230 | var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef"; 231 | var str = ""; 232 | for(var i = 0; i < binarray.length * 4; i++) 233 | { 234 | str += hex_tab.charAt((binarray[i>>2] >> ((i%4)*8+4)) & 0xF) + 235 | hex_tab.charAt((binarray[i>>2] >> ((i%4)*8 )) & 0xF); 236 | } 237 | return str; 238 | } 239 | 240 | /* 241 | * Convert an array of little-endian words to a base-64 string 242 | */ 243 | function binl2b64(binarray) 244 | { 245 | var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 246 | var str = ""; 247 | for(var i = 0; i < binarray.length * 4; i += 3) 248 | { 249 | var triplet = (((binarray[i >> 2] >> 8 * ( i %4)) & 0xFF) << 16) 250 | | (((binarray[i+1 >> 2] >> 8 * ((i+1)%4)) & 0xFF) << 8 ) 251 | | ((binarray[i+2 >> 2] >> 8 * ((i+2)%4)) & 0xFF); 252 | for(var j = 0; j < 4; j++) 253 | { 254 | if(i * 8 + j * 6 > binarray.length * 32) str += b64pad; 255 | else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F); 256 | } 257 | } 258 | return str; 259 | } 260 | 261 | exports.hex = hex_md5; 262 | -------------------------------------------------------------------------------- /vendor/couchapp/_attachments/jquery.couch.app.js: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | // use this file except in compliance with the License. You may obtain a copy 3 | // of the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | // License for the specific language governing permissions and limitations under 11 | // the License. 12 | 13 | // Usage: The passed in function is called when the page is ready. 14 | // CouchApp passes in the app object, which takes care of linking to 15 | // the proper database, and provides access to the CouchApp helpers. 16 | // $.couch.app(function(app) { 17 | // app.db.view(...) 18 | // ... 19 | // }); 20 | 21 | (function($) { 22 | 23 | function Design(db, name) { 24 | this.doc_id = "_design/"+name; 25 | this.view = function(view, opts) { 26 | db.view(name+'/'+view, opts); 27 | }; 28 | this.list = function(list, view, opts) { 29 | db.list(name+'/'+list, view, opts); 30 | }; 31 | } 32 | 33 | $.couch.app = $.couch.app || function(appFun, opts) { 34 | opts = opts || {}; 35 | $(function() { 36 | var dbname = opts.db || document.location.href.split('/')[3]; 37 | var dname = opts.design || unescape(document.location.href).split('/')[5]; 38 | var db = $.couch.db(dbname); 39 | var design = new Design(db, dname); 40 | 41 | // docForm applies CouchDB behavior to HTML forms. 42 | // todo make this a couch.app plugin 43 | function docForm(formSelector, opts) { 44 | var localFormDoc = {}; 45 | opts = opts || {}; 46 | opts.fields = opts.fields || []; 47 | 48 | // turn the form into deep json 49 | // field names like 'author-email' get turned into json like 50 | // {"author":{"email":"quentin@example.com"}} 51 | function formToDeepJSON(form, fields, doc) { 52 | form = $(form); 53 | opts.fields.forEach(function(field) { 54 | var element = form.find("[name="+field+"]"); 55 | if (element.attr('type') === 'checkbox') { 56 | var val = element.attr('checked'); 57 | } else { 58 | var val = element.val(); 59 | if (!val) return; 60 | } 61 | var parts = field.split('-'); 62 | var frontObj = doc, frontName = parts.shift(); 63 | while (parts.length > 0) { 64 | frontObj[frontName] = frontObj[frontName] || {}; 65 | frontObj = frontObj[frontName]; 66 | frontName = parts.shift(); 67 | } 68 | frontObj[frontName] = val; 69 | }); 70 | } 71 | 72 | // Apply the behavior 73 | $(formSelector).submit(function(e) { 74 | e.preventDefault(); 75 | // formToDeepJSON acts on localFormDoc by reference 76 | formToDeepJSON(this, opts.fields, localFormDoc); 77 | if (opts.beforeSave) {opts.beforeSave(localFormDoc);} 78 | db.saveDoc(localFormDoc, { 79 | success : function(resp) { 80 | if (opts.success) {opts.success(resp, localFormDoc);} 81 | } 82 | }); 83 | 84 | return false; 85 | }); 86 | 87 | // populate form from an existing doc 88 | function docToForm(doc) { 89 | var form = $(formSelector); 90 | // fills in forms 91 | opts.fields.forEach(function(field) { 92 | var parts = field.split('-'); 93 | var run = true, frontObj = doc, frontName = parts.shift(); 94 | while (frontObj && parts.length > 0) { 95 | frontObj = frontObj[frontName]; 96 | frontName = parts.shift(); 97 | } 98 | if (frontObj && frontObj[frontName]) { 99 | var element = form.find("[name="+field+"]"); 100 | if (element.attr('type') === 'checkbox') { 101 | element.attr('checked', frontObj[frontName]); 102 | } else { 103 | element.val(frontObj[frontName]); 104 | } 105 | } 106 | }); 107 | } 108 | 109 | if (opts.id) { 110 | db.openDoc(opts.id, { 111 | attachPrevRev : opts.attachPrevRev, 112 | success: function(doc) { 113 | if (opts.onLoad) {opts.onLoad(doc);} 114 | localFormDoc = doc; 115 | docToForm(doc); 116 | }}); 117 | } else if (opts.template) { 118 | if (opts.onLoad) {opts.onLoad(opts.template);} 119 | localFormDoc = opts.template; 120 | docToForm(localFormDoc); 121 | } 122 | var instance = { 123 | deleteDoc : function(opts) { 124 | opts = opts || {}; 125 | if (confirm("Really delete this document?")) { 126 | db.removeDoc(localFormDoc, opts); 127 | } 128 | }, 129 | localDoc : function() { 130 | formToDeepJSON(formSelector, opts.fields, localFormDoc); 131 | return localFormDoc; 132 | } 133 | }; 134 | return instance; 135 | } 136 | 137 | function resolveModule(names, parent, current) { 138 | if (names.length === 0) { 139 | if (typeof current != "string") { 140 | throw ["error","invalid_require_path", 141 | 'Must require a JavaScript string, not: '+(typeof current)]; 142 | } 143 | return [current, parent]; 144 | } 145 | // we need to traverse the path 146 | var n = names.shift(); 147 | if (n == '..') { 148 | if (!(parent && parent.parent)) { 149 | throw ["error", "invalid_require_path", 'Object has no parent '+JSON.stringify(current)]; 150 | } 151 | return resolveModule(names, parent.parent.parent, parent.parent); 152 | } else if (n == '.') { 153 | if (!parent) { 154 | throw ["error", "invalid_require_path", 'Object has no parent '+JSON.stringify(current)]; 155 | } 156 | return resolveModule(names, parent.parent, parent); 157 | } 158 | if (!current[n]) { 159 | throw ["error", "invalid_require_path", 'Object has no property "'+n+'". '+JSON.stringify(current)]; 160 | } 161 | var p = current; 162 | current = current[n]; 163 | current.parent = p; 164 | return resolveModule(names, p, current); 165 | } 166 | 167 | var p = document.location.pathname.split('/'); 168 | p.shift(); 169 | var qs = document.location.search.replace(/^\?/,'').split('&'); 170 | var q = {}; 171 | qs.forEach(function(param) { 172 | var ps = param.split('='); 173 | var k = decodeURIComponent(ps[0]); 174 | var v = decodeURIComponent(ps[1]); 175 | if (["startkey", "endkey", "key"].indexOf(k) != -1) { 176 | q[k] = JSON.parse(v); 177 | } else { 178 | q[k] = v; 179 | } 180 | }); 181 | var mockReq = { 182 | path : p, 183 | query : q 184 | }; 185 | 186 | var appExports = $.extend({ 187 | db : db, 188 | design : design, 189 | view : design.view, 190 | list : design.list, 191 | docForm : docForm, 192 | req : mockReq 193 | }, $.couch.app.app); 194 | 195 | function handleDDoc(ddoc) { 196 | if (ddoc) { 197 | var require = function(name, parent) { 198 | var exports = {}; 199 | var resolved = resolveModule(name.split('/'), parent, ddoc); 200 | var source = resolved[0]; 201 | parent = resolved[1]; 202 | var s = "var func = function (exports, require) { " + source + " };"; 203 | try { 204 | eval(s); 205 | func.apply(ddoc, [exports, function(name) {return require(name, parent, source)}]); 206 | } catch(e) { 207 | throw ["error","compilation_error","Module require('"+name+"') raised error "+e.toSource()]; 208 | } 209 | return exports; 210 | } 211 | appExports.ddoc = ddoc; 212 | appExports.require = require; 213 | } 214 | // todo make app-exports the this in the execution context? 215 | appFun.apply(appExports, [appExports]); 216 | } 217 | 218 | if ($.couch.app.ddocs[design.doc_id]) { 219 | handleDDoc($.couch.app.ddocs[design.doc_id]) 220 | } else { 221 | // only open 1 connection for this ddoc 222 | if ($.couch.app.ddoc_handlers[design.doc_id]) { 223 | // we are already fetching, just wait 224 | $.couch.app.ddoc_handlers[design.doc_id].push(handleDDoc); 225 | } else { 226 | $.couch.app.ddoc_handlers[design.doc_id] = [handleDDoc]; 227 | db.openDoc(design.doc_id, { 228 | success : function(doc) { 229 | $.couch.app.ddocs[design.doc_id] = doc; 230 | $.couch.app.ddoc_handlers[design.doc_id].forEach(function(h) { 231 | h(doc); 232 | }); 233 | $.couch.app.ddoc_handlers[design.doc_id] = null; 234 | }, 235 | error : function() { 236 | $.couch.app.ddoc_handlers[design.doc_id].forEach(function(h) { 237 | h(); 238 | }); 239 | $.couch.app.ddoc_handlers[design.doc_id] = null; 240 | } 241 | }); 242 | } 243 | } 244 | 245 | }); 246 | }; 247 | $.couch.app.ddocs = {}; 248 | $.couch.app.ddoc_handlers = {}; 249 | // legacy support. $.CouchApp is deprecated, please use $.couch.app 250 | $.CouchApp = $.couch.app; 251 | })(jQuery); 252 | 253 | // JavaScript 1.6 compatibility functions that are missing from IE7/IE8 254 | 255 | if (!Array.prototype.forEach) 256 | { 257 | Array.prototype.forEach = function(fun /*, thisp*/) 258 | { 259 | var len = this.length >>> 0; 260 | if (typeof fun != "function") 261 | throw new TypeError(); 262 | 263 | var thisp = arguments[1]; 264 | for (var i = 0; i < len; i++) 265 | { 266 | if (i in this) 267 | fun.call(thisp, this[i], i, this); 268 | } 269 | }; 270 | } 271 | 272 | if (!Array.prototype.indexOf) 273 | { 274 | Array.prototype.indexOf = function(elt) 275 | { 276 | var len = this.length >>> 0; 277 | 278 | var from = Number(arguments[1]) || 0; 279 | from = (from < 0) 280 | ? Math.ceil(from) 281 | : Math.floor(from); 282 | if (from < 0) 283 | from += len; 284 | 285 | for (; from < len; from++) 286 | { 287 | if (from in this && 288 | this[from] === elt) 289 | return from; 290 | } 291 | return -1; 292 | }; 293 | } 294 | -------------------------------------------------------------------------------- /vendor/showdown/_attachments/showdown.js: -------------------------------------------------------------------------------- 1 | /* 2 | A A L Source code at: 3 | T C A"+_77+"\n";
254 | return _1c(_77)+_78;
255 | });
256 | _73=_73.replace(/~0/,"");
257 | return _73;
258 | };
259 | var _1c=function(_7a){
260 | _7a=_7a.replace(/(^\n+|\n+$)/g,"");
261 | return "\n\n~K"+(_3.push(_7a)-1)+"K\n\n";
262 | };
263 | var _23=function(_7b){
264 | _7b=_7b.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm,function(_7c,m1,m2,m3,m4){
265 | var c=m3;
266 | c=c.replace(/^([ \t]*)/g,"");
267 | c=c.replace(/[ \t]*$/g,"");
268 | c=_79(c);
269 | return m1+""+c+"";
270 | });
271 | return _7b;
272 | };
273 | var _79=function(_82){
274 | _82=_82.replace(/&/g,"&");
275 | _82=_82.replace(//g,">");
277 | _82=_2e(_82,"*_{}[]\\",false);
278 | return _82;
279 | };
280 | var _29=function(_83){
281 | _83=_83.replace(/(\*\*|__)(?=\S)([^\r]*?\S[*_]*)\1/g,"$2");
282 | _83=_83.replace(/(\*|_)(?=\S)([^\r]*?\S)\1/g,"$2");
283 | return _83;
284 | };
285 | var _1f=function(_84){
286 | _84=_84.replace(/((^[ \t]*>[ \t]?.+\n(.+\n)*\n*)+)/gm,function(_85,m1){
287 | var bq=m1;
288 | bq=bq.replace(/^[ \t]*>[ \t]?/gm,"~0");
289 | bq=bq.replace(/~0/g,"");
290 | bq=bq.replace(/^[ \t]+$/gm,"");
291 | bq=_9(bq);
292 | bq=bq.replace(/(^|\n)/g,"$1 ");
293 | bq=bq.replace(/(\s*[^\r]+?<\/pre>)/gm,function(_88,m1){
294 | var pre=m1;
295 | pre=pre.replace(/^ /mg,"~0");
296 | pre=pre.replace(/~0/g,"");
297 | return pre;
298 | });
299 | return _1c("\n"+bq+"\n
");
300 | });
301 | return _84;
302 | };
303 | var _20=function(_8b){
304 | _8b=_8b.replace(/^\n+/g,"");
305 | _8b=_8b.replace(/\n+$/g,"");
306 | var _8c=_8b.split(/\n{2,}/g);
307 | var _8d=new Array();
308 | var end=_8c.length;
309 | for(var i=0;i=0){
312 | _8d.push(str);
313 | }else{
314 | if(str.search(/\S/)>=0){
315 | str=_21(str);
316 | str=str.replace(/^([ \t]*)/g,"");
317 | str+="
";
318 | _8d.push(str);
319 | }
320 | }
321 | }
322 | end=_8d.length;
323 | for(var i=0;i=0){
325 | var _91=_3[RegExp.$1];
326 | _91=_91.replace(/\$/g,"$$$$");
327 | _8d[i]=_8d[i].replace(/~K\d+K/,_91);
328 | }
329 | }
330 | return _8d.join("\n\n");
331 | };
332 | var _11=function(_92){
333 | _92=_92.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g,"&");
334 | _92=_92.replace(/<(?![a-z\/?\$!])/gi,"<");
335 | return _92;
336 | };
337 | var _25=function(_93){
338 | _93=_93.replace(/\\(\\)/g,_94);
339 | _93=_93.replace(/\\([`*_{}\[\]()>#+-.!])/g,_94);
340 | return _93;
341 | };
342 | var _28=function(_95){
343 | _95=_95.replace(/<((https?|ftp|dict):[^'">\s]+)>/gi,"$1");
344 | _95=_95.replace(/<(?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi,function(_96,m1){
345 | return _98(_a(m1));
346 | });
347 | return _95;
348 | };
349 | var _98=function(_99){
350 | function char2hex(ch){
351 | var _9b="0123456789ABCDEF";
352 | var dec=ch.charCodeAt(0);
353 | return (_9b.charAt(dec>>4)+_9b.charAt(dec&15));
354 | }
355 | var _9d=[function(ch){
356 | return ""+ch.charCodeAt(0)+";";
357 | },function(ch){
358 | return ""+char2hex(ch)+";";
359 | },function(ch){
360 | return ch;
361 | }];
362 | _99="mailto:"+_99;
363 | _99=_99.replace(/./g,function(ch){
364 | if(ch=="@"){
365 | ch=_9d[Math.floor(Math.random()*2)](ch);
366 | }else{
367 | if(ch!=":"){
368 | var r=Math.random();
369 | ch=(r>0.9?_9d[2](ch):r>0.45?_9d[1](ch):_9d[0](ch));
370 | }
371 | }
372 | return ch;
373 | });
374 | _99=""+_99+"";
375 | _99=_99.replace(/">.+:/g,"\">");
376 | return _99;
377 | };
378 | var _a=function(_a3){
379 | _a3=_a3.replace(/~E(\d+)E/g,function(_a4,m1){
380 | var _a6=parseInt(m1);
381 | return String.fromCharCode(_a6);
382 | });
383 | return _a3;
384 | };
385 | var _72=function(_a7){
386 | _a7=_a7.replace(/^(\t|[ ]{1,4})/gm,"~0");
387 | _a7=_a7.replace(/~0/g,"");
388 | return _a7;
389 | };
390 | var _6=function(_a8){
391 | _a8=_a8.replace(/\t(?=\t)/g," ");
392 | _a8=_a8.replace(/\t/g,"~A~B");
393 | _a8=_a8.replace(/~B(.+?)~A/g,function(_a9,m1,m2){
394 | var _ac=m1;
395 | var _ad=4-_ac.length%4;
396 | for(var i=0;i<_ad;i++){
397 | _ac+=" ";
398 | }
399 | return _ac;
400 | });
401 | _a8=_a8.replace(/~A/g," ");
402 | _a8=_a8.replace(/~B/g,"");
403 | return _a8;
404 | };
405 | var _2e=function(_af,_b0,_b1){
406 | var _b2="(["+_b0.replace(/([\[\]\\])/g,"\\$1")+"])";
407 | if(_b1){
408 | _b2="\\\\"+_b2;
409 | }
410 | var _b3=new RegExp(_b2,"g");
411 | _af=_af.replace(_b3,_94);
412 | return _af;
413 | };
414 | var _94=function(_b4,m1){
415 | var _b6=m1.charCodeAt(0);
416 | return "~E"+_b6+"E";
417 | };
418 | };
419 |
420 |
--------------------------------------------------------------------------------
/vendor/couchapp/_attachments/jquery.evently.js:
--------------------------------------------------------------------------------
1 | // $$ inspired by @wycats: http://yehudakatz.com/2009/04/20/evented-programming-with-jquery/
2 | function $$(node) {
3 | var data = $(node).data("$$");
4 | if (data) {
5 | return data;
6 | } else {
7 | data = {};
8 | $(node).data("$$", data);
9 | return data;
10 | }
11 | };
12 |
13 | (function($) {
14 | // utility functions used in the implementation
15 |
16 | function forIn(obj, fun) {
17 | var name;
18 | for (name in obj) {
19 | if (obj.hasOwnProperty(name)) {
20 | fun(name, obj[name]);
21 | }
22 | }
23 | };
24 | $.forIn = forIn;
25 | function funViaString(fun) {
26 | if (fun && fun.match && fun.match(/^function/)) {
27 | eval("var f = "+fun);
28 | if (typeof f == "function") {
29 | return function() {
30 | try {
31 | return f.apply(this, arguments);
32 | } catch(e) {
33 | // IF YOU SEE AN ERROR HERE IT HAPPENED WHEN WE TRIED TO RUN YOUR FUNCTION
34 | // $.log({"message": "Error in evently function.", "error": e, "src" : fun});
35 | throw(e);
36 | }
37 | };
38 | }
39 | }
40 | return fun;
41 | };
42 |
43 | function runIfFun(me, fun, args) {
44 | // if the field is a function, call it, bound to the widget
45 | var f = funViaString(fun);
46 | if (typeof f == "function") {
47 | return f.apply(me, args);
48 | } else {
49 | return fun;
50 | }
51 | }
52 |
53 | $.evently = {
54 | connect : function(source, target, events) {
55 | events.forEach(function(ev) {
56 | $(source).bind(ev, function() {
57 | var args = $.makeArray(arguments);
58 | // remove the original event to keep from stacking args extra deep
59 | // it would be nice if jquery had a way to pass the original
60 | // event to the trigger method.
61 | args.shift();
62 | $(target).trigger(ev, args);
63 | return false;
64 | });
65 | });
66 | },
67 | paths : [],
68 | changesDBs : {}
69 | };
70 |
71 | function extractFrom(name, evs) {
72 | return evs[name];
73 | };
74 |
75 | function extractEvents(name, ddoc) {
76 | // extract events from ddoc.evently and ddoc.vendor.*.evently
77 | var events = [true, {}];
78 | $.forIn(ddoc.vendor, function(k, v) {
79 | if (v.evently && v.evently[name]) {
80 | events.push(v.evently[name]);
81 | }
82 | });
83 | if (ddoc.evently[name]) {events.push(ddoc.evently[name]);}
84 | return $.extend.apply(null, events);
85 | }
86 |
87 | $.fn.evently = function(events, app, args) {
88 | var elem = $(this);
89 | // store the app on the element for later use
90 | if (app) {
91 | $$(elem).app = app;
92 | }
93 |
94 | if (typeof events == "string") {
95 | events = extractEvents(events, app.ddoc);
96 | }
97 |
98 | $$(elem).evently = events;
99 | // setup the handlers onto elem
100 | forIn(events, function(name, h) {
101 | eventlyHandler(elem, name, h, args);
102 | });
103 |
104 | if (events._init) {
105 | // $.log("ev _init", elem);
106 | elem.trigger("_init", args);
107 | }
108 |
109 | if (app && events._changes) {
110 | $("body").bind("evently.changes."+app.db.name, function() {
111 | // we want to unbind this function when the element is deleted.
112 | // maybe jquery 1.4.2 has this covered?
113 | // $.log('changes', elem);
114 | elem.trigger("_changes");
115 | });
116 | followChanges(app);
117 | elem.trigger("_changes");
118 | }
119 | };
120 |
121 | // eventlyHandler applies the user's handler (h) to the
122 | // elem, bound to trigger based on name.
123 | function eventlyHandler(elem, name, h, args) {
124 | if (h.path) {
125 | elem.pathbinder(name, h.path);
126 | }
127 | var f = funViaString(h);
128 | if (typeof f == "function") {
129 | elem.bind(name, {args:args}, f);
130 | } else if (typeof f == "string") {
131 | elem.bind(name, {args:args}, function() {
132 | $(this).trigger(f, arguments);
133 | return false;
134 | });
135 | } else if ($.isArray(h)) {
136 | // handle arrays recursively
137 | for (var i=0; i < h.length; i++) {
138 | eventlyHandler(elem, name, h[i], args);
139 | }
140 | } else {
141 | // an object is using the evently / mustache template system
142 | if (h.fun) {
143 | elem.bind(name, {args:args}, funViaString(h.fun));
144 | }
145 | // templates, selectors, etc are intepreted
146 | // when our named event is triggered.
147 | elem.bind(name, {args:args}, function() {
148 | renderElement($(this), h, arguments);
149 | return false;
150 | });
151 | }
152 | };
153 |
154 | $.fn.replace = function(elem) {
155 | // $.log("Replace", this)
156 | $(this).empty().append(elem);
157 | };
158 |
159 | // todo: ability to call this
160 | // to render and "prepend/append/etc" a new element to the host element (me)
161 | // as well as call this in a way that replaces the host elements content
162 | // this would be easy if there is a simple way to get at the element we just appended
163 | // (as html) so that we can attache the selectors
164 | function renderElement(me, h, args, qrun, arun) {
165 | // if there's a query object we run the query,
166 | // and then call the data function with the response.
167 | if (h.before && (!qrun || !arun)) {
168 | funViaString(h.before).apply(me, args);
169 | }
170 | if (h.async && !arun) {
171 | runAsync(me, h, args)
172 | } else if (h.query && !qrun) {
173 | // $.log("query before renderElement", arguments)
174 | runQuery(me, h, args)
175 | } else {
176 | // $.log("renderElement")
177 | // $.log(me, h, args, qrun)
178 | // otherwise we just render the template with the current args
179 | var selectors = runIfFun(me, h.selectors, args);
180 | var act = (h.render || "replace").replace(/\s/g,"");
181 | var app = $$(me).app;
182 | if (h.mustache) {
183 | // $.log("rendering", h.mustache)
184 | var newElem = mustachioed(me, h, args);
185 | me[act](newElem);
186 | }
187 | if (selectors) {
188 | if (act == "replace") {
189 | var s = me;
190 | } else {
191 | var s = newElem;
192 | }
193 | forIn(selectors, function(selector, handlers) {
194 | // $.log("selector", selector);
195 | // $.log("selected", $(selector, s));
196 | $(selector, s).evently(handlers, app, args);
197 | // $.log("applied", selector);
198 | });
199 | }
200 | if (h.after) {
201 | runIfFun(me, h.after, args);
202 | // funViaString(h.after).apply(me, args);
203 | }
204 | }
205 | };
206 |
207 | // todo this should return the new element
208 | function mustachioed(me, h, args) {
209 | return $($.mustache(
210 | runIfFun(me, h.mustache, args),
211 | runIfFun(me, h.data, args),
212 | runIfFun(me, h.partials, args)));
213 | };
214 |
215 | function runAsync(me, h, args) {
216 | // the callback is the first argument
217 | funViaString(h.async).apply(me, [function() {
218 | renderElement(me, h,
219 | $.argsToArray(arguments).concat($.argsToArray(args)), false, true);
220 | }].concat($.argsToArray(args)));
221 | };
222 |
223 |
224 | function runQuery(me, h, args) {
225 | // $.log("runQuery: args", args)
226 | var app = $$(me).app;
227 | var qu = runIfFun(me, h.query, args);
228 | var qType = qu.type;
229 | var viewName = qu.view;
230 | var userSuccess = qu.success;
231 | // $.log("qType", qType)
232 |
233 | var q = {};
234 | forIn(qu, function(k, v) {
235 | q[k] = v;
236 | });
237 |
238 | if (qType == "newRows") {
239 | q.success = function(resp) {
240 | // $.log("runQuery newRows success", resp.rows.length, me, resp)
241 | resp.rows.reverse().forEach(function(row) {
242 | renderElement(me, h, [row].concat($.argsToArray(args)), true)
243 | });
244 | if (userSuccess) userSuccess(resp);
245 | };
246 | newRows(me, app, viewName, q);
247 | } else {
248 | q.success = function(resp) {
249 | // $.log("runQuery success", resp)
250 | renderElement(me, h, [resp].concat($.argsToArray(args)), true);
251 | userSuccess && userSuccess(resp);
252 | };
253 | // $.log(app)
254 | app.view(viewName, q);
255 | }
256 | }
257 |
258 | // this is for the items handler
259 | // var lastViewId, highKey, inFlight;
260 | // this needs to key per elem
261 | function newRows(elem, app, view, opts) {
262 | // $.log("newRows", arguments);
263 | // on success we'll set the top key
264 | var thisViewId, successCallback = opts.success, full = false;
265 | function successFun(resp) {
266 | // $.log("newRows success", resp)
267 | $$(elem).inFlight = false;
268 | var JSONhighKey = JSON.stringify($$(elem).highKey);
269 | resp.rows = resp.rows.filter(function(r) {
270 | return JSON.stringify(r.key) != JSONhighKey;
271 | });
272 | if (resp.rows.length > 0) {
273 | if (opts.descending) {
274 | $$(elem).highKey = resp.rows[0].key;
275 | } else {
276 | $$(elem).highKey = resp.rows[resp.rows.length -1].key;
277 | }
278 | };
279 | if (successCallback) {successCallback(resp, full)};
280 | };
281 | opts.success = successFun;
282 |
283 | if (opts.descending) {
284 | thisViewId = view + (opts.startkey ? JSON.stringify(opts.startkey) : "");
285 | } else {
286 | thisViewId = view + (opts.endkey ? JSON.stringify(opts.endkey) : "");
287 | }
288 | // $.log(["thisViewId",thisViewId])
289 | // for query we'll set keys
290 | if (thisViewId == $$(elem).lastViewId) {
291 | // we only want the rows newer than changesKey
292 | var hk = $$(elem).highKey;
293 | if (hk !== undefined) {
294 | if (opts.descending) {
295 | opts.endkey = hk;
296 | // opts.inclusive_end = false;
297 | } else {
298 | opts.startkey = hk;
299 | }
300 | }
301 | // $.log("add view rows", opts)
302 | if (!$$(elem).inFlight) {
303 | $$(elem).inFlight = true;
304 | app.view(view, opts);
305 | }
306 | } else {
307 | // full refresh
308 | // $.log("new view stuff")
309 | full = true;
310 | $$(elem).lastViewId = thisViewId;
311 | $$(elem).highKey = undefined;
312 | $$(elem).inFlight = true;
313 | app.view(view, opts);
314 | }
315 | };
316 |
317 | // only start one changes listener per db
318 | function followChanges(app) {
319 | var dbName = app.db.name, changeEvent = function() {
320 | $("body").trigger("evently.changes."+dbName);
321 | };
322 | if (!$.evently.changesDBs[dbName]) {
323 | if (app.db.changes) {
324 | // new api in jquery.couch.js 1.0
325 | app.db.changes().onChange(changeEvent);
326 | } else {
327 | // in case you are still on CouchDB 0.11 ;) deprecated.
328 | connectToChanges(app, changeEvent);
329 | }
330 | $.evently.changesDBs[dbName] = true;
331 | }
332 | }
333 |
334 | // deprecated. use db.changes() from jquery.couch.js
335 | // this does not have an api for closing changes request.
336 | function connectToChanges(app, fun, update_seq) {
337 | function changesReq(seq) {
338 | $.ajax({
339 | url: app.db.uri+"_changes?feed=longpoll&since="+seq,
340 | contentType: "application/json",
341 | dataType: "json",
342 | beforeSend : beforeSend,
343 | complete: function(req) {
344 | var resp = $.httpData(req, "json");
345 | fun(resp);
346 | connectToChanges(app, fun, resp.last_seq);
347 | }
348 | });
349 | };
350 | if (update_seq) {
351 | changesReq(update_seq);
352 | } else {
353 | app.db.info({success: function(db_info) {
354 | changesReq(db_info.update_seq);
355 | }});
356 | }
357 | };
358 |
359 | })(jQuery);
360 |
--------------------------------------------------------------------------------
/vendor/couchapp/lib/markdown.js:
--------------------------------------------------------------------------------
1 | //
2 | // showdown.js -- A javascript port of Markdown.
3 | //
4 | // Copyright (c) 2007 John Fraser.
5 | //
6 | // Original Markdown Copyright (c) 2004-2005 John Gruber
7 | //
8 | //
9 | // Redistributable under a BSD-style open source license.
10 | // See license.txt for more information.
11 | //
12 | // The full source distribution is at:
13 | //
14 | // A A L
15 | // T C A
16 | // T K B
17 | //
18 | //
19 | //
20 |
21 | //
22 | // Wherever possible, Showdown is a straight, line-by-line port
23 | // of the Perl version of Markdown.
24 | //
25 | // This is not a normal parser design; it's basically just a
26 | // series of string substitutions. It's hard to read and
27 | // maintain this way, but keeping Showdown close to the original
28 | // design makes it easier to port new features.
29 | //
30 | // More importantly, Showdown behaves like markdown.pl in most
31 | // edge cases. So web applications can do client-side preview
32 | // in Javascript, and then build identical HTML on the server.
33 | //
34 | // This port needs the new RegExp functionality of ECMA 262,
35 | // 3rd Edition (i.e. Javascript 1.5). Most modern web browsers
36 | // should do fine. Even with the new regular expression features,
37 | // We do a lot of work to emulate Perl's regex functionality.
38 | // The tricky changes in this file mostly have the "attacklab:"
39 | // label. Major or self-explanatory changes don't.
40 | //
41 | // Smart diff tools like Araxis Merge will be able to match up
42 | // this file with markdown.pl in a useful way. A little tweaking
43 | // helps: in a copy of markdown.pl, replace "#" with "//" and
44 | // replace "$text" with "text". Be sure to ignore whitespace
45 | // and line endings.
46 | //
47 |
48 |
49 | //
50 | // Showdown usage:
51 | //
52 | // var text = "Markdown *rocks*.";
53 | //
54 | // var markdown = require("markdown");
55 | // var html = markdown.encode(text);
56 | //
57 | // print(html);
58 | //
59 | // Note: move the sample code to the bottom of this
60 | // file before uncommenting it.
61 | //
62 |
63 |
64 | //
65 | // Globals:
66 | //
67 |
68 | // Global hashes, used by various utility routines
69 | var g_urls;
70 | var g_titles;
71 | var g_html_blocks;
72 |
73 | // Used to track when we're inside an ordered or unordered list
74 | // (see _ProcessListItems() for details):
75 | var g_list_level = 0;
76 |
77 |
78 | exports.makeHtml = function(text) {
79 | //
80 | // Main function. The order in which other subs are called here is
81 | // essential. Link and image substitutions need to happen before
82 | // _EscapeSpecialCharsWithinTagAttributes(), so that any *'s or _'s in the
83 | // and
tags get encoded.
84 | //
85 |
86 | // Clear the global hashes. If we don't clear these, you get conflicts
87 | // from other articles when generating a page which contains more than
88 | // one article (e.g. an index page that shows the N most recent
89 | // articles):
90 | g_urls = new Array();
91 | g_titles = new Array();
92 | g_html_blocks = new Array();
93 |
94 | // attacklab: Replace ~ with ~T
95 | // This lets us use tilde as an escape char to avoid md5 hashes
96 | // The choice of character is arbitray; anything that isn't
97 | // magic in Markdown will work.
98 | text = text.replace(/~/g,"~T");
99 |
100 | // attacklab: Replace $ with ~D
101 | // RegExp interprets $ as a special character
102 | // when it's in a replacement string
103 | text = text.replace(/\$/g,"~D");
104 |
105 | // Standardize line endings
106 | text = text.replace(/\r\n/g,"\n"); // DOS to Unix
107 | text = text.replace(/\r/g,"\n"); // Mac to Unix
108 |
109 | // Make sure text begins and ends with a couple of newlines:
110 | text = "\n\n" + text + "\n\n";
111 |
112 | // Convert all tabs to spaces.
113 | text = _Detab(text);
114 |
115 | // Strip any lines consisting only of spaces and tabs.
116 | // This makes subsequent regexen easier to write, because we can
117 | // match consecutive blank lines with /\n+/ instead of something
118 | // contorted like /[ \t]*\n+/ .
119 | text = text.replace(/^[ \t]+$/mg,"");
120 |
121 | // Turn block-level HTML blocks into hash entries
122 | text = _HashHTMLBlocks(text);
123 |
124 | // Strip link definitions, store in hashes.
125 | text = _StripLinkDefinitions(text);
126 |
127 | text = _RunBlockGamut(text);
128 |
129 | text = _UnescapeSpecialChars(text);
130 |
131 | // attacklab: Restore dollar signs
132 | text = text.replace(/~D/g,"$$");
133 |
134 | // attacklab: Restore tildes
135 | text = text.replace(/~T/g,"~");
136 | return text;
137 | }
138 |
139 |
140 | var _StripLinkDefinitions = function(text) {
141 | //
142 | // Strips link definitions from text, stores the URLs and titles in
143 | // hash references.
144 | //
145 |
146 | // Link defs are in the form: ^[id]: url "optional title"
147 |
148 | /*
149 | var text = text.replace(/
150 | ^[ ]{0,3}\[(.+)\]: // id = $1 attacklab: g_tab_width - 1
151 | [ \t]*
152 | \n? // maybe *one* newline
153 | [ \t]*
154 | (\S+?)>? // url = $2
155 | [ \t]*
156 | \n? // maybe one newline
157 | [ \t]*
158 | (?:
159 | (\n*) // any lines skipped = $3 attacklab: lookbehind removed
160 | ["(]
161 | (.+?) // title = $4
162 | [")]
163 | [ \t]*
164 | )? // title is optional
165 | (?:\n+|$)
166 | /gm,
167 | function(){...});
168 | */
169 | var text = text.replace(/^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*(\S+?)>?[ \t]*\n?[ \t]*(?:(\n*)["(](.+?)[")][ \t]*)?(?:\n+|\Z)/gm,
170 | function (wholeMatch,m1,m2,m3,m4) {
171 | m1 = m1.toLowerCase();
172 | g_urls[m1] = _EncodeAmpsAndAngles(m2); // Link IDs are case-insensitive
173 | if (m3) {
174 | // Oops, found blank lines, so it's not a title.
175 | // Put back the parenthetical statement we stole.
176 | return m3+m4;
177 | } else if (m4) {
178 | g_titles[m1] = m4.replace(/"/g,""");
179 | }
180 |
181 | // Completely remove the definition from the text
182 | return "";
183 | }
184 | );
185 |
186 | return text;
187 | }
188 |
189 |
190 | var _HashHTMLBlocks = function(text) {
191 | // attacklab: Double up blank lines to reduce lookaround
192 | text = text.replace(/\n/g,"\n\n");
193 |
194 | // Hashify HTML blocks:
195 | // We only want to do this for block-level HTML tags, such as headers,
196 | // lists, and tables. That's because we still want to wrap s around
197 | // "paragraphs" that are wrapped in non-block-level tags, such as anchors,
198 | // phrase emphasis, and spans. The list of tags we're looking for is
199 | // hard-coded:
200 | var block_tags_a = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del"
201 | var block_tags_b = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math"
202 |
203 | // First, look for nested blocks, e.g.:
204 | //
205 | //
206 | // tags for inner block must be indented.
207 | //
208 | //
209 | //
210 | // The outermost tags must start at the left margin for this to match, and
211 | // the inner nested divs must be indented.
212 | // We need to do this before the next, more liberal match, because the next
213 | // match will start at the first `` and stop at the first ``.
214 |
215 | // attacklab: This regex can be expensive when it fails.
216 | /*
217 | var text = text.replace(/
218 | ( // save in $1
219 | ^ // start of line (with /m)
220 | <($block_tags_a) // start tag = $2
221 | \b // word break
222 | // attacklab: hack around khtml/pcre bug...
223 | [^\r]*?\n // any number of lines, minimally matching
224 | \2> // the matching end tag
225 | [ \t]* // trailing spaces/tabs
226 | (?=\n+) // followed by a newline
227 | ) // attacklab: there are sentinel newlines at end of document
228 | /gm,function(){...}};
229 | */
230 | 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);
231 |
232 | //
233 | // Now match more liberally, simply from `\n` to ` \n`
234 | //
235 |
236 | /*
237 | var text = text.replace(/
238 | ( // save in $1
239 | ^ // start of line (with /m)
240 | <($block_tags_b) // start tag = $2
241 | \b // word break
242 | // attacklab: hack around khtml/pcre bug...
243 | [^\r]*? // any number of lines, minimally matching
244 | .*\2> // the matching end tag
245 | [ \t]* // trailing spaces/tabs
246 | (?=\n+) // followed by a newline
247 | ) // attacklab: there are sentinel newlines at end of document
248 | /gm,function(){...}};
249 | */
250 | 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);
251 |
252 | // Special case just for
. It was easier to make a special case than
253 | // to make the other regex more complicated.
254 |
255 | /*
256 | text = text.replace(/
257 | ( // save in $1
258 | \n\n // Starting after a blank line
259 | [ ]{0,3}
260 | (<(hr) // start tag = $2
261 | \b // word break
262 | ([^<>])*? //
263 | \/?>) // the matching end tag
264 | [ \t]*
265 | (?=\n{2,}) // followed by a blank line
266 | )
267 | /g,hashElement);
268 | */
269 | text = text.replace(/(\n[ ]{0,3}(<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g,hashElement);
270 |
271 | // Special case for standalone HTML comments:
272 |
273 | /*
274 | text = text.replace(/
275 | ( // save in $1
276 | \n\n // Starting after a blank line
277 | [ ]{0,3} // attacklab: g_tab_width - 1
278 |
281 | [ \t]*
282 | (?=\n{2,}) // followed by a blank line
283 | )
284 | /g,hashElement);
285 | */
286 | text = text.replace(/(\n\n[ ]{0,3}[ \t]*(?=\n{2,}))/g,hashElement);
287 |
288 | // PHP and ASP-style processor instructions (...?> and <%...%>)
289 |
290 | /*
291 | text = text.replace(/
292 | (?:
293 | \n\n // Starting after a blank line
294 | )
295 | ( // save in $1
296 | [ ]{0,3} // attacklab: g_tab_width - 1
297 | (?:
298 | <([?%]) // $2
299 | [^\r]*?
300 | \2>
301 | )
302 | [ \t]*
303 | (?=\n{2,}) // followed by a blank line
304 | )
305 | /g,hashElement);
306 | */
307 | text = text.replace(/(?:\n\n)([ ]{0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g,hashElement);
308 |
309 | // attacklab: Undo double lines (see comment at top of this function)
310 | text = text.replace(/\n\n/g,"\n");
311 | return text;
312 | }
313 |
314 | var hashElement = function(wholeMatch,m1) {
315 | var blockText = m1;
316 |
317 | // Undo double lines
318 | blockText = blockText.replace(/\n\n/g,"\n");
319 | blockText = blockText.replace(/^\n/,"");
320 |
321 | // strip trailing blank lines
322 | blockText = blockText.replace(/\n+$/g,"");
323 |
324 | // Replace the element text with a marker ("~KxK" where x is its key)
325 | blockText = "\n\n~K" + (g_html_blocks.push(blockText)-1) + "K\n\n";
326 |
327 | return blockText;
328 | };
329 |
330 | var _RunBlockGamut = function(text) {
331 | //
332 | // These are all the transformations that form block-level
333 | // tags like paragraphs, headers, and list items.
334 | //
335 | text = _DoHeaders(text);
336 |
337 | // Do Horizontal Rules:
338 | var key = hashBlock("
");
339 | text = text.replace(/^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$/gm,key);
340 | text = text.replace(/^[ ]{0,2}([ ]?\-[ ]?){3,}[ \t]*$/gm,key);
341 | text = text.replace(/^[ ]{0,2}([ ]?\_[ ]?){3,}[ \t]*$/gm,key);
342 |
343 | text = _DoLists(text);
344 | text = _DoCodeBlocks(text);
345 | text = _DoBlockQuotes(text);
346 |
347 | // We already ran _HashHTMLBlocks() before, in Markdown(), but that
348 | // was to escape raw HTML in the original Markdown source. This time,
349 | // we're escaping the markup we've just created, so that we don't wrap
350 | // tags around block-level tags.
351 | text = _HashHTMLBlocks(text);
352 | text = _FormParagraphs(text);
353 |
354 | return text;
355 | }
356 |
357 |
358 | var _RunSpanGamut = function(text) {
359 | //
360 | // These are all the transformations that occur *within* block-level
361 | // tags like paragraphs, headers, and list items.
362 | //
363 |
364 | text = _DoCodeSpans(text);
365 | text = _EscapeSpecialCharsWithinTagAttributes(text);
366 | text = _EncodeBackslashEscapes(text);
367 |
368 | // Process anchor and image tags. Images must come first,
369 | // because ![foo][f] looks like an anchor.
370 | text = _DoImages(text);
371 | text = _DoAnchors(text);
372 |
373 | // Make links out of things like ` `
374 | // Must come after _DoAnchors(), because you can use < and >
375 | // delimiters in inline links like [this]().
376 | text = _DoAutoLinks(text);
377 | text = _EncodeAmpsAndAngles(text);
378 | text = _DoItalicsAndBold(text);
379 |
380 | // Do hard breaks:
381 | text = text.replace(/ +\n/g,"
\n");
382 |
383 | return text;
384 | }
385 |
386 | var _EscapeSpecialCharsWithinTagAttributes = function(text) {
387 | //
388 | // Within tags -- meaning between < and > -- encode [\ ` * _] so they
389 | // don't conflict with their use in Markdown for code, italics and strong.
390 | //
391 |
392 | // Build a regex to find HTML tags and comments. See Friedl's
393 | // "Mastering Regular Expressions", 2nd Ed., pp. 200-201.
394 | var regex = /(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|)/gi;
395 |
396 | text = text.replace(regex, function(wholeMatch) {
397 | var tag = wholeMatch.replace(/(.)<\/?code>(?=.)/g,"$1`");
398 | tag = escapeCharacters(tag,"\\`*_");
399 | return tag;
400 | });
401 |
402 | return text;
403 | }
404 |
405 | var _DoAnchors = function(text) {
406 | //
407 | // Turn Markdown link shortcuts into XHTML tags.
408 | //
409 | //
410 | // First, handle reference-style links: [link text] [id]
411 | //
412 |
413 | /*
414 | text = text.replace(/
415 | ( // wrap whole match in $1
416 | \[
417 | (
418 | (?:
419 | \[[^\]]*\] // allow brackets nested one level
420 | |
421 | [^\[] // or anything else
422 | )*
423 | )
424 | \]
425 |
426 | [ ]? // one optional space
427 | (?:\n[ ]*)? // one optional newline followed by spaces
428 |
429 | \[
430 | (.*?) // id = $3
431 | \]
432 | )()()()() // pad remaining backreferences
433 | /g,_DoAnchors_callback);
434 | */
435 | text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,writeAnchorTag);
436 |
437 | //
438 | // Next, inline-style links: [link text](url "optional title")
439 | //
440 |
441 | /*
442 | text = text.replace(/
443 | ( // wrap whole match in $1
444 | \[
445 | (
446 | (?:
447 | \[[^\]]*\] // allow brackets nested one level
448 | |
449 | [^\[\]] // or anything else
450 | )
451 | )
452 | \]
453 | \( // literal paren
454 | [ \t]*
455 | () // no id, so leave $3 empty
456 | (.*?)>? // href = $4
457 | [ \t]*
458 | ( // $5
459 | (['"]) // quote char = $6
460 | (.*?) // Title = $7
461 | \6 // matching quote
462 | [ \t]* // ignore any spaces/tabs between closing quote and )
463 | )? // title is optional
464 | \)
465 | )
466 | /g,writeAnchorTag);
467 | */
468 | text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\]\([ \t]*()(.*?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,writeAnchorTag);
469 |
470 | //
471 | // Last, handle reference-style shortcuts: [link text]
472 | // These must come last in case you've also got [link test][1]
473 | // or [link test](/foo)
474 | //
475 |
476 | /*
477 | text = text.replace(/
478 | ( // wrap whole match in $1
479 | \[
480 | ([^\[\]]+) // link text = $2; can't contain '[' or ']'
481 | \]
482 | )()()()()() // pad rest of backreferences
483 | /g, writeAnchorTag);
484 | */
485 | text = text.replace(/(\[([^\[\]]+)\])()()()()()/g, writeAnchorTag);
486 |
487 | return text;
488 | }
489 |
490 | var writeAnchorTag = function(wholeMatch,m1,m2,m3,m4,m5,m6,m7) {
491 | if (m7 == undefined) m7 = "";
492 | var whole_match = m1;
493 | var link_text = m2;
494 | var link_id = m3.toLowerCase();
495 | var url = m4;
496 | var title = m7;
497 |
498 | if (url == "") {
499 | if (link_id == "") {
500 | // lower-case and turn embedded newlines into spaces
501 | link_id = link_text.toLowerCase().replace(/ ?\n/g," ");
502 | }
503 | url = "#"+link_id;
504 |
505 | if (g_urls[link_id] != undefined) {
506 | url = g_urls[link_id];
507 | if (g_titles[link_id] != undefined) {
508 | title = g_titles[link_id];
509 | }
510 | }
511 | else {
512 | if (whole_match.search(/\(\s*\)$/m)>-1) {
513 | // Special case for explicit empty url
514 | url = "";
515 | } else {
516 | return whole_match;
517 | }
518 | }
519 | }
520 |
521 | url = escapeCharacters(url,"*_");
522 | var result = "" + link_text + "";
531 |
532 | return result;
533 | }
534 |
535 |
536 | var _DoImages = function(text) {
537 | //
538 | // Turn Markdown image shortcuts into
tags.
539 | //
540 |
541 | //
542 | // First, handle reference-style labeled images: ![alt text][id]
543 | //
544 |
545 | /*
546 | text = text.replace(/
547 | ( // wrap whole match in $1
548 | !\[
549 | (.*?) // alt text = $2
550 | \]
551 |
552 | [ ]? // one optional space
553 | (?:\n[ ]*)? // one optional newline followed by spaces
554 |
555 | \[
556 | (.*?) // id = $3
557 | \]
558 | )()()()() // pad rest of backreferences
559 | /g,writeImageTag);
560 | */
561 | text = text.replace(/(!\[(.*?)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,writeImageTag);
562 |
563 | //
564 | // Next, handle inline images: 
565 | // Don't forget: encode * and _
566 |
567 | /*
568 | text = text.replace(/
569 | ( // wrap whole match in $1
570 | !\[
571 | (.*?) // alt text = $2
572 | \]
573 | \s? // One optional whitespace character
574 | \( // literal paren
575 | [ \t]*
576 | () // no id, so leave $3 empty
577 | (\S+?)>? // src url = $4
578 | [ \t]*
579 | ( // $5
580 | (['"]) // quote char = $6
581 | (.*?) // title = $7
582 | \6 // matching quote
583 | [ \t]*
584 | )? // title is optional
585 | \)
586 | )
587 | /g,writeImageTag);
588 | */
589 | text = text.replace(/(!\[(.*?)\]\s?\([ \t]*()(\S+?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,writeImageTag);
590 |
591 | return text;
592 | }
593 |
594 | var writeImageTag = function(wholeMatch,m1,m2,m3,m4,m5,m6,m7) {
595 | var whole_match = m1;
596 | var alt_text = m2;
597 | var link_id = m3.toLowerCase();
598 | var url = m4;
599 | var title = m7;
600 |
601 | if (!title) title = "";
602 |
603 | if (url == "") {
604 | if (link_id == "") {
605 | // lower-case and turn embedded newlines into spaces
606 | link_id = alt_text.toLowerCase().replace(/ ?\n/g," ");
607 | }
608 | url = "#"+link_id;
609 |
610 | if (g_urls[link_id] != undefined) {
611 | url = g_urls[link_id];
612 | if (g_titles[link_id] != undefined) {
613 | title = g_titles[link_id];
614 | }
615 | }
616 | else {
617 | return whole_match;
618 | }
619 | }
620 |
621 | alt_text = alt_text.replace(/"/g,""");
622 | url = escapeCharacters(url,"*_");
623 | var result = "
";
635 |
636 | return result;
637 | }
638 |
639 |
640 | var _DoHeaders = function(text) {
641 |
642 | // Setext-style headers:
643 | // Header 1
644 | // ========
645 | //
646 | // Header 2
647 | // --------
648 | //
649 | text = text.replace(/^(.+)[ \t]*\n=+[ \t]*\n+/gm,
650 | function(wholeMatch,m1){return hashBlock("" + _RunSpanGamut(m1) + "
");});
651 |
652 | text = text.replace(/^(.+)[ \t]*\n-+[ \t]*\n+/gm,
653 | function(matchFound,m1){return hashBlock("" + _RunSpanGamut(m1) + "
");});
654 |
655 | // atx-style headers:
656 | // # Header 1
657 | // ## Header 2
658 | // ## Header 2 with closing hashes ##
659 | // ...
660 | // ###### Header 6
661 | //
662 |
663 | /*
664 | text = text.replace(/
665 | ^(\#{1,6}) // $1 = string of #'s
666 | [ \t]*
667 | (.+?) // $2 = Header text
668 | [ \t]*
669 | \#* // optional closing #'s (not counted)
670 | \n+
671 | /gm, function() {...});
672 | */
673 |
674 | text = text.replace(/^(\#{1,6})[ \t]*(.+?)[ \t]*\#*\n+/gm,
675 | function(wholeMatch,m1,m2) {
676 | var h_level = m1.length;
677 | return hashBlock("" + _RunSpanGamut(m2) + " ");
678 | });
679 |
680 | return text;
681 | }
682 |
683 | // This declaration keeps Dojo compressor from outputting garbage:
684 | var _ProcessListItems;
685 |
686 | var _DoLists = function(text) {
687 | //
688 | // Form HTML ordered (numbered) and unordered (bulleted) lists.
689 | //
690 |
691 | // attacklab: add sentinel to hack around khtml/safari bug:
692 | // http://bugs.webkit.org/show_bug.cgi?id=11231
693 | text += "~0";
694 |
695 | // Re-usable pattern to match any entirel ul or ol list:
696 |
697 | /*
698 | var whole_list = /
699 | ( // $1 = whole list
700 | ( // $2
701 | [ ]{0,3} // attacklab: g_tab_width - 1
702 | ([*+-]|\d+[.]) // $3 = first list item marker
703 | [ \t]+
704 | )
705 | [^\r]+?
706 | ( // $4
707 | ~0 // sentinel for workaround; should be $
708 | |
709 | \n{2,}
710 | (?=\S)
711 | (?! // Negative lookahead for another list item marker
712 | [ \t]*
713 | (?:[*+-]|\d+[.])[ \t]+
714 | )
715 | )
716 | )/g
717 | */
718 | var whole_list = /^(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm;
719 |
720 | if (g_list_level) {
721 | text = text.replace(whole_list,function(wholeMatch,m1,m2) {
722 | var list = m1;
723 | var list_type = (m2.search(/[*+-]/g)>-1) ? "ul" : "ol";
724 |
725 | // Turn double returns into triple returns, so that we can make a
726 | // paragraph for the last item in a list, if necessary:
727 | list = list.replace(/\n{2,}/g,"\n\n\n");;
728 | var result = _ProcessListItems(list);
729 |
730 | // Trim any trailing whitespace, to put the closing `$list_type>`
731 | // up on the preceding line, to get it past the current stupid
732 | // HTML block parser. This is a hack to work around the terrible
733 | // hack that is the HTML block parser.
734 | result = result.replace(/\s+$/,"");
735 | result = "<"+list_type+">" + result + ""+list_type+">\n";
736 | return result;
737 | });
738 | } else {
739 | whole_list = /(\n\n|^\n?)(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/g;
740 | text = text.replace(whole_list,function(wholeMatch,m1,m2,m3) {
741 | var runup = m1;
742 | var list = m2;
743 |
744 | var list_type = (m3.search(/[*+-]/g)>-1) ? "ul" : "ol";
745 | // Turn double returns into triple returns, so that we can make a
746 | // paragraph for the last item in a list, if necessary:
747 | var list = list.replace(/\n{2,}/g,"\n\n\n");;
748 | var result = _ProcessListItems(list);
749 | result = runup + "<"+list_type+">\n" + result + ""+list_type+">\n";
750 | return result;
751 | });
752 | }
753 |
754 | // attacklab: strip sentinel
755 | text = text.replace(/~0/,"");
756 |
757 | return text;
758 | }
759 |
760 | _ProcessListItems = function(list_str) {
761 | //
762 | // Process the contents of a single ordered or unordered list, splitting it
763 | // into individual list items.
764 | //
765 | // The $g_list_level global keeps track of when we're inside a list.
766 | // Each time we enter a list, we increment it; when we leave a list,
767 | // we decrement. If it's zero, we're not in a list anymore.
768 | //
769 | // We do this because when we're not inside a list, we want to treat
770 | // something like this:
771 | //
772 | // I recommend upgrading to version
773 | // 8. Oops, now this line is treated
774 | // as a sub-list.
775 | //
776 | // As a single paragraph, despite the fact that the second line starts
777 | // with a digit-period-space sequence.
778 | //
779 | // Whereas when we're inside a list (or sub-list), that line will be
780 | // treated as the start of a sub-list. What a kludge, huh? This is
781 | // an aspect of Markdown's syntax that's hard to parse perfectly
782 | // without resorting to mind-reading. Perhaps the solution is to
783 | // change the syntax rules such that sub-lists must start with a
784 | // starting cardinal number; e.g. "1." or "a.".
785 |
786 | g_list_level++;
787 |
788 | // trim trailing blank lines:
789 | list_str = list_str.replace(/\n{2,}$/,"\n");
790 |
791 | // attacklab: add sentinel to emulate \z
792 | list_str += "~0";
793 |
794 | /*
795 | list_str = list_str.replace(/
796 | (\n)? // leading line = $1
797 | (^[ \t]*) // leading whitespace = $2
798 | ([*+-]|\d+[.]) [ \t]+ // list marker = $3
799 | ([^\r]+? // list item text = $4
800 | (\n{1,2}))
801 | (?= \n* (~0 | \2 ([*+-]|\d+[.]) [ \t]+))
802 | /gm, function(){...});
803 | */
804 | list_str = list_str.replace(/(\n)?(^[ \t]*)([*+-]|\d+[.])[ \t]+([^\r]+?(\n{1,2}))(?=\n*(~0|\2([*+-]|\d+[.])[ \t]+))/gm,
805 | function(wholeMatch,m1,m2,m3,m4){
806 | var item = m4;
807 | var leading_line = m1;
808 | var leading_space = m2;
809 |
810 | if (leading_line || (item.search(/\n{2,}/)>-1)) {
811 | item = _RunBlockGamut(_Outdent(item));
812 | }
813 | else {
814 | // Recursion for sub-lists:
815 | item = _DoLists(_Outdent(item));
816 | item = item.replace(/\n$/,""); // chomp(item)
817 | item = _RunSpanGamut(item);
818 | }
819 |
820 | return "" + item + " \n";
821 | }
822 | );
823 |
824 | // attacklab: strip sentinel
825 | list_str = list_str.replace(/~0/g,"");
826 |
827 | g_list_level--;
828 | return list_str;
829 | }
830 |
831 |
832 | var _DoCodeBlocks = function(text) {
833 | //
834 | // Process Markdown `` blocks.
835 | //
836 |
837 | /*
838 | text = text.replace(text,
839 | /(?:\n\n|^)
840 | ( // $1 = the code block -- one or more lines, starting with a space/tab
841 | (?:
842 | (?:[ ]{4}|\t) // Lines must start with a tab or a tab-width of spaces - attacklab: g_tab_width
843 | .*\n+
844 | )+
845 | )
846 | (\n*[ ]{0,3}[^ \t\n]|(?=~0)) // attacklab: g_tab_width
847 | /g,function(){...});
848 | */
849 |
850 | // attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug
851 | text += "~0";
852 |
853 | text = text.replace(/(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=~0))/g,
854 | function(wholeMatch,m1,m2) {
855 | var codeblock = m1;
856 | var nextChar = m2;
857 |
858 | codeblock = _EncodeCode( _Outdent(codeblock));
859 | codeblock = _Detab(codeblock);
860 | codeblock = codeblock.replace(/^\n+/g,""); // trim leading newlines
861 | codeblock = codeblock.replace(/\n+$/g,""); // trim trailing whitespace
862 |
863 | codeblock = "" + codeblock + "\n
";
864 |
865 | return hashBlock(codeblock) + nextChar;
866 | }
867 | );
868 |
869 | // attacklab: strip sentinel
870 | text = text.replace(/~0/,"");
871 |
872 | return text;
873 | }
874 |
875 | var hashBlock = function(text) {
876 | text = text.replace(/(^\n+|\n+$)/g,"");
877 | return "\n\n~K" + (g_html_blocks.push(text)-1) + "K\n\n";
878 | }
879 |
880 |
881 | var _DoCodeSpans = function(text) {
882 | //
883 | // * Backtick quotes are used for spans.
884 | //
885 | // * You can use multiple backticks as the delimiters if you want to
886 | // include literal backticks in the code span. So, this input:
887 | //
888 | // Just type ``foo `bar` baz`` at the prompt.
889 | //
890 | // Will translate to:
891 | //
892 | // Just type foo `bar` baz at the prompt.
893 | //
894 | // There's no arbitrary limit to the number of backticks you
895 | // can use as delimters. If you need three consecutive backticks
896 | // in your code, use four for delimiters, etc.
897 | //
898 | // * You can use spaces to get literal backticks at the edges:
899 | //
900 | // ... type `` `bar` `` ...
901 | //
902 | // Turns to:
903 | //
904 | // ... type `bar` ...
905 | //
906 |
907 | /*
908 | text = text.replace(/
909 | (^|[^\\]) // Character before opening ` can't be a backslash
910 | (`+) // $2 = Opening run of `
911 | ( // $3 = The code block
912 | [^\r]*?
913 | [^`] // attacklab: work around lack of lookbehind
914 | )
915 | \2 // Matching closer
916 | (?!`)
917 | /gm, function(){...});
918 | */
919 |
920 | text = text.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm,
921 | function(wholeMatch,m1,m2,m3,m4) {
922 | var c = m3;
923 | c = c.replace(/^([ \t]*)/g,""); // leading whitespace
924 | c = c.replace(/[ \t]*$/g,""); // trailing whitespace
925 | c = _EncodeCode(c);
926 | return m1+""+c+"";
927 | });
928 |
929 | return text;
930 | }
931 |
932 |
933 | var _EncodeCode = function(text) {
934 | //
935 | // Encode/escape certain characters inside Markdown code runs.
936 | // The point is that in code, these characters are literals,
937 | // and lose their special Markdown meanings.
938 | //
939 | // Encode all ampersands; HTML entities are not
940 | // entities within a Markdown code span.
941 | text = text.replace(/&/g,"&");
942 |
943 | // Do the angle bracket song and dance:
944 | text = text.replace(//g,">");
946 |
947 | // Now, escape characters that are magic in Markdown:
948 | text = escapeCharacters(text,"\*_{}[]\\",false);
949 |
950 | // jj the line above breaks this:
951 | //---
952 |
953 | //* Item
954 |
955 | // 1. Subitem
956 |
957 | // special char: *
958 | //---
959 |
960 | return text;
961 | }
962 |
963 |
964 | var _DoItalicsAndBold = function(text) {
965 |
966 | // must go first:
967 | text = text.replace(/(\*\*|__)(?=\S)([^\r]*?\S[*_]*)\1/g,
968 | "$2");
969 |
970 | text = text.replace(/(\*|_)(?=\S)([^\r]*?\S)\1/g,
971 | "$2");
972 |
973 | return text;
974 | }
975 |
976 |
977 | var _DoBlockQuotes = function(text) {
978 |
979 | /*
980 | text = text.replace(/
981 | ( // Wrap whole match in $1
982 | (
983 | ^[ \t]*>[ \t]? // '>' at the start of a line
984 | .+\n // rest of the first line
985 | (.+\n)* // subsequent consecutive lines
986 | \n* // blanks
987 | )+
988 | )
989 | /gm, function(){...});
990 | */
991 |
992 | text = text.replace(/((^[ \t]*>[ \t]?.+\n(.+\n)*\n*)+)/gm,
993 | function(wholeMatch,m1) {
994 | var bq = m1;
995 |
996 | // attacklab: hack around Konqueror 3.5.4 bug:
997 | // "----------bug".replace(/^-/g,"") == "bug"
998 |
999 | bq = bq.replace(/^[ \t]*>[ \t]?/gm,"~0"); // trim one level of quoting
1000 |
1001 | // attacklab: clean up hack
1002 | bq = bq.replace(/~0/g,"");
1003 |
1004 | bq = bq.replace(/^[ \t]+$/gm,""); // trim whitespace-only lines
1005 | bq = _RunBlockGamut(bq); // recurse
1006 |
1007 | bq = bq.replace(/(^|\n)/g,"$1 ");
1008 | // These leading spaces screw with content, so we need to fix that:
1009 | bq = bq.replace(
1010 | /(\s*[^\r]+?<\/pre>)/gm,
1011 | function(wholeMatch,m1) {
1012 | var pre = m1;
1013 | // attacklab: hack around Konqueror 3.5.4 bug:
1014 | pre = pre.replace(/^ /mg,"~0");
1015 | pre = pre.replace(/~0/g,"");
1016 | return pre;
1017 | });
1018 |
1019 | return hashBlock("\n" + bq + "\n
");
1020 | });
1021 | return text;
1022 | }
1023 |
1024 |
1025 | var _FormParagraphs = function(text) {
1026 | //
1027 | // Params:
1028 | // $text - string to process with html tags
1029 | //
1030 |
1031 | // Strip leading and trailing lines:
1032 | text = text.replace(/^\n+/g,"");
1033 | text = text.replace(/\n+$/g,"");
1034 |
1035 | var grafs = text.split(/\n{2,}/g);
1036 | var grafsOut = new Array();
1037 |
1038 | //
1039 | // Wrap
tags.
1040 | //
1041 | var end = grafs.length;
1042 | for (var i=0; i= 0) {
1047 | grafsOut.push(str);
1048 | }
1049 | else if (str.search(/\S/) >= 0) {
1050 | str = _RunSpanGamut(str);
1051 | str = str.replace(/^([ \t]*)/g,"");
1052 | str += "
"
1053 | grafsOut.push(str);
1054 | }
1055 |
1056 | }
1057 |
1058 | //
1059 | // Unhashify HTML blocks
1060 | //
1061 | end = grafsOut.length;
1062 | for (var i=0; i= 0) {
1065 | var blockText = g_html_blocks[RegExp.$1];
1066 | blockText = blockText.replace(/\$/g,"$$$$"); // Escape any dollar signs
1067 | grafsOut[i] = grafsOut[i].replace(/~K\d+K/,blockText);
1068 | }
1069 | }
1070 |
1071 | return grafsOut.join("\n\n");
1072 | }
1073 |
1074 |
1075 | var _EncodeAmpsAndAngles = function(text) {
1076 | // Smart processing for ampersands and angle brackets that need to be encoded.
1077 |
1078 | // Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin:
1079 | // http://bumppo.net/projects/amputator/
1080 | text = text.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g,"&");
1081 |
1082 | // Encode naked <'s
1083 | text = text.replace(/<(?![a-z\/?\$!])/gi,"<");
1084 |
1085 | return text;
1086 | }
1087 |
1088 |
1089 | var _EncodeBackslashEscapes = function(text) {
1090 | //
1091 | // Parameter: String.
1092 | // Returns: The string, with after processing the following backslash
1093 | // escape sequences.
1094 | //
1095 |
1096 | // attacklab: The polite way to do this is with the new
1097 | // escapeCharacters() function:
1098 | //
1099 | // text = escapeCharacters(text,"\\",true);
1100 | // text = escapeCharacters(text,"`*_{}[]()>#+-.!",true);
1101 | //
1102 | // ...but we're sidestepping its use of the (slow) RegExp constructor
1103 | // as an optimization for Firefox. This function gets called a LOT.
1104 |
1105 | text = text.replace(/\\(\\)/g,escapeCharacters_callback);
1106 | text = text.replace(/\\([`*_{}\[\]()>#+-.!])/g,escapeCharacters_callback);
1107 | return text;
1108 | }
1109 |
1110 |
1111 | var _DoAutoLinks = function(text) {
1112 |
1113 | text = text.replace(/<((https?|ftp|dict):[^'">\s]+)>/gi,"$1");
1114 |
1115 | // Email addresses:
1116 |
1117 | /*
1118 | text = text.replace(/
1119 | <
1120 | (?:mailto:)?
1121 | (
1122 | [-.\w]+
1123 | \@
1124 | [-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+
1125 | )
1126 | >
1127 | /gi, _DoAutoLinks_callback());
1128 | */
1129 | text = text.replace(/<(?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi,
1130 | function(wholeMatch,m1) {
1131 | return _EncodeEmailAddress( _UnescapeSpecialChars(m1) );
1132 | }
1133 | );
1134 |
1135 | return text;
1136 | }
1137 |
1138 |
1139 | var _EncodeEmailAddress = function(addr) {
1140 | //
1141 | // Input: an email address, e.g. "foo@example.com"
1142 | //
1143 | // Output: the email address as a mailto link, with each character
1144 | // of the address encoded as either a decimal or hex entity, in
1145 | // the hopes of foiling most address harvesting spam bots. E.g.:
1146 | //
1147 | // foo
1149 | // @example.com
1150 | //
1151 | // Based on a filter by Matthew Wickline, posted to the BBEdit-Talk
1152 | // mailing list:
1153 | //
1154 |
1155 | // attacklab: why can't javascript speak hex?
1156 | function char2hex(ch) {
1157 | var hexDigits = '0123456789ABCDEF';
1158 | var dec = ch.charCodeAt(0);
1159 | return(hexDigits.charAt(dec>>4) + hexDigits.charAt(dec&15));
1160 | }
1161 |
1162 | var encode = [
1163 | function(ch){return ""+ch.charCodeAt(0)+";";},
1164 | function(ch){return ""+char2hex(ch)+";";},
1165 | function(ch){return ch;}
1166 | ];
1167 |
1168 | addr = "mailto:" + addr;
1169 |
1170 | addr = addr.replace(/./g, function(ch) {
1171 | if (ch == "@") {
1172 | // this *must* be encoded. I insist.
1173 | ch = encode[Math.floor(Math.random()*2)](ch);
1174 | } else if (ch !=":") {
1175 | // leave ':' alone (to spot mailto: later)
1176 | var r = Math.random();
1177 | // roughly 10% raw, 45% hex, 45% dec
1178 | ch = (
1179 | r > .9 ? encode[2](ch) :
1180 | r > .45 ? encode[1](ch) :
1181 | encode[0](ch)
1182 | );
1183 | }
1184 | return ch;
1185 | });
1186 |
1187 | addr = "" + addr + "";
1188 | addr = addr.replace(/">.+:/g,"\">"); // strip the mailto: from the visible part
1189 |
1190 | return addr;
1191 | }
1192 |
1193 |
1194 | var _UnescapeSpecialChars = function(text) {
1195 | //
1196 | // Swap back in all the special characters we've hidden.
1197 | //
1198 | text = text.replace(/~E(\d+)E/g,
1199 | function(wholeMatch,m1) {
1200 | var charCodeToReplace = parseInt(m1);
1201 | return String.fromCharCode(charCodeToReplace);
1202 | }
1203 | );
1204 | return text;
1205 | }
1206 |
1207 |
1208 | var _Outdent = function(text) {
1209 | //
1210 | // Remove one level of line-leading tabs or spaces
1211 | //
1212 |
1213 | // attacklab: hack around Konqueror 3.5.4 bug:
1214 | // "----------bug".replace(/^-/g,"") == "bug"
1215 |
1216 | text = text.replace(/^(\t|[ ]{1,4})/gm,"~0"); // attacklab: g_tab_width
1217 |
1218 | // attacklab: clean up hack
1219 | text = text.replace(/~0/g,"")
1220 |
1221 | return text;
1222 | }
1223 |
1224 | var _Detab = function(text) {
1225 | // attacklab: Detab's completely rewritten for speed.
1226 | // In perl we could fix it by anchoring the regexp with \G.
1227 | // In javascript we're less fortunate.
1228 |
1229 | // expand first n-1 tabs
1230 | text = text.replace(/\t(?=\t)/g," "); // attacklab: g_tab_width
1231 |
1232 | // replace the nth with two sentinels
1233 | text = text.replace(/\t/g,"~A~B");
1234 |
1235 | // use the sentinel to anchor our regex so it doesn't explode
1236 | text = text.replace(/~B(.+?)~A/g,
1237 | function(wholeMatch,m1,m2) {
1238 | var leadingText = m1;
1239 | var numSpaces = 4 - leadingText.length % 4; // attacklab: g_tab_width
1240 |
1241 | // there *must* be a better way to do this:
1242 | for (var i=0; i