diff --git a/scripts/gh-book/app.coffee b/scripts/gh-book/app.coffee index 8991827a..702d7992 100644 --- a/scripts/gh-book/app.coffee +++ b/scripts/gh-book/app.coffee @@ -9,6 +9,7 @@ define [ 'cs!collections/media-types' 'cs!gh-book/epub-container' 'cs!gh-book/xhtml-file' + 'cs!gh-book/gdoc-xhtml-file' 'cs!gh-book/opf-file' 'cs!gh-book/toc-node' 'cs!gh-book/binary-file' @@ -17,7 +18,7 @@ define [ 'cs!gh-book/loading' 'cs!configs/github.coffee' 'less!gh-book/gh-book' -], ($, _, Backbone, Marionette, logger, session, allContent, mediaTypes, EpubContainer, XhtmlFile, OpfFile, TocNode, BinaryFile, WelcomeSignInView, remoteUpdater, LoadingView, config) -> +], ($, _, Backbone, Marionette, logger, session, allContent, mediaTypes, EpubContainer, XhtmlFile, GoogleDocXhtmlFile, OpfFile, TocNode, BinaryFile, WelcomeSignInView, remoteUpdater, LoadingView, config) -> # Stop logging. logger.stop() @@ -94,6 +95,7 @@ define [ mediaTypes.add TocNode mediaTypes.add BinaryFile, {mediaType:'image/png'} mediaTypes.add BinaryFile, {mediaType:'image/jpeg'} + mediaTypes.add GoogleDocXhtmlFile, {mediaType:GoogleDocXhtmlFile::uniqueMediaType} # set which media formats are allowed # at the toplevel of the content diff --git a/scripts/gh-book/gdoc-xhtml-file.coffee b/scripts/gh-book/gdoc-xhtml-file.coffee new file mode 100644 index 00000000..dd8f4831 --- /dev/null +++ b/scripts/gh-book/gdoc-xhtml-file.coffee @@ -0,0 +1,93 @@ +define [ + 'underscore' + 'jquery' + 'backbone' + 'cs!models/content/module' + 'cs!gh-book/xhtml-file' + 'gh-book/googlejsapi' +], (_, $, Backbone, ModuleModel, XhtmlModel) -> + + # Picker Reference: https://developers.google.com/picker/docs/reference + + GDOC_TO_HTML_URL = 'http://testing.oerpub.org/gdoc2html' # eventually `http://remix.oerpub.org/gdoc2html` + gdocsURL = (id) -> "https://docs.google.com/document/d/#{id}/export?format=html&confirm=no_antivirus" + + # Opens a new Modal Dialog allowing the user to pick a Google Doc to import + newPicker = () -> + promise = $.Deferred() + + google.load 'picker', '1', + callback: () => + # Create a new Doc Picker Modal popup and re-ify the promise when + # 1. a document is selected + # 2. the dialog is canceled/closed + builder = new google.picker.PickerBuilder() + builder.addView(google.picker.ViewId.DOCUMENTS) + builder.setCallback (data) -> + switch data.action + when google.picker.Action.PICKED then promise.resolve(data) + when google.picker.Action.CANCEL then promise.reject('USER_CANCELLED') + else + promise.progress(data) + picker = builder.build() + picker.setVisible(true) + return picker + + return promise.promise() + + # Retreive the HTML of the 1st Google Doc selected in the Picker + getGoogleDocHtml = (data) -> + resourceId = data.docs[0].id + htmlUrl = gdocsURL(resourceId) + promise = $.get(htmlUrl) + return promise + + # Clean up HTML retrieved from Google to be used in the Editor. + # Makes an AJAX call to a service that converts the HTML + transformGoogleDocHtml = (html) -> + promise = $.ajax + url: GDOC_TO_HTML_URL + type: 'POST' + dataType: 'json' + async: true + data: + html: html + textbook_html: 0 + copy_images: 0 + + return promise + + return class GoogleDocXhtmlModel extends XhtmlModel + + title: 'Google Document Import' + + # **NOTE:** The mediaType is inherited from XhtmlModel because a successful import will + # 'appear' as a XHTML document. + # This mediaType is used in the OPF manifest + + # In order to add this type to the Add dropdown for a Book (OPF File) + # this model must have a unique mediaType (not `application/xhtml+xml`) + # This is used to register with `media-types` and is in the + # list of types `opf-file` accepts as a child (so it shows up in the filtered dropdown) + uniqueMediaType: 'application/vnd.org.cnx.gdoc-import' + + # Saves the fetched and converted Document into this model for saving + _injectHtml: (html) -> @set('body', html) # html is '
...' + + # Pop up the Picker dialog when this Model is added to a book + _loadComplex: (fetchPromise) -> + # **NOTE:** `fetchPromise` is not used because this type can only be created as a new object + # (the fetchPromise is already resolved) + + promise = newPicker() # 1. Open the picker dialog + .then (data) => + return getGoogleDocHtml(data) # 2. Get the HTML from Google + .then (html) => + return transformGoogleDocHtml(html) # 3. Send the HTML to the transform service + .then (json) => + @_injectHtml(json.html) # 4. Inject the cleaned HTML into the Model + + promise.fail => + console.warn('BUG: Import failed (maybe the user canceled it) and there is no cleanup code') + + return promise diff --git a/scripts/gh-book/gh-book.less b/scripts/gh-book/gh-book.less index 4e607574..2cbb4f82 100755 --- a/scripts/gh-book/gh-book.less +++ b/scripts/gh-book/gh-book.less @@ -9,6 +9,7 @@ .x-media-type-icon('application/xhtml+xml'; "content/module"; #999; "\f0f6"); /* icon-file-alt */ .x-media-type-icon('application/oebps-package+xml'; "Book"; @toc-color; "\f02d"); /* icon-book */ .x-media-type-icon('application/vnd.org.cnx.section'; "book division"; #999; ""); /* icon-folder-open */ +.x-media-type-icon('application/vnd.org.cnx.gdoc-import'; "Google Doc Import"; #999; "\f0d5"); /* icon-google-plus */ #login-advanced-wrapper { margin-top: 1em; @@ -173,4 +174,4 @@ nav#workspace-sidebar-toc{ content: ""; } -} \ No newline at end of file +} diff --git a/scripts/gh-book/googlejsapi.js b/scripts/gh-book/googlejsapi.js new file mode 100644 index 00000000..bba2e004 --- /dev/null +++ b/scripts/gh-book/googlejsapi.js @@ -0,0 +1,42 @@ +/* modified: http://www.google.com/jsapi e.g. ClientLocation is set to null */ + +if(!window['googleLT_']){window['googleLT_']=(new Date()).getTime();}if (!window['google']) { + window['google'] = {}; +} +if (!window['google']['loader']) { + window['google']['loader'] = {}; + google.loader.ServiceBase = 'http://www.google.com/uds'; + google.loader.GoogleApisBase = 'http://ajax.googleapis.com/ajax'; + google.loader.ApiKey = 'notsupplied'; + google.loader.KeyVerified = true; + google.loader.LoadFailure = false; + google.loader.Secure = false; + google.loader.GoogleLocale = 'www.google.com'; + google.loader.ClientLocation = null; + google.loader.AdditionalParams = ''; + (function() {var d=void 0,g=!0,h=null,j=!1,k=encodeURIComponent,l=window,m=document;function n(a,b){return a.load=b}var p="push",q="replace",r="charAt",t="indexOf",u="ServiceBase",v="name",w="getTime",x="length",y="prototype",z="setTimeout",A="loader",B="substring",C="join",D="toLowerCase";function E(a){return a in F?F[a]:F[a]=-1!=navigator.userAgent[D]()[t](a)}var F={};function G(a,b){var c=function(){};c.prototype=b[y];a.S=b[y];a.prototype=new c} + function H(a,b,c){var e=Array[y].slice.call(arguments,2)||[];return function(){var c=e.concat(Array[y].slice.call(arguments));return a.apply(b,c)}}function I(a){a=Error(a);a.toString=function(){return this.message};return a}function J(a,b){for(var c=a.split(/\./),e=l,f=0;f