From 924a7268b3225c206c19f0bfcdb9301b09982ddf Mon Sep 17 00:00:00 2001 From: Chris Anderson Date: Sat, 1 May 2010 18:23:03 -0700 Subject: [PATCH 01/33] intial commit --- README.md | 5 + _attachments/index.html | 29 + _attachments/style/main.css | 71 + _id | 1 + couchapp.json | 4 + evently/items/_changes/data.js | 11 + evently/items/_changes/mustache.html | 18 + evently/items/_changes/query.json | 5 + evently/profile/profileReady/mustache.html | 14 + .../profileReady/selectors/form/submit.js | 14 + language | 1 + vendor/couchapp/README.md | 3 + vendor/couchapp/_attachments/docs.css | 53 + vendor/couchapp/_attachments/docs.html | 32 + vendor/couchapp/_attachments/docs.js | 8 + .../couchapp/_attachments/jquery.couch.app.js | 292 ++++ .../_attachments/jquery.couch.app.util.js | 66 + .../couchapp/_attachments/jquery.evently.js | 345 +++++ .../couchapp/_attachments/jquery.mustache.js | 282 ++++ .../_attachments/jquery.pathbinder.js | 156 ++ vendor/couchapp/_attachments/loader.js | 14 + vendor/couchapp/docs/account.md | 187 +++ vendor/couchapp/docs/couchapp.md | 34 + vendor/couchapp/docs/docs.md | 11 + vendor/couchapp/docs/evently.md | 185 +++ vendor/couchapp/docs/pathbinder.md | 52 + vendor/couchapp/docs/profile.md | 3 + vendor/couchapp/evently/README.md | 22 + vendor/couchapp/evently/account/_init.js | 15 + vendor/couchapp/evently/account/adminParty.js | 3 + vendor/couchapp/evently/account/doLogin.js | 10 + vendor/couchapp/evently/account/doLogout.js | 8 + vendor/couchapp/evently/account/doSignup.js | 10 + .../evently/account/loggedIn/after.js | 5 + .../couchapp/evently/account/loggedIn/data.js | 7 + .../evently/account/loggedIn/mustache.html | 4 + .../evently/account/loggedIn/selectors.json | 3 + .../evently/account/loggedOut/mustache.html | 1 + .../evently/account/loggedOut/selectors.json | 4 + .../evently/account/loginForm/after.js | 3 + .../evently/account/loginForm/mustache.html | 6 + .../loginForm/selectors/a[href=#signup].json | 1 + .../loginForm/selectors/form/submit.js | 6 + .../evently/account/signupForm/after.js | 3 + .../evently/account/signupForm/mustache.html | 6 + .../signupForm/selectors/a[href=#login].json | 1 + .../signupForm/selectors/form/submit.js | 6 + vendor/couchapp/evently/docs/index/data.js | 11 + .../couchapp/evently/docs/index/mustache.html | 5 + vendor/couchapp/evently/docs/index/path.txt | 1 + vendor/couchapp/evently/docs/topic/after.js | 15 + vendor/couchapp/evently/docs/topic/data.js | 8 + .../evently/docs/topic/edit/_init/fun.js | 19 + .../edit/_init/selectors/a.edit/click.js | 9 + .../topic/edit/_init/selectors/a.run/click.js | 22 + .../couchapp/evently/docs/topic/mustache.html | 1 + vendor/couchapp/evently/docs/topic/path.txt | 1 + vendor/couchapp/evently/profile/loggedIn.js | 22 + .../evently/profile/loggedOut/after.js | 3 + .../evently/profile/loggedOut/mustache.html | 1 + .../evently/profile/noProfile/data.js | 3 + .../evently/profile/noProfile/mustache.html | 11 + .../noProfile/selectors/form/submit.js | 36 + .../evently/profile/profileReady/after.js | 3 + .../evently/profile/profileReady/data.js | 3 + .../profile/profileReady/mustache.html | 8 + vendor/couchapp/lib/atom.js | 39 + vendor/couchapp/lib/cache.js | 25 + vendor/couchapp/lib/docform.js | 108 ++ vendor/couchapp/lib/list.js | 13 + vendor/couchapp/lib/markdown.js | 1300 +++++++++++++++++ vendor/couchapp/lib/md5.js | 261 ++++ vendor/couchapp/lib/path.js | 83 ++ vendor/couchapp/lib/redirect.js | 8 + vendor/couchapp/metadata.json | 4 + views/recent-items/map.js | 5 + 76 files changed, 4048 insertions(+) create mode 100644 README.md create mode 100755 _attachments/index.html create mode 100755 _attachments/style/main.css create mode 100644 _id create mode 100644 couchapp.json create mode 100644 evently/items/_changes/data.js create mode 100644 evently/items/_changes/mustache.html create mode 100644 evently/items/_changes/query.json create mode 100644 evently/profile/profileReady/mustache.html create mode 100644 evently/profile/profileReady/selectors/form/submit.js create mode 100644 language create mode 100644 vendor/couchapp/README.md create mode 100644 vendor/couchapp/_attachments/docs.css create mode 100644 vendor/couchapp/_attachments/docs.html create mode 100644 vendor/couchapp/_attachments/docs.js create mode 100644 vendor/couchapp/_attachments/jquery.couch.app.js create mode 100644 vendor/couchapp/_attachments/jquery.couch.app.util.js create mode 100644 vendor/couchapp/_attachments/jquery.evently.js create mode 100644 vendor/couchapp/_attachments/jquery.mustache.js create mode 100644 vendor/couchapp/_attachments/jquery.pathbinder.js create mode 100644 vendor/couchapp/_attachments/loader.js create mode 100644 vendor/couchapp/docs/account.md create mode 100644 vendor/couchapp/docs/couchapp.md create mode 100644 vendor/couchapp/docs/docs.md create mode 100644 vendor/couchapp/docs/evently.md create mode 100644 vendor/couchapp/docs/pathbinder.md create mode 100644 vendor/couchapp/docs/profile.md create mode 100644 vendor/couchapp/evently/README.md create mode 100644 vendor/couchapp/evently/account/_init.js create mode 100644 vendor/couchapp/evently/account/adminParty.js create mode 100644 vendor/couchapp/evently/account/doLogin.js create mode 100644 vendor/couchapp/evently/account/doLogout.js create mode 100644 vendor/couchapp/evently/account/doSignup.js create mode 100644 vendor/couchapp/evently/account/loggedIn/after.js create mode 100644 vendor/couchapp/evently/account/loggedIn/data.js create mode 100644 vendor/couchapp/evently/account/loggedIn/mustache.html create mode 100644 vendor/couchapp/evently/account/loggedIn/selectors.json create mode 100644 vendor/couchapp/evently/account/loggedOut/mustache.html create mode 100644 vendor/couchapp/evently/account/loggedOut/selectors.json create mode 100644 vendor/couchapp/evently/account/loginForm/after.js create mode 100644 vendor/couchapp/evently/account/loginForm/mustache.html create mode 100644 vendor/couchapp/evently/account/loginForm/selectors/a[href=#signup].json create mode 100644 vendor/couchapp/evently/account/loginForm/selectors/form/submit.js create mode 100644 vendor/couchapp/evently/account/signupForm/after.js create mode 100644 vendor/couchapp/evently/account/signupForm/mustache.html create mode 100644 vendor/couchapp/evently/account/signupForm/selectors/a[href=#login].json create mode 100644 vendor/couchapp/evently/account/signupForm/selectors/form/submit.js create mode 100644 vendor/couchapp/evently/docs/index/data.js create mode 100644 vendor/couchapp/evently/docs/index/mustache.html create mode 100644 vendor/couchapp/evently/docs/index/path.txt create mode 100644 vendor/couchapp/evently/docs/topic/after.js create mode 100644 vendor/couchapp/evently/docs/topic/data.js create mode 100644 vendor/couchapp/evently/docs/topic/edit/_init/fun.js create mode 100644 vendor/couchapp/evently/docs/topic/edit/_init/selectors/a.edit/click.js create mode 100644 vendor/couchapp/evently/docs/topic/edit/_init/selectors/a.run/click.js create mode 100644 vendor/couchapp/evently/docs/topic/mustache.html create mode 100644 vendor/couchapp/evently/docs/topic/path.txt create mode 100644 vendor/couchapp/evently/profile/loggedIn.js create mode 100644 vendor/couchapp/evently/profile/loggedOut/after.js create mode 100644 vendor/couchapp/evently/profile/loggedOut/mustache.html create mode 100644 vendor/couchapp/evently/profile/noProfile/data.js create mode 100644 vendor/couchapp/evently/profile/noProfile/mustache.html create mode 100644 vendor/couchapp/evently/profile/noProfile/selectors/form/submit.js create mode 100644 vendor/couchapp/evently/profile/profileReady/after.js create mode 100644 vendor/couchapp/evently/profile/profileReady/data.js create mode 100644 vendor/couchapp/evently/profile/profileReady/mustache.html create mode 100644 vendor/couchapp/lib/atom.js create mode 100644 vendor/couchapp/lib/cache.js create mode 100644 vendor/couchapp/lib/docform.js create mode 100644 vendor/couchapp/lib/list.js create mode 100644 vendor/couchapp/lib/markdown.js create mode 100644 vendor/couchapp/lib/md5.js create mode 100644 vendor/couchapp/lib/path.js create mode 100644 vendor/couchapp/lib/redirect.js create mode 100644 vendor/couchapp/metadata.json create mode 100644 views/recent-items/map.js diff --git a/README.md b/README.md new file mode 100644 index 0000000..ac2d814 --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +## Generated CouchApp + +This repo is a snapshot of the development going on in [The CouchApp generate command.](http://github.com/couchapp/couchapp/tree/master/templates/app/) + +It's easier for me to receive patches against couchapp/couchapp. This repo is intended primarily so you can deploy new versions of the example app without needing to upgrade CouchApp. \ No newline at end of file diff --git a/_attachments/index.html b/_attachments/index.html new file mode 100755 index 0000000..edfd825 --- /dev/null +++ b/_attachments/index.html @@ -0,0 +1,29 @@ + + + + My New CouchApp + + + +
+ +

Generated CouchApp

+ +
+
+ + + + + + diff --git a/_attachments/style/main.css b/_attachments/style/main.css new file mode 100755 index 0000000..1d61e0e --- /dev/null +++ b/_attachments/style/main.css @@ -0,0 +1,71 @@ +/* add styles here */ + +body { + font:1em Helvetica, sans-serif; + padding:4px; +} + +#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; +} diff --git a/_id b/_id new file mode 100644 index 0000000..703f3c2 --- /dev/null +++ b/_id @@ -0,0 +1 @@ +_design/proto \ No newline at end of file diff --git a/couchapp.json b/couchapp.json new file mode 100644 index 0000000..0cc524d --- /dev/null +++ b/couchapp.json @@ -0,0 +1,4 @@ +{ + "name": "Name of your CouchApp", + "description": "CouchApp" +} \ No newline at end of file diff --git a/evently/items/_changes/data.js b/evently/items/_changes/data.js new file mode 100644 index 0000000..00ad89c --- /dev/null +++ b/evently/items/_changes/data.js @@ -0,0 +1,11 @@ +function(data) { + $.log(data) + var p; + return { + items : data.rows.map(function(r) { + p = r.value.profile; + p.message = r.value.message; + return p; + }) + } +}; \ No newline at end of file diff --git a/evently/items/_changes/mustache.html b/evently/items/_changes/mustache.html new file mode 100644 index 0000000..4634034 --- /dev/null +++ b/evently/items/_changes/mustache.html @@ -0,0 +1,18 @@ +

Customize this format here: ddoc.evently.items._changes.mustache

+

Recent Messages

+ +

Protip: If you setup continuous replication between this database and a remote one, this list will reflect remote changes in near real-time.

+

This would be a good place to add pagination.

diff --git a/evently/items/_changes/query.json b/evently/items/_changes/query.json new file mode 100644 index 0000000..f06e992 --- /dev/null +++ b/evently/items/_changes/query.json @@ -0,0 +1,5 @@ +{ + "view" : "recent-items", + "descending" : "true", + "limit" : 50 +} \ No newline at end of file diff --git a/evently/profile/profileReady/mustache.html b/evently/profile/profileReady/mustache.html new file mode 100644 index 0000000..1ad2bc0 --- /dev/null +++ b/evently/profile/profileReady/mustache.html @@ -0,0 +1,14 @@ +

Most applications will customize this template (ddoc.evently.profile.profileReady.mustache) for user input.

+ +
+ {{#gravatar_url}}{{/gravatar_url}} +
+ {{name}} +
+
+ +
+ +
+ +
\ No newline at end of file diff --git a/evently/profile/profileReady/selectors/form/submit.js b/evently/profile/profileReady/selectors/form/submit.js new file mode 100644 index 0000000..bf18dd8 --- /dev/null +++ b/evently/profile/profileReady/selectors/form/submit.js @@ -0,0 +1,14 @@ +function() { + var form = this; + var doc = { + created_at : new Date(), + profile : $$("#profile").profile, + message : $("[name=message]", form).val() + }; + $$(this).app.db.saveDoc(doc, { + success : function() { + $("[name=message]", form).val(""); + } + }); + return false; +}; diff --git a/language b/language new file mode 100644 index 0000000..f504a95 --- /dev/null +++ b/language @@ -0,0 +1 @@ +javascript \ No newline at end of file diff --git a/vendor/couchapp/README.md b/vendor/couchapp/README.md new file mode 100644 index 0000000..2606c1d --- /dev/null +++ b/vendor/couchapp/README.md @@ -0,0 +1,3 @@ +## CouchApp - more than just a filesystem mapper + +This is where documentation will go for the client and server JavaScript parts of CouchApp. \ No newline at end of file diff --git a/vendor/couchapp/_attachments/docs.css b/vendor/couchapp/_attachments/docs.css new file mode 100644 index 0000000..94b19d6 --- /dev/null +++ b/vendor/couchapp/_attachments/docs.css @@ -0,0 +1,53 @@ +body { + font:1em Helvetica, sans-serif; + margin:0; + padding:4px; +} + +h1 { + margin:0.5em; +} + +h2 { + color:#222; +} + +pre { + padding:4px; + margin:4px; + background:#bbb; +} + +#content { + padding:4px; + margin:2px; +} + +#sidebar { + float:right; + width:34%; +} + +#docs { + -moz-box-shadow:0 0 2em #000; + -webkit-box-shadow:0 0 2em #000; + width:58%; + padding:8px; + margin:4px; +} + +.example { + background:#ffd; + padding:4px; + margin:4px; + position:absolute; +} + +textarea.code { + width:100%; +} + +.edit { + float:right; + font-size:0.8em; +} diff --git a/vendor/couchapp/_attachments/docs.html b/vendor/couchapp/_attachments/docs.html new file mode 100644 index 0000000..77220b7 --- /dev/null +++ b/vendor/couchapp/_attachments/docs.html @@ -0,0 +1,32 @@ + + + + Evently and CouchApp Docs + + + + +
+ +
+
+
+
+ + + + + + + + + + + + + diff --git a/vendor/couchapp/_attachments/docs.js b/vendor/couchapp/_attachments/docs.js new file mode 100644 index 0000000..7b0e4d0 --- /dev/null +++ b/vendor/couchapp/_attachments/docs.js @@ -0,0 +1,8 @@ +$.log = function() { + // console.log(arguments) +}; + +$.couch.app(function(app) { + $("#docs").evently(app.ddoc.vendor.couchapp.evently.docs, app); + $.pathbinder.begin("/"); +}); \ No newline at end of file diff --git a/vendor/couchapp/_attachments/jquery.couch.app.js b/vendor/couchapp/_attachments/jquery.couch.app.js new file mode 100644 index 0000000..99392c4 --- /dev/null +++ b/vendor/couchapp/_attachments/jquery.couch.app.js @@ -0,0 +1,292 @@ +// 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) { + this.doc_id = "_design/"+name; + this.view = function(view, opts) { + db.view(name+'/'+view, opts); + }; + this.list = function(list, view, opts) { + db.list(name+'/'+list, view, opts); + }; + } + + $.couch.app = $.couch.app || function(appFun, opts) { + opts = opts || {}; + $(function() { + var dbname = opts.db || document.location.href.split('/')[3]; + var dname = opts.design || unescape(document.location.href).split('/')[5]; + var db = $.couch.db(dbname); + var design = new Design(db, dname); + + // docForm applies CouchDB behavior to HTML forms. + // todo make this a couch.app plugin + 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); + opts.fields.forEach(function(field) { + var element = form.find("[name="+field+"]"); + if (element.attr('type') === 'checkbox') { + var val = element.attr('checked'); + } else { + var val = element.val(); + if (!val) return; + } + var parts = field.split('-'); + var frontObj = doc, frontName = parts.shift(); + 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(); + // 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, { + success: function(doc) { + if (opts.onLoad) {opts.onLoad(doc);} + localFormDoc = doc; + docToForm(doc); + }}); + } else if (opts.template) { + if (opts.onLoad) {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; + } + + function resolveModule(names, parent, current) { + if (names.length === 0) { + if (typeof current != "string") { + throw ["error","invalid_require_path", + 'Must require a JavaScript string, not: '+(typeof current)]; + } + return [current, parent]; + } + // we need to traverse the path + var n = names.shift(); + if (n == '..') { + if (!(parent && parent.parent)) { + throw ["error", "invalid_require_path", 'Object has no parent '+JSON.stringify(current)]; + } + return resolveModule(names, parent.parent.parent, parent.parent); + } else if (n == '.') { + if (!parent) { + throw ["error", "invalid_require_path", 'Object has no parent '+JSON.stringify(current)]; + } + return resolveModule(names, parent.parent, parent); + } + if (!current[n]) { + throw ["error", "invalid_require_path", 'Object has no property "'+n+'". '+JSON.stringify(current)]; + } + var p = current; + current = current[n]; + current.parent = p; + return resolveModule(names, p, current); + } + + var p = document.location.pathname.split('/'); + p.shift(); + var qs = document.location.search.replace(/^\?/,'').split('&'); + var q = {}; + qs.forEach(function(param) { + var ps = param.split('='); + var k = decodeURIComponent(ps[0]); + var v = decodeURIComponent(ps[1]); + if (["startkey", "endkey", "key"].indexOf(k) != -1) { + q[k] = JSON.parse(v); + } else { + q[k] = v; + } + }); + var mockReq = { + path : p, + query : q + }; + + var appExports = $.extend({ + db : db, + design : design, + view : design.view, + list : design.list, + docForm : docForm, + req : mockReq + }, $.couch.app.app); + + function handleDDoc(ddoc) { + if (ddoc) { + var require = function(name, parent) { + var exports = {}; + var resolved = resolveModule(name.split('/'), parent, ddoc); + var source = resolved[0]; + parent = resolved[1]; + var s = "var func = function (exports, require) { " + source + " };"; + try { + eval(s); + func.apply(ddoc, [exports, function(name) {return require(name, parent, source)}]); + } catch(e) { + throw ["error","compilation_error","Module require('"+name+"') raised error "+e.toSource()]; + } + return exports; + } + appExports.ddoc = ddoc; + appExports.require = require; + } + // todo make app-exports the this in the execution context? + appFun.apply(appExports, [appExports]); + } + + if ($.couch.app.ddocs[design.doc_id]) { + 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]; + db.openDoc(design.doc_id, { + success : function(doc) { + $.couch.app.ddocs[design.doc_id] = doc; + $.couch.app.ddoc_handlers[design.doc_id].forEach(function(h) { + h(doc); + }); + $.couch.app.ddoc_handlers[design.doc_id] = null; + }, + error : function() { + $.couch.app.ddoc_handlers[design.doc_id].forEach(function(h) { + 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; + }; +} diff --git a/vendor/couchapp/_attachments/jquery.couch.app.util.js b/vendor/couchapp/_attachments/jquery.couch.app.util.js new file mode 100644 index 0000000..029bf97 --- /dev/null +++ b/vendor/couchapp/_attachments/jquery.couch.app.util.js @@ -0,0 +1,66 @@ +$.log = function(m) { + if (window && window.console && window.console.log) { + window.console.log(arguments.length == 1 ? m : arguments); + } +}; + +// todo remove this crap +function escapeHTML(st) { + return( + st && st.replace(/&/g,'&'). + replace(/>/g,'>'). + replace(/'+a+''; + }).replace(/\@([\w\-]+)/g,function(user,name) { + return ''+user+''; + }).replace(/\#([\w\-\.]+)/g,function(word,tag) { + return ''+word+''; + }); +}; + +$.fn.prettyDate = function() { + $(this).each(function() { + $(this).text($.prettyDate($(this).text())); + }); +}; + +$.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" || + 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; +} diff --git a/vendor/couchapp/_attachments/jquery.evently.js b/vendor/couchapp/_attachments/jquery.evently.js new file mode 100644 index 0000000..be084b9 --- /dev/null +++ b/vendor/couchapp/_attachments/jquery.evently.js @@ -0,0 +1,345 @@ +// $$ 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) { + 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}); + 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); + 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 : {} + }; + + function extractFrom(name, evs) { + return evs[name]; + }; + + function extractEvents(name, ddoc) { + // extract events from ddoc.evently and ddoc.vendor.*.evently + var events = [true, {}]; + $.forIn(ddoc.vendor, function(k, v) { + if (v.evently && v.evently[name]) { + events.push(v.evently[name]); + } + }); + if (ddoc.evently[name]) {events.push(ddoc.evently[name]);} + return $.extend.apply(null, 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); + } + + $$(elem).evently = events; + // setup the handlers onto elem + forIn(events, function(name, h) { + eventlyHandler(elem, name, h, args); + }); + + if (events._init) { + $.log("ev _init", elem); + elem.trigger("_init", args); + } + + if (app && events._changes) { + $("body").bind("evently.changes."+app.db.name, function() { + // we want to unbind this function when the element is deleted. + // maybe jquery 1.4.2 has this covered? + $.log('changes', elem); + 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 (h.path) { + elem.pathbinder(name, h.path); + } + var f = funViaString(h); + 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) { + elem.bind(name, {args:args}, funViaString(h.fun)); + } + // 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.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"; + 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) { + funViaString(h.after).apply(me, args); + } + } + }; + + // todo this should return the new element + function mustachioed(me, h, args) { + return $($.mustache( + runIfFun(me, h.mustache, args), + runIfFun(me, h.data, args), + runIfFun(me, h.partials, args))); + }; + + function runAsync(me, h, args) { + // the callback is the first argument + funViaString(h.async).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) { + 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; + if (!$.evently.changesDBs[dbName]) { + connectToChanges(app, function() { + $("body").trigger("evently.changes."+dbName); + }); + $.evently.changesDBs[dbName] = true; + } + } + + function connectToChanges(app, fun) { + function resetHXR(x) { + x.abort(); + connectToChanges(app, fun); + }; + app.db.info({success: function(db_info) { + var c_xhr = jQuery.ajaxSettings.xhr(); + c_xhr.open("GET", app.db.uri+"_changes?feed=continuous&since="+db_info.update_seq, true); + c_xhr.send(""); + // todo use a timeout to prevent rapid triggers + var t; + c_xhr.onreadystatechange = function() { + clearTimeout(t); + t = setTimeout(fun, 100); + }; + setTimeout(function() { + resetHXR(c_xhr); + }, 1000 * 60); + }}); + }; + +})(jQuery); diff --git a/vendor/couchapp/_attachments/jquery.mustache.js b/vendor/couchapp/_attachments/jquery.mustache.js new file mode 100644 index 0000000..701ed0d --- /dev/null +++ b/vendor/couchapp/_attachments/jquery.mustache.js @@ -0,0 +1,282 @@ +/* +Shameless port of a shameless port +@defunkt => @janl => @aq + +See http://github.com/defunkt/mustache for more info. +*/ + +;(function($) { + +/* + Shamless port of http://github.com/defunkt/mustache + by Jan Lehnardt , Alexander Lang , + Sebastian Cohnen + + Thanks @defunkt for the awesome code. + + See http://github.com/defunkt/mustache for more info. +*/ + +var Mustache = function() { + var Renderer = function() {}; + + Renderer.prototype = { + otag: "{{", + ctag: "}}", + pragmas: {}, + + render: function(template, context, partials) { + // fail fast + if(template.indexOf(this.otag) == -1) { + return template; + } + + template = this.render_pragmas(template); + var html = this.render_section(template, context, partials); + return this.render_tags(html, context, partials); + }, + + /* + Looks for %PRAGMAS + */ + render_pragmas: function(template) { + // no pragmas + if(template.indexOf(this.otag + "%") == -1) { + return template; + } + + var that = this; + var regex = new RegExp(this.otag + "%(.+)" + this.ctag); + return template.replace(regex, function(match, pragma) { + that.pragmas[pragma] = true; + return ""; + // ignore unknown pragmas silently + }); + }, + + /* + Tries to find a partial in the global scope and render it + */ + render_partial: function(name, context, partials) { + if(typeof(context[name]) != "object") { + throw({message: "subcontext for '" + name + "' is not an object"}); + } + if(!partials || !partials[name]) { + throw({message: "unknown_partial"}); + } + return this.render(partials[name], context[name], partials); + }, + + /* + Renders boolean and enumerable sections + */ + render_section: function(template, context, partials) { + if(template.indexOf(this.otag + "#") == -1) { + return template; + } + var that = this; + // CSW - Added "+?" so it finds the tighest bound, not the widest + var regex = new RegExp(this.otag + "\\#(.+)" + this.ctag + + "\\s*([\\s\\S]+?)" + this.otag + "\\/\\1" + this.ctag + "\\s*", "mg"); + + // for each {{#foo}}{{/foo}} section do... + return template.replace(regex, function(match, name, content) { + var value = that.find(name, context); + if(that.is_array(value)) { // Enumerable, Let's loop! + return that.map(value, function(row) { + return that.render(content, that.merge(context, + that.create_context(row)), partials); + }).join(''); + } else if(value) { // boolean section + return that.render(content, context, partials); + } else { + return ""; + } + }); + }, + + /* + Replace {{foo}} and friends with values from our view + */ + render_tags: function(template, context, partials) { + var lines = template.split("\n"); + + var new_regex = function() { + return new RegExp(that.otag + "(=|!|>|\\{|%)?([^\/#]+?)\\1?" + + that.ctag + "+", "g"); + }; + + // tit for tat + var that = this; + + var regex = new_regex(); + for (var i=0; i < lines.length; i++) { + lines[i] = lines[i].replace(regex, function (match,operator,name) { + switch(operator) { + case "!": // ignore comments + return match; + case "=": // set new delimiters, rebuild the replace regexp + that.set_delimiters(name); + regex = new_regex(); + // redo the line in order to get tags with the new delimiters + // on the same line + i--; + return ""; + case ">": // render partial + return that.render_partial(name, context, partials); + case "{": // the triple mustache is unescaped + return that.find(name, context); + return ""; + default: // escape the value + return that.escape(that.find(name, context)); + } + },this); + }; + 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); + if(typeof context[name] === "function") { + return context[name].apply(context); + } + if(context[name] !== undefined) { + return context[name]; + } + // silently ignore unkown variables + return ""; + }, + + // Utility methods + + /* + Does away with nasty characters + */ + escape: function(s) { + return s.toString().replace(/[&"<>\\]/g, function(s) { + switch(s) { + case "&": return "&"; + case "\\": return "\\\\";; + case '"': return '\"';; + case "<": return "<"; + case ">": return ">"; + default: return s; + } + }); + }, + + /* + Merges all properties of object `b` into object `a`. + `b.property` overwrites a.property` + */ + merge: function(a, b) { + var _new = {}; + for(var name in a) { + if(a.hasOwnProperty(name)) { + _new[name] = a[name]; + } + }; + for(var name in b) { + if(b.hasOwnProperty(name)) { + _new[name] = b[name]; + } + }; + return _new; + }, + + // by @langalex, support for arrays of strings + create_context: function(_context) { + if(this.is_object(_context)) { + return _context; + } else if(this.pragmas["JSTACHE-ENABLE-STRING-ARRAYS"]) { + return {'.': _context}; + } + }, + + is_object: function(a) { + return a && typeof a == 'object' + }, + + /* + Thanks Doug Crockford + JavaScript — The Good Parts lists an alternative that works better with + frames. Frames can suck it, we use the simple version. + */ + is_array: function(a) { + return (a && + typeof a === 'object' && + a.constructor === 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(i=0;i'; +}).join('')); diff --git a/vendor/couchapp/docs/account.md b/vendor/couchapp/docs/account.md new file mode 100644 index 0000000..e60928a --- /dev/null +++ b/vendor/couchapp/docs/account.md @@ -0,0 +1,187 @@ +# Docs for the account widget + +You should use this widget in any CouchApp that allows users to login or signup. + +It is easy to install. To use the account widget, just define a `div` in your page and use [CouchApp](#/topic/couchapp) to load it from the design document and [Evently](#/topic/evently) to apply it to the page. + +Here's the most basic usage: + + $.couch.app(function(app){ + $("#basic_account").evently(app.ddoc.vendor.couchapp.evently.account); + }); + +Run this example and try signing up, and logging in and out. This code is part of the CouchApp standard library, so feel free to use it in your applications. It is also "trivial" to replace and extend the functionality. In a minutes, we'll start by replacing a template, and then show how you can use Evently to connect multiple widgets, to build more complex applications. + +## A widget is a collection of event handlers + +First lets look more closely at the account widget (click Run to see the code we're about to discuss, it will appear in the sidebar -- the example code below is just used for loading and displaying the account widget source code). + + $.couch.app(function(app){ + $("#account_widget_code").evently({ + _init : { + mustache : "
{{json}}
", + data : function() { + var widget = app.ddoc.vendor.couchapp.evently.account; + return { + json : JSON.stringify(widget, null, 2) + } + } + } + }); + }); + +The top level keys are the most important: `loggedIn`, `loggedOut`, `adminParty`, `signupForm`, `loginForm`, `doLogin`, `doSignup`, and `_init`. Each one of these corresponds to an event or state the system can be in. Some of them draw user interface elements, other directly trigger further events. + +### _init + +The `_init` event is special, in that Evently will automatically trigger it when the widget is created. Here is the code for the account widget's `_init` event handler. + + function() { + var elem = $(this); + $.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"); + }; + } + }); + } + +This code does one query to CouchDB, to retrieve the session information for the current user. For this we use the `$.couch.session()` function which is part of the [jquery.couch.js](/_utils/script/jquery.couch.js) library which is part of the CouchDB distribution. + +The response is handled in one of three ways, depending on the user's session information. Either we trigger the `loggedIn` or `loggedOut` events, or in the special case where we detect that CouchDB's security is not properly configured, we trigger the `adminParty` event to warn the user. + +### loggedOut + +Because most visitors start logged out, let's now turn our attention to the `loggedOut` event handler to see what will greet a new visitor: + + "loggedOut": { + "mustache": "Signup or Login", + "selectors": { + "a[href=#login]": { + "click": "loginForm" + }, + "a[href=#signup]": { + "click": "signupForm" + } + } + } + +There are two main components to this handler: `mustache` and `selectors`. `mustache` is a template file with two HTML links. `selectors` contains a set of CSS selectors with events bound to them. You can think of each selector as a nested Evently widget. In this case, clicking "Login" will trigger the `loginForm` event, while clicking "Signup" triggers the `signupForm` event. + +### signupForm + +Let's see what happens during signup. We'll skip showing the whole handler (it should be in the sidebar anyway if you clicked "run" earlier.) + +When the `signupForm` event is triggered, a mustache template draws the form. Then the selectors are run, assigning this function to the form's submit event: + + function(e) { + var name = $('input[name=name]', this).val(), + pass = $('input[name=password]', this).val(); + $(this).trigger('doSignup', [name, pass]); + return false; + } + +This handler is as simple as possible, all it does is use jQuery to pull the user data from the form, and send the name and password to the `doSignup` event. We could just use a function call here, but it's nice to keep our individual events as small as possible, as this makes customizing Evently widgets simpler. + +### doSignup + +Here is the `doSignup` handler: + + function(e, name, pass) { + var elem = $(this); + $.couch.signup({ + name : name + }, pass, { + success : function() { + elem.trigger("doLogin", [name, pass]); + } + }); + } + +Again, all the complex signup logic (encrypting passwords, etc) is pushed to the [jquery.couch.js](/_utils/script/jquery.couch.js) library (via the `$.couch.signup()` call), so our application code can stay as simple as possible. When signup is complete, we trigger the `doLogin` event, so new users don't have to go through another action. + +### doLogin + +The code for `doLogin` isn't much different, just take the name and password, and call a jquery.couch.js library function with it. + + function(e, name, pass) { + var elem = $(this); + $.couch.login({ + name : name, + password : pass, + success : function(r) { + elem.trigger("_init") + } + }); + } + +The last thing that `doLogin` does is trigger `_init`, so we come full circle! This time, `_init` will see that the user is logged in, and trigger the `loggedIn` event. You'll probably want to hook your application to this `loggedIn` event, to activate any features which are reserved for registered users. We'll cover linking events in a later section. + +## Customizing the account widget + +Evently widgets are built out of JSON objects, which makes it easy to replace bits and pieces of them without having to mess with the entire widget. We'll start by customizing what users see when they are logged in. + + $.couch.app(function(app){ + var customizedWidget = $.extend(true, {}, app.ddoc.vendor.couchapp.evently.account, { + loggedIn : { + mustache : 'Hello {{name}} you are logged in! ' + + 'Would you like to logout?' + } + }); + $("#customWelcome").evently(customizedWidget); + }); + +Take a moment to run this example code and login to see how our custom template has replaced just one screen in the widget. The first time I did this I thought it was pretty cool. Hopefully you can think of a lot of powerful stuff you could do with it. The sky is the limit. + +Here's another quick one: + + $.couch.app(function(app){ + var customizedWidget = $.extend(true, {}, app.ddoc.vendor.couchapp.evently.account, { + loggedOut : { + after : "function(){alert('Bye bye');}" + } + }); + $("#afterAlert").evently(customizedWidget); + }); + +For a deeper reference on what the various parts of an Evently widget are named, and how you can use them, see [the Evently docs page](#/topic/evently). + +## Linking two widgets + +First, lets create a basic widget. This one just has an `_init` handler and a handler called `loggedIn`. There is nothing in this widget definition that will trigger `loggedIn`, unless something else triggers it, there's no way it will run. + + $("#link_target").evently({ + _init : { + mustache : "

Not much to see here

" + }, + loggedIn : { + mustache : "

loggedIn was triggered from another widget, {{name}}.

", + data : function(e, r) { + return { name : r.userCtx.name }; + } + } + }); + +Be sure to run the above example code before the next one, otherwise there won't be anything to link to. + +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. + + $.couch.app(function(app){ + $("#link_source").evently(app.ddoc.vendor.couchapp.evently.account); + // link the source to the target, for the loggedIn event + $.evently.connect($("#link_source"), $("#link_target"), ["loggedIn"]); + }); + +## Conclusion + +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. + +Enjoy! + + \ No newline at end of file diff --git a/vendor/couchapp/docs/couchapp.md b/vendor/couchapp/docs/couchapp.md new file mode 100644 index 0000000..ab1f603 --- /dev/null +++ b/vendor/couchapp/docs/couchapp.md @@ -0,0 +1,34 @@ +# Docs for $.couch.app + +The simplest use of CouchApp in the browser is to get access to information about the database you are running in. + + $.couch.app(function(app) { + $("#dbinfo").evently({ + _init : { + mustache : '

The db name is {{name}}

', + data : app.db + } + }); + }); + +Yay couchapp. + +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: + + $.couch.app(function(app) { + $("#ddoc").evently({ + _init : { + mustache : '

Click to show the full doc source:

{{ddoc}}
', + data : { + ddoc : JSON.stringify(app.ddoc, null, 2).slice(0,100) + '...' + } + }, + click : { + mustache : '

The full doc source (rerun to hide):

{{ddoc}}
', + data : { + ddoc : JSON.stringify(app.ddoc, null, 2) + } + } + }); + }); + diff --git a/vendor/couchapp/docs/docs.md b/vendor/couchapp/docs/docs.md new file mode 100644 index 0000000..e4c42ab --- /dev/null +++ b/vendor/couchapp/docs/docs.md @@ -0,0 +1,11 @@ +# Docs for the docs system. + +You are encouraged to use the couchapp docs system to write documentation for your plugins and applications. Extra bonus points because it's fun. + +Docs automatically make divs based on `$("#foo")` pattern matching. That is, we regex the code looking for the first id we see referenced. Remember ids need to be unique on a page. For doc examples you only get one id. + +Example Code: + + $("#hide_foo").hide("slow"); + +That's all it takes. You only get one div in each example for now. Have fun! \ No newline at end of file diff --git a/vendor/couchapp/docs/evently.md b/vendor/couchapp/docs/evently.md new file mode 100644 index 0000000..bed0a70 --- /dev/null +++ b/vendor/couchapp/docs/evently.md @@ -0,0 +1,185 @@ +# Evently Docs + +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. + +Evently has special handlers for CouchDB views and `_changes` feeds, and could be easily extended for other server-side frameworks. + +## Hello World + +At it's simplest an Evently widget is a set of events connected to a single DOM element. + +JavaScript: + + $("#hello").evently({ + _init : { + mustache : "

Hello world

", + }, + click : { + mustache : "

What a crazy world!

", + } + }); + +You can also do some more interesting things: + + $("#heyjane").evently({ + _init : { + mustache : '

Hello Jane, Joan (pick one)

', + selectors : { + 'a[href=#joan]' : { + click : 'hiJoan' + }, + 'a[href=#jane]' : { + click : 'hiJane' + } + } + }, + hiJoan : { + mustache : '

Hello Joan!

' + }, + hiJane : { + mustache : "

Darn, it's Jane...

", + after : function() { + setTimeout(function() { + // automatically trigger the "janeRocks" event after 2 seconds. + $("#heyjane").trigger("janeRocks"); + }, 2000); + } + }, + janeRocks : { + render : "append", + mustache : "

Actually Jane is awesome.

" + } + }); + + +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. + +[screenshot of the above code in textmate's file drawer] + +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! + +## Ajax Hello World + +Let's do a little Ajax. We'll just load the version of the CouchDB instance we happen to be serving our HTML from: + + $("#ajax").evently({ + _init : { + mustache : '

Loading CouchDB server info.

', + after : function() { + var widget = $(this); + $.ajax({ + url : '/', + complete : function(req) { + var resp = $.httpData(req, "json"); + widget.trigger("version", [resp]); + } + }) + } + }, + version : { + mustache : "

Running CouchDB version {{version}}

", + data : function(e, resp) { + return resp; + } + } + }); + +Explain `mustache` and `data` + +-- triggering other events + -- selectors + -- create a doc + +## Evently and CouchApp together + +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. + +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: + + _init/ + mustache.html + selectors/ + form/ + submit.js + input.name/ + change.js + a.cancel/ + click.txt + cancelled/ + mustache.html + selectors/ + a.continue/ + click.txt + +It will appear within your CouchApp design document as: + + { + _init : { + mustache : "contents of mustache.html", + selectors { + form : { + submit : "function() { ... }" + }, + "input.name" { + change : "function() { ... }" + }, + "a.cancel" { + click : "cancelled" + } + } + }, + cancelled : { + mustache : "contents of mustache.html", + selectors : { + "a.continue" : { + click : "_init" + } + } + } + } + +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. + +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. + +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. + +## Evently Queries + +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. + +-- new rows, etc + +-- run a query + +-- connect to changes + +-- links to example apps + +## Freeform Asynchronous Actions + +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: + + $("#async").evently({ + _init : { + mustache : "

How many databases on the local host?

Answer: {{number_of_dbs}}

Other stuff: {{args}}

More: {{allArgs}}

", + async : function(cb) { + var ag = Array.prototype.slice.call(arguments).map(function(a){return a.toSource ? a.toSource() : a}); + $.couch.allDbs({ + success : function(resp) { + cb(resp.length, ag); + } + }) + }, + data : function(count, args) { + return { + number_of_dbs : count, + args : JSON.stringify(args), + allArgs : JSON.stringify(Array.prototype.slice.call(arguments)) + }; + } + }, + click : { + mustache : "

What a crazy world!

", + } + }); \ No newline at end of file diff --git a/vendor/couchapp/docs/pathbinder.md b/vendor/couchapp/docs/pathbinder.md new file mode 100644 index 0000000..7b3325b --- /dev/null +++ b/vendor/couchapp/docs/pathbinder.md @@ -0,0 +1,52 @@ +# Docs about $.pathbinder + +Pathbinder is a tiny framework for triggering events based on paths in URL hash. For example, you might want to render one panel when the user clicks a link to `#/foo` and another when the URL hash changes to `#/bar`. If you've never used URL hashes for application state in an Ajax app before, prepare to be happy. + +There are two big advantages to having the state in the URL-hash. One is that users can bookmark screens they may have reached by navigating within your app. The other is that the back button will continue to work. + +The page you are on has a URL hash of `#/topic/pathbinder` right now. You can follow links to other "pages" within this application, and Pathbinder takes care of triggering the proper events. + +## A simple example + + $("#basic_path").html('

click for foo

'); + $("#basic_path").bind("foo", function() { + $(this).html("

you went to foo

"); + }); + $("#basic_path").pathbinder("foo", "/foo"); + +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. + +## Using path parameters + +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. + + $("#param_path").html('

click for super foo

'); + $("#param_path").bind("foo", function(e, params) { + $(this).html("

you went to foo - "+params.id+"

"); + }); + $("#param_path").pathbinder("foo", "/foo/:id"); + +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. + +## Pathbinder with Evently + +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: + + $("#evently_path").evently({ + _init : { + path : '/index', + mustache : '

the index. more cowbell!

' + }, + cowbell : { + path : '/cowbell', + mustache : '

Now that is a lot of cowbell. back to the index

' + } + }); + +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. + +### Too many widgets + +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). + +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. diff --git a/vendor/couchapp/docs/profile.md b/vendor/couchapp/docs/profile.md new file mode 100644 index 0000000..4ddf46e --- /dev/null +++ b/vendor/couchapp/docs/profile.md @@ -0,0 +1,3 @@ +# docs for the profile evently widget + +This widget makes it easy to give users a profile for your application. \ No newline at end of file diff --git a/vendor/couchapp/evently/README.md b/vendor/couchapp/evently/README.md new file mode 100644 index 0000000..9f9ba18 --- /dev/null +++ b/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. diff --git a/vendor/couchapp/evently/account/_init.js b/vendor/couchapp/evently/account/_init.js new file mode 100644 index 0000000..e3411c5 --- /dev/null +++ b/vendor/couchapp/evently/account/_init.js @@ -0,0 +1,15 @@ +function() { + var elem = $(this); + $.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"); + }; + } + }); +} \ No newline at end of file diff --git a/vendor/couchapp/evently/account/adminParty.js b/vendor/couchapp/evently/account/adminParty.js new file mode 100644 index 0000000..9796c95 --- /dev/null +++ b/vendor/couchapp/evently/account/adminParty.js @@ -0,0 +1,3 @@ +function() { + alert("Admin party! Fix this in Futon before proceeding."); +} \ No newline at end of file diff --git a/vendor/couchapp/evently/account/doLogin.js b/vendor/couchapp/evently/account/doLogin.js new file mode 100644 index 0000000..cc1b883 --- /dev/null +++ b/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") + } + }); +} \ No newline at end of file diff --git a/vendor/couchapp/evently/account/doLogout.js b/vendor/couchapp/evently/account/doLogout.js new file mode 100644 index 0000000..62c851c --- /dev/null +++ b/vendor/couchapp/evently/account/doLogout.js @@ -0,0 +1,8 @@ +function() { + var elem = $(this); + $.couch.logout({ + success : function() { + elem.trigger("_init"); + } + }); +} \ No newline at end of file diff --git a/vendor/couchapp/evently/account/doSignup.js b/vendor/couchapp/evently/account/doSignup.js new file mode 100644 index 0000000..cbe4031 --- /dev/null +++ b/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]); + } + }); +} \ No newline at end of file diff --git a/vendor/couchapp/evently/account/loggedIn/after.js b/vendor/couchapp/evently/account/loggedIn/after.js new file mode 100644 index 0000000..7be472c --- /dev/null +++ b/vendor/couchapp/evently/account/loggedIn/after.js @@ -0,0 +1,5 @@ +// todo move to template +function(e, r) { + $(this).attr("data-name", r.userCtx.name); + $$(this).userCtx = r.userCtx; +} diff --git a/vendor/couchapp/evently/account/loggedIn/data.js b/vendor/couchapp/evently/account/loggedIn/data.js new file mode 100644 index 0000000..dc20700 --- /dev/null +++ b/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) + }; +} \ No newline at end of file diff --git a/vendor/couchapp/evently/account/loggedIn/mustache.html b/vendor/couchapp/evently/account/loggedIn/mustache.html new file mode 100644 index 0000000..fd24ddd --- /dev/null +++ b/vendor/couchapp/evently/account/loggedIn/mustache.html @@ -0,0 +1,4 @@ +Welcome +{{name}}! +Logout? + \ No newline at end of file diff --git a/vendor/couchapp/evently/account/loggedIn/selectors.json b/vendor/couchapp/evently/account/loggedIn/selectors.json new file mode 100644 index 0000000..ea0c23f --- /dev/null +++ b/vendor/couchapp/evently/account/loggedIn/selectors.json @@ -0,0 +1,3 @@ +{ + "a[href=#logout]" : {"click" : ["doLogout"]} +} \ No newline at end of file diff --git a/vendor/couchapp/evently/account/loggedOut/mustache.html b/vendor/couchapp/evently/account/loggedOut/mustache.html new file mode 100644 index 0000000..a21500b --- /dev/null +++ b/vendor/couchapp/evently/account/loggedOut/mustache.html @@ -0,0 +1 @@ +Signup or Login \ No newline at end of file diff --git a/vendor/couchapp/evently/account/loggedOut/selectors.json b/vendor/couchapp/evently/account/loggedOut/selectors.json new file mode 100644 index 0000000..adbf792 --- /dev/null +++ b/vendor/couchapp/evently/account/loggedOut/selectors.json @@ -0,0 +1,4 @@ +{ + "a[href=#signup]" : {"click" : ["signupForm"]}, + "a[href=#login]" : {"click" : ["loginForm"]} +} \ No newline at end of file diff --git a/vendor/couchapp/evently/account/loginForm/after.js b/vendor/couchapp/evently/account/loginForm/after.js new file mode 100644 index 0000000..0a360f1 --- /dev/null +++ b/vendor/couchapp/evently/account/loginForm/after.js @@ -0,0 +1,3 @@ +function() { + $("input[name=name]", this).focus(); +} \ No newline at end of file diff --git a/vendor/couchapp/evently/account/loginForm/mustache.html b/vendor/couchapp/evently/account/loginForm/mustache.html new file mode 100644 index 0000000..1d30c1d --- /dev/null +++ b/vendor/couchapp/evently/account/loginForm/mustache.html @@ -0,0 +1,6 @@ +
+ + + + or Signup +
diff --git a/vendor/couchapp/evently/account/loginForm/selectors/a[href=#signup].json b/vendor/couchapp/evently/account/loginForm/selectors/a[href=#signup].json new file mode 100644 index 0000000..58db028 --- /dev/null +++ b/vendor/couchapp/evently/account/loginForm/selectors/a[href=#signup].json @@ -0,0 +1 @@ +{"click" : ["signupForm"]} \ No newline at end of file diff --git a/vendor/couchapp/evently/account/loginForm/selectors/form/submit.js b/vendor/couchapp/evently/account/loginForm/selectors/form/submit.js new file mode 100644 index 0000000..b12d3bd --- /dev/null +++ b/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; +} \ No newline at end of file diff --git a/vendor/couchapp/evently/account/signupForm/after.js b/vendor/couchapp/evently/account/signupForm/after.js new file mode 100644 index 0000000..0a360f1 --- /dev/null +++ b/vendor/couchapp/evently/account/signupForm/after.js @@ -0,0 +1,3 @@ +function() { + $("input[name=name]", this).focus(); +} \ No newline at end of file diff --git a/vendor/couchapp/evently/account/signupForm/mustache.html b/vendor/couchapp/evently/account/signupForm/mustache.html new file mode 100644 index 0000000..051a200 --- /dev/null +++ b/vendor/couchapp/evently/account/signupForm/mustache.html @@ -0,0 +1,6 @@ +
+ + + + or Login +
diff --git a/vendor/couchapp/evently/account/signupForm/selectors/a[href=#login].json b/vendor/couchapp/evently/account/signupForm/selectors/a[href=#login].json new file mode 100644 index 0000000..c1b5598 --- /dev/null +++ b/vendor/couchapp/evently/account/signupForm/selectors/a[href=#login].json @@ -0,0 +1 @@ +{"click" : ["loginForm"]} \ No newline at end of file diff --git a/vendor/couchapp/evently/account/signupForm/selectors/form/submit.js b/vendor/couchapp/evently/account/signupForm/selectors/form/submit.js new file mode 100644 index 0000000..b11d82f --- /dev/null +++ b/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; +} \ No newline at end of file diff --git a/vendor/couchapp/evently/docs/index/data.js b/vendor/couchapp/evently/docs/index/data.js new file mode 100644 index 0000000..4dadcb5 --- /dev/null +++ b/vendor/couchapp/evently/docs/index/data.js @@ -0,0 +1,11 @@ +function() { + var docs = $$(this).app.ddoc.vendor.couchapp.docs; + var dnames = []; + $.forIn(docs, function(d) { + dnames.push({ + title: d, + href : "#/topic/"+encodeURIComponent(d) + }); + }); + return {docs:dnames}; +}; \ No newline at end of file diff --git a/vendor/couchapp/evently/docs/index/mustache.html b/vendor/couchapp/evently/docs/index/mustache.html new file mode 100644 index 0000000..797e0b5 --- /dev/null +++ b/vendor/couchapp/evently/docs/index/mustache.html @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/vendor/couchapp/evently/docs/index/path.txt b/vendor/couchapp/evently/docs/index/path.txt new file mode 100644 index 0000000..35ec3b9 --- /dev/null +++ b/vendor/couchapp/evently/docs/index/path.txt @@ -0,0 +1 @@ +/ \ No newline at end of file diff --git a/vendor/couchapp/evently/docs/topic/after.js b/vendor/couchapp/evently/docs/topic/after.js new file mode 100644 index 0000000..f048c27 --- /dev/null +++ b/vendor/couchapp/evently/docs/topic/after.js @@ -0,0 +1,15 @@ +function() { + var app = $$(this).app; + var self = $(this); + $("pre", self).each(function() { + var pre = $(this); + var js = pre.text(); + var r = js.match(/\$\(\"\#([^\"]*)\"\)/); + if (r) { + var id = r[1]; + var code_id = 'code-'+id; + pre.wrap('
'); + $('#'+code_id).evently(app.ddoc.vendor.couchapp.evently.docs.topic.edit, app, [id]); + } + }); +}; diff --git a/vendor/couchapp/evently/docs/topic/data.js b/vendor/couchapp/evently/docs/topic/data.js new file mode 100644 index 0000000..039efab --- /dev/null +++ b/vendor/couchapp/evently/docs/topic/data.js @@ -0,0 +1,8 @@ +function(e, p) { + var doc = $$(this).app.ddoc.vendor.couchapp.docs[p.id]; + var converter = new Showdown.converter(); + var html = converter.makeHtml(doc); + return { + html : html + }; +}; \ No newline at end of file diff --git a/vendor/couchapp/evently/docs/topic/edit/_init/fun.js b/vendor/couchapp/evently/docs/topic/edit/_init/fun.js new file mode 100644 index 0000000..8571348 --- /dev/null +++ b/vendor/couchapp/evently/docs/topic/edit/_init/fun.js @@ -0,0 +1,19 @@ +function(e, id) { + var editable = $(this); + if ($$(editable)._init_ran) {return false;} + // add edit link + var edit = $('edit code'); + editable.append(edit); + + // add run box + var example = $('
run #'+id+'
#'+id+' output will be here
'); + var s = $("#sidebar"); + var o = s.offset(); + example.offset({ + left: o.left + }); + example.width(s.width()*0.75); + editable.prepend(example); + $$(editable)._init_ran = true; + return false; +} \ No newline at end of file diff --git a/vendor/couchapp/evently/docs/topic/edit/_init/selectors/a.edit/click.js b/vendor/couchapp/evently/docs/topic/edit/_init/selectors/a.edit/click.js new file mode 100644 index 0000000..b90700f --- /dev/null +++ b/vendor/couchapp/evently/docs/topic/edit/_init/selectors/a.edit/click.js @@ -0,0 +1,9 @@ +function() { + var pre = $(this).prev('pre'); + var js = pre.text(); + var lines = js.split('\n').length; + var ta = $(''); + ta.text(js); + pre.replace(ta); + return false; +}; diff --git a/vendor/couchapp/evently/docs/topic/edit/_init/selectors/a.run/click.js b/vendor/couchapp/evently/docs/topic/edit/_init/selectors/a.run/click.js new file mode 100644 index 0000000..9babec5 --- /dev/null +++ b/vendor/couchapp/evently/docs/topic/edit/_init/selectors/a.run/click.js @@ -0,0 +1,22 @@ +function(e) { + try { + function err(y, id) { + $('#'+id).html(['

Error running #', id, + ' code block:

',
+      (y.toSource ? y.toSource() : JSON.stringify(y)),
+      '

'].join('')); + } + var id = e.data.args[1]; + var example = $("#code-"+id); + var js = $('textarea',example).val() || $('pre',example).text(); + $('#'+id).unbind(); + try { + eval(js); + } catch (y) { + err(y, id); + } + } catch(x) { + err(x, id); + } + return false; +} \ No newline at end of file diff --git a/vendor/couchapp/evently/docs/topic/mustache.html b/vendor/couchapp/evently/docs/topic/mustache.html new file mode 100644 index 0000000..e1b7349 --- /dev/null +++ b/vendor/couchapp/evently/docs/topic/mustache.html @@ -0,0 +1 @@ +
{{{html}}}
\ No newline at end of file diff --git a/vendor/couchapp/evently/docs/topic/path.txt b/vendor/couchapp/evently/docs/topic/path.txt new file mode 100644 index 0000000..52370e0 --- /dev/null +++ b/vendor/couchapp/evently/docs/topic/path.txt @@ -0,0 +1 @@ +/topic/:id \ No newline at end of file diff --git a/vendor/couchapp/evently/profile/loggedIn.js b/vendor/couchapp/evently/profile/loggedIn.js new file mode 100644 index 0000000..f3ae259 --- /dev/null +++ b/vendor/couchapp/evently/profile/loggedIn.js @@ -0,0 +1,22 @@ +function(e, r) { + var userCtx = r.userCtx; + var widget = $(this); + // load the profile from the user doc + $.couch.userDb(function(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]); + } + } + }); + }); +} diff --git a/vendor/couchapp/evently/profile/loggedOut/after.js b/vendor/couchapp/evently/profile/loggedOut/after.js new file mode 100644 index 0000000..d8954bc --- /dev/null +++ b/vendor/couchapp/evently/profile/loggedOut/after.js @@ -0,0 +1,3 @@ +function() { + $$(this).profile = null; +}; \ No newline at end of file diff --git a/vendor/couchapp/evently/profile/loggedOut/mustache.html b/vendor/couchapp/evently/profile/loggedOut/mustache.html new file mode 100644 index 0000000..e052574 --- /dev/null +++ b/vendor/couchapp/evently/profile/loggedOut/mustache.html @@ -0,0 +1 @@ +

Please log in to see your profile.

\ No newline at end of file diff --git a/vendor/couchapp/evently/profile/noProfile/data.js b/vendor/couchapp/evently/profile/noProfile/data.js new file mode 100644 index 0000000..d8fe586 --- /dev/null +++ b/vendor/couchapp/evently/profile/noProfile/data.js @@ -0,0 +1,3 @@ +function(e, userCtx) { + return userCtx; +} \ No newline at end of file diff --git a/vendor/couchapp/evently/profile/noProfile/mustache.html b/vendor/couchapp/evently/profile/noProfile/mustache.html new file mode 100644 index 0000000..8cf6563 --- /dev/null +++ b/vendor/couchapp/evently/profile/noProfile/mustache.html @@ -0,0 +1,11 @@ +
+

Hello {{name}}, Please setup your user profile.

+ + + + + +
\ No newline at end of file diff --git a/vendor/couchapp/evently/profile/noProfile/selectors/form/submit.js b/vendor/couchapp/evently/profile/noProfile/selectors/form/submit.js new file mode 100644 index 0000000..2f21530 --- /dev/null +++ b/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; +} \ No newline at end of file diff --git a/vendor/couchapp/evently/profile/profileReady/after.js b/vendor/couchapp/evently/profile/profileReady/after.js new file mode 100644 index 0000000..02e9bfe --- /dev/null +++ b/vendor/couchapp/evently/profile/profileReady/after.js @@ -0,0 +1,3 @@ +function(e, p) { + $$(this).profile = p; +}; \ No newline at end of file diff --git a/vendor/couchapp/evently/profile/profileReady/data.js b/vendor/couchapp/evently/profile/profileReady/data.js new file mode 100644 index 0000000..2acd253 --- /dev/null +++ b/vendor/couchapp/evently/profile/profileReady/data.js @@ -0,0 +1,3 @@ +function(e, p) { + return p +} diff --git a/vendor/couchapp/evently/profile/profileReady/mustache.html b/vendor/couchapp/evently/profile/profileReady/mustache.html new file mode 100644 index 0000000..62fe274 --- /dev/null +++ b/vendor/couchapp/evently/profile/profileReady/mustache.html @@ -0,0 +1,8 @@ +
+ {{#gravatar_url}}{{/gravatar_url}} +
+ {{nickname}} +
+
+

Hello {{nickname}}!

+
\ No newline at end of file diff --git a/vendor/couchapp/lib/atom.js b/vendor/couchapp/lib/atom.js new file mode 100644 index 0000000..f61092e --- /dev/null +++ b/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 = ; + 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.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 = {data.author}; + entry.link.@href = data.alternate; + entry.link.@rel = "alternate"; + return entry; +} diff --git a/vendor/couchapp/lib/cache.js b/vendor/couchapp/lib/cache.js new file mode 100644 index 0000000..0c65809 --- /dev/null +++ b/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() {} + }); +}; diff --git a/vendor/couchapp/lib/docform.js b/vendor/couchapp/lib/docform.js new file mode 100644 index 0000000..41c54f3 --- /dev/null +++ b/vendor/couchapp/lib/docform.js @@ -0,0 +1,108 @@ +// 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 formToDeepJSON(form, fields, doc) { + form = $(form); + fields.forEach(function(field) { + var val = form.find("[name="+field+"]").val(); + if (!val) {return;} + var parts = field.split('-'); + var frontObj = doc, frontName = parts.shift(); + while (parts.length > 0) { + frontObj[frontName] = frontObj[frontName] || {}; + frontObj = frontObj[frontName]; + frontName = parts.shift(); + } + frontObj[frontName] = val; + }); +} + +function onSubmit(form, db, doc, opts) { + formToDeepJSON(form, opts.fields, doc); + if (opts.beforeSave) {opts.beforeSave(doc);} + db.saveDoc(localFormDoc, { + success : function(resp) { + if (opts.success) {opts.success(resp, doc);} + } + }); +}; + +function applyFields(form, doc) { + +}; +exports.applyFields = applyFields; + +// docForm applies CouchDB behavior to HTML forms. +// todo make this a couch.app plugin +function docForm(formSelector, opts) { + var localFormDoc = {}; + opts = opts || {}; + opts.fields = opts.fields || []; + + // Apply the behavior + $(formSelector).submit(function(e) { + + + 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]) { + form.find("[name="+field+"]").val(frontObj[frontName]); + } + }); + } + + if (opts.id) { + db.openDoc(opts.id, { + success: function(doc) { + if (opts.onLoad) {opts.onLoad(doc);} + localFormDoc = doc; + docToForm(doc); + }}); + } else if (opts.template) { + if (opts.onLoad) {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; + } + + + + diff --git a/vendor/couchapp/lib/list.js b/vendor/couchapp/lib/list.js new file mode 100644 index 0000000..ef20e71 --- /dev/null +++ b/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") +} \ No newline at end of file diff --git a/vendor/couchapp/lib/markdown.js b/vendor/couchapp/lib/markdown.js new file mode 100644 index 0000000..7d997a6 --- /dev/null +++ b/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 +// +// +// Redistributable under a BSD-style open source license. +// See license.txt for more information. +// +// The full source distribution is at: +// +// A A L +// T C A +// T K B +// +// +// + +// +// Wherever possible, Showdown is a straight, line-by-line port +// of the Perl version of Markdown. +// +// This is not a normal parser design; it's basically just a +// series of string substitutions. It's hard to read and +// maintain this way, but keeping Showdown close to the original +// design makes it easier to port new features. +// +// More importantly, Showdown behaves like markdown.pl in most +// edge cases. So web applications can do client-side preview +// in Javascript, and then build identical HTML on the server. +// +// This port needs the new RegExp functionality of ECMA 262, +// 3rd Edition (i.e. Javascript 1.5). Most modern web browsers +// should do fine. Even with the new regular expression features, +// We do a lot of work to emulate Perl's regex functionality. +// The tricky changes in this file mostly have the "attacklab:" +// label. Major or self-explanatory changes don't. +// +// Smart diff tools like Araxis Merge will be able to match up +// this file with markdown.pl in a useful way. A little tweaking +// helps: in a copy of markdown.pl, replace "#" with "//" and +// replace "$text" with "text". Be sure to ignore whitespace +// and line endings. +// + + +// +// Showdown usage: +// +// var text = "Markdown *rocks*."; +// +// var 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 +// and tags get encoded. +// + + // Clear the global hashes. If we don't clear these, you get conflicts + // from other articles when generating a page which contains more than + // one article (e.g. an index page that shows the N most recent + // articles): + g_urls = new Array(); + g_titles = new Array(); + g_html_blocks = new Array(); + + // attacklab: Replace ~ with ~T + // This lets us use tilde as an escape char to avoid md5 hashes + // The choice of character is arbitray; anything that isn't + // magic in Markdown will work. + text = text.replace(/~/g,"~T"); + + // attacklab: Replace $ with ~D + // RegExp interprets $ as a special character + // when it's in a replacement string + text = text.replace(/\$/g,"~D"); + + // Standardize line endings + text = text.replace(/\r\n/g,"\n"); // DOS to Unix + text = text.replace(/\r/g,"\n"); // Mac to Unix + + // Make sure text begins and ends with a couple of newlines: + text = "\n\n" + text + "\n\n"; + + // Convert all tabs to spaces. + text = _Detab(text); + + // Strip any lines consisting only of spaces and tabs. + // This makes subsequent regexen easier to write, because we can + // match consecutive blank lines with /\n+/ instead of something + // contorted like /[ \t]*\n+/ . + text = text.replace(/^[ \t]+$/mg,""); + + // Turn block-level HTML blocks into hash entries + text = _HashHTMLBlocks(text); + + // Strip link definitions, store in hashes. + text = _StripLinkDefinitions(text); + + text = _RunBlockGamut(text); + + text = _UnescapeSpecialChars(text); + + // attacklab: Restore dollar signs + text = text.replace(/~D/g,"$$"); + + // attacklab: Restore tildes + text = text.replace(/~T/g,"~"); + return text; +} + + +var _StripLinkDefinitions = function(text) { +// +// Strips link definitions from text, stores the URLs and titles in +// hash references. +// + + // Link defs are in the form: ^[id]: url "optional title" + + /* + var text = text.replace(/ + ^[ ]{0,3}\[(.+)\]: // id = $1 attacklab: g_tab_width - 1 + [ \t]* + \n? // maybe *one* newline + [ \t]* + ? // url = $2 + [ \t]* + \n? // maybe one newline + [ \t]* + (?: + (\n*) // any lines skipped = $3 attacklab: lookbehind removed + ["(] + (.+?) // title = $4 + [")] + [ \t]* + )? // title is optional + (?:\n+|$) + /gm, + function(){...}); + */ + var text = text.replace(/^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*?[ \t]*\n?[ \t]*(?:(\n*)["(](.+?)[")][ \t]*)?(?:\n+|\Z)/gm, + function (wholeMatch,m1,m2,m3,m4) { + m1 = m1.toLowerCase(); + g_urls[m1] = _EncodeAmpsAndAngles(m2); // Link IDs are case-insensitive + if (m3) { + // Oops, found blank lines, so it's not a title. + // Put back the parenthetical statement we stole. + return m3+m4; + } else if (m4) { + g_titles[m1] = m4.replace(/"/g,"""); + } + + // Completely remove the definition from the text + return ""; + } + ); + + return text; +} + + +var _HashHTMLBlocks = function(text) { + // attacklab: Double up blank lines to reduce lookaround + text = text.replace(/\n/g,"\n\n"); + + // Hashify HTML blocks: + // We only want to do this for block-level HTML tags, such as headers, + // lists, and tables. That's because we still want to wrap

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

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

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

" + _RunSpanGamut(m1) + "

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

    Just type foo `bar` baz at the prompt.

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

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

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

    "); + str += "

    " + grafsOut.push(str); + } + + } + + // + // Unhashify HTML blocks + // + end = grafsOut.length; + for (var i=0; i= 0) { + var blockText = g_html_blocks[RegExp.$1]; + blockText = blockText.replace(/\$/g,"$$$$"); // Escape any dollar signs + grafsOut[i] = grafsOut[i].replace(/~K\d+K/,blockText); + } + } + + return grafsOut.join("\n\n"); +} + + +var _EncodeAmpsAndAngles = function(text) { +// Smart processing for ampersands and angle brackets that need to be encoded. + + // Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin: + // http://bumppo.net/projects/amputator/ + text = text.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g,"&"); + + // Encode naked <'s + text = text.replace(/<(?![a-z\/?\$!])/gi,"<"); + + return text; +} + + +var _EncodeBackslashEscapes = function(text) { +// +// Parameter: String. +// Returns: The string, with after processing the following backslash +// escape sequences. +// + + // attacklab: The polite way to do this is with the new + // escapeCharacters() function: + // + // text = escapeCharacters(text,"\\",true); + // text = escapeCharacters(text,"`*_{}[]()>#+-.!",true); + // + // ...but we're sidestepping its use of the (slow) RegExp constructor + // as an optimization for Firefox. This function gets called a LOT. + + text = text.replace(/\\(\\)/g,escapeCharacters_callback); + text = text.replace(/\\([`*_{}\[\]()>#+-.!])/g,escapeCharacters_callback); + return text; +} + + +var _DoAutoLinks = function(text) { + + text = text.replace(/<((https?|ftp|dict):[^'">\s]+)>/gi,"
    $1"); + + // Email addresses: + + /* + text = text.replace(/ + < + (?:mailto:)? + ( + [-.\w]+ + \@ + [-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+ + ) + > + /gi, _DoAutoLinks_callback()); + */ + text = text.replace(/<(?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi, + function(wholeMatch,m1) { + return _EncodeEmailAddress( _UnescapeSpecialChars(m1) ); + } + ); + + return text; +} + + +var _EncodeEmailAddress = function(addr) { +// +// Input: an email address, e.g. "foo@example.com" +// +// Output: the email address as a mailto link, with each character +// of the address encoded as either a decimal or hex entity, in +// the hopes of foiling most address harvesting spam bots. E.g.: +// +// foo +// @example.com +// +// Based on a filter by Matthew Wickline, posted to the BBEdit-Talk +// mailing list: +// + + // attacklab: why can't javascript speak hex? + function char2hex(ch) { + var hexDigits = '0123456789ABCDEF'; + var dec = ch.charCodeAt(0); + return(hexDigits.charAt(dec>>4) + hexDigits.charAt(dec&15)); + } + + var encode = [ + function(ch){return "&#"+ch.charCodeAt(0)+";";}, + function(ch){return "&#x"+char2hex(ch)+";";}, + function(ch){return ch;} + ]; + + addr = "mailto:" + addr; + + addr = addr.replace(/./g, function(ch) { + if (ch == "@") { + // this *must* be encoded. I insist. + ch = encode[Math.floor(Math.random()*2)](ch); + } else if (ch !=":") { + // leave ':' alone (to spot mailto: later) + var r = Math.random(); + // roughly 10% raw, 45% hex, 45% dec + ch = ( + r > .9 ? encode[2](ch) : + r > .45 ? encode[1](ch) : + encode[0](ch) + ); + } + return ch; + }); + + addr = "" + addr + ""; + addr = addr.replace(/">.+:/g,"\">"); // strip the mailto: from the visible part + + return addr; +} + + +var _UnescapeSpecialChars = function(text) { +// +// Swap back in all the special characters we've hidden. +// + text = text.replace(/~E(\d+)E/g, + function(wholeMatch,m1) { + var charCodeToReplace = parseInt(m1); + return String.fromCharCode(charCodeToReplace); + } + ); + return text; +} + + +var _Outdent = function(text) { +// +// Remove one level of line-leading tabs or spaces +// + + // attacklab: hack around Konqueror 3.5.4 bug: + // "----------bug".replace(/^-/g,"") == "bug" + + text = text.replace(/^(\t|[ ]{1,4})/gm,"~0"); // attacklab: g_tab_width + + // attacklab: clean up hack + text = text.replace(/~0/g,"") + + return text; +} + +var _Detab = function(text) { +// attacklab: Detab's completely rewritten for speed. +// In perl we could fix it by anchoring the regexp with \G. +// In javascript we're less fortunate. + + // expand first n-1 tabs + text = text.replace(/\t(?=\t)/g," "); // attacklab: g_tab_width + + // replace the nth with two sentinels + text = text.replace(/\t/g,"~A~B"); + + // use the sentinel to anchor our regex so it doesn't explode + text = text.replace(/~B(.+?)~A/g, + function(wholeMatch,m1,m2) { + var leadingText = m1; + var numSpaces = 4 - leadingText.length % 4; // attacklab: g_tab_width + + // there *must* be a better way to do this: + for (var i=0; i> 5] |= 0x80 << ((len) % 32); + x[(((len + 64) >>> 9) << 4) + 14] = len; + + var a = 1732584193; + var b = -271733879; + var c = -1732584194; + var d = 271733878; + + for(var i = 0; i < x.length; i += 16) + { + var olda = a; + var oldb = b; + var oldc = c; + var oldd = d; + + a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936); + d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586); + c = md5_ff(c, d, a, b, x[i+ 2], 17, 606105819); + b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330); + a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897); + d = md5_ff(d, a, b, c, x[i+ 5], 12, 1200080426); + c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341); + b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983); + a = md5_ff(a, b, c, d, x[i+ 8], 7 , 1770035416); + d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417); + c = md5_ff(c, d, a, b, x[i+10], 17, -42063); + b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162); + a = md5_ff(a, b, c, d, x[i+12], 7 , 1804603682); + d = md5_ff(d, a, b, c, x[i+13], 12, -40341101); + c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290); + b = md5_ff(b, c, d, a, x[i+15], 22, 1236535329); + + a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510); + d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632); + c = md5_gg(c, d, a, b, x[i+11], 14, 643717713); + b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302); + a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691); + d = md5_gg(d, a, b, c, x[i+10], 9 , 38016083); + c = md5_gg(c, d, a, b, x[i+15], 14, -660478335); + b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848); + a = md5_gg(a, b, c, d, x[i+ 9], 5 , 568446438); + d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690); + c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961); + b = md5_gg(b, c, d, a, x[i+ 8], 20, 1163531501); + a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467); + d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784); + c = md5_gg(c, d, a, b, x[i+ 7], 14, 1735328473); + b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734); + + a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558); + d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463); + c = md5_hh(c, d, a, b, x[i+11], 16, 1839030562); + b = md5_hh(b, c, d, a, x[i+14], 23, -35309556); + a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060); + d = md5_hh(d, a, b, c, x[i+ 4], 11, 1272893353); + c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632); + b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640); + a = md5_hh(a, b, c, d, x[i+13], 4 , 681279174); + d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222); + c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979); + b = md5_hh(b, c, d, a, x[i+ 6], 23, 76029189); + a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487); + d = md5_hh(d, a, b, c, x[i+12], 11, -421815835); + c = md5_hh(c, d, a, b, x[i+15], 16, 530742520); + b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651); + + a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844); + d = md5_ii(d, a, b, c, x[i+ 7], 10, 1126891415); + c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905); + b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055); + a = md5_ii(a, b, c, d, x[i+12], 6 , 1700485571); + d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606); + c = md5_ii(c, d, a, b, x[i+10], 15, -1051523); + b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799); + a = md5_ii(a, b, c, d, x[i+ 8], 6 , 1873313359); + d = md5_ii(d, a, b, c, x[i+15], 10, -30611744); + c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380); + b = md5_ii(b, c, d, a, x[i+13], 21, 1309151649); + a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070); + d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379); + c = md5_ii(c, d, a, b, x[i+ 2], 15, 718787259); + b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551); + + a = safe_add(a, olda); + b = safe_add(b, oldb); + c = safe_add(c, oldc); + d = safe_add(d, oldd); + } + return Array(a, b, c, d); + +} + +/* + * These functions implement the four basic operations the algorithm uses. + */ +function md5_cmn(q, a, b, x, s, t) +{ + return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s),b); +} +function md5_ff(a, b, c, d, x, s, t) +{ + return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t); +} +function md5_gg(a, b, c, d, x, s, t) +{ + return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t); +} +function md5_hh(a, b, c, d, x, s, t) +{ + return md5_cmn(b ^ c ^ d, a, b, x, s, t); +} +function md5_ii(a, b, c, d, x, s, t) +{ + return md5_cmn(c ^ (b | (~d)), a, b, x, s, t); +} + +/* + * Calculate the HMAC-MD5, of a key and some data + */ +function core_hmac_md5(key, data) +{ + var bkey = str2binl(key); + if(bkey.length > 16) bkey = core_md5(bkey, key.length * chrsz); + + var ipad = Array(16), opad = Array(16); + for(var i = 0; i < 16; i++) + { + ipad[i] = bkey[i] ^ 0x36363636; + opad[i] = bkey[i] ^ 0x5C5C5C5C; + } + + var hash = core_md5(ipad.concat(str2binl(data)), 512 + data.length * chrsz); + return core_md5(opad.concat(hash), 512 + 128); +} + +/* + * Add integers, wrapping at 2^32. This uses 16-bit operations internally + * to work around bugs in some JS interpreters. + */ +function safe_add(x, y) +{ + var lsw = (x & 0xFFFF) + (y & 0xFFFF); + var msw = (x >> 16) + (y >> 16) + (lsw >> 16); + return (msw << 16) | (lsw & 0xFFFF); +} + +/* + * Bitwise rotate a 32-bit number to the left. + */ +function bit_rol(num, cnt) +{ + return (num << cnt) | (num >>> (32 - cnt)); +} + +/* + * Convert a string to an array of little-endian words + * If chrsz is ASCII, characters >255 have their hi-byte silently ignored. + keep + */ +function str2binl(str) +{ + var bin = Array(); + var mask = (1 << chrsz) - 1; + for(var i = 0; i < str.length * chrsz; i += chrsz) + bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (i%32); + return bin; +} + +/* + * Convert an array of little-endian words to a string + */ +function binl2str(bin) +{ + var str = ""; + var mask = (1 << chrsz) - 1; + for(var i = 0; i < bin.length * 32; i += chrsz) + str += String.fromCharCode((bin[i>>5] >>> (i % 32)) & mask); + return str; +} + +/* + * Convert an array of little-endian words to a hex string. + keep + */ +function binl2hex(binarray) +{ + var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef"; + var str = ""; + for(var i = 0; i < binarray.length * 4; i++) + { + str += hex_tab.charAt((binarray[i>>2] >> ((i%4)*8+4)) & 0xF) + + hex_tab.charAt((binarray[i>>2] >> ((i%4)*8 )) & 0xF); + } + return str; +} + +/* + * Convert an array of little-endian words to a base-64 string + */ +function binl2b64(binarray) +{ + var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + var str = ""; + for(var i = 0; i < binarray.length * 4; i += 3) + { + var triplet = (((binarray[i >> 2] >> 8 * ( i %4)) & 0xFF) << 16) + | (((binarray[i+1 >> 2] >> 8 * ((i+1)%4)) & 0xFF) << 8 ) + | ((binarray[i+2 >> 2] >> 8 * ((i+2)%4)) & 0xFF); + for(var j = 0; j < 4; j++) + { + if(i * 8 + j * 6 > binarray.length * 32) str += b64pad; + else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F); + } + } + return str; +} + +exports.hex = hex_md5; diff --git a/vendor/couchapp/lib/path.js b/vendor/couchapp/lib/path.js new file mode 100644 index 0000000..593ab0b --- /dev/null +++ b/vendor/couchapp/lib/path.js @@ -0,0 +1,83 @@ +// from couch.js +function encodeOptions(options) { + var buf = []; + if (typeof(options) == "object" && options !== null) { + for (var name in options) { + if (!options.hasOwnProperty(name)) {continue;} + var value = options[name]; + if (name == "key" || name == "startkey" || name == "endkey") { + value = JSON.stringify(value); + } + buf.push(encodeURIComponent(name) + "=" + encodeURIComponent(value)); + } + } + if (!buf.length) { + return ""; + } + return "?" + buf.join("&"); +} + +function concatArgs(array, args) { + for (var i=0; i < args.length; i++) { + array.push(args[i]); + }; + return array; +}; + +function makePath(array) { + var options, path; + + if (typeof array[array.length - 1] != "string") { + // it's a params hash + options = array.pop(); + } + path = array.map(function(item) {return encodeURIComponent(item)}).join('/'); + if (options) { + return path + encodeOptions(options); + } else { + return path; + } +}; + +exports.init = function(req) { + return { + asset : function() { + var p = req.path, parts = ['', p[0], p[1] , p[2]]; + return makePath(concatArgs(parts, arguments)); + }, + show : function() { + var p = req.path, parts = ['', p[0], p[1] , p[2], '_show']; + return makePath(concatArgs(parts, arguments)); + }, + list : function() { + var p = req.path, parts = ['', p[0], p[1] , p[2], '_list']; + return makePath(concatArgs(parts, arguments)); + }, + update : function() { + var p = req.path, parts = ['', p[0], p[1] , p[2], '_update']; + return makePath(concatArgs(parts, arguments)); + }, + limit : function(limit) { + var query = req.query; + var l = query.limit; + query.limit = limit; + var view = req.path[req.path.length - 1]; + var list = req.path[req.path.length - 2]; + var link = this.list(list, view, query); + query.limit = l; + return link; + }, + older : function(key) { + if (!typeof key == "undefined") return null; + var query = req.query; + query.startkey = key; + query.skip=1; + var view = req.path[req.path.length - 1]; + var list = req.path[req.path.length - 2]; + return this.list(list, view, query); + }, + absolute : function(path) { + return 'http://' + req.headers.Host + path; + } + } +}; diff --git a/vendor/couchapp/lib/redirect.js b/vendor/couchapp/lib/redirect.js new file mode 100644 index 0000000..92ce060 --- /dev/null +++ b/vendor/couchapp/lib/redirect.js @@ -0,0 +1,8 @@ +exports.permanent = function(redirect) { + return { + code : 301, + headers : { + "Location" : redirect + } + }; +}; \ No newline at end of file diff --git a/vendor/couchapp/metadata.json b/vendor/couchapp/metadata.json new file mode 100644 index 0000000..5ed1140 --- /dev/null +++ b/vendor/couchapp/metadata.json @@ -0,0 +1,4 @@ +{ + "name": "couchapp", + "description": "official couchapp vendor" +} \ No newline at end of file diff --git a/views/recent-items/map.js b/views/recent-items/map.js new file mode 100644 index 0000000..1bc28c8 --- /dev/null +++ b/views/recent-items/map.js @@ -0,0 +1,5 @@ +function(doc) { + if (doc.created_at) { + emit(doc.created_at, doc); + } +}; \ No newline at end of file From 5a6360b2b27de37821bb62a2619124556114f67f Mon Sep 17 00:00:00 2001 From: Chris Anderson Date: Sat, 1 May 2010 18:38:11 -0700 Subject: [PATCH 02/33] installation instructions in readme --- README.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ac2d814..e6ab99d 100644 --- a/README.md +++ b/README.md @@ -2,4 +2,13 @@ This repo is a snapshot of the development going on in [The CouchApp generate command.](http://github.com/couchapp/couchapp/tree/master/templates/app/) -It's easier for me to receive patches against couchapp/couchapp. This repo is intended primarily so you can deploy new versions of the example app without needing to upgrade CouchApp. \ No newline at end of file +It's easier for me to receive patches against couchapp/couchapp. This repo is intended primarily so you can deploy new versions of the example app without needing to upgrade CouchApp. + +Install with + + couchapp push . http://localhost:5984/proto + +or (if you have security turned on) + + couchapp push . http://myname:mypass@localhost:5984/proto + \ No newline at end of file From e15e1488717bd06b079207ea7510fb429bd21455 Mon Sep 17 00:00:00 2001 From: Chris Anderson Date: Sat, 15 May 2010 09:28:55 -0700 Subject: [PATCH 03/33] form for photos --- _attachments/style/main.css | 4 ++++ evently/profile/profileReady/mustache.html | 8 +++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/_attachments/style/main.css b/_attachments/style/main.css index 1d61e0e..d90022b 100755 --- a/_attachments/style/main.css +++ b/_attachments/style/main.css @@ -16,6 +16,10 @@ body { margin-bottom:8px; } +#profile form { + float:left; +} + #items { border:4px solid #dde; background:#eef; diff --git a/evently/profile/profileReady/mustache.html b/evently/profile/profileReady/mustache.html index 1ad2bc0..0263f19 100644 --- a/evently/profile/profileReady/mustache.html +++ b/evently/profile/profileReady/mustache.html @@ -8,7 +8,13 @@
    - +

    New photo from {{nickname}}.

    +

    +

    +

    \ No newline at end of file From 658b143e252c8c2f2f567e598af1eb3d93d9e5c8 Mon Sep 17 00:00:00 2001 From: Chris Anderson Date: Sat, 15 May 2010 16:23:27 -0700 Subject: [PATCH 04/33] import upload code from futon. thanks @cmlenz --- _attachments/index.html | 1 + evently/profile/profileReady/mustache.html | 3 ++- .../profileReady/selectors/form/submit.js | 19 +++++++++++++++---- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/_attachments/index.html b/_attachments/index.html index edfd825..5e56022 100755 --- a/_attachments/index.html +++ b/_attachments/index.html @@ -18,6 +18,7 @@

    Generated CouchApp

    + '; -}).join('')); +]); diff --git a/vendor/couchapp/evently/account/_init.js b/vendor/couchapp/evently/account/_init.js index e3411c5..4ead648 100644 --- a/vendor/couchapp/evently/account/_init.js +++ b/vendor/couchapp/evently/account/_init.js @@ -1,5 +1,6 @@ function() { var elem = $(this); + $$(this).userCtx = null; $.couch.session({ success : function(r) { var userCtx = r.userCtx; diff --git a/vendor/couchapp/evently/account/adminParty.js b/vendor/couchapp/evently/account/adminParty.js deleted file mode 100644 index 9796c95..0000000 --- a/vendor/couchapp/evently/account/adminParty.js +++ /dev/null @@ -1,3 +0,0 @@ -function() { - alert("Admin party! Fix this in Futon before proceeding."); -} \ No newline at end of file diff --git a/vendor/couchapp/evently/account/adminParty/mustache.html b/vendor/couchapp/evently/account/adminParty/mustache.html new file mode 100644 index 0000000..e7e41b3 --- /dev/null +++ b/vendor/couchapp/evently/account/adminParty/mustache.html @@ -0,0 +1 @@ +

    Admin party, everyone is admin! Fix this in Futon before proceeding.

    \ No newline at end of file diff --git a/vendor/couchapp/evently/account/loggedIn/after.js b/vendor/couchapp/evently/account/loggedIn/after.js index 7be472c..90a15b0 100644 --- a/vendor/couchapp/evently/account/loggedIn/after.js +++ b/vendor/couchapp/evently/account/loggedIn/after.js @@ -1,5 +1,4 @@ -// todo move to template function(e, r) { - $(this).attr("data-name", r.userCtx.name); $$(this).userCtx = r.userCtx; -} + $$(this).info = r.info; +}; \ No newline at end of file diff --git a/vendor/couchapp/evently/profile/loggedIn.js b/vendor/couchapp/evently/profile/loggedIn.js index f3ae259..1e339a6 100644 --- a/vendor/couchapp/evently/profile/loggedIn.js +++ b/vendor/couchapp/evently/profile/loggedIn.js @@ -2,21 +2,20 @@ function(e, r) { var userCtx = r.userCtx; var widget = $(this); // load the profile from the user doc - $.couch.userDb(function(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]); - } + 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]); } - }); + } }); } From c5a8762a12ceac5b08b3fa6a01a62dc869c05ef7 Mon Sep 17 00:00:00 2001 From: Chris Anderson Date: Sun, 11 Jul 2010 14:31:14 -0700 Subject: [PATCH 11/33] update from couchapp repo --- _attachments/index.html | 0 _attachments/style/main.css | 4 ++++ 2 files changed, 4 insertions(+) mode change 100755 => 100644 _attachments/index.html mode change 100755 => 100644 _attachments/style/main.css diff --git a/_attachments/index.html b/_attachments/index.html old mode 100755 new mode 100644 diff --git a/_attachments/style/main.css b/_attachments/style/main.css old mode 100755 new mode 100644 index 1d61e0e..2e1421c --- a/_attachments/style/main.css +++ b/_attachments/style/main.css @@ -5,6 +5,10 @@ body { padding:4px; } +h1 { + margin-top:0; +} + #account { float:right; } From 4c58926a1e4c3da8ddbf2f07ae0cb6abd0585e78 Mon Sep 17 00:00:00 2001 From: Chris Anderson Date: Sun, 11 Jul 2010 14:43:32 -0700 Subject: [PATCH 12/33] simplify form handler --- evently/items/_changes/data.js | 2 +- .../profile/profileReady/selectors/form/submit.js | 15 +++++++-------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/evently/items/_changes/data.js b/evently/items/_changes/data.js index 00ad89c..3abc83b 100644 --- a/evently/items/_changes/data.js +++ b/evently/items/_changes/data.js @@ -1,5 +1,5 @@ function(data) { - $.log(data) + // $.log(data) var p; return { items : data.rows.map(function(r) { diff --git a/evently/profile/profileReady/selectors/form/submit.js b/evently/profile/profileReady/selectors/form/submit.js index bf18dd8..3c58079 100644 --- a/evently/profile/profileReady/selectors/form/submit.js +++ b/evently/profile/profileReady/selectors/form/submit.js @@ -1,13 +1,12 @@ function() { - var form = this; - var doc = { - created_at : new Date(), - profile : $$("#profile").profile, - message : $("[name=message]", form).val() - }; - $$(this).app.db.saveDoc(doc, { + var form = $(this); + var fdoc = form.serializeObject(); + fdoc.created_at = new Date(); + fdoc.profile = $$("#profile").profile; + + $$(this).app.db.saveDoc(fdoc, { success : function() { - $("[name=message]", form).val(""); + form[0].reset(); } }); return false; From 75e16a1879af08ef9b1609dcefe88b28071d1a67 Mon Sep 17 00:00:00 2001 From: Chris Anderson Date: Sun, 11 Jul 2010 14:44:52 -0700 Subject: [PATCH 13/33] whitespace --- evently/profile/profileReady/selectors/form/submit.js | 1 - 1 file changed, 1 deletion(-) diff --git a/evently/profile/profileReady/selectors/form/submit.js b/evently/profile/profileReady/selectors/form/submit.js index 3c58079..35f4b6f 100644 --- a/evently/profile/profileReady/selectors/form/submit.js +++ b/evently/profile/profileReady/selectors/form/submit.js @@ -3,7 +3,6 @@ function() { var fdoc = form.serializeObject(); fdoc.created_at = new Date(); fdoc.profile = $$("#profile").profile; - $$(this).app.db.saveDoc(fdoc, { success : function() { form[0].reset(); From 8f9a942dd63bcfff1133f9e9e898b99e00a7d31d Mon Sep 17 00:00:00 2001 From: Chris Anderson Date: Mon, 12 Jul 2010 09:19:05 -0700 Subject: [PATCH 14/33] update namespaces --- vendor/couchapp/_attachments/jquery.evently.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vendor/couchapp/_attachments/jquery.evently.js b/vendor/couchapp/_attachments/jquery.evently.js index 965c068..b169a3c 100644 --- a/vendor/couchapp/_attachments/jquery.evently.js +++ b/vendor/couchapp/_attachments/jquery.evently.js @@ -108,7 +108,7 @@ function $$(node) { } if (app && events._changes) { - $("body").bind("evently.changes."+app.db.name, function() { + $("body").bind("evently-changes-"+app.db.name, function() { // we want to unbind this function when the element is deleted. // maybe jquery 1.4.2 has this covered? // $.log('changes', elem); @@ -318,7 +318,7 @@ function $$(node) { // only start one changes listener per db function followChanges(app) { var dbName = app.db.name, changeEvent = function(resp) { - $("body").trigger("evently.changes."+dbName, [resp]); + $("body").trigger("evently-changes-"+dbName, [resp]); }; if (!$.evently.changesDBs[dbName]) { if (app.db.changes) { From 0cf6c07c1e9f301f3dcbed7774ebd545035f0efa Mon Sep 17 00:00:00 2001 From: Chris Anderson Date: Mon, 19 Jul 2010 21:45:20 -0700 Subject: [PATCH 15/33] update vendor --- vendor/couchapp/_attachments/docs.css | 53 ----- vendor/couchapp/_attachments/docs.html | 32 --- vendor/couchapp/_attachments/docs.js | 8 - vendor/couchapp/docs/account.md | 187 ------------------ vendor/couchapp/docs/couchapp.md | 34 ---- vendor/couchapp/docs/docs.md | 11 -- vendor/couchapp/docs/evently.md | 185 ----------------- vendor/couchapp/docs/pathbinder.md | 52 ----- vendor/couchapp/docs/profile.md | 3 - vendor/couchapp/evently/docs/index/data.js | 11 -- .../couchapp/evently/docs/index/mustache.html | 5 - vendor/couchapp/evently/docs/index/path.txt | 1 - vendor/couchapp/evently/docs/topic/after.js | 15 -- vendor/couchapp/evently/docs/topic/data.js | 8 - .../evently/docs/topic/edit/_init/fun.js | 19 -- .../edit/_init/selectors/a.edit/click.js | 9 - .../topic/edit/_init/selectors/a.run/click.js | 22 --- .../couchapp/evently/docs/topic/mustache.html | 1 - vendor/couchapp/evently/docs/topic/path.txt | 1 - vendor/couchapp/metadata.json | 3 +- 20 files changed, 2 insertions(+), 658 deletions(-) delete mode 100644 vendor/couchapp/_attachments/docs.css delete mode 100644 vendor/couchapp/_attachments/docs.html delete mode 100644 vendor/couchapp/_attachments/docs.js delete mode 100644 vendor/couchapp/docs/account.md delete mode 100644 vendor/couchapp/docs/couchapp.md delete mode 100644 vendor/couchapp/docs/docs.md delete mode 100644 vendor/couchapp/docs/evently.md delete mode 100644 vendor/couchapp/docs/pathbinder.md delete mode 100644 vendor/couchapp/docs/profile.md delete mode 100644 vendor/couchapp/evently/docs/index/data.js delete mode 100644 vendor/couchapp/evently/docs/index/mustache.html delete mode 100644 vendor/couchapp/evently/docs/index/path.txt delete mode 100644 vendor/couchapp/evently/docs/topic/after.js delete mode 100644 vendor/couchapp/evently/docs/topic/data.js delete mode 100644 vendor/couchapp/evently/docs/topic/edit/_init/fun.js delete mode 100644 vendor/couchapp/evently/docs/topic/edit/_init/selectors/a.edit/click.js delete mode 100644 vendor/couchapp/evently/docs/topic/edit/_init/selectors/a.run/click.js delete mode 100644 vendor/couchapp/evently/docs/topic/mustache.html delete mode 100644 vendor/couchapp/evently/docs/topic/path.txt diff --git a/vendor/couchapp/_attachments/docs.css b/vendor/couchapp/_attachments/docs.css deleted file mode 100644 index 94b19d6..0000000 --- a/vendor/couchapp/_attachments/docs.css +++ /dev/null @@ -1,53 +0,0 @@ -body { - font:1em Helvetica, sans-serif; - margin:0; - padding:4px; -} - -h1 { - margin:0.5em; -} - -h2 { - color:#222; -} - -pre { - padding:4px; - margin:4px; - background:#bbb; -} - -#content { - padding:4px; - margin:2px; -} - -#sidebar { - float:right; - width:34%; -} - -#docs { - -moz-box-shadow:0 0 2em #000; - -webkit-box-shadow:0 0 2em #000; - width:58%; - padding:8px; - margin:4px; -} - -.example { - background:#ffd; - padding:4px; - margin:4px; - position:absolute; -} - -textarea.code { - width:100%; -} - -.edit { - float:right; - font-size:0.8em; -} diff --git a/vendor/couchapp/_attachments/docs.html b/vendor/couchapp/_attachments/docs.html deleted file mode 100644 index 77220b7..0000000 --- a/vendor/couchapp/_attachments/docs.html +++ /dev/null @@ -1,32 +0,0 @@ - - - - Evently and CouchApp Docs - - - - -
    - -
    -
    -
    -
    - - - - - - - - - - - - - diff --git a/vendor/couchapp/_attachments/docs.js b/vendor/couchapp/_attachments/docs.js deleted file mode 100644 index 7b0e4d0..0000000 --- a/vendor/couchapp/_attachments/docs.js +++ /dev/null @@ -1,8 +0,0 @@ -$.log = function() { - // console.log(arguments) -}; - -$.couch.app(function(app) { - $("#docs").evently(app.ddoc.vendor.couchapp.evently.docs, app); - $.pathbinder.begin("/"); -}); \ No newline at end of file diff --git a/vendor/couchapp/docs/account.md b/vendor/couchapp/docs/account.md deleted file mode 100644 index e60928a..0000000 --- a/vendor/couchapp/docs/account.md +++ /dev/null @@ -1,187 +0,0 @@ -# Docs for the account widget - -You should use this widget in any CouchApp that allows users to login or signup. - -It is easy to install. To use the account widget, just define a `div` in your page and use [CouchApp](#/topic/couchapp) to load it from the design document and [Evently](#/topic/evently) to apply it to the page. - -Here's the most basic usage: - - $.couch.app(function(app){ - $("#basic_account").evently(app.ddoc.vendor.couchapp.evently.account); - }); - -Run this example and try signing up, and logging in and out. This code is part of the CouchApp standard library, so feel free to use it in your applications. It is also "trivial" to replace and extend the functionality. In a minutes, we'll start by replacing a template, and then show how you can use Evently to connect multiple widgets, to build more complex applications. - -## A widget is a collection of event handlers - -First lets look more closely at the account widget (click Run to see the code we're about to discuss, it will appear in the sidebar -- the example code below is just used for loading and displaying the account widget source code). - - $.couch.app(function(app){ - $("#account_widget_code").evently({ - _init : { - mustache : "
    {{json}}
    ", - data : function() { - var widget = app.ddoc.vendor.couchapp.evently.account; - return { - json : JSON.stringify(widget, null, 2) - } - } - } - }); - }); - -The top level keys are the most important: `loggedIn`, `loggedOut`, `adminParty`, `signupForm`, `loginForm`, `doLogin`, `doSignup`, and `_init`. Each one of these corresponds to an event or state the system can be in. Some of them draw user interface elements, other directly trigger further events. - -### _init - -The `_init` event is special, in that Evently will automatically trigger it when the widget is created. Here is the code for the account widget's `_init` event handler. - - function() { - var elem = $(this); - $.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"); - }; - } - }); - } - -This code does one query to CouchDB, to retrieve the session information for the current user. For this we use the `$.couch.session()` function which is part of the [jquery.couch.js](/_utils/script/jquery.couch.js) library which is part of the CouchDB distribution. - -The response is handled in one of three ways, depending on the user's session information. Either we trigger the `loggedIn` or `loggedOut` events, or in the special case where we detect that CouchDB's security is not properly configured, we trigger the `adminParty` event to warn the user. - -### loggedOut - -Because most visitors start logged out, let's now turn our attention to the `loggedOut` event handler to see what will greet a new visitor: - - "loggedOut": { - "mustache": "Signup or Login", - "selectors": { - "a[href=#login]": { - "click": "loginForm" - }, - "a[href=#signup]": { - "click": "signupForm" - } - } - } - -There are two main components to this handler: `mustache` and `selectors`. `mustache` is a template file with two HTML links. `selectors` contains a set of CSS selectors with events bound to them. You can think of each selector as a nested Evently widget. In this case, clicking "Login" will trigger the `loginForm` event, while clicking "Signup" triggers the `signupForm` event. - -### signupForm - -Let's see what happens during signup. We'll skip showing the whole handler (it should be in the sidebar anyway if you clicked "run" earlier.) - -When the `signupForm` event is triggered, a mustache template draws the form. Then the selectors are run, assigning this function to the form's submit event: - - function(e) { - var name = $('input[name=name]', this).val(), - pass = $('input[name=password]', this).val(); - $(this).trigger('doSignup', [name, pass]); - return false; - } - -This handler is as simple as possible, all it does is use jQuery to pull the user data from the form, and send the name and password to the `doSignup` event. We could just use a function call here, but it's nice to keep our individual events as small as possible, as this makes customizing Evently widgets simpler. - -### doSignup - -Here is the `doSignup` handler: - - function(e, name, pass) { - var elem = $(this); - $.couch.signup({ - name : name - }, pass, { - success : function() { - elem.trigger("doLogin", [name, pass]); - } - }); - } - -Again, all the complex signup logic (encrypting passwords, etc) is pushed to the [jquery.couch.js](/_utils/script/jquery.couch.js) library (via the `$.couch.signup()` call), so our application code can stay as simple as possible. When signup is complete, we trigger the `doLogin` event, so new users don't have to go through another action. - -### doLogin - -The code for `doLogin` isn't much different, just take the name and password, and call a jquery.couch.js library function with it. - - function(e, name, pass) { - var elem = $(this); - $.couch.login({ - name : name, - password : pass, - success : function(r) { - elem.trigger("_init") - } - }); - } - -The last thing that `doLogin` does is trigger `_init`, so we come full circle! This time, `_init` will see that the user is logged in, and trigger the `loggedIn` event. You'll probably want to hook your application to this `loggedIn` event, to activate any features which are reserved for registered users. We'll cover linking events in a later section. - -## Customizing the account widget - -Evently widgets are built out of JSON objects, which makes it easy to replace bits and pieces of them without having to mess with the entire widget. We'll start by customizing what users see when they are logged in. - - $.couch.app(function(app){ - var customizedWidget = $.extend(true, {}, app.ddoc.vendor.couchapp.evently.account, { - loggedIn : { - mustache : 'Hello {{name}} you are logged in! ' + - 'Would you like to logout?' - } - }); - $("#customWelcome").evently(customizedWidget); - }); - -Take a moment to run this example code and login to see how our custom template has replaced just one screen in the widget. The first time I did this I thought it was pretty cool. Hopefully you can think of a lot of powerful stuff you could do with it. The sky is the limit. - -Here's another quick one: - - $.couch.app(function(app){ - var customizedWidget = $.extend(true, {}, app.ddoc.vendor.couchapp.evently.account, { - loggedOut : { - after : "function(){alert('Bye bye');}" - } - }); - $("#afterAlert").evently(customizedWidget); - }); - -For a deeper reference on what the various parts of an Evently widget are named, and how you can use them, see [the Evently docs page](#/topic/evently). - -## Linking two widgets - -First, lets create a basic widget. This one just has an `_init` handler and a handler called `loggedIn`. There is nothing in this widget definition that will trigger `loggedIn`, unless something else triggers it, there's no way it will run. - - $("#link_target").evently({ - _init : { - mustache : "

    Not much to see here

    " - }, - loggedIn : { - mustache : "

    loggedIn was triggered from another widget, {{name}}.

    ", - data : function(e, r) { - return { name : r.userCtx.name }; - } - } - }); - -Be sure to run the above example code before the next one, otherwise there won't be anything to link to. - -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. - - $.couch.app(function(app){ - $("#link_source").evently(app.ddoc.vendor.couchapp.evently.account); - // link the source to the target, for the loggedIn event - $.evently.connect($("#link_source"), $("#link_target"), ["loggedIn"]); - }); - -## Conclusion - -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. - -Enjoy! - - \ No newline at end of file diff --git a/vendor/couchapp/docs/couchapp.md b/vendor/couchapp/docs/couchapp.md deleted file mode 100644 index ab1f603..0000000 --- a/vendor/couchapp/docs/couchapp.md +++ /dev/null @@ -1,34 +0,0 @@ -# Docs for $.couch.app - -The simplest use of CouchApp in the browser is to get access to information about the database you are running in. - - $.couch.app(function(app) { - $("#dbinfo").evently({ - _init : { - mustache : '

    The db name is {{name}}

    ', - data : app.db - } - }); - }); - -Yay couchapp. - -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: - - $.couch.app(function(app) { - $("#ddoc").evently({ - _init : { - mustache : '

    Click to show the full doc source:

    {{ddoc}}
    ', - data : { - ddoc : JSON.stringify(app.ddoc, null, 2).slice(0,100) + '...' - } - }, - click : { - mustache : '

    The full doc source (rerun to hide):

    {{ddoc}}
    ', - data : { - ddoc : JSON.stringify(app.ddoc, null, 2) - } - } - }); - }); - diff --git a/vendor/couchapp/docs/docs.md b/vendor/couchapp/docs/docs.md deleted file mode 100644 index e4c42ab..0000000 --- a/vendor/couchapp/docs/docs.md +++ /dev/null @@ -1,11 +0,0 @@ -# Docs for the docs system. - -You are encouraged to use the couchapp docs system to write documentation for your plugins and applications. Extra bonus points because it's fun. - -Docs automatically make divs based on `$("#foo")` pattern matching. That is, we regex the code looking for the first id we see referenced. Remember ids need to be unique on a page. For doc examples you only get one id. - -Example Code: - - $("#hide_foo").hide("slow"); - -That's all it takes. You only get one div in each example for now. Have fun! \ No newline at end of file diff --git a/vendor/couchapp/docs/evently.md b/vendor/couchapp/docs/evently.md deleted file mode 100644 index bed0a70..0000000 --- a/vendor/couchapp/docs/evently.md +++ /dev/null @@ -1,185 +0,0 @@ -# Evently Docs - -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. - -Evently has special handlers for CouchDB views and `_changes` feeds, and could be easily extended for other server-side frameworks. - -## Hello World - -At it's simplest an Evently widget is a set of events connected to a single DOM element. - -JavaScript: - - $("#hello").evently({ - _init : { - mustache : "

    Hello world

    ", - }, - click : { - mustache : "

    What a crazy world!

    ", - } - }); - -You can also do some more interesting things: - - $("#heyjane").evently({ - _init : { - mustache : '

    Hello Jane, Joan (pick one)

    ', - selectors : { - 'a[href=#joan]' : { - click : 'hiJoan' - }, - 'a[href=#jane]' : { - click : 'hiJane' - } - } - }, - hiJoan : { - mustache : '

    Hello Joan!

    ' - }, - hiJane : { - mustache : "

    Darn, it's Jane...

    ", - after : function() { - setTimeout(function() { - // automatically trigger the "janeRocks" event after 2 seconds. - $("#heyjane").trigger("janeRocks"); - }, 2000); - } - }, - janeRocks : { - render : "append", - mustache : "

    Actually Jane is awesome.

    " - } - }); - - -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. - -[screenshot of the above code in textmate's file drawer] - -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! - -## Ajax Hello World - -Let's do a little Ajax. We'll just load the version of the CouchDB instance we happen to be serving our HTML from: - - $("#ajax").evently({ - _init : { - mustache : '

    Loading CouchDB server info.

    ', - after : function() { - var widget = $(this); - $.ajax({ - url : '/', - complete : function(req) { - var resp = $.httpData(req, "json"); - widget.trigger("version", [resp]); - } - }) - } - }, - version : { - mustache : "

    Running CouchDB version {{version}}

    ", - data : function(e, resp) { - return resp; - } - } - }); - -Explain `mustache` and `data` - --- triggering other events - -- selectors - -- create a doc - -## Evently and CouchApp together - -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. - -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: - - _init/ - mustache.html - selectors/ - form/ - submit.js - input.name/ - change.js - a.cancel/ - click.txt - cancelled/ - mustache.html - selectors/ - a.continue/ - click.txt - -It will appear within your CouchApp design document as: - - { - _init : { - mustache : "contents of mustache.html", - selectors { - form : { - submit : "function() { ... }" - }, - "input.name" { - change : "function() { ... }" - }, - "a.cancel" { - click : "cancelled" - } - } - }, - cancelled : { - mustache : "contents of mustache.html", - selectors : { - "a.continue" : { - click : "_init" - } - } - } - } - -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. - -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. - -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. - -## Evently Queries - -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. - --- new rows, etc - --- run a query - --- connect to changes - --- links to example apps - -## Freeform Asynchronous Actions - -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: - - $("#async").evently({ - _init : { - mustache : "

    How many databases on the local host?

    Answer: {{number_of_dbs}}

    Other stuff: {{args}}

    More: {{allArgs}}

    ", - async : function(cb) { - var ag = Array.prototype.slice.call(arguments).map(function(a){return a.toSource ? a.toSource() : a}); - $.couch.allDbs({ - success : function(resp) { - cb(resp.length, ag); - } - }) - }, - data : function(count, args) { - return { - number_of_dbs : count, - args : JSON.stringify(args), - allArgs : JSON.stringify(Array.prototype.slice.call(arguments)) - }; - } - }, - click : { - mustache : "

    What a crazy world!

    ", - } - }); \ No newline at end of file diff --git a/vendor/couchapp/docs/pathbinder.md b/vendor/couchapp/docs/pathbinder.md deleted file mode 100644 index 7b3325b..0000000 --- a/vendor/couchapp/docs/pathbinder.md +++ /dev/null @@ -1,52 +0,0 @@ -# Docs about $.pathbinder - -Pathbinder is a tiny framework for triggering events based on paths in URL hash. For example, you might want to render one panel when the user clicks a link to `#/foo` and another when the URL hash changes to `#/bar`. If you've never used URL hashes for application state in an Ajax app before, prepare to be happy. - -There are two big advantages to having the state in the URL-hash. One is that users can bookmark screens they may have reached by navigating within your app. The other is that the back button will continue to work. - -The page you are on has a URL hash of `#/topic/pathbinder` right now. You can follow links to other "pages" within this application, and Pathbinder takes care of triggering the proper events. - -## A simple example - - $("#basic_path").html('

    click for foo

    '); - $("#basic_path").bind("foo", function() { - $(this).html("

    you went to foo

    "); - }); - $("#basic_path").pathbinder("foo", "/foo"); - -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. - -## Using path parameters - -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. - - $("#param_path").html('

    click for super foo

    '); - $("#param_path").bind("foo", function(e, params) { - $(this).html("

    you went to foo - "+params.id+"

    "); - }); - $("#param_path").pathbinder("foo", "/foo/:id"); - -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. - -## Pathbinder with Evently - -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: - - $("#evently_path").evently({ - _init : { - path : '/index', - mustache : '

    the index. more cowbell!

    ' - }, - cowbell : { - path : '/cowbell', - mustache : '

    Now that is a lot of cowbell. back to the index

    ' - } - }); - -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. - -### Too many widgets - -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). - -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. diff --git a/vendor/couchapp/docs/profile.md b/vendor/couchapp/docs/profile.md deleted file mode 100644 index 4ddf46e..0000000 --- a/vendor/couchapp/docs/profile.md +++ /dev/null @@ -1,3 +0,0 @@ -# docs for the profile evently widget - -This widget makes it easy to give users a profile for your application. \ No newline at end of file diff --git a/vendor/couchapp/evently/docs/index/data.js b/vendor/couchapp/evently/docs/index/data.js deleted file mode 100644 index 4dadcb5..0000000 --- a/vendor/couchapp/evently/docs/index/data.js +++ /dev/null @@ -1,11 +0,0 @@ -function() { - var docs = $$(this).app.ddoc.vendor.couchapp.docs; - var dnames = []; - $.forIn(docs, function(d) { - dnames.push({ - title: d, - href : "#/topic/"+encodeURIComponent(d) - }); - }); - return {docs:dnames}; -}; \ No newline at end of file diff --git a/vendor/couchapp/evently/docs/index/mustache.html b/vendor/couchapp/evently/docs/index/mustache.html deleted file mode 100644 index 797e0b5..0000000 --- a/vendor/couchapp/evently/docs/index/mustache.html +++ /dev/null @@ -1,5 +0,0 @@ - \ No newline at end of file diff --git a/vendor/couchapp/evently/docs/index/path.txt b/vendor/couchapp/evently/docs/index/path.txt deleted file mode 100644 index 35ec3b9..0000000 --- a/vendor/couchapp/evently/docs/index/path.txt +++ /dev/null @@ -1 +0,0 @@ -/ \ No newline at end of file diff --git a/vendor/couchapp/evently/docs/topic/after.js b/vendor/couchapp/evently/docs/topic/after.js deleted file mode 100644 index f048c27..0000000 --- a/vendor/couchapp/evently/docs/topic/after.js +++ /dev/null @@ -1,15 +0,0 @@ -function() { - var app = $$(this).app; - var self = $(this); - $("pre", self).each(function() { - var pre = $(this); - var js = pre.text(); - var r = js.match(/\$\(\"\#([^\"]*)\"\)/); - if (r) { - var id = r[1]; - var code_id = 'code-'+id; - pre.wrap('
    '); - $('#'+code_id).evently(app.ddoc.vendor.couchapp.evently.docs.topic.edit, app, [id]); - } - }); -}; diff --git a/vendor/couchapp/evently/docs/topic/data.js b/vendor/couchapp/evently/docs/topic/data.js deleted file mode 100644 index 039efab..0000000 --- a/vendor/couchapp/evently/docs/topic/data.js +++ /dev/null @@ -1,8 +0,0 @@ -function(e, p) { - var doc = $$(this).app.ddoc.vendor.couchapp.docs[p.id]; - var converter = new Showdown.converter(); - var html = converter.makeHtml(doc); - return { - html : html - }; -}; \ No newline at end of file diff --git a/vendor/couchapp/evently/docs/topic/edit/_init/fun.js b/vendor/couchapp/evently/docs/topic/edit/_init/fun.js deleted file mode 100644 index 8571348..0000000 --- a/vendor/couchapp/evently/docs/topic/edit/_init/fun.js +++ /dev/null @@ -1,19 +0,0 @@ -function(e, id) { - var editable = $(this); - if ($$(editable)._init_ran) {return false;} - // add edit link - var edit = $('edit code'); - editable.append(edit); - - // add run box - var example = $('
    run #'+id+'
    #'+id+' output will be here
    '); - var s = $("#sidebar"); - var o = s.offset(); - example.offset({ - left: o.left - }); - example.width(s.width()*0.75); - editable.prepend(example); - $$(editable)._init_ran = true; - return false; -} \ No newline at end of file diff --git a/vendor/couchapp/evently/docs/topic/edit/_init/selectors/a.edit/click.js b/vendor/couchapp/evently/docs/topic/edit/_init/selectors/a.edit/click.js deleted file mode 100644 index b90700f..0000000 --- a/vendor/couchapp/evently/docs/topic/edit/_init/selectors/a.edit/click.js +++ /dev/null @@ -1,9 +0,0 @@ -function() { - var pre = $(this).prev('pre'); - var js = pre.text(); - var lines = js.split('\n').length; - var ta = $(''); - ta.text(js); - pre.replace(ta); - return false; -}; diff --git a/vendor/couchapp/evently/docs/topic/edit/_init/selectors/a.run/click.js b/vendor/couchapp/evently/docs/topic/edit/_init/selectors/a.run/click.js deleted file mode 100644 index 9babec5..0000000 --- a/vendor/couchapp/evently/docs/topic/edit/_init/selectors/a.run/click.js +++ /dev/null @@ -1,22 +0,0 @@ -function(e) { - try { - function err(y, id) { - $('#'+id).html(['

    Error running #', id, - ' code block:

    ',
    -      (y.toSource ? y.toSource() : JSON.stringify(y)),
    -      '

    '].join('')); - } - var id = e.data.args[1]; - var example = $("#code-"+id); - var js = $('textarea',example).val() || $('pre',example).text(); - $('#'+id).unbind(); - try { - eval(js); - } catch (y) { - err(y, id); - } - } catch(x) { - err(x, id); - } - return false; -} \ No newline at end of file diff --git a/vendor/couchapp/evently/docs/topic/mustache.html b/vendor/couchapp/evently/docs/topic/mustache.html deleted file mode 100644 index e1b7349..0000000 --- a/vendor/couchapp/evently/docs/topic/mustache.html +++ /dev/null @@ -1 +0,0 @@ -
    {{{html}}}
    \ No newline at end of file diff --git a/vendor/couchapp/evently/docs/topic/path.txt b/vendor/couchapp/evently/docs/topic/path.txt deleted file mode 100644 index 52370e0..0000000 --- a/vendor/couchapp/evently/docs/topic/path.txt +++ /dev/null @@ -1 +0,0 @@ -/topic/:id \ No newline at end of file diff --git a/vendor/couchapp/metadata.json b/vendor/couchapp/metadata.json index 5ed1140..d54a5e4 100644 --- a/vendor/couchapp/metadata.json +++ b/vendor/couchapp/metadata.json @@ -1,4 +1,5 @@ { "name": "couchapp", - "description": "official couchapp vendor" + "description": "official couchapp vendor", + "fetch_uri": "git://github.com/couchapp/couchapp.git" } \ No newline at end of file From 31c38e04510c1104e5a452879df08cd8a46b87af Mon Sep 17 00:00:00 2001 From: Chris Anderson Date: Mon, 19 Jul 2010 22:34:46 -0700 Subject: [PATCH 16/33] update from vendor --- evently/items/_changes/data.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/evently/items/_changes/data.js b/evently/items/_changes/data.js index 3abc83b..d6ac7b9 100644 --- a/evently/items/_changes/data.js +++ b/evently/items/_changes/data.js @@ -3,8 +3,8 @@ function(data) { var p; return { items : data.rows.map(function(r) { - p = r.value.profile; - p.message = r.value.message; + p = (r.value && r.value.profile) || {}; + p.message = r.value && r.value.message; return p; }) } From 0d014860a7ba9aa20eeb3c3f40e2a07973306639 Mon Sep 17 00:00:00 2001 From: Chris Anderson Date: Fri, 23 Jul 2010 08:34:48 -0700 Subject: [PATCH 17/33] udpate vendor --- vendor/couchapp/lib/mustache.js | 339 ++++++++++++++++++++++++++++++++ 1 file changed, 339 insertions(+) create mode 100644 vendor/couchapp/lib/mustache.js diff --git a/vendor/couchapp/lib/mustache.js b/vendor/couchapp/lib/mustache.js new file mode 100644 index 0000000..8e5a9a8 --- /dev/null +++ b/vendor/couchapp/lib/mustache.js @@ -0,0 +1,339 @@ +/* + * CommonJS-compatible mustache.js module + * + * See http://github.com/janl/mustache.js for more info. + */ + +/* + 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 "&"; + case "\\": return "\\\\"; + case '"': return '\"'; + case "<": return "<"; + case ">": return ">"; + 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); + } + }); +}(); + +exports.name = Mustache.name; +exports.version = Mustache.version; + +exports.to_html = Mustache.to_html; +exports.escape = Mustache.escape; From 951006a80c76507126ed5763af3da054537ec612 Mon Sep 17 00:00:00 2001 From: Chris Anderson Date: Tue, 27 Jul 2010 13:49:04 -0700 Subject: [PATCH 18/33] update vendor couchapp --- .../couchapp/_attachments/jquery.couch.app.js | 12 +- .../couchapp/_attachments/jquery.mustache.js | 290 +++++++++++------- .../_attachments/jquery.pathbinder.js | 1 + vendor/couchapp/lib/linkup.js | 18 ++ 4 files changed, 205 insertions(+), 116 deletions(-) create mode 100644 vendor/couchapp/lib/linkup.js diff --git a/vendor/couchapp/_attachments/jquery.couch.app.js b/vendor/couchapp/_attachments/jquery.couch.app.js index b99b136..5a14478 100644 --- a/vendor/couchapp/_attachments/jquery.couch.app.js +++ b/vendor/couchapp/_attachments/jquery.couch.app.js @@ -33,11 +33,17 @@ $.couch.app = $.couch.app || function(appFun, opts) { opts = opts || {}; $(function() { - var dbname = opts.db || document.location.href.split('/')[3]; - var dname = opts.design || unescape(document.location.href).split('/')[5]; + var urlPrefix = opts.urlPrefix || ""; + $.couch.urlPrefix = urlPrefix; + + var index = urlPrefix.split('/').length; + var fragments = unescape(document.location.href).split('/'); + var dbname = opts.db || fragments[index + 2]; + var dname = opts.design || fragments[index + 4]; + var db = $.couch.db(dbname); var design = new Design(db, dname); - + // docForm applies CouchDB behavior to HTML forms. // todo make this a couch.app plugin function docForm(formSelector, opts) { diff --git a/vendor/couchapp/_attachments/jquery.mustache.js b/vendor/couchapp/_attachments/jquery.mustache.js index 701ed0d..5aa67de 100644 --- a/vendor/couchapp/_attachments/jquery.mustache.js +++ b/vendor/couchapp/_attachments/jquery.mustache.js @@ -8,13 +8,9 @@ See http://github.com/defunkt/mustache for more info. ;(function($) { /* - Shamless port of http://github.com/defunkt/mustache - by Jan Lehnardt , Alexander Lang , - Sebastian Cohnen + mustache.js — Logic-less templates in JavaScript - Thanks @defunkt for the awesome code. - - See http://github.com/defunkt/mustache for more info. + See http://mustache.github.com/ for more info. */ var Mustache = function() { @@ -24,16 +20,45 @@ var Mustache = function() { 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 + } - render: function(template, context, partials) { // fail fast - if(template.indexOf(this.otag) == -1) { - return template; + 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); - return this.render_tags(html, 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); + } }, /* @@ -41,56 +66,86 @@ var Mustache = function() { */ render_pragmas: function(template) { // no pragmas - if(template.indexOf(this.otag + "%") == -1) { + if(!this.includes("%", template)) { return template; } var that = this; - var regex = new RegExp(this.otag + "%(.+)" + this.ctag); - return template.replace(regex, function(match, pragma) { - that.pragmas[pragma] = true; + 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 global scope and render it + /* + Tries to find a partial in the curent scope and render it */ render_partial: function(name, context, partials) { - if(typeof(context[name]) != "object") { - throw({message: "subcontext for '" + name + "' is not an object"}); + name = this.trim(name); + if(!partials || partials[name] === undefined) { + throw({message: "unknown_partial '" + name + "'"}); } - if(!partials || !partials[name]) { - throw({message: "unknown_partial"}); + if(typeof(context[name]) != "object") { + return this.render(partials[name], context, partials, true); } - return this.render(partials[name], context[name], partials); + return this.render(partials[name], context[name], partials, true); }, /* - Renders boolean and enumerable sections + Renders inverted (^) and normal (#) sections */ render_section: function(template, context, partials) { - if(template.indexOf(this.otag + "#") == -1) { + 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 + "\\#(.+)" + this.ctag + - "\\s*([\\s\\S]+?)" + this.otag + "\\/\\1" + this.ctag + "\\s*", "mg"); + 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, name, content) { + return template.replace(regex, function(match, type, name, content) { var value = that.find(name, context); - if(that.is_array(value)) { // Enumerable, Let's loop! - return that.map(value, function(row) { - return that.render(content, that.merge(context, - that.create_context(row)), partials); - }).join(''); - } else if(value) { // boolean section - return that.render(content, context, partials); - } else { - return ""; + 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 ""; + } } }); }, @@ -98,41 +153,43 @@ var Mustache = function() { /* Replace {{foo}} and friends with values from our view */ - render_tags: function(template, context, partials) { - var lines = template.split("\n"); + render_tags: function(template, context, partials, in_recursion) { + // tit for tat + var that = this; var new_regex = function() { - return new RegExp(that.otag + "(=|!|>|\\{|%)?([^\/#]+?)\\1?" + + return new RegExp(that.otag + "(=|!|>|\\{|%)?([^\\/#\\^]+?)\\1?" + that.ctag + "+", "g"); }; - // tit for tat - var that = this; - var regex = new_regex(); - for (var i=0; i < lines.length; i++) { - lines[i] = lines[i].replace(regex, function (match,operator,name) { - switch(operator) { - case "!": // ignore comments - return match; - case "=": // set new delimiters, rebuild the replace regexp - that.set_delimiters(name); - regex = new_regex(); - // redo the line in order to get tags with the new delimiters - // on the same line - i--; - return ""; - case ">": // render partial - return that.render_partial(name, context, partials); - case "{": // the triple mustache is unescaped - return that.find(name, context); - return ""; - default: // escape the value - return that.escape(that.find(name, context)); - } - },this); + 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)); + } }; - return lines.join("\n"); + 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) { @@ -152,20 +209,33 @@ var Mustache = function() { '(\\' + specials.join('|\\') + ')', 'g' ); } - return text.replace(arguments.callee.sRE, '\\$1'); + return text.replace(arguments.callee.sRE, '\\$1'); }, /* - find `name` in current `context`. That is find me a value + find `name` in current `context`. That is find me a value from the view object */ find: function(name, context) { name = this.trim(name); - if(typeof context[name] === "function") { - return context[name].apply(context); + + // 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(context[name] !== undefined) { - return context[name]; + if(value !== undefined) { + return value; } // silently ignore unkown variables return ""; @@ -173,82 +243,68 @@ var Mustache = function() { // Utility methods + /* includes tag */ + includes: function(needle, haystack) { + return haystack.indexOf(this.otag + needle) != -1; + }, + /* Does away with nasty characters */ escape: function(s) { - return s.toString().replace(/[&"<>\\]/g, function(s) { + s = String(s === null ? "" : s); + return s.replace(/&(?!\w+;)|["<>\\]/g, function(s) { switch(s) { - case "&": return "&"; - case "\\": return "\\\\";; - case '"': return '\"';; - case "<": return "<"; - case ">": return ">"; - default: return s; + case "&": return "&"; + case "\\": return "\\\\"; + case '"': return '\"'; + case "<": return "<"; + case ">": return ">"; + default: return s; } }); }, - /* - Merges all properties of object `b` into object `a`. - `b.property` overwrites a.property` - */ - merge: function(a, b) { - var _new = {}; - for(var name in a) { - if(a.hasOwnProperty(name)) { - _new[name] = a[name]; - } - }; - for(var name in b) { - if(b.hasOwnProperty(name)) { - _new[name] = b[name]; - } - }; - return _new; - }, - // by @langalex, support for arrays of strings create_context: function(_context) { if(this.is_object(_context)) { return _context; - } else if(this.pragmas["JSTACHE-ENABLE-STRING-ARRAYS"]) { - 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' + return a && typeof a == "object"; }, - /* - Thanks Doug Crockford - JavaScript — The Good Parts lists an alternative that works better with - frames. Frames can suck it, we use the simple version. - */ is_array: function(a) { - return (a && - typeof a === 'object' && - a.constructor === Array); + return Object.prototype.toString.call(a) === '[object Array]'; }, /* Gets rid of leading and trailing whitespace */ trim: function(s) { - return s.replace(/^\s*|\s*$/g, ''); + 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) + return array.map(fn); } else { var r = []; var l = array.length; - for(i=0;i'+a+''; + }).replace(/\@([\w\-]+)/g,function(user,name) { + return ''+user+''; + }).replace(/\#([\w\-\.]+)/g,function(word,tag) { + return ''+word+''; + }); +}; From abbbea4c29313d0f052e2446805c129e0ca2a4d8 Mon Sep 17 00:00:00 2001 From: Chris Anderson Date: Wed, 15 Sep 2010 11:56:37 -0700 Subject: [PATCH 19/33] update vendor --- vendor/couchapp/README.md | 3 - .../couchapp/_attachments/jquery.couch.app.js | 350 +++++++----------- .../_attachments/jquery.couch.app.util.js | 13 +- .../couchapp/_attachments/jquery.evently.js | 74 +++- .../_attachments/jquery.pathbinder.js | 6 +- vendor/couchapp/lib/code.js | 20 + vendor/couchapp/lib/docform.js | 133 ++++--- vendor/couchapp/lib/linkup.js | 4 +- vendor/couchapp/lib/utils.js | 21 ++ vendor/couchapp/lib/validate.js | 53 +++ 10 files changed, 366 insertions(+), 311 deletions(-) delete mode 100644 vendor/couchapp/README.md create mode 100644 vendor/couchapp/lib/code.js create mode 100644 vendor/couchapp/lib/utils.js create mode 100644 vendor/couchapp/lib/validate.js diff --git a/vendor/couchapp/README.md b/vendor/couchapp/README.md deleted file mode 100644 index 2606c1d..0000000 --- a/vendor/couchapp/README.md +++ /dev/null @@ -1,3 +0,0 @@ -## CouchApp - more than just a filesystem mapper - -This is where documentation will go for the client and server JavaScript parts of CouchApp. \ No newline at end of file diff --git a/vendor/couchapp/_attachments/jquery.couch.app.js b/vendor/couchapp/_attachments/jquery.couch.app.js index 5a14478..fc500a2 100644 --- a/vendor/couchapp/_attachments/jquery.couch.app.js +++ b/vendor/couchapp/_attachments/jquery.couch.app.js @@ -19,9 +19,14 @@ // }); (function($) { - - function Design(db, name) { + + 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) { db.view(name+'/'+view, opts); }; @@ -30,229 +35,131 @@ }; } - $.couch.app = $.couch.app || function(appFun, opts) { - opts = opts || {}; - $(function() { - var urlPrefix = opts.urlPrefix || ""; - $.couch.urlPrefix = urlPrefix; - - var index = urlPrefix.split('/').length; - var fragments = unescape(document.location.href).split('/'); - var dbname = opts.db || fragments[index + 2]; - var dname = opts.design || fragments[index + 4]; - - var db = $.couch.db(dbname); - var design = new Design(db, dname); + function docForm() { alert("docForm has been moved to vendor/couchapp/lib/docForm.js, use app.require to load") }; - // docForm applies CouchDB behavior to HTML forms. - // todo make this a couch.app plugin - 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); - opts.fields.forEach(function(field) { - var element = form.find("[name="+field+"]"); - if (element.attr('type') === 'checkbox') { - var val = element.attr('checked'); - } else { - var val = element.val(); - if (!val) return; - } - var parts = field.split('-'); - var frontObj = doc, frontName = parts.shift(); - 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; - }); + 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]); + } - // 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); + function makeRequire(ddoc) { + var moduleCache = []; + function getCachedModule(name, parents) { + var key, i, len = moduleCache.length; + for (i=0;i 0) { - frontObj[frontName] = frontObj[frontName] || {}; - frontObj = frontObj[frontName]; - frontName = parts.shift(); - } - frontObj[frontName] = val; - }); -} - -function onSubmit(form, db, doc, opts) { - formToDeepJSON(form, opts.fields, doc); - if (opts.beforeSave) {opts.beforeSave(doc);} - db.saveDoc(localFormDoc, { - success : function(resp) { - if (opts.success) {opts.success(resp, doc);} - } - }); -}; - -function applyFields(form, doc) { - -}; -exports.applyFields = applyFields; -// docForm applies CouchDB behavior to HTML forms. -// todo make this a couch.app plugin 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; }); @@ -71,38 +79,43 @@ function docForm(formSelector, opts) { frontName = parts.shift(); } if (frontObj && frontObj[frontName]) { - form.find("[name="+field+"]").val(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.onLoad) {opts.onLoad(doc);} + if (opts.load || opts.onLoad) {(opts.load || opts.onLoad)(doc);} localFormDoc = doc; docToForm(doc); - }}); - } else if (opts.template) { - if (opts.onLoad) {opts.onLoad(opts.template);} - localFormDoc = opts.template; - docToForm(localFormDoc); + }}); + } 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); } - 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; + }, + localDoc : function() { + formToDeepJSON(formSelector, opts.fields, localFormDoc); + return localFormDoc; } - - - - + }; + return instance; +} diff --git a/vendor/couchapp/lib/linkup.js b/vendor/couchapp/lib/linkup.js index 8aa5c71..1de6bcf 100644 --- a/vendor/couchapp/lib/linkup.js +++ b/vendor/couchapp/lib/linkup.js @@ -11,8 +11,8 @@ exports.encode = function(body, person_prefix, tag_prefix) { return body.replace(/((ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?)/gi,function(a) { return ''+a+''; }).replace(/\@([\w\-]+)/g,function(user,name) { - return ''+user+''; + return ''+user+''; }).replace(/\#([\w\-\.]+)/g,function(word,tag) { - return ''+word+''; + return ''+word+''; }); }; diff --git a/vendor/couchapp/lib/utils.js b/vendor/couchapp/lib/utils.js new file mode 100644 index 0000000..19dfbe0 --- /dev/null +++ b/vendor/couchapp/lib/utils.js @@ -0,0 +1,21 @@ +exports.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"; +}; diff --git a/vendor/couchapp/lib/validate.js b/vendor/couchapp/lib/validate.js new file mode 100644 index 0000000..8d591df --- /dev/null +++ b/vendor/couchapp/lib/validate.js @@ -0,0 +1,53 @@ +// a library for validations +// over time we expect to extract more helpers and move them here. +exports.init = function(newDoc, oldDoc, userCtx, secObj) { + var v = {}; + + v.forbidden = function(message) { + throw({forbidden : message}); + }; + + v.unauthorized = function(message) { + throw({unauthorized : message}); + }; + + v.assert = function(should, message) { + if (!should) v.forbidden(message); + } + + v.isAdmin = function() { + return userCtx.roles.indexOf('_admin') != -1 + }; + + v.isRole = function(role) { + return userCtx.roles.indexOf(role) != -1 + }; + + v.require = function() { + for (var i=0; i < arguments.length; i++) { + var field = arguments[i]; + message = "The '"+field+"' field is required."; + if (typeof newDoc[field] == "undefined") v.forbidden(message); + }; + }; + + v.unchanged = function(field) { + if (oldDoc && oldDoc[field] != newDoc[field]) + v.forbidden("You may not change the '"+field+"' field."); + }; + + v.matches = function(field, regex, message) { + if (!newDoc[field].match(regex)) { + message = message || "Format of '"+field+"' field is invalid."; + v.forbidden(message); + } + }; + + // this ensures that the date will be UTC, parseable, and collate correctly + v.dateFormat = function(field) { + message = "Sorry, '"+field+"' is not a valid date format. Try: 2010-02-24T17:00:03.432Z"; + v.matches(field, /\d{4}\-\d{2}\-\d{2}T\d{2}:\d{2}:\d{2}(\.\d*)?Z/, message); + } + + return v; +}; From 39f15589ebf99b0f9bcb51ea78c675a27841c2da Mon Sep 17 00:00:00 2001 From: Chris Anderson Date: Wed, 15 Sep 2010 12:25:54 -0700 Subject: [PATCH 20/33] selenas changes --- evently/items/_changes/data.js | 56 ++++++++++++++++++---------- evently/items/_changes/mustache.html | 32 ++++++++-------- evently/items/_changes/query.json | 6 +-- 3 files changed, 53 insertions(+), 41 deletions(-) diff --git a/evently/items/_changes/data.js b/evently/items/_changes/data.js index b84453c..06214ff 100644 --- a/evently/items/_changes/data.js +++ b/evently/items/_changes/data.js @@ -1,21 +1,39 @@ function(data) { - var db = $$(this).app.db; - function attachments(doc) { - var as = [], a = doc._attachments; - if (a) { - for (var n in a) { - as.push({name: n, path: ["",db.name,doc._id,n].join('/')}); - } - return as; - } - }; - var p; + +// $.log(data); + var items = []; + for (var i=0; i < data.rows.length; i++) { + var r = data.rows[i].value, item = {}; + item.bio = r.bio || ""; + item.urls = []; + item.name = data.rows[i].id; + + if (r.url) { + item.urls.push({url:r.url, label:"www"}); + } + +// TODO: handle those people who handed us STRINGS or ARRAYS + if (r.urls) { + if (typeof(r.urls) == 'string') { + item.urls.push({url:r.urls, label:"www"}); + } else { + var u = r.urls; + for (var k in u) { + item.urls.push({url:u[k], label:k}); + } + } + } + + if (r._attachments) { + item.attachments = []; + var v = r._attachments; + for (var w in v) { + item.attachments.push({src:[encodeURIComponent(data.rows[i].id), encodeURIComponent(w)].join('/')}); + } + } + items.push(item); + }; return { - items : data.rows.map(function(r) { - p = r.value.profile; - p.message = r.value.message; - p.atts = attachments(r.value); - return p; - }) - } -}; \ No newline at end of file + items: items + }; +}; diff --git a/evently/items/_changes/mustache.html b/evently/items/_changes/mustache.html index 23a9ab3..df09d70 100644 --- a/evently/items/_changes/mustache.html +++ b/evently/items/_changes/mustache.html @@ -1,22 +1,20 @@ -

    Customize this format here: ddoc.evently.items._changes.mustache

    -

    Recent Messages

    +

    CouchCampers

      - {{#items}} + {{#items}}
    • -
      - {{#gravatar_url}}{{name}}{{/gravatar_url}} -
      - {{nickname}} -
      -
      -

      {{message}}

      - {{#atts}} -

      {{name}}

      - - {{/atts}} + {{#attachments}} + + {{/attachments}} + {{name}}
      + {{bio}}
      +
    • - {{/items}} + {{/items}}
    -

    Protip: If you setup continuous replication between this database and a remote one, this list will reflect remote changes in near real-time.

    -

    This would be a good place to add pagination.

    diff --git a/evently/items/_changes/query.json b/evently/items/_changes/query.json index f06e992..3b2a659 100644 --- a/evently/items/_changes/query.json +++ b/evently/items/_changes/query.json @@ -1,5 +1 @@ -{ - "view" : "recent-items", - "descending" : "true", - "limit" : 50 -} \ No newline at end of file +{"descending": "true", "limit": 50, "view": "recent-items"} \ No newline at end of file From d606babe7f758c57b23703f4369a87a911a1dee7 Mon Sep 17 00:00:00 2001 From: Chris Anderson Date: Wed, 15 Sep 2010 12:40:50 -0700 Subject: [PATCH 21/33] merge more from selena --- _id | 2 +- couchapp.json | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/_id b/_id index 703f3c2..87c2078 100644 --- a/_id +++ b/_id @@ -1 +1 @@ -_design/proto \ No newline at end of file +_design/profiles \ No newline at end of file diff --git a/couchapp.json b/couchapp.json index 0cc524d..412f616 100644 --- a/couchapp.json +++ b/couchapp.json @@ -1,4 +1 @@ -{ - "name": "Name of your CouchApp", - "description": "CouchApp" -} \ No newline at end of file +{"description": "CouchCamp Attendees", "name": "Proto-Sproingg"} From f0b6c9c261fd97bbc979dc7baf70d9e4be287606 Mon Sep 17 00:00:00 2001 From: Chris Anderson Date: Wed, 15 Sep 2010 13:23:00 -0700 Subject: [PATCH 22/33] migrate docs to have uuid ids --- views/recent-items/map.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/views/recent-items/map.js b/views/recent-items/map.js index 1bc28c8..fe66ea7 100644 --- a/views/recent-items/map.js +++ b/views/recent-items/map.js @@ -1,5 +1,3 @@ function(doc) { - if (doc.created_at) { - emit(doc.created_at, doc); - } -}; \ No newline at end of file + emit(doc._rev.split("-")[1], doc); +} From 0a6fe5792874e9ae292da84513b40687edb26831 Mon Sep 17 00:00:00 2001 From: Chris Anderson Date: Wed, 15 Sep 2010 13:23:06 -0700 Subject: [PATCH 23/33] migrate script docs to have uuid ids --- migrate.js | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 migrate.js diff --git a/migrate.js b/migrate.js new file mode 100644 index 0000000..98b4068 --- /dev/null +++ b/migrate.js @@ -0,0 +1,40 @@ +var couchdb = require("couchdb") + , client = couchdb.createClient(5984, "localhost", "xxx", "xxx") + , db = client.db("profiles") + ; + +db.allDocs(function(er, view) { + view.rows.forEach(function(r) { + console.log(r.id) + db.getDoc(r.id, {attachments:true}, function(er, doc) { + if (er) { + console.log("getDoc", er) + } else { + console.log(doc._id) + var rev = doc._rev; + var name = doc._id; + if (name.length < 30) { + delete doc._rev; + delete doc._id; + doc.name = name; + db.removeDoc(name, rev, function(er) { + if (er) { + console.log("error deleting", name, er) + } else { + console.log("deleted", name) + } + }) + // console.log("update", doc) + // db.saveDoc(doc, function(er, resp) { + // if (er) { + // console.log(er) + // } else { + // + // } + // }) + } + } + }); + }); + +}); \ No newline at end of file From 1e1d04063d45ab31e9c38809131a65a2e61f9bef Mon Sep 17 00:00:00 2001 From: Chris Anderson Date: Wed, 15 Sep 2010 13:30:22 -0700 Subject: [PATCH 24/33] got profiles displaying properly --- evently/items/_changes/data.js | 7 ++++--- evently/items/_changes/mustache.html | 1 + views/recent-items/map.js | 4 +++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/evently/items/_changes/data.js b/evently/items/_changes/data.js index 06214ff..ed847bc 100644 --- a/evently/items/_changes/data.js +++ b/evently/items/_changes/data.js @@ -1,13 +1,14 @@ function(data) { - + var dbname = $$(this).app.db.name; // $.log(data); var items = []; for (var i=0; i < data.rows.length; i++) { var r = data.rows[i].value, item = {}; item.bio = r.bio || ""; item.urls = []; - item.name = data.rows[i].id; - + item.name = data.rows[i].value.name; + item.id = data.rows[i].id; + item.dbname = dbname; if (r.url) { item.urls.push({url:r.url, label:"www"}); } diff --git a/evently/items/_changes/mustache.html b/evently/items/_changes/mustache.html index df09d70..d57ef74 100644 --- a/evently/items/_changes/mustache.html +++ b/evently/items/_changes/mustache.html @@ -14,6 +14,7 @@

    CouchCampers

    {{/urls}} + edit in Futon
    {{/items}} diff --git a/views/recent-items/map.js b/views/recent-items/map.js index fe66ea7..3e0edb4 100644 --- a/views/recent-items/map.js +++ b/views/recent-items/map.js @@ -1,3 +1,5 @@ function(doc) { - emit(doc._rev.split("-")[1], doc); + if (doc.name) { + emit(doc._rev.split("-")[1], doc); + } } From 67d1986d353dff12b2602d456b791df03c514992 Mon Sep 17 00:00:00 2001 From: Chris Anderson Date: Wed, 15 Sep 2010 14:04:59 -0700 Subject: [PATCH 25/33] cosmetics --- _attachments/index.html | 6 +++--- evently/items/_changes/data.js | 8 ++++--- evently/items/_changes/mustache.html | 6 ++++-- evently/profile/loggedOut/mustache.html | 1 + evently/profile/profileReady/mustache.html | 21 +++++++++++++------ .../profileReady/selectors/form/submit.js | 18 ++++++++++++---- views/recent-items/map.js | 3 ++- 7 files changed, 44 insertions(+), 19 deletions(-) create mode 100644 evently/profile/loggedOut/mustache.html diff --git a/_attachments/index.html b/_attachments/index.html index 5e56022..34a0e1d 100644 --- a/_attachments/index.html +++ b/_attachments/index.html @@ -1,14 +1,14 @@ - My New CouchApp + CouchCamp Profiles
    -

    Generated CouchApp

    - +

    CouchCamp Profiles

    +

    If you were at CouchCamp (even in spirit) please add yourself to this list!

    diff --git a/evently/items/_changes/data.js b/evently/items/_changes/data.js index ed847bc..f4bd1b7 100644 --- a/evently/items/_changes/data.js +++ b/evently/items/_changes/data.js @@ -3,12 +3,14 @@ function(data) { // $.log(data); var items = []; for (var i=0; i < data.rows.length; i++) { - var r = data.rows[i].value, item = {}; + var r = data.rows[i].value, item = {}, v=r; item.bio = r.bio || ""; - item.urls = []; - item.name = data.rows[i].value.name; + item.name = v.name; + item.company = v.company; + item.email = v.email; item.id = data.rows[i].id; item.dbname = dbname; + item.urls = []; if (r.url) { item.urls.push({url:r.url, label:"www"}); } diff --git a/evently/items/_changes/mustache.html b/evently/items/_changes/mustache.html index d57ef74..d624e24 100644 --- a/evently/items/_changes/mustache.html +++ b/evently/items/_changes/mustache.html @@ -5,8 +5,10 @@

    CouchCampers

    {{#attachments}} {{/attachments}} - {{name}}
    - {{bio}}
    + {{name}} + {{company}}
    + {{#email}}{{email}}{{/email}} +

    {{bio}}