1<!DOCTYPE html> 2<!-- 3 * Copyright (c) 2010 The Chromium Authors. All rights reserved. Use of this 4 * source code is governed by a BSD-style license that can be found in the 5 * LICENSE file. 6 * 7 * Author: Eric Bidelman <ericbidelman@chromium.org> 8--> 9<html> 10<head> 11<title>Your Google Documents List</title> 12<script type="text/javascript" src="js/jquery-1.4.1.min.js"></script> 13<style type="text/css"> 14body { 15 font: 12px 'Myriad Pro', 'Tw Cen MT', Arial, Verdana, sans-serif; 16 color: #666666; 17 overflow-x: hidden; 18} 19ul { 20 padding: 0; 21 list-style: none; 22} 23li { 24 clear: both; 25 padding: 2px 0; 26} 27li div img { 28 margin: 0 5px; 29 vertical-align: middle; 30} 31li div { 32 text-overflow: ellipsis; 33 white-space: nowrap; 34 overflow: hidden; 35 width: 250px; 36 float: left; 37 padding: 2px 0; 38} 39li span { 40 margin-left: 5px; 41} 42li:hover { 43 background-color: #fffccc; 44} 45a { 46 color: #4E7DC2; 47 text-decoration: none; 48} 49a:hover { 50 color: #880000; 51 text-decoration: underline; 52} 53#butter { 54 color: #fff; 55 background-color: #000033; 56 padding: 5px 20px; 57 border-radius: 15px; 58 width: auto; 59 text-align: center; 60 float: right; 61 display: none; 62} 63#butter.error { 64 background-color: red; 65} 66#new_doc_container { 67 display: none; 68} 69#new_doc_container input[type='text'],textarea { 70 width: 100%; 71} 72#output { 73 width: 375px; 74 clear: both; 75} 76[contenteditable]:hover { 77 outline: 1px dotted #666; 78} 79.star { 80 margin-top: 1px; 81 margin-right: 3px; 82 width: 16px; 83 height: 16px; 84 background: no-repeat url(data:image/png;base64,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) !important; 85} 86.star.selected { 87 background: no-repeat url(data:image/png;base64,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) !important; 88} 89</style> 90</head> 91<body> 92 93<div style="height:15px;"> 94 <div style="float:left;"> 95 <a href="javascript:void(0);" onclick="gdocs.refreshDocs();return false;">Refresh list</a>, 96 <a href="javascript:void(0);" onclick="$('#new_doc_container').toggle();return false;">New Document</a> 97 </div> 98 <div id="butter">Fetching your docs</div> 99</div> 100<div id="new_doc_container"> 101 Create a: <select id="doc_type"> 102 <option value="document">document</option> 103 <option value="presentation">presentation</option> 104 <option value="spreadsheet">spreadsheet</option> 105 </select> 106 <input type="text" id="doc_title" placeholder="Enter a title"><br> 107 <textarea id="doc_content" placeholder="Enter document content"></textarea> 108 Star it? <input type="checkbox" id="doc_starred"> 109 <button onclick="gdocs.createDoc();" style="float:right;">Create new doc</button> 110</div> 111<div id="output"></div> 112 113<script type="text/javascript"> 114// Protected namespaces. 115var util = {}; 116var gdocs = {}; 117 118var bgPage = chrome.extension.getBackgroundPage(); 119var pollIntervalMax = 1000 * 60 * 60; // 1 hour 120var requestFailureCount = 0; // used for exponential backoff 121var requestTimeout = 1000 * 2; // 5 seconds 122 123var DEFAULT_MIMETYPES = { 124 'atom': 'application/atom+xml', 125 'document': 'text/plain', 126 'spreadsheet': 'text/csv', 127 'presentation': 'text/plain', 128 'pdf': 'application/pdf' 129}; 130 131// Persistent click handler for star icons. 132$('#doc_type').change(function() { 133 if ($(this).val() === 'presentation') { 134 $('#doc_content').attr('disabled', 'true') 135 .attr('placeholder', 'N/A for presentations'); 136 } else { 137 $('#doc_content').removeAttr('disabled') 138 .attr('placeholder', 'Enter document content'); 139 } 140}); 141 142 143// Persistent click handler for changing the title of a document. 144$('[contenteditable="true"]').live('blur', function(index) { 145 var index = $(this).parent().parent().attr('data-index'); 146 147 // Only make the XHR if the user chose a new title. 148 if ($(this).text() != bgPage.docs[index].title) { 149 bgPage.docs[index].title = $(this).text(); 150 gdocs.updateDoc(bgPage.docs[index]); 151 } 152}); 153 154// Persistent click handler for star icons. 155$('.star').live('click', function() { 156 $(this).toggleClass('selected'); 157 158 var index = $(this).parent().attr('data-index'); 159 bgPage.docs[index].starred = $(this).hasClass('selected'); 160 gdocs.updateDoc(bgPage.docs[index]); 161}); 162 163/** 164 * Class to compartmentalize properties of a Google document. 165 * @param {Object} entry A JSON representation of a DocList atom entry. 166 * @constructor 167 */ 168gdocs.GoogleDoc = function(entry) { 169 this.entry = entry; 170 this.title = entry.title.$t; 171 this.resourceId = entry.gd$resourceId.$t; 172 this.type = gdocs.getCategory( 173 entry.category, 'http://schemas.google.com/g/2005#kind'); 174 this.starred = gdocs.getCategory( 175 entry.category, 'http://schemas.google.com/g/2005/labels', 176 'http://schemas.google.com/g/2005/labels#starred') ? true : false; 177 this.link = { 178 'alternate': gdocs.getLink(entry.link, 'alternate').href 179 }; 180 this.contentSrc = entry.content.src; 181}; 182 183/** 184 * Sets up a future poll for the user's document list. 185 */ 186util.scheduleRequest = function() { 187 var exponent = Math.pow(2, requestFailureCount); 188 var delay = Math.min(bgPage.pollIntervalMin * exponent, 189 pollIntervalMax); 190 delay = Math.round(delay); 191 192 if (bgPage.oauth.hasToken()) { 193 var req = bgPage.window.setTimeout(function() { 194 gdocs.getDocumentList(); 195 util.scheduleRequest(); 196 }, delay); 197 bgPage.requests.push(req); 198 } 199}; 200 201/** 202 * Urlencodes a JSON object of key/value query parameters. 203 * @param {Object} parameters Key value pairs representing URL parameters. 204 * @return {string} query parameters concatenated together. 205 */ 206util.stringify = function(parameters) { 207 var params = []; 208 for(var p in parameters) { 209 params.push(encodeURIComponent(p) + '=' + 210 encodeURIComponent(parameters[p])); 211 } 212 return params.join('&'); 213}; 214 215/** 216 * Creates a JSON object of key/value pairs 217 * @param {string} paramStr A string of Url query parmeters. 218 * For example: max-results=5&startindex=2&showfolders=true 219 * @return {Object} The query parameters as key/value pairs. 220 */ 221util.unstringify = function(paramStr) { 222 var parts = paramStr.split('&'); 223 224 var params = {}; 225 for (var i = 0, pair; pair = parts[i]; ++i) { 226 var param = pair.split('='); 227 params[decodeURIComponent(param[0])] = decodeURIComponent(param[1]); 228 } 229 return params; 230}; 231 232/** 233 * Utility for displaying a message to the user. 234 * @param {string} msg The message. 235 */ 236util.displayMsg = function(msg) { 237 $('#butter').removeClass('error').text(msg).show(); 238}; 239 240/** 241 * Utility for removing any messages currently showing to the user. 242 */ 243util.hideMsg = function() { 244 $('#butter').fadeOut(1500); 245}; 246 247/** 248 * Utility for displaying an error to the user. 249 * @param {string} msg The message. 250 */ 251util.displayError = function(msg) { 252 util.displayMsg(msg); 253 $('#butter').addClass('error'); 254}; 255 256/** 257 * Returns the correct atom link corresponding to the 'rel' value passed in. 258 * @param {Array<Object>} links A list of atom link objects. 259 * @param {string} rel The rel value of the link to return. For example: 'next'. 260 * @return {string|null} The appropriate link for the 'rel' passed in, or null 261 * if one is not found. 262 */ 263gdocs.getLink = function(links, rel) { 264 for (var i = 0, link; link = links[i]; ++i) { 265 if (link.rel === rel) { 266 return link; 267 } 268 } 269 return null; 270}; 271 272/** 273 * Returns the correct atom category corresponding to the scheme/term passed in. 274 * @param {Array<Object>} categories A list of atom category objects. 275 * @param {string} scheme The category's scheme to look up. 276 * @param {opt_term?} An optional term value for the category to look up. 277 * @return {string|null} The appropriate category, or null if one is not found. 278 */ 279gdocs.getCategory = function(categories, scheme, opt_term) { 280 for (var i = 0, cat; cat = categories[i]; ++i) { 281 if (opt_term) { 282 if (cat.scheme === scheme && opt_term === cat.term) { 283 return cat; 284 } 285 } else if (cat.scheme === scheme) { 286 return cat; 287 } 288 } 289 return null; 290}; 291 292/** 293 * A generic error handler for failed XHR requests. 294 * @param {XMLHttpRequest} xhr The xhr request that failed. 295 * @param {string} textStatus The server's returned status. 296 */ 297gdocs.handleError = function(xhr, textStatus) { 298 util.displayError('Failed to fetch docs. Please try again.'); 299 ++requestFailureCount; 300}; 301 302/** 303 * A helper for constructing the raw Atom xml send in the body of an HTTP post. 304 * @param {XMLHttpRequest} xhr The xhr request that failed. 305 * @param {string} docTitle A title for the document. 306 * @param {string} docType The type of document to create. 307 * (eg. 'document', 'spreadsheet', etc.) 308 * @param {boolean?} opt_starred Whether the document should be starred. 309 * @return {string} The Atom xml as a string. 310 */ 311gdocs.constructAtomXml_ = function(docTitle, docType, opt_starred) { 312 var starred = opt_starred || null; 313 314 var starCat = ['<category scheme="http://schemas.google.com/g/2005/labels" ', 315 'term="http://schemas.google.com/g/2005/labels#starred" ', 316 'label="starred"/>'].join(''); 317 318 var atom = ["<?xml version='1.0' encoding='UTF-8'?>", 319 '<entry xmlns="http://www.w3.org/2005/Atom">', 320 '<category scheme="http://schemas.google.com/g/2005#kind"', 321 ' term="http://schemas.google.com/docs/2007#', docType, '"/>', 322 starred ? starCat : '', 323 '<title>', docTitle, '</title>', 324 '</entry>'].join(''); 325 return atom; 326}; 327 328/** 329 * A helper for constructing the body of a mime-mutlipart HTTP request. 330 * @param {string} title A title for the new document. 331 * @param {string} docType The type of document to create. 332 * (eg. 'document', 'spreadsheet', etc.) 333 * @param {string} body The body of the HTTP request. 334 * @param {string} contentType The Content-Type of the (non-Atom) portion of the 335 * http body. 336 * @param {boolean?} opt_starred Whether the document should be starred. 337 * @return {string} The Atom xml as a string. 338 */ 339gdocs.constructContentBody_ = function(title, docType, body, contentType, 340 opt_starred) { 341 var body = ['--END_OF_PART\r\n', 342 'Content-Type: application/atom+xml;\r\n\r\n', 343 gdocs.constructAtomXml_(title, docType, opt_starred), '\r\n', 344 '--END_OF_PART\r\n', 345 'Content-Type: ', contentType, '\r\n\r\n', 346 body, '\r\n', 347 '--END_OF_PART--\r\n'].join(''); 348 return body; 349}; 350 351/** 352 * Creates a new document in Google Docs. 353 */ 354gdocs.createDoc = function() { 355 var title = $.trim($('#doc_title').val()); 356 if (!title) { 357 alert('Please provide a title'); 358 return; 359 } 360 var content = $('#doc_content').val(); 361 var starred = $('#doc_starred').is(':checked'); 362 var docType = $('#doc_type').val(); 363 364 util.displayMsg('Creating doc...'); 365 366 var handleSuccess = function(resp, xhr) { 367 bgPage.docs.splice(0, 0, new gdocs.GoogleDoc(JSON.parse(resp).entry)); 368 369 gdocs.renderDocList(); 370 bgPage.setIcon({'text': bgPage.docs.length.toString()}); 371 372 $('#new_doc_container').hide(); 373 $('#doc_title').val(''); 374 $('#doc_content').val(''); 375 util.displayMsg('Document created!'); 376 util.hideMsg(); 377 378 requestFailureCount = 0; 379 }; 380 381 var params = { 382 'method': 'POST', 383 'headers': { 384 'GData-Version': '3.0', 385 'Content-Type': 'multipart/related; boundary=END_OF_PART', 386 }, 387 'parameters': {'alt': 'json'}, 388 'body': gdocs.constructContentBody_(title, docType, content, 389 DEFAULT_MIMETYPES[docType], starred) 390 }; 391 392 // Presentation can only be created from binary content. Instead, create a 393 // blank presentation. 394 if (docType === 'presentation') { 395 params['headers']['Content-Type'] = DEFAULT_MIMETYPES['atom']; 396 params['body'] = gdocs.constructAtomXml_(title, docType, starred); 397 } 398 399 bgPage.oauth.sendSignedRequest(bgPage.DOCLIST_FEED, handleSuccess, params); 400}; 401 402/** 403 * Updates a document's metadata (title, starred, etc.). 404 * @param {gdocs.GoogleDoc} googleDocObj An object containing the document to 405 * update. 406 */ 407gdocs.updateDoc = function(googleDocObj) { 408 var handleSuccess = function(resp) { 409 util.displayMsg('Updated!'); 410 util.hideMsg(); 411 requestFailureCount = 0; 412 }; 413 414 var params = { 415 'method': 'PUT', 416 'headers': { 417 'GData-Version': '3.0', 418 'Content-Type': 'application/atom+xml', 419 'If-Match': '*' 420 }, 421 'body': gdocs.constructAtomXml_(googleDocObj.title, googleDocObj.type, 422 googleDocObj.starred) 423 }; 424 425 var url = bgPage.DOCLIST_FEED + googleDocObj.resourceId; 426 bgPage.oauth.sendSignedRequest(url, handleSuccess, params); 427}; 428 429/** 430 * Deletes a document from the user's document list. 431 * @param {integer} index An index intro the background page's docs array. 432 */ 433gdocs.deleteDoc = function(index) { 434 var handleSuccess = function(resp, xhr) { 435 util.displayMsg('Document trashed!'); 436 util.hideMsg(); 437 requestFailureCount = 0; 438 bgPage.docs.splice(index, 1); 439 bgPage.setIcon({'text': bgPage.docs.length.toString()}); 440 } 441 442 var params = { 443 'method': 'DELETE', 444 'headers': { 445 'GData-Version': '3.0', 446 'If-Match': '*' 447 } 448 }; 449 450 $('#output li').eq(index).fadeOut('slow'); 451 452 bgPage.oauth.sendSignedRequest( 453 bgPage.DOCLIST_FEED + bgPage.docs[index].resourceId, 454 handleSuccess, params); 455}; 456 457/** 458 * Callback for processing the JSON feed returned by the DocList API. 459 * @param {string} response The server's response. 460 * @param {XMLHttpRequest} xhr The xhr request that was made. 461 */ 462gdocs.processDocListResults = function(response, xhr) { 463 if (xhr.status != 200) { 464 gdocs.handleError(xhr, response); 465 return; 466 } else { 467 requestFailureCount = 0; 468 } 469 470 var data = JSON.parse(response); 471 472 for (var i = 0, entry; entry = data.feed.entry[i]; ++i) { 473 bgPage.docs.push(new gdocs.GoogleDoc(entry)); 474 } 475 476 var nextLink = gdocs.getLink(data.feed.link, 'next'); 477 if (nextLink) { 478 gdocs.getDocumentList(nextLink.href); // Fetch next page of results. 479 } else { 480 gdocs.renderDocList(); 481 } 482}; 483 484/** 485 * Presents the in-memory documents that were fetched from the server as HTML. 486 */ 487gdocs.renderDocList = function() { 488 util.hideMsg(); 489 490 // Construct the iframe's HTML. 491 var html = []; 492 for (var i = 0, doc; doc = bgPage.docs[i]; ++i) { 493 // If we have an arbitrary file, use generic file icon. 494 var type = doc.type.label; 495 if (doc.type.term == 'http://schemas.google.com/docs/2007#file') { 496 type = 'file'; 497 } 498 499 var starred = doc.starred ? ' selected' : ''; 500 html.push( 501 '<li data-index="', i , '"><div class="star', starred, '"></div>', 502 '<div><img src="img/icons/', type, '.gif">', 503 '<span contenteditable="true" class="doc_title"></span></div>', 504 '<span>[<a href="', doc.link['alternate'], 505 '" target="_new">view</a> | <a href="javascript:void(0);" ', 506 'onclick="gdocs.deleteDoc(',i, 507 ');return false;">delete</a>]','</span></li>'); 508 } 509 $('#output').html('<ul>' + html.join('') + '</ul>'); 510 511 // Set each span's innerText to be the doc title. We're filling this after 512 // the html has been rendered to the page prevent XSS attacks when using 513 // innerHTML. 514 $('#output li span.doc_title').each(function(i, ul) { 515 $(ul).text(bgPage.docs[i].title); 516 }); 517 518 bgPage.setIcon({'text': bgPage.docs.length.toString()}); 519}; 520 521/** 522 * Fetches the user's document list. 523 * @param {string?} opt_url A url to query the doclist API with. If omitted, 524 * the main doclist feed uri is used. 525 */ 526gdocs.getDocumentList = function(opt_url) { 527 var url = opt_url || null; 528 529 var params = { 530 'headers': { 531 'GData-Version': '3.0' 532 } 533 }; 534 535 if (!url) { 536 util.displayMsg('Fetching your docs'); 537 bgPage.setIcon({'text': '...'}); 538 539 bgPage.docs = []; // Clear document list. We're doing a refresh. 540 541 url = bgPage.DOCLIST_FEED; 542 params['parameters'] = { 543 'alt': 'json', 544 'showfolders': 'true' 545 }; 546 } else { 547 util.displayMsg($('#butter').text() + '.'); 548 549 var parts = url.split('?'); 550 if (parts.length > 1) { 551 url = parts[0]; // Extract base URI. Params are passed in separately. 552 params['parameters'] = util.unstringify(parts[1]); 553 } 554 } 555 556 bgPage.oauth.sendSignedRequest(url, gdocs.processDocListResults, params); 557}; 558 559/** 560 * Refreshes the user's document list. 561 */ 562gdocs.refreshDocs = function() { 563 bgPage.clearPendingRequests(); 564 gdocs.getDocumentList(); 565 util.scheduleRequest(); 566}; 567 568 569bgPage.oauth.authorize(function() { 570 if (!bgPage.docs.length) { 571 gdocs.getDocumentList(); 572 } else { 573 gdocs.renderDocList(); 574 } 575 util.scheduleRequest(); 576}); 577</script> 578</body> 579</html> 580