diff --git a/bower.json b/bower.json index b142b0e5..b15df413 100644 --- a/bower.json +++ b/bower.json @@ -4,7 +4,7 @@ "main": "scripts/main.js", "dependencies": { "bookish": "oerpub/bookish#master", - "octokit": "0.9.12" + "octokit": "0.9.16" }, "ignore": [ "**/.*", diff --git a/scripts/gh-book/auth-template.html b/scripts/gh-book/auth-template.html index 35e12f0f..eb9010fb 100644 --- a/scripts/gh-book/auth-template.html +++ b/scripts/gh-book/auth-template.html @@ -51,7 +51,7 @@

Sign in to Book Editor

To be able to save your changes, you must provide us with your github username and password.

-

If you refresh this page you may need to re-enter this information.

+

If you refresh this page you may need to re-enter this information.

@@ -80,7 +80,7 @@

Sign in to Book Editor

+ + + diff --git a/scripts/gh-book/auth.coffee b/scripts/gh-book/auth.coffee index 4cf38ebd..4703cb49 100644 --- a/scripts/gh-book/auth.coffee +++ b/scripts/gh-book/auth.coffee @@ -1,5 +1,6 @@ define [ 'jquery' + 'underscore' 'marionette' 'cs!collections/content' 'cs!session' @@ -10,7 +11,7 @@ define [ 'cs!configs/github' 'bootstrapModal' 'bootstrapCollapse' -], ($, Marionette, allContent, session, remoteUpdater, authTemplate, difflib, diffview, config) -> +], ($, _, Marionette, allContent, session, remoteUpdater, authTemplate, difflib, diffview, config) -> return class GithubAuthView extends Marionette.ItemView template: authTemplate @@ -25,6 +26,8 @@ define [ 'click #show-diffs': 'showDiffsModal' 'submit #edit-repo-form': 'editRepo' 'click [data-select-repo]': 'selectRepo' + 'click #fork-book-modal .organisation-block': 'selectOrg' + 'click #fork-redirect-modal .btn-primary': 'selectFork' 'click [data-create-repo]': 'createRepo' 'shown #edit-repo-modal': 'createRepoModal' @@ -77,18 +80,39 @@ define [ @isDirty = true @render() - signInModal: () -> + signInModal: (options) -> $modal = @$el.find('#sign-in-modal') # The hidden event on #login-advanced should not propagate $modal.find('#login-advanced').on 'hidden', (e) => e.stopPropagation() + # We'll return a promise, and resolve it upon login or close. + promise = $.Deferred() + $modal.data('login-promise', promise) + # attach a close listener - $modal.on 'hidden', () => @trigger 'close' + $modal.on 'hidden', () => + if promise.state() == 'pending' + promise.reject() + @trigger 'close' + + # Hide parts of the modal, if requested, for a simpler UI. + if options + if options.anonymous != undefined and options.anonymous == false + $modal.find('#login-anonymous').hide() + else + $modal.find('#login-anonymous').show() + + if options.info != undefined and options.info == false + $modal.find('#login-info-wrapper').hide() + else + $modal.find('#login-info-wrapper').show() # Show the modal $modal.modal {show:true} + return promise + # Show a diff of all unsaved models showDiffsModal: () -> $modal = @$el.find('#diffs-modal') @@ -134,17 +158,97 @@ define [ $modal.modal {show:true} - forkContent: () -> + organisationModal: (info, orgs) -> + $modal = @$el.find('#fork-book-modal') + $body = $modal.find('.modal-body').empty() - if not (@model.get('password') or @model.get('token')) - alert 'Please Sign In before trying to fork a book' - return + # Own account + $block = $('
') + $avatar = $('avatar').attr('src', info.avatar_url) + $name = $('').html(info.login) + $block.append($avatar).append($name).data('org-name', info.login) + $body.append($block) + for org in orgs + $block = $('
') + $avatar = $('avatar').attr('src', org.avatar_url) + $name = $('').html(org.login) + $block.append($avatar).append($name).data('org-name', org.login) + $body.append($block) - @model.getClient().getLogin().done (login) => - @model.getRepo()?.fork().done () => - @model.set 'repoUser', login + $modal.modal {show:true} - + selectOrg: (e) -> + $block = $(e.target).addBack().closest('.organisation-block') + org = $block.data('org-name') or null + @$el.find('#fork-book-modal').modal('hide') + @__forkContent(org) + + selectFork: (e) -> + e.preventDefault() + login = @$el.find('#fork-redirect-modal').modal('hide').data('login') + @_selectRepo(login, @model.get('repoName')) + + forkContent: () -> + if not (@model.get('password') or @model.get('token')) + @signInModal + anonymous: false + info: false + .done () => @_forkContent() + return + @_forkContent() + + _forkContent: () -> + # If user has more than one organisation, ask which one. + @model.getClient().getUser().getInfo().done (userinfo) => + @model.getClient().getUser().getOrgs().done (orgs) => + if orgs.length > 1 + @organisationModal(userinfo, orgs) + else + @__forkContent(userinfo.login) + + __forkContent: (login) -> + # If repo exists, go to it or cancel. Else fork. + @model.getClient().getRepo(login, @model.get('repoName')).getInfo().done () => + @$el.find('#fork-redirect-modal').data('login', login).modal + show: true + .fail () => + @___forkContent(login) + + ___forkContent: (org) -> + $modal = @$el.find('#fork-progress-modal') + $body = $modal.find('.modal-body') + $body.html('Creating a Fork...') + $modal.modal {show: true} + + # If the chosen organisation is myself, leave it out. + @model.getRepo()?.fork(org != @model.get('id') and org or null).done () => + $body.html('Waiting for Fork to become available...') + + # Change upstream repo + wait = 2000 + @model.set 'repoUser', org + + # Poll until repo becomes available + pollRepo = () => + @model.getRepo()?.getInfo().done (info) => + require ['backbone', 'cs!controllers/routing'], (bb, controller) => + # Filter out the view bit, then set the url to reflect the fork + v = RegExp('repo/[^/]*/[^/]*(/branch/[^/]*)?/(.*)').exec( + bb.history.getHash())[2] + controller.trigger 'navigate', v + .fail () => + if wait < 30 + setTimeout(pollRepo, wait) + wait = wait * 2 # exponential backoff + else + alert('Fork failed') + .always () => + # Leave it open for another two seconds, because sometimes + # the fork happens so quickly that people are unnerved by + # the flashing modal they couldn't read. + window.setTimeout((() -> $modal.modal('hide')), 2000) + pollRepo() + signIn: (e) -> # Prevent form submission @@ -156,6 +260,9 @@ define [ token: @$el.find('#github-token').val() password: @$el.find('#github-password').val() + # signInModal persists the promise on the modal + promise = @$el.find('#sign-in-modal').data('login-promise') + if not (attrs.password or attrs.token) alert 'We are terribly sorry but github recently changed so you must login to use their API.\nPlease refresh and provide a password or an OAuth token.' else @@ -166,6 +273,7 @@ define [ # The 1st time the editor loads up it waits for the modal to close # but `render` will hide the modal without triggering 'close' @trigger 'close' + promise.resolve() .fail (err) => alert 'Login failed. Did you use the correct credentials?' diff --git a/scripts/gh-book/gh-book.less b/scripts/gh-book/gh-book.less index 6b532c88..7060ce1e 100755 --- a/scripts/gh-book/gh-book.less +++ b/scripts/gh-book/gh-book.less @@ -218,3 +218,16 @@ nav#workspace-sidebar-toc{ font-size: 120%; } } + +#fork-book-modal { + .organisation-block { + cursor: pointer; + img { + height: 45px; + width: 45px; + } + span { + margin-left: 0.5em; + } + } +}