Commit 21cc578fca7a843b4e7ca548b198c192f4bf529c
Integrating annotator with the application.
/annotate is the API endpoint for getting a web page with annotator
loaded.
- swtr/server.py 80 ----++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
- swtr/static/js/app.js 244 ------++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
| | | | 6 | import config | 6 | import config |
---|
7 | import requests | 7 | import requests |
---|
8 | import json | 8 | import json |
---|
9 | import urllib2 | | import urllib2 |
---|
| | 9 | import StringIO | 10 | import imghdr | 10 | import imghdr |
---|
11 | | 11 | |
---|
12 | app = flask.Flask(__name__) | 12 | app = flask.Flask(__name__) |
---|
… | | … | |
---|
53 | @app.route('/annotate', methods=['GET']) | 53 | @app.route('/annotate', methods=['GET']) |
---|
54 | def annotate(): | 54 | def annotate(): |
---|
55 | print flask.request.args['where'] | 55 | print flask.request.args['where'] |
---|
56 | img = urllib2.urlopen(flask.request.args['where']).read() | | img = urllib2.urlopen(flask.request.args['where']).read() |
---|
57 | if imghdr.what('ignore', img) is None: | | if imghdr.what('ignore', img) is None: |
---|
58 | root = lxml.html.parse(flask.request.args['where']).getroot() | | root = lxml.html.parse(flask.request.args['where']).getroot() |
---|
| | 56 | # img = urllib2.urlopen(flask.request.args['where']).read() | | | 57 | request = requests.get(flask.request.args['where']) |
---|
| | 58 | content = request.text |
---|
| | 59 | if imghdr.what('ignore', content) is None: |
---|
| | 60 | root = lxml.html.parse(StringIO.StringIO(content)).getroot() |
---|
59 | root.make_links_absolute(flask.request.args['where'], | 61 | root.make_links_absolute(flask.request.args['where'], |
---|
60 | resolve_base_href=True) | 62 | resolve_base_href=True) |
---|
61 | | 63 | |
---|
… | | … | |
---|
80 | "css/annotator.min.css")) | 80 | "css/annotator.min.css")) |
---|
81 | annotatorCSS.set("rel", "stylesheet") | 81 | annotatorCSS.set("rel", "stylesheet") |
---|
82 | annotatorCSS.set("type", "text/css") | 82 | annotatorCSS.set("type", "text/css") |
---|
| | 83 | |
---|
| | 84 | swtmakerCSS = root.makeelement('link') |
---|
| | 85 | root.body.append(swtmakerCSS) |
---|
| | 86 | swtmakerCSS.set("href", flask.url_for('static', |
---|
| | 87 | filename= |
---|
| | 88 | "css/swtmaker.css")) |
---|
| | 89 | swtmakerCSS.set("rel", "stylesheet") |
---|
| | 90 | swtmakerCSS.set("type", "text/css") |
---|
| | 91 | |
---|
| | 92 | bootstrapCSS = root.makeelement('link') |
---|
| | 93 | root.body.append(bootstrapCSS) |
---|
| | 94 | bootstrapCSS.set("href", flask.url_for('static', |
---|
| | 95 | filename= |
---|
| | 96 | "css/bootstrap.min.css")) |
---|
| | 97 | bootstrapCSS.set("rel", "stylesheet") |
---|
| | 98 | bootstrapCSS.set("type", "text/css") |
---|
| | 99 | |
---|
| | 100 | underscoreJS = root.makeelement('script') |
---|
| | 101 | root.body.append(underscoreJS) |
---|
| | 102 | underscoreJS.set("src", flask.url_for('static', |
---|
| | 103 | filename="js/lib/" + |
---|
| | 104 | "underscore-1.5.2.min.js")) |
---|
| | 105 | underscoreJS.set("type", "text/javascript") |
---|
| | 106 | |
---|
| | 107 | backboneJS = root.makeelement('script') |
---|
| | 108 | root.body.append(backboneJS) |
---|
| | 109 | backboneJS.set("src", flask.url_for('static', |
---|
| | 110 | filename= |
---|
| | 111 | "js/lib/backbone-1.0.0.min.js")) |
---|
| | 112 | backboneJS.set("type", "text/javascript") |
---|
| | 113 | |
---|
| | 114 | if 'auth_tok' in session: |
---|
| | 115 | auth_tok = session['auth_tok'] |
---|
| | 116 | else: |
---|
| | 117 | auth_tok = {'access_token': '', 'refresh_token': ''} |
---|
| | 118 | |
---|
| | 119 | configScript = root.makeelement('script') |
---|
| | 120 | root.body.append(configScript) |
---|
| | 121 | configScript.text = """window.swtr = window.swtr || {}; |
---|
| | 122 | swtr.swtstoreURL = {} |
---|
| | 123 | swtr.endpoints = {} |
---|
| | 124 | 'get': '/api/sweets/q', |
---|
| | 125 | 'post': '/api/sweets', |
---|
| | 126 | 'auth': '/oauth/authorize', |
---|
| | 127 | 'login': '/auth/login', |
---|
| | 128 | 'logout': '/auth/logout' |
---|
| | 129 | {}; |
---|
| | 130 | |
---|
| | 131 | swtr.access_token = '{}'; |
---|
| | 132 | swtr.refresh_token = '{}'; |
---|
| | 133 | swtr.app_id = '{}';swtr.app_secret = '{}'; |
---|
| | 134 | swtr.oauth_redirect_uri = '{}';""".format( |
---|
| | 135 | '{}', 'function() {}return "{}"{}'.format('{', |
---|
| | 136 | config.swtstoreURL, '};'), |
---|
| | 137 | '{', '}', auth_tok['access_token'], auth_tok['refresh_token'], |
---|
| | 138 | config.app_id, config.app_secret, |
---|
| | 139 | config.redirect_uri) |
---|
| | 140 | configScript.set("type", "text/javascript") |
---|
| | 141 | |
---|
| | 142 | # swtmakerScript = root.makeelement('script') |
---|
| | 143 | # root.body.append(swtmakerScript) |
---|
| | 144 | # swtmakerScript.set("src", flask.url_for('static', |
---|
| | 145 | # filename="js/swtmaker.js")) |
---|
| | 146 | # swtmakerScript.set("type", "text/javascript") |
---|
| | 147 | |
---|
| | 148 | oAuthScript = root.makeelement('script') |
---|
| | 149 | root.body.append(oAuthScript) |
---|
| | 150 | oAuthScript.set("src", flask.url_for('static', |
---|
| | 151 | filename="js/oauth.js")) |
---|
| | 152 | oAuthScript.set("type", "text/javascript") |
---|
83 | | 153 | |
---|
84 | appScript = root.makeelement('script') | 154 | appScript = root.makeelement('script') |
---|
85 | root.body.append(appScript) | 155 | root.body.append(appScript) |
---|
| | | | 1 | (function() { | | (function() { |
---|
2 | $(document).ready(function() { | | $(document).ready(function() { |
---|
3 | var annotator = new Annotator(document.body); | | var annotator = new Annotator(document.body); |
---|
4 | annotator.addPlugin("Tags"); | | annotator.addPlugin("Tags"); |
---|
5 | annotator.addPlugin("Filter"); | | annotator.addPlugin("Filter"); |
---|
| | 1 | window.swtr = window.swtr || {}; | | | 2 | (function(swtr) { |
---|
| | 3 | |
---|
| | 4 | swtr.handleOAuth = function() { |
---|
| | 5 | if(swtr.access_token) { |
---|
| | 6 | $('#signinview').html('Signing you in..'); |
---|
| | 7 | $.ajax({ |
---|
| | 8 | url: swtr.swtstoreURL()+'/api/users/me?access_token='+ |
---|
| | 9 | swtr.access_token, |
---|
| | 10 | success: function(data) { |
---|
| | 11 | $('#signinview').html('Signed in as ' + data.username); |
---|
| | 12 | swtr.who = data.username; |
---|
| | 13 | $("#sign-in").hide(); |
---|
| | 14 | }, |
---|
| | 15 | error: function() { |
---|
| | 16 | // $('#signinview').html('Error signing in! Please try again'); |
---|
| | 17 | $("#sign-in").show(); |
---|
| | 18 | } |
---|
| | 19 | }); |
---|
| | 20 | } else { |
---|
| | 21 | $('#signinview').html('Please sign in.'); |
---|
| | 22 | } |
---|
| | 23 | }; |
---|
| | 24 | |
---|
| | 25 | var TxtAnnoSwt = Backbone.Model.extend({ |
---|
| | 26 | defaults: { |
---|
| | 27 | 'who': '', |
---|
| | 28 | 'what': 'txt-anno', |
---|
| | 29 | 'where': '', |
---|
| | 30 | 'how': {} |
---|
| | 31 | }, |
---|
| | 32 | initialize: function(options) { |
---|
| | 33 | this.set(options); |
---|
| | 34 | } |
---|
6 | }); | 35 | }); |
---|
7 | })(); | | })(); |
---|
| | 36 | | | | 37 | var TxtAnnoSwts = Backbone.Collection.extend({ |
---|
| | 38 | model: TxtAnnoSwt, |
---|
| | 39 | url: function() { |
---|
| | 40 | return swtr.swtstoreURL() + '/sweets'; |
---|
| | 41 | }, |
---|
| | 42 | // get all sweets/annotations of type #img-anno for a particular URI |
---|
| | 43 | // (where) |
---|
| | 44 | // @options is a javascript object, |
---|
| | 45 | // @options.where : URI of the resource for which swts to be fetched |
---|
| | 46 | // @options.who: optional username to filter sweets |
---|
| | 47 | // @options.success: success callback to call |
---|
| | 48 | // @options.error: error callback to call |
---|
| | 49 | getAll: function(options) { |
---|
| | 50 | // error checking |
---|
| | 51 | if(!options.where) { |
---|
| | 52 | throw Error('"where" option must be passed to get sweets of a URI'); |
---|
| | 53 | return false; |
---|
| | 54 | } |
---|
| | 55 | /*if(!swtr.access_token) { |
---|
| | 56 | throw new Error('Access Token required to get query that API'); |
---|
| | 57 | }*/ |
---|
| | 58 | // setting up params |
---|
| | 59 | var where = options.where, |
---|
| | 60 | who = options.who || null; |
---|
| | 61 | url = swtr.swtstoreURL() + swtr.endpoints.get + '?where=' + |
---|
| | 62 | encodeURIComponent(where) + '&access_token=' + swtr.access_token; |
---|
| | 63 | if(who) { |
---|
| | 64 | url += '&who=' + who; |
---|
| | 65 | } |
---|
| | 66 | // get them! |
---|
| | 67 | this.sync('read', this, { |
---|
| | 68 | url: url, |
---|
| | 69 | success: function() { |
---|
| | 70 | if(typeof options.success === 'function') { |
---|
| | 71 | options.success.apply(this, arguments); |
---|
| | 72 | } |
---|
| | 73 | }, |
---|
| | 74 | error: function() { |
---|
| | 75 | if(typeof options.error === 'function') { |
---|
| | 76 | options.error.apply(this, arguments); |
---|
| | 77 | } |
---|
| | 78 | } |
---|
| | 79 | }); |
---|
| | 80 | }, |
---|
| | 81 | // post newly created sweets to a sweet store |
---|
| | 82 | // @options is a javascript object, |
---|
| | 83 | // @options.where : URI of the resource for which swts to be fetched |
---|
| | 84 | // @options.who: optional username to filter sweets |
---|
| | 85 | // @options.success: success callback to call |
---|
| | 86 | // @options.error: error callback to call, |
---|
| | 87 | post: function(options) { |
---|
| | 88 | var new_sweets = this.getNew(); |
---|
| | 89 | var dummy_collection = new Backbone.Collection(new_sweets); |
---|
| | 90 | |
---|
| | 91 | if(!swtr.access_token) { |
---|
| | 92 | throw new Error('Access Token is required to sweet'); |
---|
| | 93 | return; |
---|
| | 94 | } |
---|
| | 95 | |
---|
| | 96 | var url = swtr.swtstoreURL() + swtr.endpoints.post + |
---|
| | 97 | '?access_token=' + swtr.access_token; |
---|
| | 98 | |
---|
| | 99 | this.sync('create', dummy_collection, { |
---|
| | 100 | url: url, |
---|
| | 101 | success: function() { |
---|
| | 102 | if(typeof options.success === 'function') { |
---|
| | 103 | options.success.apply(this, arguments); |
---|
| | 104 | } |
---|
| | 105 | }, |
---|
| | 106 | error: function() { |
---|
| | 107 | if(typeof options.error === 'function') { |
---|
| | 108 | options.error.apply(this, arguments); |
---|
| | 109 | } |
---|
| | 110 | } |
---|
| | 111 | }); |
---|
| | 112 | }, |
---|
| | 113 | // return newly created models from the collection |
---|
| | 114 | getNew: function() { |
---|
| | 115 | var new_models = []; |
---|
| | 116 | this.each(function(model) { |
---|
| | 117 | if(model.isNew()) { |
---|
| | 118 | new_models.push(model); |
---|
| | 119 | } |
---|
| | 120 | }); |
---|
| | 121 | return new_models; |
---|
| | 122 | }, |
---|
| | 123 | // update part of the collection after a save on the server |
---|
| | 124 | update: function() { |
---|
| | 125 | } |
---|
| | 126 | }); |
---|
| | 127 | |
---|
| | 128 | var TxtAnnoView = Backbone.View.extend({ |
---|
| | 129 | initialize: function() { |
---|
| | 130 | this.annotator = new Annotator(document.body); |
---|
| | 131 | swtr.anno = this.annotator; |
---|
| | 132 | this.annotator.addPlugin("Tags"); |
---|
| | 133 | this.listenTo(this.collection, "add", this.loadAnno); |
---|
| | 134 | this.annotator.subscribe("annotationCreated", this.storeAnno); |
---|
| | 135 | }, |
---|
| | 136 | storeAnno: function(annotation) { |
---|
| | 137 | var pageURL = window.location.search.split("=")[1]; |
---|
| | 138 | if(!(swtr.who)) { |
---|
| | 139 | swtr.who = "Guest"; |
---|
| | 140 | } |
---|
| | 141 | swtr.TxtAnnoSwts.add(new TxtAnnoSwt({how: annotation, |
---|
| | 142 | where: pageURL, |
---|
| | 143 | who: swtr.who})); |
---|
| | 144 | if($("#show-sweets").attr('disabled')) { |
---|
| | 145 | $("#show-sweets").removeAttr('disabled'); |
---|
| | 146 | } |
---|
| | 147 | }, |
---|
| | 148 | loadAnno: function(annotation) { |
---|
| | 149 | if(!(annotation.isNew())) { |
---|
| | 150 | swtr.anno.createAnnotation(annotation.get('how')); |
---|
| | 151 | swtr.anno.setupAnnotation(annotation.get('how')); |
---|
| | 152 | } |
---|
| | 153 | } |
---|
| | 154 | }); |
---|
| | 155 | |
---|
| | 156 | var AppView = Backbone.View.extend({ |
---|
| | 157 | el: document.body, |
---|
| | 158 | events: { |
---|
| | 159 | "click #show-sweets": 'showSweets', |
---|
| | 160 | "click #sweet-cancel": 'cancelSweet', |
---|
| | 161 | "click #post-sweet": 'postSweet', |
---|
| | 162 | "click #sign-in": 'signIn' |
---|
| | 163 | }, |
---|
| | 164 | oauth: new Oauth({ |
---|
| | 165 | app_id: swtr.app_id, |
---|
| | 166 | app_secret: swtr.app_secret, |
---|
| | 167 | endpoint: swtr.swtstoreURL() + swtr.endpoints.auth, |
---|
| | 168 | redirect_uri: swtr.oauth_redirect_uri, |
---|
| | 169 | scopes: 'email,sweet' |
---|
| | 170 | }), |
---|
| | 171 | sweetTemplate: '<div id="sweet-list-wrapper">'+ |
---|
| | 172 | '<h4>These are your sweet annotations!</h4>'+ |
---|
| | 173 | '<ul id="sweet-list"></ul>'+ |
---|
| | 174 | '<div class="btn-grp">'+ |
---|
| | 175 | '<button class="btn btn-default" id="sweet-cancel">Cancel</button>'+ |
---|
| | 176 | '<button class="btn btn-primary" id="post-sweet">Sweet</button>'+ |
---|
| | 177 | '</div>'+ |
---|
| | 178 | '</div>'+ |
---|
| | 179 | '</div>', |
---|
| | 180 | initialize: function() { |
---|
| | 181 | swtr.TxtAnnoSwts = new TxtAnnoSwts(); |
---|
| | 182 | var txtAnnoView = new TxtAnnoView({collection: swtr.TxtAnnoSwts}); |
---|
| | 183 | swtr.TxtAnnoSwts.getAll({where: window.location.search.split("=")[1], |
---|
| | 184 | success: function(data) { |
---|
| | 185 | swtr.TxtAnnoSwts.add(data); |
---|
| | 186 | }}); |
---|
| | 187 | swtr.handleOAuth(); |
---|
| | 188 | $(this.el).append(this.sweetTemplate); |
---|
| | 189 | this.loadOverlayBar(); |
---|
| | 190 | }, |
---|
| | 191 | loadOverlayBar: function() { |
---|
| | 192 | var template = "<div id='overlayBar' class='navbar' style='background:grey;position:fixed; top:0px; left:0px;width:100%;'><div class='navbar-inner'><div class='container-fluid'>"+ |
---|
| | 193 | "<a href='/' >Try a different website.</a>"+ |
---|
| | 194 | "<button id='show-sweets' disabled='enabled' class='btn btn-default'>Sweets"+ |
---|
| | 195 | "</button><button id='sign-in' class='btn btn-sm btn-primary'>Sign In</button><span id='signinview'></span></div></div></div>"; |
---|
| | 196 | $(document.body).append(template); |
---|
| | 197 | }, |
---|
| | 198 | signIn: function(e) { |
---|
| | 199 | this.oauth.authorize(); |
---|
| | 200 | swtr.handleOAuth(); |
---|
| | 201 | }, |
---|
| | 202 | showSweets: function(e) { |
---|
| | 203 | $("#sweet-list-wrapper").show(); |
---|
| | 204 | swtr.TxtAnnoSwts.each(function(model){ |
---|
| | 205 | var templateStr ='<li class="sweet">'+ |
---|
| | 206 | '<a href="#">@<%= who %></a> <strong>#<%= what %></strong> '+ |
---|
| | 207 | '<a href="<%= where %>"><%= where.substr(0, 30) + "..." %></a> '+ |
---|
| | 208 | '<p><%= how %></p>'+ |
---|
| | 209 | '</li>'; |
---|
| | 210 | var template = _.template(templateStr); |
---|
| | 211 | if(model.isNew()) { |
---|
| | 212 | $("#sweet-list").append(template({ |
---|
| | 213 | 'who': swtr.who, |
---|
| | 214 | 'what': 'txt-anno', |
---|
| | 215 | 'where': window.location.search.split("=")[1], |
---|
| | 216 | 'how': JSON.stringify(model.get('how')) |
---|
| | 217 | })); |
---|
| | 218 | } |
---|
| | 219 | }, this); |
---|
| | 220 | $("#sweet-list-wrapper").focus(); |
---|
| | 221 | |
---|
| | 222 | }, |
---|
| | 223 | cancelSweet: function(e) { |
---|
| | 224 | $("#sweet-list-wrapper").hide(); |
---|
| | 225 | $("#sweet-list").html(''); |
---|
| | 226 | }, |
---|
| | 227 | postSweet: function(e) { |
---|
| | 228 | swtr.TxtAnnoSwts.post({success: function(data) { |
---|
| | 229 | alert("Your SWeets are posted!!"); |
---|
| | 230 | }, |
---|
| | 231 | error: function(data) { |
---|
| | 232 | alert("Failed to post your SWeets, please try again."); |
---|
| | 233 | } |
---|
| | 234 | }); |
---|
| | 235 | } |
---|
| | 236 | }); |
---|
| | 237 | new AppView; |
---|
| | 238 | |
---|
| | 239 | })(swtr); |
---|