├── src └── soundstorm │ ├── user.clj │ ├── util.clj │ ├── config.clj │ ├── jrepl.clj │ ├── soundcloud.clj │ ├── view.clj │ └── core.clj ├── resources ├── public │ ├── js │ │ ├── app.js │ │ └── lib │ │ │ ├── holder.js │ │ │ ├── underscore.min.js │ │ │ ├── backbone.min.js │ │ │ ├── typeahead.min.js │ │ │ ├── bootstrap.min.js │ │ │ ├── underscore.js │ │ │ └── typeahead.js │ ├── img │ │ ├── favicon.ico │ │ ├── sc-btn-connect-l.png │ │ └── soundstorm-logo-500x500.png │ └── css │ │ ├── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.ttf │ │ └── glyphicons-halflings-regular.woff │ │ └── base.css └── logback.xml ├── test └── soundstorm │ └── core_test.clj ├── .gitignore ├── project.clj └── README.md /src/soundstorm/user.clj: -------------------------------------------------------------------------------- 1 | (ns soundstorm.user) 2 | 3 | -------------------------------------------------------------------------------- /resources/public/js/app.js: -------------------------------------------------------------------------------- 1 | /* soundstorm - app.js */ 2 | 3 | -------------------------------------------------------------------------------- /resources/public/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marsmining/soundstorm/master/resources/public/img/favicon.ico -------------------------------------------------------------------------------- /resources/public/img/sc-btn-connect-l.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marsmining/soundstorm/master/resources/public/img/sc-btn-connect-l.png -------------------------------------------------------------------------------- /resources/public/img/soundstorm-logo-500x500.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marsmining/soundstorm/master/resources/public/img/soundstorm-logo-500x500.png -------------------------------------------------------------------------------- /src/soundstorm/util.clj: -------------------------------------------------------------------------------- 1 | (ns soundstorm.util) 2 | 3 | (defn update-values 4 | "'Updates' every value in map" 5 | [m f] 6 | (into {} (for [[k v] m] [k (f v)]))) -------------------------------------------------------------------------------- /resources/public/css/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marsmining/soundstorm/master/resources/public/css/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /resources/public/css/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marsmining/soundstorm/master/resources/public/css/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /resources/public/css/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marsmining/soundstorm/master/resources/public/css/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /test/soundstorm/core_test.clj: -------------------------------------------------------------------------------- 1 | (ns soundstorm.core-test 2 | (:require [clojure.test :refer :all] 3 | [soundstorm.core :refer :all])) 4 | 5 | (deftest a-test 6 | (testing "FIXME, I fail." 7 | (is (= 0 1)))) 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /lib 3 | /classes 4 | /checkouts 5 | /logs 6 | /config 7 | pom.xml 8 | pom.xml.asc 9 | *.jar 10 | *.class 11 | .lein-deps-sum 12 | .lein-failures 13 | .lein-plugins 14 | .lein-repl-history 15 | .nrepl-port 16 | .DS_Store 17 | -------------------------------------------------------------------------------- /src/soundstorm/config.clj: -------------------------------------------------------------------------------- 1 | (ns soundstorm.config 2 | "Load edn config file" 3 | (:require [clojure.tools.reader :as edn] 4 | [clojure.java.io :refer [resource]])) 5 | 6 | (def cp-slurp (comp slurp resource)) 7 | 8 | (def config (edn/read-string (cp-slurp "ss.edn"))) 9 | 10 | (defn fetch [k] 11 | (k config)) 12 | -------------------------------------------------------------------------------- /src/soundstorm/jrepl.clj: -------------------------------------------------------------------------------- 1 | (ns soundstorm.jrepl 2 | (:require [soundstorm.core :as core] 3 | [ring.server.standalone :as rs] 4 | [ring.adapter.jetty :refer [run-jetty]])) 5 | 6 | (defonce jetty (atom nil)) 7 | 8 | (defn start [] 9 | (reset! jetty (rs/serve #'core/secured-app {:port 3000 :browser-uri "/" :auto-reload? false}))) 10 | 11 | (defn stop [] 12 | (.stop @jetty)) 13 | 14 | (defn -main [port] 15 | (run-jetty core/secured-app {:port (Integer. port)})) 16 | 17 | (comment 18 | (start) 19 | 20 | (stop) 21 | 22 | ) -------------------------------------------------------------------------------- /resources/public/css/base.css: -------------------------------------------------------------------------------- 1 | 2 | /* base.css */ 3 | 4 | .jumbotron { 5 | margin-top: 1em; 6 | } 7 | 8 | .ss-sc-button { 9 | padding-top: 1em; 10 | } 11 | 12 | .ss-sub { 13 | vertical-align: text-top; 14 | } 15 | 16 | .ss-waveform { 17 | background-color: #fbbbbc; 18 | background-clip: border-box; 19 | background-size: cover; 20 | } 21 | 22 | body:after { 23 | content: ""; 24 | background: url('/img/soundstorm-logo-500x500.png'); 25 | opacity: 0.3; 26 | top: 0; 27 | left: 0; 28 | bottom: 0; 29 | right: 0; 30 | position: absolute; 31 | z-index: -1; 32 | } 33 | -------------------------------------------------------------------------------- /resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 6 | 7 | 8 | 9 | 10 | logs/soundstorm.log 11 | 12 | logs/old/soundstorm.%d{yyyy-MM-dd}.log 13 | 3 14 | 15 | 16 | %date %6level [%16thread] %-15logger{15} %msg%n 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/soundstorm/soundcloud.clj: -------------------------------------------------------------------------------- 1 | (ns soundstorm.soundcloud 2 | "Concerns of SoundCloud API usage as a client" 3 | (require [soundstorm.util :as util] 4 | [clojure.tools.logging :as log] 5 | [clj-http.client :as http])) 6 | 7 | (def api-root "https://api.soundcloud.com") 8 | 9 | (def resources-partial 10 | {:me "/me" 11 | :tracks "/me/tracks" 12 | :track "/me/tracks/{track}" 13 | :track-favoriter "/me/tracks/{track}/favoriters"}) 14 | 15 | (def resources (util/update-values 16 | resources-partial 17 | #(str api-root % ".json?oauth_token={oauth-token}"))) 18 | 19 | (defn substitute [[k v] s] 20 | "Given a k, v pair, substitute v for '{k}' in s" 21 | (let [tok (format "{%s}" (name k))] 22 | (clojure.string/replace s tok v))) 23 | 24 | (defn build-uri [resource-key args] 25 | (let [uri (resource-key resources)] 26 | (reduce #(substitute %2 %1) uri args))) 27 | 28 | (defonce conn-mgr 29 | (clj-http.conn-mgr/make-reusable-conn-manager 30 | {:timeout 2 :threads 3})) 31 | 32 | (defn sget [resource token] 33 | (http/get 34 | (build-uri resource token) 35 | {:as :json 36 | :connection-manager conn-mgr})) 37 | 38 | (defn get-resources [resources sub-map] 39 | (log/info "get-resources:" resources sub-map) 40 | (reduce #(assoc %1 %2 (:body (sget %2 sub-map))) {} resources)) 41 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject soundstorm "0.1.0-SNAPSHOT" 2 | :description "soundstorm - ring, ouath and soundcloud" 3 | :url "http://ss.ockhamsolutions.de" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | :dependencies [[org.clojure/clojure "1.5.1"] 7 | 8 | [org.clojure/tools.logging "0.2.6"] 9 | [ch.qos.logback/logback-classic "1.0.13"] 10 | [org.slf4j/jcl-over-slf4j "1.7.5"] 11 | 12 | [org.clojure/tools.reader "0.7.6"] 13 | [org.clojure/tools.macro "0.1.2"] 14 | [org.clojure/core.incubator "0.1.3"] 15 | 16 | [slingshot "0.10.3"] 17 | [commons-codec "1.8"] 18 | [org.apache.httpcomponents/httpclient "4.2.5"] 19 | 20 | [ring/ring-core "1.2.0"] 21 | [ring/ring-devel "1.2.0"] 22 | [ring/ring-servlet "1.2.0"] 23 | [ring/ring-jetty-adapter "1.2.0"] 24 | [ring-server "0.3.0"] 25 | 26 | [com.cemerick/friend "0.1.5"] 27 | [friend-oauth2 "0.0.4"] 28 | [clj-http "0.7.6"] 29 | [cheshire "5.2.0"] 30 | [compojure "1.1.5"] 31 | [hiccup "1.0.4"]] 32 | :plugins [[lein-ring "0.7.1"]] 33 | :ring {:handler soundstorm.core/secured-app} 34 | :profiles {:dev {:resource-paths ["config/dev"]} 35 | :prod {:resource-paths ["config/prod"]}}) 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # soundstorm 2 | 3 | Simple [ring](https://github.com/ring-clojure) app which uses the [SoundCloud API](http://developers.soundcloud.com/docs/api/reference). Mostly covers the basic boilerplate of a typical clojure webapp. Currently you can login to the app via SoundCloud and view your tracks. Apart from a few lines of json fetching, mostly just a copy-paste job from [cemerick/friend](https://github.com/cemerick/friend) and [ddellacosta/friend-oauth2](https://github.com/ddellacosta/friend-oauth2) libraries examples. 4 | 5 | Demo of the app is here: http://ss.ockhamsolutions.de/ 6 | 7 | _note: the app uses no persistence, so nothing is saved or even cached_ 8 | 9 | ### Configuration 10 | 11 | To run the app, you must have a directory `config` in the project root directory. In this directory, there must also be a `dev` and `prod` directory, each containing a file `ss.edn`. Which directories contents is included, is dictated by the active leiningen profile. The namespace `config` looks for a file named `ss.edn` which is looked up as a classpath resource. The contents of `ss.edn` should look like: 12 | 13 | ```clj 14 | {:client-config {:client-id "YOUR_SOUNDCLOUD_APP_CLIENT_ID" 15 | :client-secret "YOUR_SOUNDCLOUD_APP_CLIENT_SECRET" 16 | :callback {:domain "http://localhost.com:3000" 17 | :path "/sc-redirect-uri"}}} 18 | ``` 19 | 20 | The above is my development config. I have `localhost.com` set to resolve to localhost on my development machine. This allows you to test the oauth process locally. 21 | 22 | ### License 23 | 24 | Copyright © 2013 25 | 26 | Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version. 27 | -------------------------------------------------------------------------------- /src/soundstorm/view.clj: -------------------------------------------------------------------------------- 1 | (ns soundstorm.view 2 | (:require 3 | [hiccup.core :as hc] 4 | [hiccup.page :as hp] 5 | [hiccup.form :as hf] 6 | [cemerick.friend :as friend])) 7 | 8 | (declare row col whois) 9 | 10 | (defmacro base-page [req & body] 11 | `(hp/html5 12 | {:lang "en"} 13 | [:head 14 | [:title "soundstorm"] 15 | [:meta {:charset "utf-8"}] 16 | [:link {:rel "shortcut icon" :type "image/x-icon" :href "/img/favicon.ico"}] 17 | (hp/include-css "/css/lib/bootstrap.min.css") 18 | (hp/include-css "/css/base.css")] 19 | [:body 20 | [:div.container 21 | ~@body] 22 | (hp/include-js "/js/app.js")])) 23 | 24 | (defn jumbo [req] 25 | (row 12 [:div.jumbotron 26 | [:h1 "soundstorm " [:span.ss-sub.glyphicon.glyphicon-forward]] 27 | [:h2 "Tinkering with SoundCloud API"] 28 | (if-let [identity (friend/identity req)] 29 | nil 30 | [:div [:a {:href "sc-redirect-uri"} 31 | [:img {:src "img/sc-btn-connect-l.png" :class "ss-sc-button"}]]])])) 32 | 33 | (defn whois [req] 34 | [:div.panel.panel-default 35 | [:div.panel-heading [:h4 "Authentication Status"]] 36 | [:div.panel-body 37 | (if-let [identity (friend/identity req)] 38 | (let [id-str (apply str "Logged in, with these roles: " 39 | (-> identity friend/current-authentication :roles))] 40 | [:div 41 | [:p id-str] 42 | [:a {:href "logout"} "Logout"]]) 43 | "Anonymous user")]]) 44 | 45 | (defn track [{:keys [title waveform_url permalink_url playback_count]}] 46 | [:li.list-group-item.ss-waveform 47 | {:style (str "background-image: url('" waveform_url "');")} 48 | [:a {:href permalink_url} title] 49 | [:span.badge playback_count]]) 50 | 51 | (defn profile [p t] 52 | [:div.panel.panel-default 53 | [:div.panel-heading [:h4 (:username p)]] 54 | [:div.panel-body 55 | (col 2 [:img.img-thumbnail {:src (:avatar_url p)}]) 56 | (col 6 [:ul.list-group 57 | (map track t)])]]) 58 | 59 | (defn index-page [r] (base-page r (jumbo r))) 60 | (defn main-page [r p t] (base-page r (jumbo r) (row (profile p t)) (row (whois r)))) 61 | 62 | (defn row 63 | ([body] (row 12 body)) 64 | ([n body] 65 | [:div.row 66 | (col n body)])) 67 | 68 | (defn col [n body] 69 | [(keyword (str "div.col-lg-" n)) 70 | body]) -------------------------------------------------------------------------------- /src/soundstorm/core.clj: -------------------------------------------------------------------------------- 1 | (ns soundstorm.core 2 | (:require [soundstorm.view :as view] 3 | [soundstorm.config :as config] 4 | [soundstorm.soundcloud :as sc] 5 | [clojure.tools.logging :as log] 6 | [compojure.handler :as handler] 7 | [compojure.route :as route] 8 | [compojure.core :refer [GET ANY defroutes]] 9 | [hiccup.middleware :refer [wrap-base-url]] 10 | [cemerick.friend :as friend] 11 | [cemerick.friend.credentials :as creds] 12 | [friend-oauth2.workflow :as oauth2] 13 | [cheshire.core :as json] 14 | [clj-http.client :as http])) 15 | 16 | ;; controllers 17 | ;; 18 | 19 | (defn home [req] 20 | (if-let [token (:current (friend/identity req))] 21 | (let [{me :me tracks :tracks} 22 | (sc/get-resources [:me :tracks] {:oauth-token token})] 23 | (view/main-page req me tracks)) 24 | (view/index-page req))) 25 | 26 | ;; soundcloud oauth2 config 27 | ;; 28 | 29 | (defn access-token-parsefn 30 | [response] 31 | (log/info "attempting token parse:" response) 32 | (let [token (-> response 33 | :body 34 | (json/parse-string true) 35 | :access_token)] 36 | (log/info "token:" token) 37 | token)) 38 | 39 | (def config-auth {:roles #{::user/user}}) 40 | 41 | (def client-config (config/fetch :client-config)) 42 | 43 | (def uri-config 44 | {:authentication-uri {:url "https://soundcloud.com/connect" 45 | :query {:client_id (:client-id client-config) 46 | :response_type "code" 47 | :redirect_uri (oauth2/format-config-uri client-config) 48 | :scope "non-expiring"}} 49 | 50 | :access-token-uri {:url "https://api.soundcloud.com/oauth2/token" 51 | :query {:client_id (:client-id client-config) 52 | :client_secret (:client-secret client-config) 53 | :grant_type "authorization_code" 54 | :redirect_uri (oauth2/format-config-uri client-config) 55 | :code ""}}}) 56 | 57 | ;; ring / compojure / friend config 58 | ;; 59 | 60 | (defroutes main-routes 61 | (GET "/" req (home req)) 62 | (friend/logout (ANY "/logout" req (ring.util.response/redirect "/"))) 63 | (route/resources "/") 64 | (route/not-found "Page not found")) 65 | 66 | (defn is-media? [str] 67 | (or (.endsWith str ".js") 68 | (.endsWith str ".css") 69 | (.endsWith str ".ico") 70 | (.endsWith str ".png") 71 | (.endsWith str ".jpg") 72 | (.endsWith str ".woff") 73 | (.endsWith str ".ttf"))) 74 | 75 | (defn log-requests [handler] 76 | (fn [req] 77 | (if (is-media? (:uri req)) 78 | (handler req) 79 | (do 80 | (log/info (:request-method req) (:uri req) (:query-string req)) 81 | (let [start (System/currentTimeMillis) 82 | rez (handler req)] 83 | (log/info {:uri (:uri req) :elapsed (- (System/currentTimeMillis) start)}) 84 | rez))))) 85 | 86 | (def secured-app 87 | (-> (handler/site 88 | (friend/authenticate 89 | main-routes 90 | {:allow-anon? true 91 | :login-uri "/login" 92 | :default-landing-uri "/" 93 | :workflows [(oauth2/workflow 94 | {:client-config client-config 95 | :uri-config uri-config 96 | :access-token-parsefn access-token-parsefn 97 | :config-auth config-auth})]})) 98 | (wrap-base-url) 99 | (log-requests))) 100 | -------------------------------------------------------------------------------- /resources/public/js/lib/holder.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Holder - 1.7 - client side image placeholders 4 | (c) 2012 Ivan Malopinsky / http://imsky.co 5 | 6 | Provided under the Apache 2.0 License: http://www.apache.org/licenses/LICENSE-2.0 7 | Commercial use requires attribution. 8 | 9 | */ 10 | 11 | var Holder = Holder || {}; 12 | (function (app, win) { 13 | 14 | var preempted = false, 15 | fallback = false, 16 | canvas = document.createElement('canvas'); 17 | 18 | //getElementsByClassName polyfill 19 | document.getElementsByClassName||(document.getElementsByClassName=function(e){var t=document,n,r,i,s=[];if(t.querySelectorAll)return t.querySelectorAll("."+e);if(t.evaluate){r=".//*[contains(concat(' ', @class, ' '), ' "+e+" ')]",n=t.evaluate(r,t,null,0,null);while(i=n.iterateNext())s.push(i)}else{n=t.getElementsByTagName("*"),r=new RegExp("(^|\\s)"+e+"(\\s|$)");for(i=0;i 1) { 60 | text_height = template.size / (ctx.measureText(text).width / width); 61 | } 62 | ctx.font = "bold " + (text_height * ratio) + "px sans-serif"; 63 | ctx.fillText(text, (width / 2), (height / 2), width); 64 | return canvas.toDataURL("image/png"); 65 | } 66 | 67 | function render(mode, el, holder, src) { 68 | 69 | var dimensions = holder.dimensions, 70 | theme = holder.theme, 71 | text = holder.text; 72 | var dimensions_caption = dimensions.width + "x" + dimensions.height; 73 | theme = (text ? extend(theme, { 74 | text: text 75 | }) : theme); 76 | 77 | var ratio = 1; 78 | if(window.devicePixelRatio && window.devicePixelRatio > 1){ 79 | ratio = window.devicePixelRatio; 80 | } 81 | 82 | if (mode == "image") { 83 | el.setAttribute("data-src", src); 84 | el.setAttribute("alt", text ? text : theme.text ? theme.text + " [" + dimensions_caption + "]" : dimensions_caption); 85 | el.style.width = dimensions.width + "px"; 86 | el.style.height = dimensions.height + "px"; 87 | 88 | if (fallback) { 89 | el.style.backgroundColor = theme.background; 90 | } 91 | else{ 92 | el.setAttribute("src", draw(ctx, dimensions, theme, ratio)); 93 | } 94 | } else { 95 | if (!fallback) { 96 | el.style.backgroundImage = "url(" + draw(ctx, dimensions, theme, ratio) + ")"; 97 | el.style.backgroundSize = dimensions.width+"px "+dimensions.height+"px"; 98 | } 99 | } 100 | }; 101 | 102 | function fluid(el, holder, src) { 103 | var dimensions = holder.dimensions, 104 | theme = holder.theme, 105 | text = holder.text; 106 | var dimensions_caption = dimensions.width + "x" + dimensions.height; 107 | theme = (text ? extend(theme, { 108 | text: text 109 | }) : theme); 110 | 111 | var fluid = document.createElement("div"); 112 | 113 | fluid.style.backgroundColor = theme.background; 114 | fluid.style.color = theme.foreground; 115 | fluid.className = el.className + " holderjs-fluid"; 116 | fluid.style.width = holder.dimensions.width + (holder.dimensions.width.indexOf("%")>0?"":"px"); 117 | fluid.style.height = holder.dimensions.height + (holder.dimensions.height.indexOf("%")>0?"":"px"); 118 | fluid.id = el.id; 119 | 120 | if (theme.text) { 121 | fluid.appendChild(document.createTextNode(theme.text)) 122 | } else { 123 | fluid.appendChild(document.createTextNode(dimensions_caption)) 124 | fluid_images.push(fluid); 125 | setTimeout(fluid_update, 0); 126 | } 127 | 128 | el.parentNode.replaceChild(fluid, el); 129 | } 130 | 131 | function fluid_update() { 132 | for (i in fluid_images) { 133 | var el = fluid_images[i], 134 | label = el.firstChild; 135 | 136 | el.style.lineHeight = el.offsetHeight+"px"; 137 | label.data = el.offsetWidth + "x" + el.offsetHeight; 138 | } 139 | } 140 | 141 | function parse_flags(flags, options) { 142 | 143 | var ret = { 144 | theme: settings.themes.gray 145 | }, render = false; 146 | 147 | for (sl = flags.length, j = 0; j < sl; j++) { 148 | var flag = flags[j]; 149 | if (app.flags.dimensions.match(flag)) { 150 | render = true; 151 | ret.dimensions = app.flags.dimensions.output(flag); 152 | } else if (app.flags.fluid.match(flag)) { 153 | render = true; 154 | ret.dimensions = app.flags.fluid.output(flag); 155 | ret.fluid = true; 156 | } else if (app.flags.colors.match(flag)) { 157 | ret.theme = app.flags.colors.output(flag); 158 | } else if (options.themes[flag]) { 159 | //If a theme is specified, it will override custom colors 160 | ret.theme = options.themes[flag]; 161 | } else if (app.flags.text.match(flag)) { 162 | ret.text = app.flags.text.output(flag); 163 | } 164 | } 165 | 166 | return render ? ret : false; 167 | 168 | }; 169 | 170 | if (!canvas.getContext) { 171 | fallback = true; 172 | } else { 173 | if (canvas.toDataURL("image/png") 174 | .indexOf("data:image/png") < 0) { 175 | //Android doesn't support data URI 176 | fallback = true; 177 | } else { 178 | var ctx = canvas.getContext("2d"); 179 | } 180 | } 181 | 182 | var fluid_images = []; 183 | 184 | var settings = { 185 | domain: "holder.js", 186 | images: "img", 187 | elements: ".holderjs", 188 | themes: { 189 | "gray": { 190 | background: "#eee", 191 | foreground: "#aaa", 192 | size: 12 193 | }, 194 | "social": { 195 | background: "#3a5a97", 196 | foreground: "#fff", 197 | size: 12 198 | }, 199 | "industrial": { 200 | background: "#434A52", 201 | foreground: "#C2F200", 202 | size: 12 203 | } 204 | }, 205 | stylesheet: ".holderjs-fluid {font-size:16px;font-weight:bold;text-align:center;font-family:sans-serif;margin:0}" 206 | }; 207 | 208 | 209 | app.flags = { 210 | dimensions: { 211 | regex: /^(\d+)x(\d+)$/, 212 | output: function (val) { 213 | var exec = this.regex.exec(val); 214 | return { 215 | width: +exec[1], 216 | height: +exec[2] 217 | } 218 | } 219 | }, 220 | fluid: { 221 | regex: /^([0-9%]+)x([0-9%]+)$/, 222 | output: function (val) { 223 | var exec = this.regex.exec(val); 224 | return { 225 | width: exec[1], 226 | height: exec[2] 227 | } 228 | } 229 | }, 230 | colors: { 231 | regex: /#([0-9a-f]{3,})\:#([0-9a-f]{3,})/i, 232 | output: function (val) { 233 | var exec = this.regex.exec(val); 234 | return { 235 | size: settings.themes.gray.size, 236 | foreground: "#" + exec[2], 237 | background: "#" + exec[1] 238 | } 239 | } 240 | }, 241 | text: { 242 | regex: /text\:(.*)/, 243 | output: function (val) { 244 | return this.regex.exec(val)[1]; 245 | } 246 | } 247 | } 248 | 249 | for (var flag in app.flags) { 250 | app.flags[flag].match = function (val) { 251 | return val.match(this.regex) 252 | } 253 | } 254 | 255 | app.add_theme = function (name, theme) { 256 | name != null && theme != null && (settings.themes[name] = theme); 257 | return app; 258 | }; 259 | 260 | app.add_image = function (src, el) { 261 | var node = selector(el); 262 | if (node.length) { 263 | for (var i = 0, l = node.length; i < l; i++) { 264 | var img = document.createElement("img") 265 | img.setAttribute("data-src", src); 266 | node[i].appendChild(img); 267 | } 268 | } 269 | return app; 270 | }; 271 | 272 | app.run = function (o) { 273 | var options = extend(settings, o), 274 | images_nodes = selector(options.images), 275 | elements = selector(options.elements), 276 | preempted = true, 277 | images = []; 278 | 279 | for (i = 0, l = images_nodes.length; i < l; i++) images.push(images_nodes[i]); 280 | 281 | var holdercss = document.createElement("style"); 282 | holdercss.type = "text/css"; 283 | holdercss.styleSheet ? holdercss.styleSheet.cssText = options.stylesheet : holdercss.textContent = options.stylesheet; 284 | document.getElementsByTagName("head")[0].appendChild(holdercss); 285 | 286 | var cssregex = new RegExp(options.domain + "\/(.*?)\"?\\)"); 287 | 288 | for (var l = elements.length, i = 0; i < l; i++) { 289 | var src = window.getComputedStyle(elements[i], null) 290 | .getPropertyValue("background-image"); 291 | var flags = src.match(cssregex); 292 | if (flags) { 293 | var holder = parse_flags(flags[1].split("/"), options); 294 | if (holder) { 295 | render("background", elements[i], holder, src); 296 | } 297 | } 298 | } 299 | 300 | for (var l = images.length, i = 0; i < l; i++) { 301 | var src = images[i].getAttribute("src") || images[i].getAttribute("data-src"); 302 | if (src != null && src.indexOf(options.domain) >= 0) { 303 | var holder = parse_flags(src.substr(src.lastIndexOf(options.domain) + options.domain.length + 1) 304 | .split("/"), options); 305 | if (holder) { 306 | if (holder.fluid) { 307 | fluid(images[i], holder, src); 308 | } else { 309 | render("image", images[i], holder, src); 310 | } 311 | } 312 | } 313 | } 314 | return app; 315 | }; 316 | 317 | contentLoaded(win, function () { 318 | if (window.addEventListener) { 319 | window.addEventListener("resize", fluid_update, false); 320 | window.addEventListener("orientationchange", fluid_update, false); 321 | } else { 322 | window.attachEvent("onresize", fluid_update) 323 | } 324 | preempted || app.run(); 325 | }); 326 | 327 | })(Holder, window); 328 | -------------------------------------------------------------------------------- /resources/public/js/lib/underscore.min.js: -------------------------------------------------------------------------------- 1 | // Underscore.js 1.5.0 2 | // http://underscorejs.org 3 | // (c) 2009-2011 Jeremy Ashkenas, DocumentCloud Inc. 4 | // (c) 2011-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 5 | // Underscore may be freely distributed under the MIT license. 6 | !function(){var n=this,t=n._,r={},e=Array.prototype,u=Object.prototype,i=Function.prototype,a=e.push,o=e.slice,c=e.concat,l=u.toString,f=u.hasOwnProperty,s=e.forEach,p=e.map,v=e.reduce,h=e.reduceRight,d=e.filter,g=e.every,m=e.some,y=e.indexOf,b=e.lastIndexOf,x=Array.isArray,_=Object.keys,w=i.bind,j=function(n){return n instanceof j?n:this instanceof j?(this._wrapped=n,void 0):new j(n)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=j),exports._=j):n._=j,j.VERSION="1.5.0";var A=j.each=j.forEach=function(n,t,e){if(null!=n)if(s&&n.forEach===s)n.forEach(t,e);else if(n.length===+n.length){for(var u=0,i=n.length;i>u;u++)if(t.call(e,n[u],u,n)===r)return}else for(var a in n)if(j.has(n,a)&&t.call(e,n[a],a,n)===r)return};j.map=j.collect=function(n,t,r){var e=[];return null==n?e:p&&n.map===p?n.map(t,r):(A(n,function(n,u,i){e.push(t.call(r,n,u,i))}),e)};var E="Reduce of empty array with no initial value";j.reduce=j.foldl=j.inject=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),v&&n.reduce===v)return e&&(t=j.bind(t,e)),u?n.reduce(t,r):n.reduce(t);if(A(n,function(n,i,a){u?r=t.call(e,r,n,i,a):(r=n,u=!0)}),!u)throw new TypeError(E);return r},j.reduceRight=j.foldr=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),h&&n.reduceRight===h)return e&&(t=j.bind(t,e)),u?n.reduceRight(t,r):n.reduceRight(t);var i=n.length;if(i!==+i){var a=j.keys(n);i=a.length}if(A(n,function(o,c,l){c=a?a[--i]:--i,u?r=t.call(e,r,n[c],c,l):(r=n[c],u=!0)}),!u)throw new TypeError(E);return r},j.find=j.detect=function(n,t,r){var e;return O(n,function(n,u,i){return t.call(r,n,u,i)?(e=n,!0):void 0}),e},j.filter=j.select=function(n,t,r){var e=[];return null==n?e:d&&n.filter===d?n.filter(t,r):(A(n,function(n,u,i){t.call(r,n,u,i)&&e.push(n)}),e)},j.reject=function(n,t,r){return j.filter(n,function(n,e,u){return!t.call(r,n,e,u)},r)},j.every=j.all=function(n,t,e){t||(t=j.identity);var u=!0;return null==n?u:g&&n.every===g?n.every(t,e):(A(n,function(n,i,a){return(u=u&&t.call(e,n,i,a))?void 0:r}),!!u)};var O=j.some=j.any=function(n,t,e){t||(t=j.identity);var u=!1;return null==n?u:m&&n.some===m?n.some(t,e):(A(n,function(n,i,a){return u||(u=t.call(e,n,i,a))?r:void 0}),!!u)};j.contains=j.include=function(n,t){return null==n?!1:y&&n.indexOf===y?n.indexOf(t)!=-1:O(n,function(n){return n===t})},j.invoke=function(n,t){var r=o.call(arguments,2),e=j.isFunction(t);return j.map(n,function(n){return(e?t:n[t]).apply(n,r)})},j.pluck=function(n,t){return j.map(n,function(n){return n[t]})},j.where=function(n,t,r){return j.isEmpty(t)?r?void 0:[]:j[r?"find":"filter"](n,function(n){for(var r in t)if(t[r]!==n[r])return!1;return!0})},j.findWhere=function(n,t){return j.where(n,t,!0)},j.max=function(n,t,r){if(!t&&j.isArray(n)&&n[0]===+n[0]&&n.length<65535)return Math.max.apply(Math,n);if(!t&&j.isEmpty(n))return-1/0;var e={computed:-1/0,value:-1/0};return A(n,function(n,u,i){var a=t?t.call(r,n,u,i):n;a>e.computed&&(e={value:n,computed:a})}),e.value},j.min=function(n,t,r){if(!t&&j.isArray(n)&&n[0]===+n[0]&&n.length<65535)return Math.min.apply(Math,n);if(!t&&j.isEmpty(n))return 1/0;var e={computed:1/0,value:1/0};return A(n,function(n,u,i){var a=t?t.call(r,n,u,i):n;ae||r===void 0)return 1;if(e>r||e===void 0)return-1}return n.indexi;){var o=i+a>>>1;r.call(e,n[o])=0})})},j.difference=function(n){var t=c.apply(e,o.call(arguments,1));return j.filter(n,function(n){return!j.contains(t,n)})},j.zip=function(){return j.unzip.apply(j,o.call(arguments))},j.unzip=function(){for(var n=j.max(j.pluck(arguments,"length").concat(0)),t=new Array(n),r=0;n>r;r++)t[r]=j.pluck(arguments,""+r);return t},j.object=function(n,t){if(null==n)return{};for(var r={},e=0,u=n.length;u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},j.indexOf=function(n,t,r){if(null==n)return-1;var e=0,u=n.length;if(r){if("number"!=typeof r)return e=j.sortedIndex(n,t),n[e]===t?e:-1;e=0>r?Math.max(0,u+r):r}if(y&&n.indexOf===y)return n.indexOf(t,r);for(;u>e;e++)if(n[e]===t)return e;return-1},j.lastIndexOf=function(n,t,r){if(null==n)return-1;var e=null!=r;if(b&&n.lastIndexOf===b)return e?n.lastIndexOf(t,r):n.lastIndexOf(t);for(var u=e?r:n.length;u--;)if(n[u]===t)return u;return-1},j.range=function(n,t,r){arguments.length<=1&&(t=n||0,n=0),r=arguments[2]||1;for(var e=Math.max(Math.ceil((t-n)/r),0),u=0,i=new Array(e);e>u;)i[u++]=n,n+=r;return i};var M=function(){};j.bind=function(n,t){var r,e;if(w&&n.bind===w)return w.apply(n,o.call(arguments,1));if(!j.isFunction(n))throw new TypeError;return r=o.call(arguments,2),e=function(){if(!(this instanceof e))return n.apply(t,r.concat(o.call(arguments)));M.prototype=n.prototype;var u=new M;M.prototype=null;var i=n.apply(u,r.concat(o.call(arguments)));return Object(i)===i?i:u}},j.partial=function(n){var t=o.call(arguments,1);return function(){return n.apply(this,t.concat(o.call(arguments)))}},j.bindAll=function(n){var t=o.call(arguments,1);if(0===t.length)throw new Error("bindAll must be passed function names");return A(t,function(t){n[t]=j.bind(n[t],n)}),n},j.memoize=function(n,t){var r={};return t||(t=j.identity),function(){var e=t.apply(this,arguments);return j.has(r,e)?r[e]:r[e]=n.apply(this,arguments)}},j.delay=function(n,t){var r=o.call(arguments,2);return setTimeout(function(){return n.apply(null,r)},t)},j.defer=function(n){return j.delay.apply(j,[n,1].concat(o.call(arguments,1)))},j.throttle=function(n,t,r){var e,u,i,a=null,o=0;r||(r={});var c=function(){o=new Date,a=null,i=n.apply(e,u)};return function(){var l=new Date;o||r.leading!==!1||(o=l);var f=t-(l-o);return e=this,u=arguments,0>=f?(clearTimeout(a),a=null,o=l,i=n.apply(e,u)):a||r.trailing===!1||(a=setTimeout(c,f)),i}},j.debounce=function(n,t,r){var e,u=null;return function(){var i=this,a=arguments,o=function(){u=null,r||(e=n.apply(i,a))},c=r&&!u;return clearTimeout(u),u=setTimeout(o,t),c&&(e=n.apply(i,a)),e}},j.once=function(n){var t,r=!1;return function(){return r?t:(r=!0,t=n.apply(this,arguments),n=null,t)}},j.wrap=function(n,t){return function(){var r=[n];return a.apply(r,arguments),t.apply(this,r)}},j.compose=function(){var n=arguments;return function(){for(var t=arguments,r=n.length-1;r>=0;r--)t=[n[r].apply(this,t)];return t[0]}},j.after=function(n,t){return function(){return--n<1?t.apply(this,arguments):void 0}},j.keys=_||function(n){if(n!==Object(n))throw new TypeError("Invalid object");var t=[];for(var r in n)j.has(n,r)&&t.push(r);return t},j.values=function(n){var t=[];for(var r in n)j.has(n,r)&&t.push(n[r]);return t},j.pairs=function(n){var t=[];for(var r in n)j.has(n,r)&&t.push([r,n[r]]);return t},j.invert=function(n){var t={};for(var r in n)j.has(n,r)&&(t[n[r]]=r);return t},j.functions=j.methods=function(n){var t=[];for(var r in n)j.isFunction(n[r])&&t.push(r);return t.sort()},j.extend=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]=t[r]}),n},j.pick=function(n){var t={},r=c.apply(e,o.call(arguments,1));return A(r,function(r){r in n&&(t[r]=n[r])}),t},j.omit=function(n){var t={},r=c.apply(e,o.call(arguments,1));for(var u in n)j.contains(r,u)||(t[u]=n[u]);return t},j.defaults=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]===void 0&&(n[r]=t[r])}),n},j.clone=function(n){return j.isObject(n)?j.isArray(n)?n.slice():j.extend({},n):n},j.tap=function(n,t){return t(n),n};var S=function(n,t,r,e){if(n===t)return 0!==n||1/n==1/t;if(null==n||null==t)return n===t;n instanceof j&&(n=n._wrapped),t instanceof j&&(t=t._wrapped);var u=l.call(n);if(u!=l.call(t))return!1;switch(u){case"[object String]":return n==String(t);case"[object Number]":return n!=+n?t!=+t:0==n?1/n==1/t:n==+t;case"[object Date]":case"[object Boolean]":return+n==+t;case"[object RegExp]":return n.source==t.source&&n.global==t.global&&n.multiline==t.multiline&&n.ignoreCase==t.ignoreCase}if("object"!=typeof n||"object"!=typeof t)return!1;for(var i=r.length;i--;)if(r[i]==n)return e[i]==t;var a=n.constructor,o=t.constructor;if(a!==o&&!(j.isFunction(a)&&a instanceof a&&j.isFunction(o)&&o instanceof o))return!1;r.push(n),e.push(t);var c=0,f=!0;if("[object Array]"==u){if(c=n.length,f=c==t.length)for(;c--&&(f=S(n[c],t[c],r,e)););}else{for(var s in n)if(j.has(n,s)&&(c++,!(f=j.has(t,s)&&S(n[s],t[s],r,e))))break;if(f){for(s in t)if(j.has(t,s)&&!c--)break;f=!c}}return r.pop(),e.pop(),f};j.isEqual=function(n,t){return S(n,t,[],[])},j.isEmpty=function(n){if(null==n)return!0;if(j.isArray(n)||j.isString(n))return 0===n.length;for(var t in n)if(j.has(n,t))return!1;return!0},j.isElement=function(n){return!(!n||1!==n.nodeType)},j.isArray=x||function(n){return"[object Array]"==l.call(n)},j.isObject=function(n){return n===Object(n)},A(["Arguments","Function","String","Number","Date","RegExp"],function(n){j["is"+n]=function(t){return l.call(t)=="[object "+n+"]"}}),j.isArguments(arguments)||(j.isArguments=function(n){return!(!n||!j.has(n,"callee"))}),"function"!=typeof/./&&(j.isFunction=function(n){return"function"==typeof n}),j.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},j.isNaN=function(n){return j.isNumber(n)&&n!=+n},j.isBoolean=function(n){return n===!0||n===!1||"[object Boolean]"==l.call(n)},j.isNull=function(n){return null===n},j.isUndefined=function(n){return n===void 0},j.has=function(n,t){return f.call(n,t)},j.noConflict=function(){return n._=t,this},j.identity=function(n){return n},j.times=function(n,t,r){for(var e=Array(Math.max(0,n)),u=0;n>u;u++)e[u]=t.call(r,u);return e},j.random=function(n,t){return null==t&&(t=n,n=0),n+Math.floor(Math.random()*(t-n+1))};var I={escape:{"&":"&","<":"<",">":">",'"':""","'":"'","/":"/"}};I.unescape=j.invert(I.escape);var T={escape:new RegExp("["+j.keys(I.escape).join("")+"]","g"),unescape:new RegExp("("+j.keys(I.unescape).join("|")+")","g")};j.each(["escape","unescape"],function(n){j[n]=function(t){return null==t?"":(""+t).replace(T[n],function(t){return I[n][t]})}}),j.result=function(n,t){if(null==n)return void 0;var r=n[t];return j.isFunction(r)?r.call(n):r},j.mixin=function(n){A(j.functions(n),function(t){var r=j[t]=n[t];j.prototype[t]=function(){var n=[this._wrapped];return a.apply(n,arguments),D.call(this,r.apply(j,n))}})};var N=0;j.uniqueId=function(n){var t=++N+"";return n?n+t:t},j.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var q=/(.)^/,B={"'":"'","\\":"\\","\r":"r","\n":"n"," ":"t","\u2028":"u2028","\u2029":"u2029"},z=/\\|'|\r|\n|\t|\u2028|\u2029/g;j.template=function(n,t,r){var e;r=j.defaults({},r,j.templateSettings);var u=new RegExp([(r.escape||q).source,(r.interpolate||q).source,(r.evaluate||q).source].join("|")+"|$","g"),i=0,a="__p+='";n.replace(u,function(t,r,e,u,o){return a+=n.slice(i,o).replace(z,function(n){return"\\"+B[n]}),r&&(a+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'"),e&&(a+="'+\n((__t=("+e+"))==null?'':__t)+\n'"),u&&(a+="';\n"+u+"\n__p+='"),i=o+t.length,t}),a+="';\n",r.variable||(a="with(obj||{}){\n"+a+"}\n"),a="var __t,__p='',__j=Array.prototype.join,"+"print=function(){__p+=__j.call(arguments,'');};\n"+a+"return __p;\n";try{e=new Function(r.variable||"obj","_",a)}catch(o){throw o.source=a,o}if(t)return e(t,j);var c=function(n){return e.call(this,n,j)};return c.source="function("+(r.variable||"obj")+"){\n"+a+"}",c},j.chain=function(n){return j(n).chain()};var D=function(n){return this._chain?j(n).chain():n};j.mixin(j),A(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var t=e[n];j.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),"shift"!=n&&"splice"!=n||0!==r.length||delete r[0],D.call(this,r)}}),A(["concat","join","slice"],function(n){var t=e[n];j.prototype[n]=function(){return D.call(this,t.apply(this._wrapped,arguments))}}),j.extend(j.prototype,{chain:function(){return this._chain=!0,this},value:function(){return this._wrapped}})}.call(this); 7 | //# sourceMappingURL=underscore-min.map -------------------------------------------------------------------------------- /resources/public/js/lib/backbone.min.js: -------------------------------------------------------------------------------- 1 | (function(){var t=this;var e=t.Backbone;var i=[];var r=i.push;var s=i.slice;var n=i.splice;var a;if(typeof exports!=="undefined"){a=exports}else{a=t.Backbone={}}a.VERSION="1.0.0";var h=t._;if(!h&&typeof require!=="undefined")h=require("underscore");a.$=t.jQuery||t.Zepto||t.ender||t.$;a.noConflict=function(){t.Backbone=e;return this};a.emulateHTTP=false;a.emulateJSON=false;var o=a.Events={on:function(t,e,i){if(!l(this,"on",t,[e,i])||!e)return this;this._events||(this._events={});var r=this._events[t]||(this._events[t]=[]);r.push({callback:e,context:i,ctx:i||this});return this},once:function(t,e,i){if(!l(this,"once",t,[e,i])||!e)return this;var r=this;var s=h.once(function(){r.off(t,s);e.apply(this,arguments)});s._callback=e;return this.on(t,s,i)},off:function(t,e,i){var r,s,n,a,o,u,c,f;if(!this._events||!l(this,"off",t,[e,i]))return this;if(!t&&!e&&!i){this._events={};return this}a=t?[t]:h.keys(this._events);for(o=0,u=a.length;o").attr(t);this.setElement(e,false)}else{this.setElement(h.result(this,"el"),false)}}});a.sync=function(t,e,i){var r=k[t];h.defaults(i||(i={}),{emulateHTTP:a.emulateHTTP,emulateJSON:a.emulateJSON});var s={type:r,dataType:"json"};if(!i.url){s.url=h.result(e,"url")||U()}if(i.data==null&&e&&(t==="create"||t==="update"||t==="patch")){s.contentType="application/json";s.data=JSON.stringify(i.attrs||e.toJSON(i))}if(i.emulateJSON){s.contentType="application/x-www-form-urlencoded";s.data=s.data?{model:s.data}:{}}if(i.emulateHTTP&&(r==="PUT"||r==="DELETE"||r==="PATCH")){s.type="POST";if(i.emulateJSON)s.data._method=r;var n=i.beforeSend;i.beforeSend=function(t){t.setRequestHeader("X-HTTP-Method-Override",r);if(n)return n.apply(this,arguments)}}if(s.type!=="GET"&&!i.emulateJSON){s.processData=false}if(s.type==="PATCH"&&window.ActiveXObject&&!(window.external&&window.external.msActiveXFilteringEnabled)){s.xhr=function(){return new ActiveXObject("Microsoft.XMLHTTP")}}var o=i.xhr=a.ajax(h.extend(s,i));e.trigger("request",e,o,i);return o};var k={create:"POST",update:"PUT",patch:"PATCH","delete":"DELETE",read:"GET"};a.ajax=function(){return a.$.ajax.apply(a.$,arguments)};var S=a.Router=function(t){t||(t={});if(t.routes)this.routes=t.routes;this._bindRoutes();this.initialize.apply(this,arguments)};var $=/\((.*?)\)/g;var T=/(\(\?)?:\w+/g;var H=/\*\w+/g;var A=/[\-{}\[\]+?.,\\\^$|#\s]/g;h.extend(S.prototype,o,{initialize:function(){},route:function(t,e,i){if(!h.isRegExp(t))t=this._routeToRegExp(t);if(h.isFunction(e)){i=e;e=""}if(!i)i=this[e];var r=this;a.history.route(t,function(s){var n=r._extractParameters(t,s);i&&i.apply(r,n);r.trigger.apply(r,["route:"+e].concat(n));r.trigger("route",e,n);a.history.trigger("route",r,e,n)});return this},navigate:function(t,e){a.history.navigate(t,e);return this},_bindRoutes:function(){if(!this.routes)return;this.routes=h.result(this,"routes");var t,e=h.keys(this.routes);while((t=e.pop())!=null){this.route(t,this.routes[t])}},_routeToRegExp:function(t){t=t.replace(A,"\\$&").replace($,"(?:$1)?").replace(T,function(t,e){return e?t:"([^/]+)"}).replace(H,"(.*?)");return new RegExp("^"+t+"$")},_extractParameters:function(t,e){var i=t.exec(e).slice(1);return h.map(i,function(t){return t?decodeURIComponent(t):null})}});var I=a.History=function(){this.handlers=[];h.bindAll(this,"checkUrl");if(typeof window!=="undefined"){this.location=window.location;this.history=window.history}};var N=/^[#\/]|\s+$/g;var P=/^\/+|\/+$/g;var O=/msie [\w.]+/;var C=/\/$/;I.started=false;h.extend(I.prototype,o,{interval:50,getHash:function(t){var e=(t||this).location.href.match(/#(.*)$/);return e?e[1]:""},getFragment:function(t,e){if(t==null){if(this._hasPushState||!this._wantsHashChange||e){t=this.location.pathname;var i=this.root.replace(C,"");if(!t.indexOf(i))t=t.substr(i.length)}else{t=this.getHash()}}return t.replace(N,"")},start:function(t){if(I.started)throw new Error("Backbone.history has already been started");I.started=true;this.options=h.extend({},{root:"/"},this.options,t);this.root=this.options.root;this._wantsHashChange=this.options.hashChange!==false;this._wantsPushState=!!this.options.pushState;this._hasPushState=!!(this.options.pushState&&this.history&&this.history.pushState);var e=this.getFragment();var i=document.documentMode;var r=O.exec(navigator.userAgent.toLowerCase())&&(!i||i<=7);this.root=("/"+this.root+"/").replace(P,"/");if(r&&this._wantsHashChange){this.iframe=a.$('