Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Somewhat working step 1.

  • Loading branch information...
commit c6255e13c2e097c849d7f675a565e5d385865fb6 0 parents
@dustin dustin authored
Showing with 6,917 additions and 0 deletions.
  1. +6 −0 .couchappignore
  2. +11 −0 .gitignore
  3. +22 −0 _attachments/index.html
  4. +75 −0 _attachments/style/main.css
  5. +1 −0  _id
  6. +4 −0 couchapp.json
  7. +20 −0 evently/items/_changes/after.js
  8. +8 −0 evently/items/_changes/data.js
  9. +16 −0 evently/items/_changes/mustache.html
  10. +6 −0 evently/items/_changes/query.json
  11. +14 −0 evently/profile/profileReady/mustache.html
  12. +12 −0 evently/profile/profileReady/selectors/form/submit.js
  13. +1 −0  language
  14. +2,858 −0 tools/feedparser.py
  15. +70 −0 tools/fetch.py
  16. +4 −0 updates/set_state.js
  17. +239 −0 vendor/couchapp/_attachments/jquery.couch.app.js
  18. +90 −0 vendor/couchapp/_attachments/jquery.couch.app.util.js
  19. +399 −0 vendor/couchapp/_attachments/jquery.evently.js
  20. +346 −0 vendor/couchapp/_attachments/jquery.mustache.js
  21. +174 −0 vendor/couchapp/_attachments/jquery.pathbinder.js
  22. +17 −0 vendor/couchapp/_attachments/loader.js
  23. +22 −0 vendor/couchapp/evently/README.md
  24. +16 −0 vendor/couchapp/evently/account/_init.js
  25. +1 −0  vendor/couchapp/evently/account/adminParty/mustache.html
  26. +10 −0 vendor/couchapp/evently/account/doLogin.js
  27. +8 −0 vendor/couchapp/evently/account/doLogout.js
  28. +10 −0 vendor/couchapp/evently/account/doSignup.js
  29. +4 −0 vendor/couchapp/evently/account/loggedIn/after.js
  30. +7 −0 vendor/couchapp/evently/account/loggedIn/data.js
  31. +4 −0 vendor/couchapp/evently/account/loggedIn/mustache.html
  32. +3 −0  vendor/couchapp/evently/account/loggedIn/selectors.json
  33. +1 −0  vendor/couchapp/evently/account/loggedOut/mustache.html
  34. +4 −0 vendor/couchapp/evently/account/loggedOut/selectors.json
  35. +3 −0  vendor/couchapp/evently/account/loginForm/after.js
  36. +6 −0 vendor/couchapp/evently/account/loginForm/mustache.html
  37. +6 −0 vendor/couchapp/evently/account/loginForm/selectors/form/submit.js
  38. +3 −0  vendor/couchapp/evently/account/signupForm/after.js
  39. +6 −0 vendor/couchapp/evently/account/signupForm/mustache.html
  40. +6 −0 vendor/couchapp/evently/account/signupForm/selectors/form/submit.js
  41. +21 −0 vendor/couchapp/evently/profile/loggedIn.js
  42. +3 −0  vendor/couchapp/evently/profile/loggedOut/after.js
  43. +1 −0  vendor/couchapp/evently/profile/loggedOut/mustache.html
  44. +3 −0  vendor/couchapp/evently/profile/noProfile/data.js
  45. +11 −0 vendor/couchapp/evently/profile/noProfile/mustache.html
  46. +36 −0 vendor/couchapp/evently/profile/noProfile/selectors/form/submit.js
  47. +3 −0  vendor/couchapp/evently/profile/profileReady/after.js
  48. +3 −0  vendor/couchapp/evently/profile/profileReady/data.js
  49. +8 −0 vendor/couchapp/evently/profile/profileReady/mustache.html
  50. +39 −0 vendor/couchapp/lib/atom.js
  51. +25 −0 vendor/couchapp/lib/cache.js
  52. +20 −0 vendor/couchapp/lib/code.js
  53. +121 −0 vendor/couchapp/lib/docform.js
  54. +18 −0 vendor/couchapp/lib/linkup.js
  55. +13 −0 vendor/couchapp/lib/list.js
  56. +1,300 −0 vendor/couchapp/lib/markdown.js
  57. +261 −0 vendor/couchapp/lib/md5.js
  58. +339 −0 vendor/couchapp/lib/mustache.js
  59. +87 −0 vendor/couchapp/lib/path.js
  60. +8 −0 vendor/couchapp/lib/redirect.js
  61. +21 −0 vendor/couchapp/lib/utils.js
  62. +53 −0 vendor/couchapp/lib/validate.js
  63. +5 −0 vendor/couchapp/metadata.json
  64. +5 −0 views/recent-items/map.js
6 .couchappignore
@@ -0,0 +1,6 @@
+[
+ ".*\\.swp$",
+ ".*~$",
+ "^compass.*",
+ "^tools.*"
+]
11 .gitignore
@@ -0,0 +1,11 @@
+#*
+*#
+*.pyc
+*~
+.#*
+.DS_Store
+/.couchapprc
+/_attachments/style/ie.css
+/_attachments/style/print.css
+/_attachments/style/screen.css
+/compass/.sass-cache/
22 _attachments/index.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>README</title>
+ <link rel="stylesheet" href="style/main.css" type="text/css">
+ </head>
+ <body>
+ <h1>README</h1>
+
+ <div id="items"></div>
+
+ <div id="sidebar">
+ <p>Some tools here would be nice.</p>
+ </div>
+ </body>
+ <script src="vendor/couchapp/loader.js"></script>
+ <script type="text/javascript" charset="utf-8">
+ $.couch.app(function(app) {
+ $("#items").evently("items", app);
+ });
+ </script>
+</html>
75 _attachments/style/main.css
@@ -0,0 +1,75 @@
+/* add styles here */
+
+body {
+ font:1em Helvetica, sans-serif;
+ padding:4px;
+}
+
+h1 {
+ margin-top:0;
+}
+
+#account {
+ float:right;
+}
+
+#profile {
+ border:4px solid #edd;
+ background:#fee;
+ padding:8px;
+ margin-bottom:8px;
+}
+
+#items {
+ border:4px solid #dde;
+ background:#eef;
+ padding:8px;
+ width:60%;
+ float:left;
+}
+
+#sidebar {
+ border:4px solid #dfd;
+ padding:8px;
+ float:right;
+ width:30%;
+}
+
+#items li {
+ border:4px solid #f5f5ff;
+ background:#fff;
+ padding:8px;
+ margin:4px 0;
+}
+
+form {
+ padding:4px;
+ margin:6px;
+ background-color:#ddd;
+}
+
+div.avatar {
+ padding:2px;
+ padding-bottom:0;
+ margin-right:4px;
+ float:left;
+ font-size:0.78em;
+ width : 60px;
+ height : 60px;
+ text-align: center;
+}
+
+div.avatar .name {
+ padding-top:2px;
+}
+
+div.avatar img {
+ margin:0 auto;
+ padding:0;
+ width : 40px;
+ height : 40px;
+}
+
+#items ul {
+ list-style: none;
+}
1  _id
@@ -0,0 +1 @@
+_design/app
4 couchapp.json
@@ -0,0 +1,4 @@
+{
+ "name": "Name of your CouchApp",
+ "description": "CouchApp"
+}
20 evently/items/_changes/after.js
@@ -0,0 +1,20 @@
+function(me, args) {
+ var app = $$(this).app;
+ var baseUri = app.db.uri;
+ var ddoc = app.ddoc._id;
+
+ $(".statechange").each(function(a, el) {
+ var parts = el.id.split('-');
+ $(el).click(function() {
+ $.ajax({type: 'POST',
+ url: baseUri + ddoc + "/_update/set_state/" + parts[1],
+ data: 'new_state=' + encodeURIComponent(parts[0]),
+ dataType: "json",
+ complete: function(res) {
+ console.log("Result", res);
+ }});
+
+ return false;
+ });
+ });
+}
8 evently/items/_changes/data.js
@@ -0,0 +1,8 @@
+function(data) {
+ var p;
+ return {
+ items : data.rows.map(function(r) {
+ return r.doc;
+ })
+ };
+};
16 evently/items/_changes/mustache.html
@@ -0,0 +1,16 @@
+<h1>Recent Messages</h1>
+<ul id="items">
+ {{#items}}
+ <li>
+ <div class="header">
+ <img src="{{rm_favicon}}" alt="favicon" /><span class="name">{{author}}</span>
+ - <a href="{{link}}" class="title">{{title}}</a>
+ [<a href="#" class="statechange" id="uninteresting-{{_id}}" title="uninteresting">U</a>,
+ <a href="#" class="statechange" id="important-{{_id}}" title="important">!</a>]
+ </div>
+ <div id="message">{{{summary}}}</div>
+ <div style="clear:left;"></div>
+ </li>
+ {{/items}}
+</ul>
+
6 evently/items/_changes/query.json
@@ -0,0 +1,6 @@
+{
+ "view" : "recent-items",
+ "descending" : "true",
+ "limit" : 50,
+ "include_docs": "true"
+}
14 evently/profile/profileReady/mustache.html
@@ -0,0 +1,14 @@
+<p>Most applications will customize this template (<tt>ddoc.evently.profile.profileReady.mustache</tt>) for user input.</p>
+
+<div class="avatar">
+ {{#gravatar_url}}<img src="{{gravatar_url}}"/>{{/gravatar_url}}
+ <div class="name">
+ {{name}}
+ </div>
+</div>
+
+<form>
+ <label>New message from {{nickname}}: <input type="text" name="message" size=60 value=""></label>
+</form>
+
+<div style="clear:left;"></div>
12 evently/profile/profileReady/selectors/form/submit.js
@@ -0,0 +1,12 @@
+function() {
+ var form = $(this);
+ var fdoc = form.serializeObject();
+ fdoc.created_at = new Date();
+ fdoc.profile = $$("#profile").profile;
+ $$(this).app.db.saveDoc(fdoc, {
+ success : function() {
+ form[0].reset();
+ }
+ });
+ return false;
+};
1  language
@@ -0,0 +1 @@
+javascript
2,858 tools/feedparser.py
2,858 additions, 0 deletions not shown
70 tools/fetch.py
@@ -0,0 +1,70 @@
+#!/usr/bin/env python
+
+import os
+import sys
+import time
+import json
+import signal
+import urllib
+import base64
+import hashlib
+import traceback
+
+import couchdb
+
+import feedparser
+
+SERVER = os.getenv("COUCHDB") or 'http://127.0.0.1:5984/'
+
+DB = couchdb.Server(SERVER)['readme']
+
+ISO8601 = "%Y-%m-%dT%H:%M:%S"
+
+def is_plain(x):
+ return type(x) in [float, int, str, unicode]
+
+def cleanupThing(thing):
+ if isinstance(thing, dict):
+ rv = {}
+ for k, v in thing.iteritems():
+ newv = cleanupThing(v)
+ if newv:
+ rv[k] = newv
+ return rv
+ elif isinstance(thing, list):
+ return [cleanupThing(x) for x in thing]
+ elif is_plain(thing):
+ return thing
+ else:
+ pass
+ # print "NOT CONVERTING: %s (%s)" % (thing, type(thing))
+
+def favicon(src):
+ return 'http://www.google.com/s2/favicons?domain=' + src.split('/')[2]
+
+def handle(src, e):
+
+ doc = cleanupThing(e)
+ doc['_id'] = hashlib.md5(doc['id'].encode('utf-8')).hexdigest()
+ doc['rm_src'] = src
+ doc['rm_favicon'] = favicon(src)
+ doc['rm_state'] = 'unread'
+ doc['rm_updated'] = time.strftime("%Y-%m-%dT%H:%M:%S", e.updated_parsed)
+
+ return doc
+
+if __name__ == '__main__':
+ for src in sys.argv[1:]:
+ signal.alarm(10)
+ f = feedparser.parse(src)
+
+ docs = []
+
+ for e in f.entries:
+ try:
+ signal.alarm(30)
+ docs.append(handle(src, e))
+ except:
+ traceback.print_exc()
+
+ DB.update(docs)
4 updates/set_state.js
@@ -0,0 +1,4 @@
+function(doc, req) {
+ doc['rm_state'] = req.form.new_state;
+ return [doc, 'State set to ' + doc['rm_state']];
+}
239 vendor/couchapp/_attachments/jquery.couch.app.js
@@ -0,0 +1,239 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+// Usage: The passed in function is called when the page is ready.
+// CouchApp passes in the app object, which takes care of linking to
+// the proper database, and provides access to the CouchApp helpers.
+// $.couch.app(function(app) {
+// app.db.view(...)
+// ...
+// });
+
+(function($) {
+
+ function Design(db, name, code) {
+ this.doc_id = "_design/"+name;
+ if (code) {
+ this.code_path = this.doc_id + "/" + code;
+ } else {
+ this.code_path = this.doc_id;
+ }
+ this.view = function(view, opts) {
+ if (view.indexOf('/') === -1) {
+ db.view(name+'/'+view, opts);
+ } else {
+ db.view(view, opts);
+ }
+ };
+ this.list = function(list, view, opts) {
+ db.list(name+'/'+list, view, opts);
+ };
+ }
+
+ function docForm() { alert("docForm has been moved to vendor/couchapp/lib/docForm.js, use app.require to load") };
+
+ function resolveModule(path, names, parents, current) {
+ parents = parents || [];
+ if (names.length === 0) {
+ if (typeof current != "string") {
+ throw ["error","invalid_require_path",
+ 'Must require a JavaScript string, not: '+(typeof current)];
+ }
+ return [current, parents];
+ }
+ var n = names.shift();
+ if (n == '..') {
+ parents.pop();
+ var pp = parents.pop();
+ if (!pp) {
+ throw ["error", "invalid_require_path", path];
+ }
+ return resolveModule(path, names, parents, pp);
+ } else if (n == '.') {
+ var p = parents.pop();
+ if (!p) {
+ throw ["error", "invalid_require_path", path];
+ }
+ return resolveModule(path, names, parents, p);
+ } else {
+ parents = [];
+ }
+ if (!current[n]) {
+ throw ["error", "invalid_require_path", path];
+ }
+ parents.push(current);
+ return resolveModule(path, names, parents, current[n]);
+ }
+
+ function makeRequire(ddoc) {
+ var moduleCache = [];
+ function getCachedModule(name, parents) {
+ var key, i, len = moduleCache.length;
+ for (i=0;i<len;++i) {
+ key = moduleCache[i].key;
+ if (key[0] === name && key[1] === parents) {
+ return moduleCache[i].module;
+ }
+ }
+ return null;
+ }
+ function setCachedModule(name, parents, module) {
+ moduleCache.push({ key: [name, parents], module: module });
+ }
+ var require = function (name, parents) {
+ var cachedModule = getCachedModule(name, parents);
+ if (cachedModule !== null) {
+ return cachedModule;
+ }
+ var exports = {};
+ var resolved = resolveModule(name, name.split('/'), parents, ddoc);
+ var source = resolved[0];
+ parents = resolved[1];
+ var s = "var func = function (exports, require) { " + source + " };";
+ try {
+ eval(s);
+ func.apply(ddoc, [exports, function(name) {return require(name, parents)}]);
+ } catch(e) {
+ throw ["error","compilation_error","Module require('"+name+"') raised error "+e.toSource()];
+ }
+ setCachedModule(name, parents, exports);
+ return exports;
+ }
+ return require;
+ };
+
+ function mockReq() {
+ var p = document.location.pathname.split('/'),
+ qs = document.location.search.replace(/^\?/,'').split('&'),
+ q = {};
+ qs.forEach(function(param) {
+ var ps = param.split('='),
+ k = decodeURIComponent(ps[0]),
+ v = decodeURIComponent(ps[1]);
+ if (["startkey", "endkey", "key"].indexOf(k) != -1) {
+ q[k] = JSON.parse(v);
+ } else {
+ q[k] = v;
+ }
+ });
+ p.shift();
+ return {
+ path : p,
+ query : q
+ };
+ };
+
+ $.couch.app = $.couch.app || function(appFun, opts) {
+ opts = opts || {};
+ var urlPrefix = (opts.urlPrefix || ""),
+ index = urlPrefix.split('/').length,
+ fragments = unescape(document.location.href).split('/'),
+ dbname = opts.db || fragments[index + 2],
+ dname = opts.design || fragments[index + 4];
+ $.couch.urlPrefix = urlPrefix;
+ var db = $.couch.db(dbname),
+ design = new Design(db, dname, opts.load_path);
+ var appExports = $.extend({
+ db : db,
+ design : design,
+ view : design.view,
+ list : design.list,
+ docForm : docForm, // deprecated
+ req : mockReq()
+ }, $.couch.app.app);
+ function handleDDoc(ddoc) {
+ if (ddoc) {
+ appExports.ddoc = ddoc;
+ appExports.require = makeRequire(ddoc);
+ }
+ appFun.apply(appExports, [appExports]);
+ }
+ if (opts.ddoc) {
+ // allow the ddoc to be embedded in the html
+ // to avoid a second http request
+ $.couch.app.ddocs[design.doc_id] = opts.ddoc;
+ }
+ if ($.couch.app.ddocs[design.doc_id]) {
+ $(function() {handleDDoc($.couch.app.ddocs[design.doc_id])});
+ } else {
+ // only open 1 connection for this ddoc
+ if ($.couch.app.ddoc_handlers[design.doc_id]) {
+ // we are already fetching, just wait
+ $.couch.app.ddoc_handlers[design.doc_id].push(handleDDoc);
+ } else {
+ $.couch.app.ddoc_handlers[design.doc_id] = [handleDDoc];
+ // use getDbProperty to bypass %2F encoding on _show/app
+ db.getDbProperty(design.code_path, {
+ success : function(doc) {
+ $.couch.app.ddocs[design.doc_id] = doc;
+ $.couch.app.ddoc_handlers[design.doc_id].forEach(function(h) {
+ $(function() {h(doc)});
+ });
+ $.couch.app.ddoc_handlers[design.doc_id] = null;
+ },
+ error : function() {
+ $.couch.app.ddoc_handlers[design.doc_id].forEach(function(h) {
+ $(function() {h()});
+ });
+ $.couch.app.ddoc_handlers[design.doc_id] = null;
+ }
+ });
+ }
+ }
+ };
+ $.couch.app.ddocs = {};
+ $.couch.app.ddoc_handlers = {};
+ // legacy support. $.CouchApp is deprecated, please use $.couch.app
+ $.CouchApp = $.couch.app;
+})(jQuery);
+
+// JavaScript 1.6 compatibility functions that are missing from IE7/IE8
+
+if (!Array.prototype.forEach)
+{
+ Array.prototype.forEach = function(fun /*, thisp*/)
+ {
+ var len = this.length >>> 0;
+ if (typeof fun != "function")
+ throw new TypeError();
+
+ var thisp = arguments[1];
+ for (var i = 0; i < len; i++)
+ {
+ if (i in this)
+ fun.call(thisp, this[i], i, this);
+ }
+ };
+}
+
+if (!Array.prototype.indexOf)
+{
+ Array.prototype.indexOf = function(elt)
+ {
+ var len = this.length >>> 0;
+
+ var from = Number(arguments[1]) || 0;
+ from = (from < 0)
+ ? Math.ceil(from)
+ : Math.floor(from);
+ if (from < 0)
+ from += len;
+
+ for (; from < len; from++)
+ {
+ if (from in this &&
+ this[from] === elt)
+ return from;
+ }
+ return -1;
+ };
+}
90 vendor/couchapp/_attachments/jquery.couch.app.util.js
@@ -0,0 +1,90 @@
+$.log = function(m) {
+ if (window && window.console && window.console.log) {
+ window.console.log(arguments.length == 1 ? m : arguments);
+ }
+};
+
+// http://stackoverflow.com/questions/1184624/serialize-form-to-json-with-jquery/1186309#1186309
+$.fn.serializeObject = function() {
+ var o = {};
+ var a = this.serializeArray();
+ $.each(a, function() {
+ if (o[this.name]) {
+ if (!o[this.name].push) {
+ o[this.name] = [o[this.name]];
+ }
+ o[this.name].push(this.value || '');
+ } else {
+ o[this.name] = this.value || '';
+ }
+ });
+ return o;
+};
+
+// todo remove this crap
+function escapeHTML(st) {
+ return(
+ st && st.replace(/&/g,'&amp;').
+ replace(/>/g,'&gt;').
+ replace(/</g,'&lt;').
+ replace(/"/g,'&quot;')
+ );
+};
+
+function safeHTML(st, len) {
+ return st ? escapeHTML(st.substring(0,len)) : '';
+}
+
+// todo this should take a replacement template
+$.linkify = function(body) {
+ return body.replace(/((ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?)/gi,function(a) {
+ return '<a target="_blank" href="'+a+'">'+a+'</a>';
+ }).replace(/\@([\w\-]+)/g,function(user,name) {
+ return '<a href="#/mentions/'+encodeURIComponent(name.toLowerCase())+'">'+user+'</a>';
+ }).replace(/\#([\w\-\.]+)/g,function(word,tag) {
+ return '<a href="#/tags/'+encodeURIComponent(tag.toLowerCase())+'">'+word+'</a>';
+ });
+};
+
+$.fn.prettyDate = function() {
+ $(this).each(function() {
+ var string, title = $(this).attr("title");
+ if (title) {
+ string = $.prettyDate(title);
+ } else {
+ string = $.prettyDate($(this).text());
+ }
+ $(this).text(string);
+ });
+};
+
+$.prettyDate = function(time){
+
+ var date = new Date(time.replace(/-/g,"/").replace("T", " ").replace("Z", " +0000").replace(/(\d*\:\d*:\d*)\.\d*/g,"$1")),
+ diff = (((new Date()).getTime() - date.getTime()) / 1000),
+ day_diff = Math.floor(diff / 86400);
+
+ if (isNaN(day_diff)) return time;
+
+ return day_diff < 1 && (
+ diff < 60 && "just now" ||
+ diff < 120 && "1 minute ago" ||
+ diff < 3600 && Math.floor( diff / 60 ) + " minutes ago" ||
+ diff < 7200 && "1 hour ago" ||
+ diff < 86400 && Math.floor( diff / 3600 ) + " hours ago") ||
+ day_diff == 1 && "yesterday" ||
+ day_diff < 21 && day_diff + " days ago" ||
+ day_diff < 45 && Math.ceil( day_diff / 7 ) + " weeks ago" ||
+ time;
+ // day_diff < 730 && Math.ceil( day_diff / 31 ) + " months ago" ||
+ // Math.ceil( day_diff / 365 ) + " years ago";
+};
+
+$.argsToArray = function(args) {
+ if (!args.callee) return args;
+ var array = [];
+ for (var i=0; i < args.length; i++) {
+ array.push(args[i]);
+ };
+ return array;
+}
399 vendor/couchapp/_attachments/jquery.evently.js
@@ -0,0 +1,399 @@
+// $$ inspired by @wycats: http://yehudakatz.com/2009/04/20/evented-programming-with-jquery/
+function $$(node) {
+ var data = $(node).data("$$");
+ if (data) {
+ return data;
+ } else {
+ data = {};
+ $(node).data("$$", data);
+ return data;
+ }
+};
+
+(function($) {
+ // utility functions used in the implementation
+
+ function forIn(obj, fun) {
+ var name;
+ for (name in obj) {
+ if (obj.hasOwnProperty(name)) {
+ fun(name, obj[name]);
+ }
+ }
+ };
+ $.forIn = forIn;
+ function funViaString(fun, hint) {
+ if (fun && fun.match && fun.match(/^function/)) {
+ eval("var f = "+fun);
+ if (typeof f == "function") {
+ return function() {
+ try {
+ return f.apply(this, arguments);
+ } catch(e) {
+ // IF YOU SEE AN ERROR HERE IT HAPPENED WHEN WE TRIED TO RUN YOUR FUNCTION
+ $.log({"message": "Error in evently function.", "error": e,
+ "src" : fun, "hint":hint});
+ throw(e);
+ }
+ };
+ }
+ }
+ return fun;
+ };
+
+ function runIfFun(me, fun, args) {
+ // if the field is a function, call it, bound to the widget
+ var f = funViaString(fun, me);
+ if (typeof f == "function") {
+ return f.apply(me, args);
+ } else {
+ return fun;
+ }
+ }
+
+ $.evently = {
+ connect : function(source, target, events) {
+ events.forEach(function(ev) {
+ $(source).bind(ev, function() {
+ var args = $.makeArray(arguments);
+ // remove the original event to keep from stacking args extra deep
+ // it would be nice if jquery had a way to pass the original
+ // event to the trigger method.
+ args.shift();
+ $(target).trigger(ev, args);
+ return false;
+ });
+ });
+ },
+ paths : [],
+ changesDBs : {},
+ changesOpts : {}
+ };
+
+ function extractFrom(name, evs) {
+ return evs[name];
+ };
+
+ function extractEvents(name, ddoc) {
+ // extract events from ddoc.evently and ddoc.vendor.*.evently
+ var events = [true, {}]
+ , vendor = ddoc.vendor || {}
+ , evently = ddoc.evently || {}
+ ;
+ $.forIn(vendor, function(k, v) {
+ if (v.evently && v.evently[name]) {
+ events.push(v.evently[name]);
+ }
+ });
+ if (evently[name]) {events.push(evently[name]);}
+ return $.extend.apply(null, events);
+ }
+
+ function extractPartials(ddoc) {
+ var partials = [true, {}]
+ , vendor = ddoc.vendor || {}
+ , evently = ddoc.evently || {}
+ ;
+ $.forIn(vendor, function(k, v) {
+ if (v.evently && v.evently._partials) {
+ partials.push(v.evently._partials);
+ }
+ });
+ if (evently._partials) {partials.push(evently._partials);}
+ return $.extend.apply(null, partials);
+ };
+
+ function applyCommon(events) {
+ if (events._common) {
+ $.forIn(events, function(k, v) {
+ events[k] = $.extend(true, {}, events._common, v);
+ });
+ delete events._common;
+ return events;
+ } else {
+ return events;
+ }
+ }
+
+ $.fn.evently = function(events, app, args) {
+ var elem = $(this);
+ // store the app on the element for later use
+ if (app) {
+ $$(elem).app = app;
+ }
+
+ if (typeof events == "string") {
+ events = extractEvents(events, app.ddoc);
+ }
+ events = applyCommon(events);
+ $$(elem).evently = events;
+ if (app && app.ddoc) {
+ $$(elem).partials = extractPartials(app.ddoc);
+ }
+ // setup the handlers onto elem
+ forIn(events, function(name, h) {
+ eventlyHandler(elem, name, h, args);
+ });
+
+ if (events._init) {
+ elem.trigger("_init", args);
+ }
+
+ if (app && events._changes) {
+ $("body").bind("evently-changes-"+app.db.name, function() {
+ elem.trigger("_changes");
+ });
+ followChanges(app);
+ elem.trigger("_changes");
+ }
+ };
+
+ // eventlyHandler applies the user's handler (h) to the
+ // elem, bound to trigger based on name.
+ function eventlyHandler(elem, name, h, args) {
+ if ($.evently.log) {
+ elem.bind(name, function() {
+ $.log(elem, name);
+ });
+ }
+ if (h.path) {
+ elem.pathbinder(name, h.path);
+ }
+ var f = funViaString(h, name);
+ if (typeof f == "function") {
+ elem.bind(name, {args:args}, f);
+ } else if (typeof f == "string") {
+ elem.bind(name, {args:args}, function() {
+ $(this).trigger(f, arguments);
+ return false;
+ });
+ } else if ($.isArray(h)) {
+ // handle arrays recursively
+ for (var i=0; i < h.length; i++) {
+ eventlyHandler(elem, name, h[i], args);
+ }
+ } else {
+ // an object is using the evently / mustache template system
+ if (h.fun) {
+ throw("e.fun has been removed, please rename to e.before")
+ }
+ // templates, selectors, etc are intepreted
+ // when our named event is triggered.
+ elem.bind(name, {args:args}, function() {
+ renderElement($(this), h, arguments);
+ return false;
+ });
+ }
+ };
+
+ $.fn.replace = function(elem) {
+ // $.log("Replace", this)
+ $(this).empty().append(elem);
+ };
+
+ // todo: ability to call this
+ // to render and "prepend/append/etc" a new element to the host element (me)
+ // as well as call this in a way that replaces the host elements content
+ // this would be easy if there is a simple way to get at the element we just appended
+ // (as html) so that we can attache the selectors
+ function renderElement(me, h, args, qrun, arun) {
+ // if there's a query object we run the query,
+ // and then call the data function with the response.
+ if (h.before && (!qrun || !arun)) {
+ funViaString(h.before, me).apply(me, args);
+ }
+ if (h.async && !arun) {
+ runAsync(me, h, args)
+ } else if (h.query && !qrun) {
+ // $.log("query before renderElement", arguments)
+ runQuery(me, h, args)
+ } else {
+ // $.log("renderElement")
+ // $.log(me, h, args, qrun)
+ // otherwise we just render the template with the current args
+ var selectors = runIfFun(me, h.selectors, args);
+ var act = (h.render || "replace").replace(/\s/g,"");
+ var app = $$(me).app;
+ if (h.mustache) {
+ // $.log("rendering", h.mustache)
+ var newElem = mustachioed(me, h, args);
+ me[act](newElem);
+ }
+ if (selectors) {
+ if (act == "replace") {
+ var s = me;
+ } else {
+ var s = newElem;
+ }
+ forIn(selectors, function(selector, handlers) {
+ // $.log("selector", selector);
+ // $.log("selected", $(selector, s));
+ $(selector, s).evently(handlers, app, args);
+ // $.log("applied", selector);
+ });
+ }
+ if (h.after) {
+ runIfFun(me, h.after, args);
+ }
+ }
+ };
+
+ // todo this should return the new element
+ function mustachioed(me, h, args) {
+ var partials = $$(me).partials;
+ return $($.mustache(
+ runIfFun(me, h.mustache, args),
+ runIfFun(me, h.data, args),
+ runIfFun(me, $.extend(true, partials, h.partials), args)));
+ };
+
+ function runAsync(me, h, args) {
+ // the callback is the first argument
+ funViaString(h.async, me).apply(me, [function() {
+ renderElement(me, h,
+ $.argsToArray(arguments).concat($.argsToArray(args)), false, true);
+ }].concat($.argsToArray(args)));
+ };
+
+
+ function runQuery(me, h, args) {
+ // $.log("runQuery: args", args)
+ var app = $$(me).app;
+ var qu = runIfFun(me, h.query, args);
+ var qType = qu.type;
+ var viewName = qu.view;
+ var userSuccess = qu.success;
+ // $.log("qType", qType)
+
+ var q = {};
+ forIn(qu, function(k, v) {
+ if (["type", "view"].indexOf(k) == -1) {
+ q[k] = v;
+ }
+ });
+
+ if (qType == "newRows") {
+ q.success = function(resp) {
+ // $.log("runQuery newRows success", resp.rows.length, me, resp)
+ resp.rows.reverse().forEach(function(row) {
+ renderElement(me, h, [row].concat($.argsToArray(args)), true)
+ });
+ if (userSuccess) userSuccess(resp);
+ };
+ newRows(me, app, viewName, q);
+ } else {
+ q.success = function(resp) {
+ // $.log("runQuery success", resp)
+ renderElement(me, h, [resp].concat($.argsToArray(args)), true);
+ userSuccess && userSuccess(resp);
+ };
+ // $.log(app)
+ app.view(viewName, q);
+ }
+ }
+
+ // this is for the items handler
+ // var lastViewId, highKey, inFlight;
+ // this needs to key per elem
+ function newRows(elem, app, view, opts) {
+ // $.log("newRows", arguments);
+ // on success we'll set the top key
+ var thisViewId, successCallback = opts.success, full = false;
+ function successFun(resp) {
+ // $.log("newRows success", resp)
+ $$(elem).inFlight = false;
+ var JSONhighKey = JSON.stringify($$(elem).highKey);
+ resp.rows = resp.rows.filter(function(r) {
+ return JSON.stringify(r.key) != JSONhighKey;
+ });
+ if (resp.rows.length > 0) {
+ if (opts.descending) {
+ $$(elem).highKey = resp.rows[0].key;
+ } else {
+ $$(elem).highKey = resp.rows[resp.rows.length -1].key;
+ }
+ };
+ if (successCallback) {successCallback(resp, full)};
+ };
+ opts.success = successFun;
+
+ if (opts.descending) {
+ thisViewId = view + (opts.startkey ? JSON.stringify(opts.startkey) : "");
+ } else {
+ thisViewId = view + (opts.endkey ? JSON.stringify(opts.endkey) : "");
+ }
+ // $.log(["thisViewId",thisViewId])
+ // for query we'll set keys
+ if (thisViewId == $$(elem).lastViewId) {
+ // we only want the rows newer than changesKey
+ var hk = $$(elem).highKey;
+ if (hk !== undefined) {
+ if (opts.descending) {
+ opts.endkey = hk;
+ // opts.inclusive_end = false;
+ } else {
+ opts.startkey = hk;
+ }
+ }
+ // $.log("add view rows", opts)
+ if (!$$(elem).inFlight) {
+ $$(elem).inFlight = true;
+ app.view(view, opts);
+ }
+ } else {
+ // full refresh
+ // $.log("new view stuff")
+ full = true;
+ $$(elem).lastViewId = thisViewId;
+ $$(elem).highKey = undefined;
+ $$(elem).inFlight = true;
+ app.view(view, opts);
+ }
+ };
+
+ // only start one changes listener per db
+ function followChanges(app) {
+ var dbName = app.db.name, changeEvent = function(resp) {
+ $("body").trigger("evently-changes-"+dbName, [resp]);
+ };
+ if (!$.evently.changesDBs[dbName]) {
+ if (app.db.changes) {
+ // new api in jquery.couch.js 1.0
+ app.db.changes(null, $.evently.changesOpts).onChange(changeEvent);
+ } else {
+ // in case you are still on CouchDB 0.11 ;) deprecated.
+ connectToChanges(app, changeEvent);
+ }
+ $.evently.changesDBs[dbName] = true;
+ }
+ }
+ $.evently.followChanges = followChanges;
+ // deprecated. use db.changes() from jquery.couch.js
+ // this does not have an api for closing changes request.
+ function connectToChanges(app, fun, update_seq) {
+ function changesReq(seq) {
+ var url = app.db.uri+"_changes?heartbeat=10000&feed=longpoll&since="+seq;
+ if ($.evently.changesOpts.include_docs) {
+ url = url + "&include_docs=true";
+ }
+ $.ajax({
+ url: url,
+ contentType: "application/json",
+ dataType: "json",
+ complete: function(req) {
+ var resp = $.httpData(req, "json");
+ fun(resp);
+ connectToChanges(app, fun, resp.last_seq);
+ }
+ });
+ };
+ if (update_seq) {
+ changesReq(update_seq);
+ } else {
+ app.db.info({success: function(db_info) {
+ changesReq(db_info.update_seq);
+ }});
+ }
+ };
+
+})(jQuery);
346 vendor/couchapp/_attachments/jquery.mustache.js
@@ -0,0 +1,346 @@
+/*
+Shameless port of a shameless port
+@defunkt => @janl => @aq
+
+See http://github.com/defunkt/mustache for more info.
+*/
+
+;(function($) {
+
+/*
+ mustache.js — Logic-less templates in JavaScript
+
+ See http://mustache.github.com/ for more info.
+*/
+
+var Mustache = function() {
+ var Renderer = function() {};
+
+ Renderer.prototype = {
+ otag: "{{",
+ ctag: "}}",
+ pragmas: {},
+ buffer: [],
+ pragmas_implemented: {
+ "IMPLICIT-ITERATOR": true
+ },
+ context: {},
+
+ render: function(template, context, partials, in_recursion) {
+ // reset buffer & set context
+ if(!in_recursion) {
+ this.context = context;
+ this.buffer = []; // TODO: make this non-lazy
+ }
+
+ // fail fast
+ if(!this.includes("", template)) {
+ if(in_recursion) {
+ return template;
+ } else {
+ this.send(template);
+ return;
+ }
+ }
+
+ template = this.render_pragmas(template);
+ var html = this.render_section(template, context, partials);
+ if(in_recursion) {
+ return this.render_tags(html, context, partials, in_recursion);
+ }
+
+ this.render_tags(html, context, partials, in_recursion);
+ },
+
+ /*
+ Sends parsed lines
+ */
+ send: function(line) {
+ if(line != "") {
+ this.buffer.push(line);
+ }
+ },
+
+ /*
+ Looks for %PRAGMAS
+ */
+ render_pragmas: function(template) {
+ // no pragmas
+ if(!this.includes("%", template)) {
+ return template;
+ }
+
+ var that = this;
+ var regex = new RegExp(this.otag + "%([\\w-]+) ?([\\w]+=[\\w]+)?" +
+ this.ctag);
+ return template.replace(regex, function(match, pragma, options) {
+ if(!that.pragmas_implemented[pragma]) {
+ throw({message:
+ "This implementation of mustache doesn't understand the '" +
+ pragma + "' pragma"});
+ }
+ that.pragmas[pragma] = {};
+ if(options) {
+ var opts = options.split("=");
+ that.pragmas[pragma][opts[0]] = opts[1];
+ }
+ return "";
+ // ignore unknown pragmas silently
+ });
+ },
+
+ /*
+ Tries to find a partial in the curent scope and render it
+ */
+ render_partial: function(name, context, partials) {
+ name = this.trim(name);
+ if(!partials || partials[name] === undefined) {
+ throw({message: "unknown_partial '" + name + "'"});
+ }
+ if(typeof(context[name]) != "object") {
+ return this.render(partials[name], context, partials, true);
+ }
+ return this.render(partials[name], context[name], partials, true);
+ },
+
+ /*
+ Renders inverted (^) and normal (#) sections
+ */
+ render_section: function(template, context, partials) {
+ if(!this.includes("#", template) && !this.includes("^", template)) {
+ return template;
+ }
+
+ var that = this;
+ // CSW - Added "+?" so it finds the tighest bound, not the widest
+ var regex = new RegExp(this.otag + "(\\^|\\#)\\s*(.+)\\s*" + this.ctag +
+ "\n*([\\s\\S]+?)" + this.otag + "\\/\\s*\\2\\s*" + this.ctag +
+ "\\s*", "mg");
+
+ // for each {{#foo}}{{/foo}} section do...
+ return template.replace(regex, function(match, type, name, content) {
+ var value = that.find(name, context);
+ if(type == "^") { // inverted section
+ if(!value || that.is_array(value) && value.length === 0) {
+ // false or empty list, render it
+ return that.render(content, context, partials, true);
+ } else {
+ return "";
+ }
+ } else if(type == "#") { // normal section
+ if(that.is_array(value)) { // Enumerable, Let's loop!
+ return that.map(value, function(row) {
+ return that.render(content, that.create_context(row),
+ partials, true);
+ }).join("");
+ } else if(that.is_object(value)) { // Object, Use it as subcontext!
+ return that.render(content, that.create_context(value),
+ partials, true);
+ } else if(typeof value === "function") {
+ // higher order section
+ return value.call(context, content, function(text) {
+ return that.render(text, context, partials, true);
+ });
+ } else if(value) { // boolean section
+ return that.render(content, context, partials, true);
+ } else {
+ return "";
+ }
+ }
+ });
+ },
+
+ /*
+ Replace {{foo}} and friends with values from our view
+ */
+ render_tags: function(template, context, partials, in_recursion) {
+ // tit for tat
+ var that = this;
+
+ var new_regex = function() {
+ return new RegExp(that.otag + "(=|!|>|\\{|%)?([^\\/#\\^]+?)\\1?" +
+ that.ctag + "+", "g");
+ };
+
+ var regex = new_regex();
+ var tag_replace_callback = function(match, operator, name) {
+ switch(operator) {
+ case "!": // ignore comments
+ return "";
+ case "=": // set new delimiters, rebuild the replace regexp
+ that.set_delimiters(name);
+ regex = new_regex();
+ return "";
+ case ">": // render partial
+ return that.render_partial(name, context, partials);
+ case "{": // the triple mustache is unescaped
+ return that.find(name, context);
+ default: // escape the value
+ return that.escape(that.find(name, context));
+ }
+ };
+ var lines = template.split("\n");
+ for(var i = 0; i < lines.length; i++) {
+ lines[i] = lines[i].replace(regex, tag_replace_callback, this);
+ if(!in_recursion) {
+ this.send(lines[i]);
+ }
+ }
+
+ if(in_recursion) {
+ return lines.join("\n");
+ }
+ },
+
+ set_delimiters: function(delimiters) {
+ var dels = delimiters.split(" ");
+ this.otag = this.escape_regex(dels[0]);
+ this.ctag = this.escape_regex(dels[1]);
+ },
+
+ escape_regex: function(text) {
+ // thank you Simon Willison
+ if(!arguments.callee.sRE) {
+ var specials = [
+ '/', '.', '*', '+', '?', '|',
+ '(', ')', '[', ']', '{', '}', '\\'
+ ];
+ arguments.callee.sRE = new RegExp(
+ '(\\' + specials.join('|\\') + ')', 'g'
+ );
+ }
+ return text.replace(arguments.callee.sRE, '\\$1');
+ },
+
+ /*
+ find `name` in current `context`. That is find me a value
+ from the view object
+ */
+ find: function(name, context) {
+ name = this.trim(name);
+
+ // Checks whether a value is thruthy or false or 0
+ function is_kinda_truthy(bool) {
+ return bool === false || bool === 0 || bool;
+ }
+
+ var value;
+ if(is_kinda_truthy(context[name])) {
+ value = context[name];
+ } else if(is_kinda_truthy(this.context[name])) {
+ value = this.context[name];
+ }
+
+ if(typeof value === "function") {
+ return value.apply(context);
+ }
+ if(value !== undefined) {
+ return value;
+ }
+ // silently ignore unkown variables
+ return "";
+ },
+
+ // Utility methods
+
+ /* includes tag */
+ includes: function(needle, haystack) {
+ return haystack.indexOf(this.otag + needle) != -1;
+ },
+
+ /*
+ Does away with nasty characters
+ */
+ escape: function(s) {
+ s = String(s === null ? "" : s);
+ return s.replace(/&(?!\w+;)|["<>\\]/g, function(s) {
+ switch(s) {
+ case "&": return "&amp;";
+ case "\\": return "\\\\";
+ case '"': return '\"';
+ case "<": return "&lt;";
+ case ">": return "&gt;";
+ default: return s;
+ }
+ });
+ },
+
+ // by @langalex, support for arrays of strings
+ create_context: function(_context) {
+ if(this.is_object(_context)) {
+ return _context;
+ } else {
+ var iterator = ".";
+ if(this.pragmas["IMPLICIT-ITERATOR"]) {
+ iterator = this.pragmas["IMPLICIT-ITERATOR"].iterator;
+ }
+ var ctx = {};
+ ctx[iterator] = _context;
+ return ctx;
+ }
+ },
+
+ is_object: function(a) {
+ return a && typeof a == "object";
+ },
+
+ is_array: function(a) {
+ return Object.prototype.toString.call(a) === '[object Array]';
+ },
+
+ /*
+ Gets rid of leading and trailing whitespace
+ */
+ trim: function(s) {
+ return s.replace(/^\s*|\s*$/g, "");
+ },
+
+ /*
+ Why, why, why? Because IE. Cry, cry cry.
+ */
+ map: function(array, fn) {
+ if (typeof array.map == "function") {
+ return array.map(fn);
+ } else {
+ var r = [];
+ var l = array.length;
+ for(var i = 0; i < l; i++) {
+ r.push(fn(array[i]));
+ }
+ return r;
+ }
+ }
+ };
+
+ return({
+ name: "mustache.js",
+ version: "0.3.1-dev",
+
+ /*
+ Turns a template and view into HTML
+ */
+ to_html: function(template, view, partials, send_fun) {
+ var renderer = new Renderer();
+ if(send_fun) {
+ renderer.send = send_fun;
+ }
+ renderer.render(template, view, partials);
+ if(!send_fun) {
+ return renderer.buffer.join("\n");
+ }
+ },
+ escape : function(text) {
+ return new Renderer().escape(text);
+ }
+ });
+}();
+
+ $.mustache = function(template, view, partials) {
+ return Mustache.to_html(template, view, partials);
+ };
+
+ $.mustache.escape = function(text) {
+ return Mustache.escape(text);
+ };
+
+})(jQuery);
174 vendor/couchapp/_attachments/jquery.pathbinder.js
@@ -0,0 +1,174 @@
+(function($) {
+ // functions for handling the path
+ // thanks sammy.js
+ var PATH_REPLACER = "([^\/]+)",
+ PATH_NAME_MATCHER = /:([\w\d]+)/g,
+ QUERY_STRING_MATCHER = /\?([^#]*)$/,
+ SPLAT_MATCHER = /(\*)/,
+ SPLAT_REPLACER = "(.+)",
+ _currentPath,
+ _lastPath,
+ _pathInterval;
+
+ function hashChanged() {
+ _currentPath = getPath();
+ // if path is actually changed from what we thought it was, then react
+ if (_lastPath != _currentPath) {
+ _lastPath = _currentPath;
+ return triggerOnPath(_currentPath);
+ }
+ }
+
+ $.pathbinder = {
+ changeFuns : [],
+ paths : [],
+ begin : function(defaultPath) {
+ // this should trigger the defaultPath if there's not a path in the URL
+ // otherwise it should trigger the URL's path
+ $(function() {
+ var loadPath = getPath();
+ if (loadPath) {
+ triggerOnPath(loadPath);
+ } else {
+ goPath(defaultPath);
+ triggerOnPath(defaultPath);
+ }
+ })
+ },
+ go : function(path) {
+ goPath(path);
+ triggerOnPath(path);
+ },
+ currentPath : function() {
+ return getPath();
+ },
+ onChange : function (fun) {
+ $.pathbinder.changeFuns.push(fun);
+ }
+ };
+
+ function pollPath(every) {
+ function hashCheck() {
+ _currentPath = getPath();
+ // path changed if _currentPath != _lastPath
+ if (_lastPath != _currentPath) {
+ setTimeout(function() {
+ $(window).trigger('hashchange');
+ }, 1);
+ }
+ };
+ hashCheck();
+ _pathInterval = setInterval(hashCheck, every);
+ $(window).bind('unload', function() {
+ clearInterval(_pathInterval);
+ });
+ }
+
+ function triggerOnPath(path) {
+ path = path.replace(/^#/,'');
+ $.pathbinder.changeFuns.forEach(function(fun) {fun(path)});
+ var pathSpec, path_params, params = {}, param_name, param;
+ for (var i=0; i < $.pathbinder.paths.length; i++) {
+ pathSpec = $.pathbinder.paths[i];
+ // $.log("pathSpec", pathSpec);
+ if ((path_params = pathSpec.matcher.exec(path)) !== null) {
+ // $.log("path_params", path_params);
+ path_params.shift();
+ for (var j=0; j < path_params.length; j++) {
+ param_name = pathSpec.param_names[j];
+ param = decodeURIComponent(path_params[j]);
+ if (param_name) {
+ params[param_name] = param;
+ } else {
+ if (!params.splat) params.splat = [];
+ params.splat.push(param);
+ }
+ };
+ pathSpec.callback(params);
+ // return true; // removed this to allow for multi match
+ }
+ };
+ };
+
+ // bind the event
+ $(function() {
+ if ('onhashchange' in window) {
+ // we have a native event
+ } else {
+ pollPath(10);
+ }
+ // setTimeout(hashChanged,50);
+ $(window).bind('hashchange', hashChanged);
+ });
+
+ function registerPath(pathSpec) {
+ $.pathbinder.paths.push(pathSpec);
+ };
+
+ function setPath(pathSpec, params) {
+ var newPath = $.mustache(pathSpec.template, params);
+ goPath(newPath);
+ };
+
+ function goPath(newPath) {
+ if (newPath) {
+ // $.log("goPath", newPath)
+ window.location = '#'+newPath;
+ }
+ _lastPath = getPath();
+ };
+
+ function getPath() {
+ var matches = window.location.toString().match(/^[^#]*(#.+)$/);
+ return matches ? matches[1] : '';
+ };
+
+ function makePathSpec(path, callback) {
+ var param_names = [];
+ var template = "";
+
+ PATH_NAME_MATCHER.lastIndex = 0;
+
+ while ((path_match = PATH_NAME_MATCHER.exec(path)) !== null) {
+ param_names.push(path_match[1]);
+ }
+
+ return {
+ param_names : param_names,
+ matcher : new RegExp("^" + path.replace(
+ PATH_NAME_MATCHER, PATH_REPLACER).replace(
+ SPLAT_MATCHER, SPLAT_REPLACER) + "/?$"),
+ template : path.replace(PATH_NAME_MATCHER, function(a, b) {
+ return '{{'+b+'}}';
+ }).replace(SPLAT_MATCHER, '{{splat}}'),
+ callback : callback
+ };
+ };
+
+ $.fn.pathbinder = function(name, paths, options) {
+ options = options || {};
+ var self = $(this), pathList = paths.split(/\n/);
+ $.each(pathList, function() {
+ var path = this;
+ if (path) {
+ // $.log("bind path", path);
+ var pathSpec = makePathSpec(path, function(params) {
+ // $.log("path cb", name, path, self)
+ // $.log("trigger path: "+path+" params: ", params);
+ self.trigger(name, [params]);
+ });
+ // set the path when the event triggered through other means
+ if (options.bindPath) {
+ self.bind(name, function(ev, params) {
+ params = params || {};
+ // $.log("set path", name, pathSpec)
+ setPath(pathSpec, params);
+ });
+ }
+ // trigger when the path matches
+ registerPath(pathSpec);
+ }
+ });
+ };
+})(jQuery);
+
17 vendor/couchapp/_attachments/loader.js
@@ -0,0 +1,17 @@
+
+function couchapp_load(scripts) {
+ for (var i=0; i < scripts.length; i++) {
+ document.write('<script src="'+scripts[i]+'"><\/script>')
+ };
+};
+
+couchapp_load([
+ "/_utils/script/sha1.js",
+ "/_utils/script/json2.js",
+ "/_utils/script/jquery.js",
+ "/_utils/script/jquery.couch.js",
+ "vendor/couchapp/jquery.couch.app.js",
+ "vendor/couchapp/jquery.couch.app.util.js",
+ "vendor/couchapp/jquery.mustache.js",
+ "vendor/couchapp/jquery.evently.js"
+]);
22 vendor/couchapp/evently/README.md
@@ -0,0 +1,22 @@
+## Starting the Document this code challenge
+
+I need help on this code. I only have so many hours in the day. Please be liberal about patching and hacking (and sharing code!) so we can all benefit.
+
+Docs patches are deeply appreciated. For now you can just stick Markdown files in the Docs directory.
+
+# Evently
+
+These are some vendor Evently widgets that are running on the CouchApp system.
+
+## Account
+ This is how you signup, login and logout without worry about the code.
+ Todo, we could have this work against remote APIs like that Facebook stuff or whatever.
+
+
+## Profile
+ Use this to load the local users profile for the logged in user. Useful if you're going to be posting new messages. Most applications end up customizing `profile.profileReady` to render the primary data-entry form. This gets you benefits like refreshing on login / logout, etc, automatically.
+
+
+## Docs
+ This needs to be moved to it's own app.
+ I have this vision of a docs app designed for offline editing, that involves each Markdown paragraph being it's own document, with automatic use of Bespin for code samples. Any help on this would be thanked much.
16 vendor/couchapp/evently/account/_init.js
@@ -0,0 +1,16 @@
+function() {
+ var elem = $(this);
+ $$(this).userCtx = null;
+ $.couch.session({
+ success : function(r) {
+ var userCtx = r.userCtx;
+ if (userCtx.name) {
+ elem.trigger("loggedIn", [r]);
+ } else if (userCtx.roles.indexOf("_admin") != -1) {
+ elem.trigger("adminParty");
+ } else {
+ elem.trigger("loggedOut");
+ };
+ }
+ });
+}
1  vendor/couchapp/evently/account/adminParty/mustache.html
@@ -0,0 +1 @@
+<p><strong>Admin party, everyone is admin!</strong> Fix this in <a href="/_utils/index.html">Futon</a> before proceeding.</p>
10 vendor/couchapp/evently/account/doLogin.js
@@ -0,0 +1,10 @@
+function(e, name, pass) {
+ var elem = $(this);
+ $.couch.login({
+ name : name,
+ password : pass,
+ success : function(r) {
+ elem.trigger("_init")
+ }
+ });
+}
8 vendor/couchapp/evently/account/doLogout.js
@@ -0,0 +1,8 @@
+function() {
+ var elem = $(this);
+ $.couch.logout({
+ success : function() {
+ elem.trigger("_init");
+ }
+ });
+}
10 vendor/couchapp/evently/account/doSignup.js
@@ -0,0 +1,10 @@
+function(e, name, pass) {
+ var elem = $(this);
+ $.couch.signup({
+ name : name
+ }, pass, {
+ success : function() {
+ elem.trigger("doLogin", [name, pass]);
+ }
+ });
+}
4 vendor/couchapp/evently/account/loggedIn/after.js
@@ -0,0 +1,4 @@
+function(e, r) {
+ $$(this).userCtx = r.userCtx;
+ $$(this).info = r.info;
+};
7 vendor/couchapp/evently/account/loggedIn/data.js
@@ -0,0 +1,7 @@
+function(e, r) {
+ return {
+ name : r.userCtx.name,
+ uri_name : encodeURIComponent(r.userCtx.name),
+ auth_db : encodeURIComponent(r.info.authentication_db)
+ };
+}
4 vendor/couchapp/evently/account/loggedIn/mustache.html
@@ -0,0 +1,4 @@
+<span>Welcome
+<a target="_new" href="/_utils/document.html?{{auth_db}}/org.couchdb.user%3A{{uri_name}}">{{name}}</a>!
+<a href="#logout">Logout?</a>
+</span>
3  vendor/couchapp/evently/account/loggedIn/selectors.json
@@ -0,0 +1,3 @@
+{
+ "a[href=#logout]" : {"click" : ["doLogout"]}
+}
1  vendor/couchapp/evently/account/loggedOut/mustache.html
@@ -0,0 +1 @@
+<a href="#signup">Signup</a> or <a href="#login">Login</a>
4 vendor/couchapp/evently/account/loggedOut/selectors.json
@@ -0,0 +1,4 @@
+{
+ "a[href=#signup]" : {"click" : ["signupForm"]},
+ "a[href=#login]" : {"click" : ["loginForm"]}
+}
3  vendor/couchapp/evently/account/loginForm/after.js
@@ -0,0 +1,3 @@
+function() {
+ $("input[name=name]", this).focus();
+}
6 vendor/couchapp/evently/account/loginForm/mustache.html
@@ -0,0 +1,6 @@
+<form>
+ <label for="name">Name</label> <input type="text" name="name" value="" autocapitalize="off" autocorrect="off">
+ <label for="password">Password</label> <input type="password" name="password" value="">
+ <input type="submit" value="Login">
+ <a href="#signup">or Signup</a>
+</form>
6 vendor/couchapp/evently/account/loginForm/selectors/form/submit.js
@@ -0,0 +1,6 @@
+function(e) {
+ var name = $('input[name=name]', this).val(),
+ pass = $('input[name=password]', this).val();
+ $(this).trigger('doLogin', [name, pass]);
+ return false;
+}
3  vendor/couchapp/evently/account/signupForm/after.js
@@ -0,0 +1,3 @@
+function() {
+ $("input[name=name]", this).focus();
+}
6 vendor/couchapp/evently/account/signupForm/mustache.html
@@ -0,0 +1,6 @@
+<form>
+ <label for="name">Name</label> <input type="text" name="name" value="" autocapitalize="off" autocorrect="off">
+ <label for="password">Password</label> <input type="password" name="password" value="">
+ <input type="submit" value="Signup">
+ <a href="#login">or Login</a>
+</form>
6 vendor/couchapp/evently/account/signupForm/selectors/form/submit.js
@@ -0,0 +1,6 @@
+function(e) {
+ var name = $('input[name=name]', this).val(),
+ pass = $('input[name=password]', this).val();
+ $(this).trigger('doSignup', [name, pass]);
+ return false;
+}
21 vendor/couchapp/evently/profile/loggedIn.js
@@ -0,0 +1,21 @@
+function(e, r) {
+ var userCtx = r.userCtx;
+ var widget = $(this);
+ // load the profile from the user doc
+ var db = $.couch.db(r.info.authentication_db);
+ var userDocId = "org.couchdb.user:"+userCtx.name;
+ db.openDoc(userDocId, {
+ success : function(userDoc) {
+ var profile = userDoc["couch.app.profile"];
+ if (profile) {
+ // we copy the name to the profile so it can be used later
+ // without publishing the entire userdoc (roles, pass, etc)
+ profile.name = userDoc.name;
+ $$(widget).profile = profile;
+ widget.trigger("profileReady", [profile]);
+ } else {
+ widget.trigger("noProfile", [userCtx]);
+ }
+ }
+ });
+}
3  vendor/couchapp/evently/profile/loggedOut/after.js
@@ -0,0 +1,3 @@
+function() {
+ $$(this).profile = null;
+};
1  vendor/couchapp/evently/profile/loggedOut/mustache.html
@@ -0,0 +1 @@
+<p>Please log in to see your profile.</p>
3  vendor/couchapp/evently/profile/noProfile/data.js
@@ -0,0 +1,3 @@
+function(e, userCtx) {
+ return userCtx;
+}
11 vendor/couchapp/evently/profile/noProfile/mustache.html
@@ -0,0 +1,11 @@
+<form>
+ <p>Hello {{name}}, Please setup your user profile.</p>
+ <label for="nickname">Nickname
+ <input type="text" name="nickname" value=""></label>
+ <label for="email">Email (<em>for <a href="http://gravatar.com">Gravatar</a></em>)
+ <input type="text" name="email" value=""></label>
+ <label for="url">URL
+ <input type="text" name="url" value=""></label>
+ <input type="submit" value="Go &rarr;">
+ <input type="hidden" name="userCtxName" value="{{name}}" id="userCtxName">
+</form>
36 vendor/couchapp/evently/profile/noProfile/selectors/form/submit.js
@@ -0,0 +1,36 @@
+function() {
+ var md5 = $$(this).app.require("vendor/couchapp/lib/md5");
+
+ // TODO this can be cleaned up with docForm?
+ // it still needs the workflow to edit an existing profile
+ var name = $("input[name=userCtxName]",this).val();
+ var newProfile = {
+ rand : Math.random().toString(),
+ nickname : $("input[name=nickname]",this).val(),
+ email : $("input[name=email]",this).val(),
+ url : $("input[name=url]",this).val()
+ }, widget = $(this);
+
+ // setup gravatar_url
+ if (md5) {
+ newProfile.gravatar_url = 'http://www.gravatar.com/avatar/'+md5.hex(newProfile.email || newProfile.rand)+'.jpg?s=40&d=identicon';
+ }
+
+ // store the user profile on the user account document
+ $.couch.userDb(function(db) {
+ var userDocId = "org.couchdb.user:"+name;
+ db.openDoc(userDocId, {
+ success : function(userDoc) {
+ userDoc["couch.app.profile"] = newProfile;
+ db.saveDoc(userDoc, {
+ success : function() {
+ newProfile.name = userDoc.name;
+ $$(widget).profile = newProfile;
+ widget.trigger("profileReady", [newProfile]);
+ }
+ });
+ }
+ });
+ });
+ return false;
+}
3  vendor/couchapp/evently/profile/profileReady/after.js
@@ -0,0 +1,3 @@
+function(e, p) {
+ $$(this).profile = p;
+};
3  vendor/couchapp/evently/profile/profileReady/data.js
@@ -0,0 +1,3 @@
+function(e, p) {
+ return p
+}
8 vendor/couchapp/evently/profile/profileReady/mustache.html
@@ -0,0 +1,8 @@
+<div class="avatar">
+ {{#gravatar_url}}<img src="{{gravatar_url}}"/>{{/gravatar_url}}
+ <div class="name">
+ {{nickname}}
+ </div>
+</div>
+<p>Hello {{nickname}}!</p>
+<div style="clear:left;"></div>
39 vendor/couchapp/lib/atom.js
@@ -0,0 +1,39 @@
+// atom feed generator
+// requries E4X support.
+
+function f(n) { // Format integers to have at least two digits.
+ return n < 10 ? '0' + n : n;
+}
+
+function rfc3339(date) {
+ return date.getUTCFullYear() + '-' +
+ f(date.getUTCMonth() + 1) + '-' +
+ f(date.getUTCDate()) + 'T' +
+ f(date.getUTCHours()) + ':' +
+ f(date.getUTCMinutes()) + ':' +
+ f(date.getUTCSeconds()) + 'Z';
+};
+
+exports.header = function(data) {
+ var f = <feed xmlns="http://www.w3.org/2005/Atom"/>;
+ f.title = data.title;
+ f.id = data.feed_id;
+ f.link.@href = data.feed_link;
+ f.link.@rel = "self";
+ f.generator = "CouchApp on CouchDB";
+ f.updated = rfc3339(data.updated);
+ return f.toXMLString().replace(/\<\/feed\>/,'');
+};
+
+exports.entry = function(data) {
+ var entry = <entry/>;
+ entry.id = data.entry_id;
+ entry.title = data.title;
+ entry.content = data.content;
+ entry.content.@type = (data.content_type || 'html');
+ entry.updated = rfc3339(data.updated);
+ entry.author = <author><name>{data.author}</name></author>;
+ entry.link.@href = data.alternate;
+ entry.link.@rel = "alternate";
+ return entry;
+}
25 vendor/couchapp/lib/cache.js
@@ -0,0 +1,25 @@
+exports.get = function(db, docid, setFun, getFun) {
+ db.openDoc(docid, {
+ success : function(doc) {
+ getFun(doc.cache);
+ },
+ error : function() {
+ setFun(function(cache) {
+ db.saveDoc({
+ _id : docid,
+ cache : cache
+ });
+ getFun(cache);
+ });
+ }
+ });
+};
+
+exports.clear = function(db, docid) {
+ db.openDoc(docid, {
+ success : function(doc) {
+ db.removeDoc(doc);
+ },
+ error : function() {}
+ });
+};
20 vendor/couchapp/lib/code.js
@@ -0,0 +1,20 @@
+exports.ddoc = function(ddoc) {
+ // only return the parts of the app that we use
+ var i, j, path, key, obj, ref, out = {},
+ resources = ddoc.couchapp && ddoc.couchapp.load && ddoc.couchapp.load.app || [];
+ for (i=0; i < resources.length; i++) {
+ path = resources[i].split('/');
+ obj = ddoc;
+ ref = out;
+ for (j=0; j < path.length; j++) {
+ key = path[j];
+ ref[key] = ref[key] || {};
+ if (j < path.length - 1) {
+ obj = obj[key];
+ ref = ref[key];
+ }
+ };
+ ref[key] = obj[key];
+ };
+ return out;
+};
121 vendor/couchapp/lib/docform.js
@@ -0,0 +1,121 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+// turn the form into deep json
+// field names like 'author-email' get turned into json like
+// {"author":{"email":"quentin@example.com"}}
+// acts on doc by reference, so you can safely pass non-form fields through
+
+function docForm(formSelector, opts) {
+ var localFormDoc = {};
+ opts = opts || {};
+ opts.fields = opts.fields || [];
+
+ // turn the form into deep json
+ // field names like 'author-email' get turned into json like
+ // {"author":{"email":"quentin@example.com"}}
+ function formToDeepJSON(form, fields, doc) {
+ form = $(form);
+ fields.forEach(function(field) {
+ var element = form.find("[name="+field+"]"),
+ parts = field.split('-'),
+ frontObj = doc, frontName = parts.shift();
+
+ if (element.attr('type') === 'checkbox') {
+ var val = element.attr('checked');
+ } else {
+ var val = element.val();
+ if (!val) {
+ if (frontObj[field]) {
+ delete frontObj[field];
+ }
+ return;
+ }
+ }
+
+ while (parts.length > 0) {
+ frontObj[frontName] = frontObj[frontName] || {};
+ frontObj = frontObj[frontName];
+ frontName = parts.shift();
+ }
+ frontObj[frontName] = val;
+ });
+ }
+
+ // Apply the behavior
+ $(formSelector).submit(function(e) {
+ e.preventDefault();
+ if (opts.validate && opts.validate() == false) { return false;}
+ // formToDeepJSON acts on localFormDoc by reference
+ formToDeepJSON(this, opts.fields, localFormDoc);
+ if (opts.beforeSave) {opts.beforeSave(localFormDoc);}
+ db.saveDoc(localFormDoc, {
+ success : function(resp) {
+ if (opts.success) {opts.success(resp, localFormDoc);}
+ }
+ });
+
+ return false;
+ });
+
+ // populate form from an existing doc
+ function docToForm(doc) {
+ var form = $(formSelector);
+ // fills in forms
+ opts.fields.forEach(function(field) {
+ var parts = field.split('-');
+ var run = true, frontObj = doc, frontName = parts.shift();
+ while (frontObj && parts.length > 0) {
+ frontObj = frontObj[frontName];
+ frontName = parts.shift();
+ }
+ if (frontObj && frontObj[frontName]) {
+ var element = form.find("[name="+field+"]");
+ if (element.attr('type') === 'checkbox') {
+ element.attr('checked', frontObj[frontName]);
+ } else {
+ element.val(frontObj[frontName]);
+ }
+ }
+ });
+ }
+
+ if (opts.id) {
+ db.openDoc(opts.id, {
+ attachPrevRev : opts.attachPrevRev,
+ error: function() {
+ if (opts.error) {opts.error.apply(opts, arguments);}
+ },
+ success: function(doc) {
+ if (opts.load || opts.onLoad) {(opts.load || opts.onLoad)(doc);}
+ localFormDoc = doc;
+ docToForm(doc);
+ }});
+ } else if (opts.template) {
+ if (opts.load || opts.onLoad) {(opts.load || opts.onLoad)(opts.template);}
+ localFormDoc = opts.template;
+ docToForm(localFormDoc);
+ }
+ var instance = {
+ deleteDoc : function(opts) {
+ opts = opts || {};
+ if (confirm("Really delete this document?")) {
+ db.removeDoc(localFormDoc, opts);
+ }
+ },
+ localDoc : function() {
+ formToDeepJSON(formSelector, opts.fields, localFormDoc);
+ return localFormDoc;
+ }
+ };
+ return instance;
+}
18 vendor/couchapp/lib/linkup.js
@@ -0,0 +1,18 @@
+// this code makes http://example.com into a link,
+// and also handles @name and #hashtag
+
+// todo add [[wiki_links]]
+
+var mustache = require("vendor/couchapp/lib/mustache");
+exports.encode = function(body, person_prefix, tag_prefix) {
+ body = mustache.escape(body);
+ person_prefix = person_prefix || "http://twitter.com/";
+ tag_prefix = tag_prefix || "http://delicious.com/tag/";
+ return body.replace(/((ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?)/gi,function(a) {
+ return '<a target="_blank" href="'+a+'">'+a+'</a>';
+ }).replace(/\@([\w\-]+)/g,function(user,name) {
+ return '<a href="'+person_prefix+encodeURIComponent(name)+'">'+user+'</a>';
+ }).replace(/\#([\w\-\.]+)/g,function(word,tag) {
+ return '<a href="'+tag_prefix+encodeURIComponent(tag)+'">'+word+'</a>';
+ });
+};
13 vendor/couchapp/lib/list.js
@@ -0,0 +1,13 @@
+// Helpers for writing server-side _list functions in CouchDB
+exports.withRows = function(fun) {
+ var f = function() {
+ var row = getRow();
+ return row && fun(row);
+ };
+ f.iterator = true;
+ return f;
+}
+
+exports.send = function(chunk) {
+ send(chunk + "\n")
+}
1,300 vendor/couchapp/lib/markdown.js
@@ -0,0 +1,1300 @@
+//
+// showdown.js -- A javascript port of Markdown.
+//
+// Copyright (c) 2007 John Fraser.
+//
+// Original Markdown Copyright (c) 2004-2005 John Gruber
+// <http://daringfireball.net/projects/markdown/>
+//
+// Redistributable under a BSD-style open source license.
+// See license.txt for more information.
+//
+// The full source distribution is at:
+//
+// A A L
+// T C A
+// T K B
+//
+// <http://www.attacklab.net/>
+//
+
+//
+// Wherever possible, Showdown is a straight, line-by-line port
+// of the Perl version of Markdown.
+//
+// This is not a normal parser design; it's basically just a
+// series of string substitutions. It's hard to read and
+// maintain this way, but keeping Showdown close to the original
+// design makes it easier to port new features.
+//
+// More importantly, Showdown behaves like markdown.pl in most
+// edge cases. So web applications can do client-side preview
+// in Javascript, and then build identical HTML on the server.
+//
+// This port needs the new RegExp functionality of ECMA 262,
+// 3rd Edition (i.e. Javascript 1.5). Most modern web browsers
+// should do fine. Even with the new regular expression features,
+// We do a lot of work to emulate Perl's regex functionality.
+// The tricky changes in this file mostly have the "attacklab:"
+// label. Major or self-explanatory changes don't.
+//
+// Smart diff tools like Araxis Merge will be able to match up
+// this file with markdown.pl in a useful way. A little tweaking
+// helps: in a copy of markdown.pl, replace "#" with "//" and
+// replace "$text" with "text". Be sure to ignore whitespace
+// and line endings.
+//
+
+
+//
+// Showdown usage:
+//
+// var text = "Markdown *rocks*.";
+//
+// var markdown = require("markdown");
+// var html = markdown.encode(text);
+//
+// print(html);
+//
+// Note: move the sample code to the bottom of this
+// file before uncommenting it.
+//
+
+
+//
+// Globals:
+//
+
+// Global hashes, used by various utility routines
+var g_urls;
+var g_titles;
+var g_html_blocks;
+
+// Used to track when we're inside an ordered or unordered list
+// (see _ProcessListItems() for details):
+var g_list_level = 0;
+
+
+exports.makeHtml = function(text) {
+//
+// Main function. The order in which other subs are called here is
+// essential. Link and image substitutions need to happen before
+// _EscapeSpecialCharsWithinTagAttributes(), so that any *'s or _'s in the <a>
+// and <img> tags get encoded.
+//
+
+ // Clear the global hashes. If we don't clear these, you get conflicts
+ // from other articles when generating a page which contains more than
+ // one article (e.g. an index page that shows the N most recent
+ // articles):
+ g_urls = new Array();
+ g_titles = new Array();
+ g_html_blocks = new Array();
+
+ // attacklab: Replace ~ with ~T
+ // This lets us use tilde as an escape char to avoid md5 hashes
+ // The choice of character is arbitray; anything that isn't
+ // magic in Markdown will work.
+ text = text.replace(/~/g,"~T");
+
+ // attacklab: Replace $ with ~D
+ // RegExp interprets $ as a special character
+ // when it's in a replacement string
+ text = text.replace(/\$/g,"~D");
+
+ // Standardize line endings
+ text = text.replace(/\r\n/g,"\n"); // DOS to Unix
+ text = text.replace(/\r/g,"\n"); // Mac to Unix
+
+ // Make sure text begins and ends with a couple of newlines:
+ text = "\n\n" + text + "\n\n";
+
+ // Convert all tabs to spaces.
+ text = _Detab(text);
+
+ // Strip any lines consisting only of spaces and tabs.
+ // This makes subsequent regexen easier to write, because we can
+ // match consecutive blank lines with /\n+/ instead of something
+ // contorted like /[ \t]*\n+/ .
+ text = text.replace(/^[ \t]+$/mg,"");
+
+ // Turn block-level HTML blocks into hash entries
+ text = _HashHTMLBlocks(text);
+
+ // Strip link definitions, store in hashes.
+ text = _StripLinkDefinitions(text);
+
+ text = _RunBlockGamut(text);
+
+ text = _UnescapeSpecialChars(text);
+<