From: dsc Date: Mon, 26 Dec 2011 20:21:37 +0000 (-0800) Subject: Model fixes, rejiggers configuration, adds JS libs. X-Git-Url: http://git.less.ly:3516/?a=commitdiff_plain;h=d2ecb520d226445ed19d6adb713781b82bf1e37b;p=crisishaiku.git Model fixes, rejiggers configuration, adds JS libs. --- diff --git a/README.md b/README.md index ebc7c4b..e47a865 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,9 @@ A website dedicated to the poetry of financial disaster~ -## Install +## Installation + +### Install & Setup First up, `virtualenv`: @@ -14,8 +16,32 @@ Then install the egg: pip install -e . +Now that we've got all the deps, we'll want to create the database. + + >>> from crisishaiku import app, db, User + >>> db.create_all() + +You'll see it blorp out some SQL. Let's make sure it worked. + + >>> u = User('leigh', 'trouble@crisishaiku.com') + >>> db.session.add(u) + >>> db.session.commit() + +Yet more SQL, and hopefully no error message. Hooray! + +Finally, run the server: + + bin/server + +And point your browser at http://localhost:5000/ -- you should see the request +blow by. Hello, world! + +Oh, and finally: as a convenience, `bin/server` will attempt to activate the +virtualenv if one isn't already active (as obviously I find that obnoxious +and always forget). + -## Dev Install +### Haiku Finder Install **OPTIONAL** diff --git a/bin/find_haiku.py b/bin/find_haiku.py index d1e7ebf..254dad3 100755 --- a/bin/find_haiku.py +++ b/bin/find_haiku.py @@ -29,7 +29,7 @@ VAR_DIR = path('var') DATA_DIR = path('data') STATE_FILE = 'state.json' SYLLABLE_FILE = 'syllables.json' -REPORT_FILE = DATA_DIR/'report/fcir.txt' +REPORT_FILE = DATA_DIR/'report/docs/fcir.txt' OUT_DIR = DATA_DIR/'haikus' OUTFILE_OVERLAP = 'haikus.txt' diff --git a/crisishaiku/__init__.py b/crisishaiku/__init__.py index 15a2a30..b1a97df 100644 --- a/crisishaiku/__init__.py +++ b/crisishaiku/__init__.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # -*- coding: utf-8 -*- """ crisishaiku.com -- a website dedicated to the poetry of financial disaster """ @@ -6,6 +5,8 @@ __version__ = '0.0.1' VERSION = tuple(map(int, __version__.split('.'))) +import os +from path import path from flask import (Flask, request, session, g, redirect, url_for, abort, render_template, flash, ) @@ -13,21 +14,32 @@ from flaskext.sqlalchemy import SQLAlchemy from flaskext.bcrypt import Bcrypt from flaskext.oauth import OAuth +HERE = path(__file__).dirname().dirname().abspath() -app = Flask('crisishaiku') -### Config -# Load from our base config file -app.config.from_pyfile('../etc/flask.cfg') -# Load any overrides from the file specified in the env var FLASK_SETTINGS -app.config.from_envvar('FLASK_SETTINGS', silent=True) -# render .jade files using pyjade +app = Flask('crisishaiku', + instance_relative_config=True, instance_path=HERE/'var') + + +# Load Config +mode = os.environ.get('CRISISHAIKU_MODE', 'dev').lower() +config_obj = 'DevelopmentConfig' +if mode in ('prod', 'production'): + config_obj = 'ProductionConfig' +elif mode in ('test', 'testing', 'unittest'): + config_obj = 'UnitTestConfig' +app.config.from_object('crisishaiku.config.'+config_obj) + +# Load any overrides from the file specified in the env var CRISISHAIKU_SETTINGS +app.config.from_envvar('CRISISHAIKU_SETTINGS', silent=True) + +# Render .jade files using pyjade app.jinja_env.add_extension('pyjade.ext.jinja.PyJadeExtension') + db = SQLAlchemy(app) crypt = Bcrypt(app) - oauth = OAuth() twitter = oauth.remote_app('twitter', base_url = 'https://api.twitter.com/1/', @@ -39,6 +51,7 @@ twitter = oauth.remote_app('twitter', ) -import crisishaiku.models -import crisishaiku.views +import crisishaiku.model +from crisishaiku.model import * +import crisishaiku.view diff --git a/crisishaiku/config.py b/crisishaiku/config.py new file mode 100644 index 0000000..461736c --- /dev/null +++ b/crisishaiku/config.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- + +class Config(object): + MODE = None + DEBUG = False + TESTING = False + TWITTER_CONSUMER_KEY = 'tQww8eCQbbtF12hHPTJA' + TWITTER_CONSUMER_SECRET = 'OE9AIRTkxWTBJU88PJjqGI4soC6k0pUOtrRur0Q8d4w' + SQLALCHEMY_ECHO = False + SQLALCHEMY_DATABASE_URI = 'sqlite://:memory:' + + +class UnitTestConfig(Config): + MODE = 'unittest' + TESTING = True + + +class DevelopmentConfig(Config): + MODE = 'development' + DEBUG = True + SQLALCHEMY_ECHO = True + SQLALCHEMY_DATABASE_URI = 'sqlite:///../var/haiku.db' + + +class ProductionConfig(Config): + MODE = 'prod' + SQLALCHEMY_DATABASE_URI = 'mysql://crisishaiku@localhost/crisishaiku' + diff --git a/crisishaiku/models.py b/crisishaiku/model.py similarity index 58% rename from crisishaiku/models.py rename to crisishaiku/model.py index 804f478..ff51596 100644 --- a/crisishaiku/models.py +++ b/crisishaiku/model.py @@ -1,7 +1,7 @@ -#!/usr/bin/env python # -*- coding: utf-8 -*- +__all__ = ('Section', 'Haiku', 'User',) +# __all__ = ('Section', 'Haiku', 'User', 'Target', 'Comment', 'Like', 'Tag',) -# __all__ from sqlalchemy import ( Table, Column, ForeignKey, @@ -13,10 +13,7 @@ from sqlalchemy import ( from sqlalchemy.orm import relationship, backref, aliased import anyjson as json -from crisishaiku import db - -# pw_hash = crypt.generate_password_hash('hunter2') -# crypt.check_password_hash(pw_hash, 'hunter2') # returns True +from crisishaiku import db, crypt ### Helpers @@ -35,21 +32,38 @@ class JSONData(TypeDecorator): value = json.loads(value) return value -class TimestampMixin(object): - ctime = Column(DateTime, default=func.now()) - mtime = Column(DateTime, default=func.now()) ### Model Classes -class User(TimestampMixin, db.Model): +class Section(db.Model): + "A chunk of the Report. May represent a chapter/section node, or a paragraph of text." + id = Column(Integer, primary_key=True) + title = Column(String(200)) + text = Column(Text()) + parent_id = Column(Integer, ForeignKey('section.id')) + children = relationship('Section', lazy='dynamic', backref=backref('parent', lazy='dynamic', remote_side=[id])) + haikus = relationship('Haiku', lazy='dynamic', backref=backref('context', lazy='dynamic')) + + +class Haiku(db.Model): + id = Column(Integer, primary_key=True) + text = Column(Text()) + start = Column(Integer) # character index into the section where this haiku begins + context_id = Column(Integer, ForeignKey('section.id')) + + +class User(db.Model): id = Column(Integer, primary_key=True) username = Column(String(30), unique=True) email = Column(String(120), unique=True) pwhash = Column(String(60)) + ctime = Column(DateTime, default=func.now()) + mtime = Column(DateTime, default=func.now()) last_seen = Column(DateTime, default=func.now()) + verified = Column(Boolean, default=False) banned = Column(Boolean, default=False) deleted = Column(Boolean, default=False) @@ -58,7 +72,7 @@ class User(TimestampMixin, db.Model): accounts = Column(JSONData()) # likes = relationship('Like', lazy='dynamic', backref=backref('user', lazy='dynamic')) - comments = relationship('Comment', lazy='dynamic', backref=backref('author', lazy='dynamic')) + # comments = relationship('Comment', lazy='dynamic', backref=backref('author', lazy='dynamic')) # per-user tag tracking disabled for now ## tags = relationship('UserTag', backref=backref('user', lazy='dynamic')) @@ -71,38 +85,39 @@ class User(TimestampMixin, db.Model): self.username = username self.email = email + @property + def password(self): + return self.pwhash + + @password.setter + def password(self, pw): + self.pwhash = crypt.generate_password_hash(pw) + + def checkPassword(self, pw): + return crypt.check_password_hash(self.pwhash, pw) + + + ### OAuth + + def addAccount(self, type, data): + "Associate an account (after authentication & authorization has taken place)." + accounts = self.accounts or {} + accounts[type] = data + self.accounts = accounts.copy() # copy to ensure SQLAlchemy picks up the change + + def removeAccount(self, type): + "Removes an associated account." + accounts = self.accounts + if not accounts or type not in accounts: + return + del accounts[type] + self.accounts = accounts.copy() # copy to ensure SQLAlchemy picks up the change + + def __repr__(self): return '' % (self.username, self.email) -# class Section(db.Model): -# "A chunk of the Report. May represent a chapter/section node, or a paragraph of text." -# id = Column(Integer, primary_key=True) -# title = Column(String(200)) -# text = Column(Text()) -# parent_id = Column(Integer, ForeignKey('section.id')) -# children = relationship('Section', lazy='dynamic', backref=backref('parent', lazy='dynamic')) -# haikus = relationship('Haiku', lazy='dynamic', backref=backref('context', lazy='dynamic')) - - -# class Haiku(db.Model): -# id = Column(Integer, primary_key=True) -# text = Column(Text()) -# start = Column(Integer) # character index into the section where this haiku begins -# context_id = Column(Integer, ForeignKey('section.id')) - - -class Comment(TimestampMixin, db.Model): - id = Column(Integer, primary_key=True) - # target = relationship('Target', lazy='dynamic') - author_id = Column(Integer, ForeignKey('user.id')) - text = Column(Text()) - approved = Column(Boolean, default=False) # spam filter approval - deleted = Column(Boolean, default=False) - - - - # class Target(db.Model): # "A polymorphic pointer to a Comment, Haiku, or Section." # id = Column(Integer, primary_key=True) @@ -115,6 +130,17 @@ class Comment(TimestampMixin, db.Model): # return self.section or self.haiku or self.comment +# class Comment(db.Model): +# id = Column(Integer, primary_key=True) +# # target = relationship('Target', lazy='dynamic') +# author_id = Column(Integer, ForeignKey('user.id')) +# text = Column(Text()) +# ctime = Column(DateTime, default=func.now()) +# mtime = Column(DateTime, default=func.now()) +# approved = Column(Boolean, default=False) # spam filter approval +# deleted = Column(Boolean, default=False) + + # likes = db.Table('likes', # Column('user_id', Integer, ForeignKey('user.id')), # Column('target_id', Integer, ForeignKey('target.id')), diff --git a/crisishaiku/static/css/reset.css b/crisishaiku/static/css/reset.css new file mode 100644 index 0000000..29e62cd --- /dev/null +++ b/crisishaiku/static/css/reset.css @@ -0,0 +1,160 @@ +html, body, div, span, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +abbr, address, cite, code, del, dfn, em, img, ins, kbd, q, samp, +small, strong, sub, sup, var, b, i, dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td, +article, aside, canvas, details, figcaption, figure, +footer, header, hgroup, menu, nav, section, summary, +time, mark, audio, video { + margin:0; padding:0; + border: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline; } + +article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section { + display: block; } + +html { color:#000; background:#fff; width:100%; height:100%; overflow-y: scroll; } +body { position:absolute; width:100%; top:0; left:0; } + +address,caption,cite,code,dfn,em,strong,th,var,optgroup { font-style:inherit; font-weight:inherit; } +fieldset,img { border:0; } +caption,th { text-align:left; } +caption { margin-bottom:.5em; text-align:center; } + +h1,h2,h3,h4,h5,h6 { font-size:100%; font-weight:normal; } +h1,h2,h3,h4,h5,h6,strong { font-weight:bold; } +h1 { font-size:250%; } +h2 { font-size:150%; } +h3 { font-size:125%; } +h4 { font-size:110%; } +h1,h2,h3 { margin:0.5em 0 1em; } + + +ins { background-color: #ff9; color: #000; text-decoration: none; } +mark { background-color: #ff9; color: #000; font-style: italic; font-weight: bold; } +del { text-decoration: line-through; } +abbr[title], dfn[title] { border-bottom: 1px dotted; cursor: help; } +abbr,acronym { border:0; font-variant:normal; border-bottom:1px dotted #000; cursor:help; } +em, i { font-style:italic; } +small { font-size: 85%; } +strong, b, th { font-weight: bold; } +u { text-decoration:underline; } + +/* Set sub, sup without affecting line-height: gist.github.com/413930 */ +sub, sup { font-size: 75%; line-height: 0; position: relative; } +sup { top: -0.5em; } +sub { bottom: -0.25em; } + + +blockquote, q { quotes: none; } +blockquote:before, blockquote:after, +q:before, q:after { content: ''; content: none; } +hr { display: block; height: 1px; border: 0; border-top: 1px solid #ccc; margin: 1em 0; padding: 0; } + + +/** Font normalization inspired by YUI Library's fonts.css: developer.yahoo.com/yui/ */ +//body { font:13px/1.231 sans-serif; *font-size:small; } /* Hack retained to preserve specificity */ +body { font:16px/1.231 sans-serif; *font-size:small; } /* Hack retained to preserve specificity */ +select, input, textarea, button { font:99% sans-serif; } + +/* Normalize monospace sizing: en.wikipedia.org/wiki/MediaWiki_talk:Common.css/Archive_11#Teletype_style_fix_for_Chrome */ +pre, code, kbd, samp { font-family: monospace, sans-serif; } +/* www.pathf.com/blogs/2008/05/formatting-quoted-code-in-blog-posts-css21-white-space-pre-wrap/ */ +pre { + white-space: pre; + white-space: pre-wrap; /* css-3 */ + white-space: -moz-pre-wrap; /* Mozilla, since 1999 */ + white-space: -pre-wrap; /* Opera 4-6 */ + white-space: -o-pre-wrap; /* Opera 7 */ + + word-wrap: break-word; + padding: 15px; } + +p,fieldset,table,pre { margin-bottom:1em; } + + +/* Accessible focus treatment: people.opera.com/patrickl/experiments/keyboard/test */ +a:hover, a:active { outline: none; } + + +/* Lists */ + +ul,ol,dl,blockquote { margin:0.5em; } +ol,ul,dl { margin-left:1.0em; } +li { list-style:none; } +ol { list-style-type: decimal; } +ol li { list-style:decimal inside; } +ul li { list-style:disc inside; } +dl dd { margin-left:0.5em; } + +/* Remove margins for navigation lists */ +nav ul, nav li { margin: 0; list-style:none; list-style-image: none; } + + +/* Forms */ + +input,select { vertical-align: middle; } +input,button,textarea,select,optgroup,option { font-family:inherit; font-size:inherit; font-style:inherit; font-weight:inherit; } +input,button,textarea,select { *font-size:100%; } +input[type=text],input[type=password],textarea { width:12.25em; *width:11.9em; } +textarea { overflow: auto; } /* www.sitepoint.com/blogs/2010/08/20/ie-remove-textarea-scrollbars/ */ + +legend { color:#000; } +.ie6 legend, .ie7 legend { margin-left: -7px; } + +/* Align checkboxes, radios, text inputs with their label by: Thierry Koblentz tjkdesign.com/ez-css/css/base.css */ +input[type="radio"] { vertical-align: text-bottom; } +input[type="checkbox"] { vertical-align: bottom; } +.ie7 input[type="checkbox"] { vertical-align: baseline; } +.ie6 input { vertical-align: text-bottom; } + +/* Hand cursor on clickable input elements */ +a[href], input[type='button'], input[type='submit'], input[type='image'], label[for], select, button, .pointer { cursor: pointer; } + + +/* Webkit browsers add a 2px margin outside the chrome of form elements */ +button, input, select, textarea { margin: 0; } + +/* Colors for form validity */ +input:valid, textarea:valid { } +input:invalid, textarea:invalid { + border-radius: 1px; -moz-box-shadow: 0px 0px 5px red; -webkit-box-shadow: 0px 0px 5px red; box-shadow: 0px 0px 5px red; +} +.no-boxshadow input:invalid, .no-boxshadow textarea:invalid { background-color: #f0dddd; } + +/* Make buttons play nice in IE: + www.viget.com/inspire/styling-the-button-element-in-internet-explorer/ */ +button { width: auto; overflow: visible; } + + +/* Tables */ + +table { border-collapse:collapse; border-spacing:0; } +th,td { padding:.5em; } +th { font-weight:bold; text-align:center; } +td { vertical-align: top; } + + +/* Misc */ + +/* These selection declarations have to be separate + No text-shadow: twitter.com/miketaylr/status/12228805301 + Also: hot pink! */ +::-moz-selection{ background: #FF5E99; color:#fff; text-shadow: none; } +::selection { background:#FF5E99; color:#fff; text-shadow: none; } + +/* j.mp/webkit-tap-highlight-color */ +a:link { -webkit-tap-highlight-color: #FF5E99; } + +/* Bicubic resizing for non-native sized IMG: + code.flickr.com/blog/2008/11/12/on-ui-quality-the-little-things-client-side-image-resizing/ */ +.ie7 img { -ms-interpolation-mode: bicubic; } + + +/* Minimal Basic Classes */ + +.clearer { clear:both !important; float:none !important; margin:0 !important; padding:0 !important; } +.rounded { border-radius:1em; -moz-border-radius:1em; -webkit-border-radius:1em; } diff --git a/crisishaiku/static/css/reset.min.css b/crisishaiku/static/css/reset.min.css new file mode 100644 index 0000000..c63078c --- /dev/null +++ b/crisishaiku/static/css/reset.min.css @@ -0,0 +1 @@ +html,body,div,span,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,abbr,address,cite,code,del,dfn,em,img,ins,kbd,q,samp,small,strong,sub,sup,var,b,i,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,figcaption,figure,footer,header,hgroup,menu,nav,section,summary,time,mark,audio,video{margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline;}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block;}html{color:#000;background:#fff;width:100%;height:100%;overflow-y:scroll;}body{position:absolute;width:100%;top:0;left:0;}address,caption,cite,code,dfn,em,strong,th,var,optgroup{font-style:inherit;font-weight:inherit;}fieldset,img{border:0;}caption,th{text-align:left;}caption{margin-bottom:.5em;text-align:center;}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;}h1,h2,h3,h4,h5,h6,strong{font-weight:bold;}h1{font-size:250%;}h2{font-size:150%;}h3{font-size:125%;}h4{font-size:110%;}h1,h2,h3{margin:.5em 0 1em;}ins{background-color:#ff9;color:#000;text-decoration:none;}mark{background-color:#ff9;color:#000;font-style:italic;font-weight:bold;}del{text-decoration:line-through;}abbr[title],dfn[title]{border-bottom:1px dotted;cursor:help;}abbr,acronym{border:0;font-variant:normal;border-bottom:1px dotted #000;cursor:help;}em{font-style:italic;}small{font-size:85%;}strong,th{font-weight:bold;}sub,sup{font-size:75%;line-height:0;position:relative;}sup{top:-0.5em;}sub{bottom:-0.25em;}blockquote,q{quotes:none;}blockquote:before,blockquote:after,q:before,q:after{content:'';content:none;}hr{display:block;height:1px;border:0;border-top:1px solid #ccc;margin:1em 0;padding:0;}//body{font:13px/1.231 sans-serif;*font-size:small;}body{font:16px/1.231 sans-serif;*font-size:small;}select,input,textarea,button{font:99% sans-serif;}pre,code,kbd,samp{font-family:monospace,sans-serif;}pre{white-space:pre;white-space:pre-wrap;word-wrap:break-word;padding:15px;}p,fieldset,table,pre{margin-bottom:1em;}a:hover,a:active{outline:none;}ul,ol,dl,blockquote{margin:1em;}ol,ul,dl{margin-left:2em;}li{list-style:none;}ol{list-style-type:decimal;}ol li{list-style:decimal inside;}ul li{list-style:disc inside;}dl dd{margin-left:1em;}nav ul,nav li{margin:0;list-style:none;list-style-image:none;}input,select{vertical-align:middle;}input,button,textarea,select,optgroup,option{font-family:inherit;font-size:inherit;font-style:inherit;font-weight:inherit;}input,button,textarea,select{*font-size:100%;}input[type=text],input[type=password],textarea{width:12.25em;*width:11.9em;}textarea{overflow:auto;}legend{color:#000;}.ie6 legend,.ie7 legend{margin-left:-7px;}input[type="radio"]{vertical-align:text-bottom;}input[type="checkbox"]{vertical-align:bottom;}.ie7 input[type="checkbox"]{vertical-align:baseline;}.ie6 input{vertical-align:text-bottom;}label,input[type="button"],input[type="submit"],input[type="image"],button{cursor:pointer;}button,input,select,textarea{margin:0;}input:invalid,textarea:invalid{border-radius:1px;-moz-box-shadow:0 0 5px red;-webkit-box-shadow:0 0 5px red;box-shadow:0 0 5px red;}.no-boxshadow input:invalid,.no-boxshadow textarea:invalid{background-color:#f0dddd;}button{width:auto;overflow:visible;}table{border-collapse:collapse;border-spacing:0;}th,td{padding:.5em;}th{font-weight:bold;text-align:center;}td{vertical-align:top;}::-moz-selection{background:#FF5E99;color:#fff;text-shadow:none;}::selection{background:#FF5E99;color:#fff;text-shadow:none;}a:link{-webkit-tap-highlight-color:#FF5E99;}.ie7 img{-ms-interpolation-mode:bicubic;}.clearer{clear:both!important;float:none!important;margin:0!important;padding:0!important;}.rounded{border-radius:1em;-moz-border-radius:1em;-webkit-border-radius:1em;} \ No newline at end of file diff --git a/crisishaiku/static/lib/backbone-min.js b/crisishaiku/static/lib/backbone-min.js new file mode 100644 index 0000000..3f0d495 --- /dev/null +++ b/crisishaiku/static/lib/backbone-min.js @@ -0,0 +1,33 @@ +// Backbone.js 0.5.3 +// (c) 2010 Jeremy Ashkenas, DocumentCloud Inc. +// Backbone may be freely distributed under the MIT license. +// For all details and documentation: +// http://documentcloud.github.com/backbone +(function(){var h=this,p=h.Backbone,e;e=typeof exports!=="undefined"?exports:h.Backbone={};e.VERSION="0.5.3";var f=h._;if(!f&&typeof require!=="undefined")f=require("underscore")._;var g=h.jQuery||h.Zepto;e.noConflict=function(){h.Backbone=p;return this};e.emulateHTTP=!1;e.emulateJSON=!1;e.Events={bind:function(a,b,c){var d=this._callbacks||(this._callbacks={});(d[a]||(d[a]=[])).push([b,c]);return this},unbind:function(a,b){var c;if(a){if(c=this._callbacks)if(b){c=c[a];if(!c)return this;for(var d= +0,e=c.length;d/g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g,"/")},has:function(a){return this.attributes[a]!=null},set:function(a,b){b||(b={});if(!a)return this;if(a.attributes)a=a.attributes;var c=this.attributes,d=this._escapedAttributes;if(!b.silent&&this.validate&&!this._performValidation(a,b))return!1;if(this.idAttribute in a)this.id=a[this.idAttribute]; +var e=this._changing;this._changing=!0;for(var g in a){var h=a[g];if(!f.isEqual(c[g],h))c[g]=h,delete d[g],this._changed=!0,b.silent||this.trigger("change:"+g,this,h,b)}!e&&!b.silent&&this._changed&&this.change(b);this._changing=!1;return this},unset:function(a,b){if(!(a in this.attributes))return this;b||(b={});var c={};c[a]=void 0;if(!b.silent&&this.validate&&!this._performValidation(c,b))return!1;delete this.attributes[a];delete this._escapedAttributes[a];a==this.idAttribute&&delete this.id;this._changed= +!0;b.silent||(this.trigger("change:"+a,this,void 0,b),this.change(b));return this},clear:function(a){a||(a={});var b,c=this.attributes,d={};for(b in c)d[b]=void 0;if(!a.silent&&this.validate&&!this._performValidation(d,a))return!1;this.attributes={};this._escapedAttributes={};this._changed=!0;if(!a.silent){for(b in c)this.trigger("change:"+b,this,void 0,a);this.change(a)}return this},fetch:function(a){a||(a={});var b=this,c=a.success;a.success=function(d,e,f){if(!b.set(b.parse(d,f),a))return!1;c&& +c(b,d)};a.error=i(a.error,b,a);return(this.sync||e.sync).call(this,"read",this,a)},save:function(a,b){b||(b={});if(a&&!this.set(a,b))return!1;var c=this,d=b.success;b.success=function(a,e,f){if(!c.set(c.parse(a,f),b))return!1;d&&d(c,a,f)};b.error=i(b.error,c,b);var f=this.isNew()?"create":"update";return(this.sync||e.sync).call(this,f,this,b)},destroy:function(a){a||(a={});if(this.isNew())return this.trigger("destroy",this,this.collection,a);var b=this,c=a.success;a.success=function(d){b.trigger("destroy", +b,b.collection,a);c&&c(b,d)};a.error=i(a.error,b,a);return(this.sync||e.sync).call(this,"delete",this,a)},url:function(){var a=k(this.collection)||this.urlRoot||l();if(this.isNew())return a;return a+(a.charAt(a.length-1)=="/"?"":"/")+encodeURIComponent(this.id)},parse:function(a){return a},clone:function(){return new this.constructor(this)},isNew:function(){return this.id==null},change:function(a){this.trigger("change",this,a);this._previousAttributes=f.clone(this.attributes);this._changed=!1},hasChanged:function(a){if(a)return this._previousAttributes[a]!= +this.attributes[a];return this._changed},changedAttributes:function(a){a||(a=this.attributes);var b=this._previousAttributes,c=!1,d;for(d in a)f.isEqual(b[d],a[d])||(c=c||{},c[d]=a[d]);return c},previous:function(a){if(!a||!this._previousAttributes)return null;return this._previousAttributes[a]},previousAttributes:function(){return f.clone(this._previousAttributes)},_performValidation:function(a,b){var c=this.validate(a);if(c)return b.error?b.error(this,c,b):this.trigger("error",this,c,b),!1;return!0}}); +e.Collection=function(a,b){b||(b={});if(b.comparator)this.comparator=b.comparator;f.bindAll(this,"_onModelEvent","_removeReference");this._reset();a&&this.reset(a,{silent:!0});this.initialize.apply(this,arguments)};f.extend(e.Collection.prototype,e.Events,{model:e.Model,initialize:function(){},toJSON:function(){return this.map(function(a){return a.toJSON()})},add:function(a,b){if(f.isArray(a))for(var c=0,d=a.length;c').hide().appendTo("body")[0].contentWindow,this.navigate(a); +this._hasPushState?g(window).bind("popstate",this.checkUrl):"onhashchange"in window&&!b?g(window).bind("hashchange",this.checkUrl):setInterval(this.checkUrl,this.interval);this.fragment=a;m=!0;a=window.location;b=a.pathname==this.options.root;if(this._wantsPushState&&!this._hasPushState&&!b)return this.fragment=this.getFragment(null,!0),window.location.replace(this.options.root+"#"+this.fragment),!0;else if(this._wantsPushState&&this._hasPushState&&b&&a.hash)this.fragment=a.hash.replace(j,""),window.history.replaceState({}, +document.title,a.protocol+"//"+a.host+this.options.root+this.fragment);if(!this.options.silent)return this.loadUrl()},route:function(a,b){this.handlers.unshift({route:a,callback:b})},checkUrl:function(){var a=this.getFragment();a==this.fragment&&this.iframe&&(a=this.getFragment(this.iframe.location.hash));if(a==this.fragment||a==decodeURIComponent(this.fragment))return!1;this.iframe&&this.navigate(a);this.loadUrl()||this.loadUrl(window.location.hash)},loadUrl:function(a){var b=this.fragment=this.getFragment(a); +return f.any(this.handlers,function(a){if(a.route.test(b))return a.callback(b),!0})},navigate:function(a,b){var c=(a||"").replace(j,"");if(!(this.fragment==c||this.fragment==decodeURIComponent(c))){if(this._hasPushState){var d=window.location;c.indexOf(this.options.root)!=0&&(c=this.options.root+c);this.fragment=c;window.history.pushState({},document.title,d.protocol+"//"+d.host+c)}else if(window.location.hash=this.fragment=c,this.iframe&&c!=this.getFragment(this.iframe.location.hash))this.iframe.document.open().close(), +this.iframe.location.hash=c;b&&this.loadUrl(a)}}});e.View=function(a){this.cid=f.uniqueId("view");this._configure(a||{});this._ensureElement();this.delegateEvents();this.initialize.apply(this,arguments)};var u=/^(\S+)\s*(.*)$/,n=["model","collection","el","id","attributes","className","tagName"];f.extend(e.View.prototype,e.Events,{tagName:"div",$:function(a){return g(a,this.el)},initialize:function(){},render:function(){return this},remove:function(){g(this.el).remove();return this},make:function(a, +b,c){a=document.createElement(a);b&&g(a).attr(b);c&&g(a).html(c);return a},delegateEvents:function(a){if(a||(a=this.events))for(var b in f.isFunction(a)&&(a=a.call(this)),g(this.el).unbind(".delegateEvents"+this.cid),a){var c=this[a[b]];if(!c)throw Error('Event "'+a[b]+'" does not exist');var d=b.match(u),e=d[1];d=d[2];c=f.bind(c,this);e+=".delegateEvents"+this.cid;d===""?g(this.el).bind(e,c):g(this.el).delegate(d,e,c)}},_configure:function(a){this.options&&(a=f.extend({},this.options,a));for(var b= +0,c=n.length;b').hide().appendTo('body')[0].contentWindow; + this.navigate(fragment); + } + + // Depending on whether we're using pushState or hashes, and whether + // 'onhashchange' is supported, determine how we check the URL state. + if (this._hasPushState) { + $(window).bind('popstate', this.checkUrl); + } else if ('onhashchange' in window && !oldIE) { + $(window).bind('hashchange', this.checkUrl); + } else { + setInterval(this.checkUrl, this.interval); + } + + // Determine if we need to change the base url, for a pushState link + // opened by a non-pushState browser. + this.fragment = fragment; + historyStarted = true; + var loc = window.location; + var atRoot = loc.pathname == this.options.root; + if (this._wantsPushState && !this._hasPushState && !atRoot) { + this.fragment = this.getFragment(null, true); + window.location.replace(this.options.root + '#' + this.fragment); + // Return immediately as browser will do redirect to new url + return true; + } else if (this._wantsPushState && this._hasPushState && atRoot && loc.hash) { + this.fragment = loc.hash.replace(hashStrip, ''); + window.history.replaceState({}, document.title, loc.protocol + '//' + loc.host + this.options.root + this.fragment); + } + + if (!this.options.silent) { + return this.loadUrl(); + } + }, + + // Add a route to be tested when the fragment changes. Routes added later may + // override previous routes. + route : function(route, callback) { + this.handlers.unshift({route : route, callback : callback}); + }, + + // Checks the current URL to see if it has changed, and if it has, + // calls `loadUrl`, normalizing across the hidden iframe. + checkUrl : function(e) { + var current = this.getFragment(); + if (current == this.fragment && this.iframe) current = this.getFragment(this.iframe.location.hash); + if (current == this.fragment || current == decodeURIComponent(this.fragment)) return false; + if (this.iframe) this.navigate(current); + this.loadUrl() || this.loadUrl(window.location.hash); + }, + + // Attempt to load the current URL fragment. If a route succeeds with a + // match, returns `true`. If no defined routes matches the fragment, + // returns `false`. + loadUrl : function(fragmentOverride) { + var fragment = this.fragment = this.getFragment(fragmentOverride); + var matched = _.any(this.handlers, function(handler) { + if (handler.route.test(fragment)) { + handler.callback(fragment); + return true; + } + }); + return matched; + }, + + // Save a fragment into the hash history. You are responsible for properly + // URL-encoding the fragment in advance. This does not trigger + // a `hashchange` event. + navigate : function(fragment, triggerRoute) { + var frag = (fragment || '').replace(hashStrip, ''); + if (this.fragment == frag || this.fragment == decodeURIComponent(frag)) return; + if (this._hasPushState) { + var loc = window.location; + if (frag.indexOf(this.options.root) != 0) frag = this.options.root + frag; + this.fragment = frag; + window.history.pushState({}, document.title, loc.protocol + '//' + loc.host + frag); + } else { + window.location.hash = this.fragment = frag; + if (this.iframe && (frag != this.getFragment(this.iframe.location.hash))) { + this.iframe.document.open().close(); + this.iframe.location.hash = frag; + } + } + if (triggerRoute) this.loadUrl(fragment); + } + + }); + + // Backbone.View + // ------------- + + // Creating a Backbone.View creates its initial element outside of the DOM, + // if an existing element is not provided... + Backbone.View = function(options) { + this.cid = _.uniqueId('view'); + this._configure(options || {}); + this._ensureElement(); + this.delegateEvents(); + this.initialize.apply(this, arguments); + }; + + // Element lookup, scoped to DOM elements within the current view. + // This should be prefered to global lookups, if you're dealing with + // a specific view. + var selectorDelegate = function(selector) { + return $(selector, this.el); + }; + + // Cached regex to split keys for `delegate`. + var eventSplitter = /^(\S+)\s*(.*)$/; + + // List of view options to be merged as properties. + var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName']; + + // Set up all inheritable **Backbone.View** properties and methods. + _.extend(Backbone.View.prototype, Backbone.Events, { + + // The default `tagName` of a View's element is `"div"`. + tagName : 'div', + + // Attach the `selectorDelegate` function as the `$` property. + $ : selectorDelegate, + + // Initialize is an empty function by default. Override it with your own + // initialization logic. + initialize : function(){}, + + // **render** is the core function that your view should override, in order + // to populate its element (`this.el`), with the appropriate HTML. The + // convention is for **render** to always return `this`. + render : function() { + return this; + }, + + // Remove this view from the DOM. Note that the view isn't present in the + // DOM by default, so calling this method may be a no-op. + remove : function() { + $(this.el).remove(); + return this; + }, + + // For small amounts of DOM Elements, where a full-blown template isn't + // needed, use **make** to manufacture elements, one at a time. + // + // var el = this.make('li', {'class': 'row'}, this.model.escape('title')); + // + make : function(tagName, attributes, content) { + var el = document.createElement(tagName); + if (attributes) $(el).attr(attributes); + if (content) $(el).html(content); + return el; + }, + + // Set callbacks, where `this.callbacks` is a hash of + // + // *{"event selector": "callback"}* + // + // { + // 'mousedown .title': 'edit', + // 'click .button': 'save' + // } + // + // pairs. Callbacks will be bound to the view, with `this` set properly. + // Uses event delegation for efficiency. + // Omitting the selector binds the event to `this.el`. + // This only works for delegate-able events: not `focus`, `blur`, and + // not `change`, `submit`, and `reset` in Internet Explorer. + delegateEvents : function(events) { + if (!(events || (events = this.events))) return; + if (_.isFunction(events)) events = events.call(this); + $(this.el).unbind('.delegateEvents' + this.cid); + for (var key in events) { + var method = this[events[key]]; + if (!method) throw new Error('Event "' + events[key] + '" does not exist'); + var match = key.match(eventSplitter); + var eventName = match[1], selector = match[2]; + method = _.bind(method, this); + eventName += '.delegateEvents' + this.cid; + if (selector === '') { + $(this.el).bind(eventName, method); + } else { + $(this.el).delegate(selector, eventName, method); + } + } + }, + + // Performs the initial configuration of a View with a set of options. + // Keys with special meaning *(model, collection, id, className)*, are + // attached directly to the view. + _configure : function(options) { + if (this.options) options = _.extend({}, this.options, options); + for (var i = 0, l = viewOptions.length; i < l; i++) { + var attr = viewOptions[i]; + if (options[attr]) this[attr] = options[attr]; + } + this.options = options; + }, + + // Ensure that the View has a DOM element to render into. + // If `this.el` is a string, pass it through `$()`, take the first + // matching element, and re-assign it to `el`. Otherwise, create + // an element from the `id`, `className` and `tagName` proeprties. + _ensureElement : function() { + if (!this.el) { + var attrs = this.attributes || {}; + if (this.id) attrs.id = this.id; + if (this.className) attrs['class'] = this.className; + this.el = this.make(this.tagName, attrs); + } else if (_.isString(this.el)) { + this.el = $(this.el).get(0); + } + } + + }); + + // The self-propagating extend function that Backbone classes use. + var extend = function (protoProps, classProps) { + var child = inherits(this, protoProps, classProps); + child.extend = this.extend; + return child; + }; + + // Set up inheritance for the model, collection, and view. + Backbone.Model.extend = Backbone.Collection.extend = + Backbone.Router.extend = Backbone.View.extend = extend; + + // Map from CRUD to HTTP for our default `Backbone.sync` implementation. + var methodMap = { + 'create': 'POST', + 'update': 'PUT', + 'delete': 'DELETE', + 'read' : 'GET' + }; + + // Backbone.sync + // ------------- + + // Override this function to change the manner in which Backbone persists + // models to the server. You will be passed the type of request, and the + // model in question. By default, uses makes a RESTful Ajax request + // to the model's `url()`. Some possible customizations could be: + // + // * Use `setTimeout` to batch rapid-fire updates into a single request. + // * Send up the models as XML instead of JSON. + // * Persist models via WebSockets instead of Ajax. + // + // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests + // as `POST`, with a `_method` parameter containing the true HTTP method, + // as well as all requests with the body as `application/x-www-form-urlencoded` instead of + // `application/json` with the model in a param named `model`. + // Useful when interfacing with server-side languages like **PHP** that make + // it difficult to read the body of `PUT` requests. + Backbone.sync = function(method, model, options) { + var type = methodMap[method]; + + // Default JSON-request options. + var params = _.extend({ + type: type, + dataType: 'json' + }, options); + + // Ensure that we have a URL. + if (!params.url) { + params.url = getUrl(model) || urlError(); + } + + // Ensure that we have the appropriate request data. + if (!params.data && model && (method == 'create' || method == 'update')) { + params.contentType = 'application/json'; + params.data = JSON.stringify(model.toJSON()); + } + + // For older servers, emulate JSON by encoding the request into an HTML-form. + if (Backbone.emulateJSON) { + params.contentType = 'application/x-www-form-urlencoded'; + params.data = params.data ? {model : params.data} : {}; + } + + // For older servers, emulate HTTP by mimicking the HTTP method with `_method` + // And an `X-HTTP-Method-Override` header. + if (Backbone.emulateHTTP) { + if (type === 'PUT' || type === 'DELETE') { + if (Backbone.emulateJSON) params.data._method = type; + params.type = 'POST'; + params.beforeSend = function(xhr) { + xhr.setRequestHeader('X-HTTP-Method-Override', type); + }; + } + } + + // Don't process data on a non-GET request. + if (params.type !== 'GET' && !Backbone.emulateJSON) { + params.processData = false; + } + + // Make the request. + return $.ajax(params); + }; + + // Helpers + // ------- + + // Shared empty constructor function to aid in prototype-chain creation. + var ctor = function(){}; + + // Helper function to correctly set up the prototype chain, for subclasses. + // Similar to `goog.inherits`, but uses a hash of prototype properties and + // class properties to be extended. + var inherits = function(parent, protoProps, staticProps) { + var child; + + // The constructor function for the new subclass is either defined by you + // (the "constructor" property in your `extend` definition), or defaulted + // by us to simply call `super()`. + if (protoProps && protoProps.hasOwnProperty('constructor')) { + child = protoProps.constructor; + } else { + child = function(){ return parent.apply(this, arguments); }; + } + + // Inherit class (static) properties from parent. + _.extend(child, parent); + + // Set the prototype chain to inherit from `parent`, without calling + // `parent`'s constructor function. + ctor.prototype = parent.prototype; + child.prototype = new ctor(); + + // Add prototype properties (instance properties) to the subclass, + // if supplied. + if (protoProps) _.extend(child.prototype, protoProps); + + // Add static properties to the constructor function, if supplied. + if (staticProps) _.extend(child, staticProps); + + // Correctly set child's `prototype.constructor`. + child.prototype.constructor = child; + + // Set a convenience property in case the parent's prototype is needed later. + child.__super__ = parent.prototype; + + return child; + }; + + // Helper function to get a URL from a Model or Collection as a property + // or as a function. + var getUrl = function(object) { + if (!(object && object.url)) return null; + return _.isFunction(object.url) ? object.url() : object.url; + }; + + // Throw an error when a URL is needed, and none is supplied. + var urlError = function() { + throw new Error('A "url" property or function must be specified'); + }; + + // Wrap an optional error callback with a fallback error event. + var wrapError = function(onError, model, options) { + return function(resp) { + if (onError) { + onError(model, resp, options); + } else { + model.trigger('error', model, resp, options); + } + }; + }; + + // Helper function to escape a string for HTML rendering. + var escapeHTML = function(string) { + return string.replace(/&(?!\w+;|#\d+;|#x[\da-f]+;)/gi, '&').replace(//g, '>').replace(/"/g, '"').replace(/'/g, ''').replace(/\//g,'/'); + }; + +}).call(this); diff --git a/crisishaiku/static/lib/es5-shim.js b/crisishaiku/static/lib/es5-shim.js new file mode 100644 index 0000000..9eb7f07 --- /dev/null +++ b/crisishaiku/static/lib/es5-shim.js @@ -0,0 +1,1102 @@ +// vim: ts=4 sts=4 sw=4 expandtab +// -- kriskowal Kris Kowal Copyright (C) 2009-2011 MIT License +// -- tlrobinson Tom Robinson Copyright (C) 2009-2010 MIT License (Narwhal Project) +// -- dantman Daniel Friesen Copyright (C) 2010 XXX TODO License or CLA +// -- fschaefer Florian Schäfer Copyright (C) 2010 MIT License +// -- Gozala Irakli Gozalishvili Copyright (C) 2010 MIT License +// -- kitcambridge Kit Cambridge Copyright (C) 2011 MIT License +// -- kossnocorp Sasha Koss XXX TODO License or CLA +// -- bryanforbes Bryan Forbes XXX TODO License or CLA +// -- killdream Quildreen Motta Copyright (C) 2011 MIT Licence +// -- michaelficarra Michael Ficarra Copyright (C) 2011 3-clause BSD License +// -- sharkbrainguy Gerard Paapu Copyright (C) 2011 MIT License +// -- bbqsrc Brendan Molloy (C) 2011 Creative Commons Zero (public domain) +// -- iwyg XXX TODO License or CLA +// -- DomenicDenicola Domenic Denicola Copyright (C) 2011 MIT License +// -- xavierm02 Montillet Xavier Copyright (C) 2011 MIT License +// -- Raynos Raynos XXX TODO License or CLA +// -- samsonjs Sami Samhuri Copyright (C) 2010 MIT License +// -- rwldrn Rick Waldron Copyright (C) 2011 MIT License +// -- lexer Alexey Zakharov XXX TODO License or CLA + +/*! + Copyright (c) 2009, 280 North Inc. http://280north.com/ + MIT License. http://github.com/280north/narwhal/blob/master/README.md +*/ + +// Module systems magic dance +(function (definition) { + // RequireJS + if (typeof define == "function") { + define(definition); + // CommonJS and