- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / common / extensions / docs / examples / extensions / gdocs / popup.html
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">
14 body {
15   font: 12px 'Myriad Pro', 'Tw Cen MT', Arial, Verdana, sans-serif;
16   color: #666666;
17   overflow-x: hidden;
18 }
19 ul {
20   padding: 0;
21   list-style: none;
22 }
23 li {
24   clear: both;
25   padding: 2px 0;
26 }
27 li div img {
28   margin: 0 5px;
29   vertical-align: middle;
30 }
31 li div {
32   text-overflow: ellipsis;
33   white-space: nowrap;
34   overflow: hidden;
35   width: 250px;
36   float: left;
37   padding: 2px 0;
38 }
39 li span {
40   margin-left: 5px;
41 }
42 li:hover {
43   background-color: #fffccc;
44 }
45 a {
46  color: #4E7DC2;
47  text-decoration: none;
48 }
49 a: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.
115 var util = {};
116 var gdocs = {};
117
118 var bgPage = chrome.extension.getBackgroundPage();
119 var pollIntervalMax = 1000 * 60 * 60;  // 1 hour
120 var requestFailureCount = 0;  // used for exponential backoff
121 var requestTimeout = 1000 * 2;  // 5 seconds
122
123 var 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  */
168 gdocs.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  */
186 util.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  */
206 util.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  */
221 util.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  */
236 util.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  */
243 util.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  */
251 util.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  */
263 gdocs.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  */
279 gdocs.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  */
297 gdocs.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  */
311 gdocs.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  */
339 gdocs.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  */
354 gdocs.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  */
407 gdocs.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  */
433 gdocs.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  */
462 gdocs.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  */
487 gdocs.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  */
526 gdocs.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  */
562 gdocs.refreshDocs = function() {
563   bgPage.clearPendingRequests();
564   gdocs.getDocumentList();
565   util.scheduleRequest();
566 };
567
568
569 bgPage.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>