diff --git a/public/res/constants.js b/public/res/constants.js index ece21fb4..6eb70f29 100644 --- a/public/res/constants.js +++ b/public/res/constants.js @@ -28,7 +28,7 @@ define([], function() { constants.SSH_PROXY_URL = "https://stackedit-ssh-proxy.herokuapp.com/"; constants.HTMLTOPDF_URL = "https://stackedit-htmltopdf.herokuapp.com/"; - // Site dependent + // Site dependent constants.BASE_URL = "http://localhost/"; constants.GOOGLE_CLIENT_ID = '241271498917-lev37kef013q85avc91am1gccg5g8lrb.apps.googleusercontent.com'; constants.GITHUB_CLIENT_ID = 'e47fef6055344579799d'; diff --git a/public/res/editor.js b/public/res/editor.js index f01cce6a..2510a7a4 100644 --- a/public/res/editor.js +++ b/public/res/editor.js @@ -179,8 +179,6 @@ define([ var selection = rangy.getSelection(); selection.removeAllRanges(); selection.addRange(range, this.selectionStart > this.selectionEnd); - selection.detach(); - range.detach(); }; this.setSelectionStartEnd = function(start, end) { if(start === undefined) { @@ -232,9 +230,7 @@ define([ selectionEnd = offset + (range + '').length; } } - selectionRange.detach(); } - selection.detach(); self.setSelectionStartEnd(selectionStart, selectionEnd); } undoMgr.saveSelectionState(); diff --git a/public/res/helpers/googleHelper.js b/public/res/helpers/googleHelper.js index f1d46ebf..c2f52df1 100644 --- a/public/res/helpers/googleHelper.js +++ b/public/res/helpers/googleHelper.js @@ -1,984 +1,981 @@ /*global gapi, google */ define([ - "underscore", - "jquery", - "constants", - "core", - "utils", - "storage", - "logger", - "settings", - "eventMgr", - "classes/AsyncTask", + "underscore", + "jquery", + "constants", + "core", + "utils", + "storage", + "logger", + "settings", + "eventMgr", + "classes/AsyncTask" ], function(_, $, constants, core, utils, storage, logger, settings, eventMgr, AsyncTask) { - var connected = false; - var authorizationMgrMap = {}; - function AuthorizationMgr(accountId) { - var permissionList = { - profile: true - }; - var refreshFlag = true; - _.each((storage[accountId + '.permissions'] || '').split(';'), function(permission) { - permission && (permissionList[permission] = true); - }); - this.setRefreshFlag = function() { - refreshFlag = true; - }; - this.isAuthorized = function(permission) { - return refreshFlag === false && _.has(permissionList, permission); - }; - this.add = function(permission) { - permissionList[permission] = true; - storage[accountId + '.permissions'] = _.keys(permissionList).join(';'); - refreshFlag = false; - }; - this.getListWithNew = function(permission) { - var result = _.keys(permissionList); - if(!_.has(permissionList, permission)) { - result.push(permission); - } - return result; - }; - var userId = storage[accountId + '.userId']; - this.setUserId = function(value) { - userId = value; - storage[accountId + '.userId'] = userId; - }; - this.getUserId = function() { - return userId; - }; - } + var connected = false; + var authorizationMgrMap = {}; - var googleHelper = {}; + function AuthorizationMgr(accountId) { + var permissionList = { + profile: true + }; + var refreshFlag = true; + _.each((storage[accountId + '.permissions'] || '').split(';'), function(permission) { + permission && (permissionList[permission] = true); + }); + this.setRefreshFlag = function() { + refreshFlag = true; + }; + this.isAuthorized = function(permission) { + return refreshFlag === false && _.has(permissionList, permission); + }; + this.add = function(permission) { + permissionList[permission] = true; + storage[accountId + '.permissions'] = _.keys(permissionList).join(';'); + refreshFlag = false; + }; + this.getListWithNew = function(permission) { + var result = _.keys(permissionList); + if(!_.has(permissionList, permission)) { + result.push(permission); + } + return result; + }; + var userId = storage[accountId + '.userId']; + this.setUserId = function(value) { + userId = value; + storage[accountId + '.userId'] = userId; + }; + this.getUserId = function() { + return userId; + }; + } - // Listen to offline status changes - var isOffline = false; - eventMgr.addListener("onOfflineChanged", function(isOfflineParam) { - isOffline = isOfflineParam; - }); + var googleHelper = {}; - // Try to connect by downloading client.js - function connect(task) { - task.onRun(function() { - if(isOffline === true) { - connected = false; - task.error(new Error("Operation not available in offline mode.|stopPublish")); - return; - } - if(connected === true) { - task.chain(); - return; - } - window.delayedFunction = function() { - gapi.load("client", function() { - gapi.client.load('drive', 'v2', function() { - connected = true; - task.chain(); - }); - }); - }; - $.ajax({ - url: "https://apis.google.com/js/api.js?onload=runDelayedFunction", - dataType: "script", - timeout: constants.AJAX_TIMEOUT - }).fail(function(jqXHR) { - var error = { - code: jqXHR.status, - message: jqXHR.statusText - }; - handleError(error, task); - }); - }); - } + // Listen to offline status changes + var isOffline = false; + eventMgr.addListener("onOfflineChanged", function(isOfflineParam) { + isOffline = isOfflineParam; + }); - // Try to authenticate with Oauth - var scopeMap = { - profile: [ - 'https://www.googleapis.com/auth/userinfo.profile' - ], - gdrive: [ - 'https://www.googleapis.com/auth/drive.install', - settings.gdriveFullAccess === true ? 'https://www.googleapis.com/auth/drive' : 'https://www.googleapis.com/auth/drive.file' - ], - blogger: [ - 'https://www.googleapis.com/auth/blogger' - ], - picasa: [ - 'https://www.googleapis.com/auth/photos' - ] - }; - var oauthIframes = []; - function authenticate(task, permission, accountId) { - var authorizationMgr = authorizationMgrMap[accountId]; - if(!authorizationMgr) { - authorizationMgr = new AuthorizationMgr(accountId); - authorizationMgrMap[accountId] = authorizationMgr; - } - task.onRun(function() { - var currentToken = gapi.auth.getToken(); - var newToken; - function getTokenInfo() { - $.ajax({ - url: 'https://www.googleapis.com/oauth2/v1/tokeninfo', - data: { - access_token: newToken.access_token - }, - timeout: constants.AJAX_TIMEOUT, - type: "GET" - }).done(function(data) { - if(authorizationMgr.getUserId() && authorizationMgr.getUserId() != data.user_id) { - // Wrong user id, try again - startAuthenticate(); - } - else { - authorizationMgr.setUserId(data.user_id); - authorizationMgr.add(permission); - authorizationMgr.token = newToken; - task.chain(); - } - }).fail(function(jqXHR) { - var error = { - code: jqXHR.status, - message: jqXHR.statusText - }; - handleError(error, task); - }); - } - var authuser = 0; - var immediate; - function localAuthenticate() { - if(authuser > 5) { - task.error(new Error('Unable to authenticate user ' + authorizationMgr.getUserId() + ', please sign in with Google.')); - return; - } - if(immediate === false) { - task.timeout = constants.ASYNC_TASK_LONG_TIMEOUT; - } - var scopeList = _.chain(scopeMap).pick(authorizationMgr.getListWithNew(permission)).flatten().value(); - gapi.auth.authorize({ - client_id: constants.GOOGLE_CLIENT_ID, - scope: scopeList, - immediate: immediate, - authuser: immediate === false ? '' : authuser - }, function(authResult) { + // Try to connect by downloading client.js + function connect(task) { + task.onRun(function() { + if(isOffline === true) { + connected = false; + return task.error(new Error("Operation not available in offline mode.|stopPublish")); + } + if(connected === true) { + return task.chain(); + } + window.delayedFunction = function() { + gapi.load("client", function() { + gapi.client.load('drive', 'v2', function() { + connected = true; + task.chain(); + }); + }); + }; + $.ajax({ + url: "https://apis.google.com/js/api.js?onload=runDelayedFunction", + dataType: "script", + timeout: constants.AJAX_TIMEOUT + }).fail(function(jqXHR) { + var error = { + code: jqXHR.status, + message: jqXHR.statusText + }; + handleError(error, task); + }); + }); + } - // Hack to clean window from old oauth iframes - authorizationMgr.$oauthIframe && authorizationMgr.$oauthIframe.remove(); - var currentOauthIframes = _.filter(document.querySelectorAll('iframe'), function(iframe) { - var src = iframe.getAttribute('src'); - return src && src.indexOf('https://accounts.google.com/o/oauth2/auth') === 0; - }); - authorizationMgr.$oauthIframe = $(_.difference(currentOauthIframes, oauthIframes)); - oauthIframes = currentOauthIframes; + // Try to authenticate with Oauth + var scopeMap = { + profile: [ + 'https://www.googleapis.com/auth/userinfo.profile' + ], + gdrive: [ + 'https://www.googleapis.com/auth/drive.install', + settings.gdriveFullAccess === true ? 'https://www.googleapis.com/auth/drive' : 'https://www.googleapis.com/auth/drive.file' + ], + blogger: [ + 'https://www.googleapis.com/auth/blogger' + ], + picasa: [ + 'https://www.googleapis.com/auth/photos' + ] + }; - newToken = gapi.auth.getToken(); - gapi.auth.setToken(currentToken); - if(!authResult || authResult.error) { - if(connected === true && immediate === true) { - // If immediate did not work retry without immediate - // flag - immediate = false; - task.chain(oauthRedirect); - } - else { - // Error - task.error(new Error("Access to Google account is not authorized.")); - } - } - else { - // Success but we need to check the user id - immediate === true && authuser++; - task.chain(getTokenInfo); - } - }); - } - function oauthRedirect() { - if(immediate === true) { - task.chain(localAuthenticate); - return; - } - utils.redirectConfirm('You are being redirected to Google authorization page.', function() { - task.chain(localAuthenticate); - }, function() { - task.error(new Error('Operation canceled.')); - }); - } - function startAuthenticate() { - immediate = true; - if(authorizationMgr.token && authorizationMgr.isAuthorized(permission)) { - task.chain(); - return; - } - if(!authorizationMgr.getUserId()) { - immediate = false; - } - task.chain(oauthRedirect); - } - startAuthenticate(); - }); - } - googleHelper.refreshGdriveToken = function(accountId) { - var task = new AsyncTask(); - connect(task); - var authorizationMgr = authorizationMgrMap[accountId]; - authorizationMgr && authorizationMgr.setRefreshFlag(); - authenticate(task, 'gdrive', accountId); - task.enqueue(); - }; + function authenticate(task, permission, accountId) { + var authorizationMgr = authorizationMgrMap[accountId]; + if(!authorizationMgr) { + authorizationMgr = new AuthorizationMgr(accountId); + authorizationMgrMap[accountId] = authorizationMgr; + } + task.onRun(function() { + var currentToken = gapi.auth.getToken(); + var newToken; - function runWithToken(accountId, functionToRun) { - var currentToken = gapi.auth.getToken(); - var authorizationMgr = authorizationMgrMap[accountId]; - gapi.auth.setToken(authorizationMgr.token); - functionToRun(); - gapi.auth.setToken(currentToken); - } + function getTokenInfo() { + $.ajax({ + url: 'https://www.googleapis.com/oauth2/v1/tokeninfo', + data: { + access_token: newToken.access_token + }, + timeout: constants.AJAX_TIMEOUT, + type: "GET" + }).done(function(data) { + if(authorizationMgr.getUserId() && authorizationMgr.getUserId() != data.user_id) { + // Wrong user id, try again + startAuthenticate(); + } + else { + authorizationMgr.setUserId(data.user_id); + authorizationMgr.add(permission); + authorizationMgr.token = newToken; + task.chain(); + } + }).fail(function(jqXHR) { + var error = { + code: jqXHR.status, + message: jqXHR.statusText + }; + handleError(error, task); + }); + } - googleHelper.upload = function(fileId, parentId, title, content, contentType, etag, accountId, callback) { - var result; - var task = new AsyncTask(); - connect(task); - authenticate(task, 'gdrive', accountId); - task.onRun(function() { - var boundary = '-------314159265358979323846'; - var delimiter = "\r\n--" + boundary + "\r\n"; - var close_delim = "\r\n--" + boundary + "--"; - contentType = contentType || settings.markdownMimeType; - var metadata = { - title: title, - mimeType: contentType - }; - if(parentId) { - // Specify the directory - metadata.parents = [ - { - kind: 'drive#fileLink', - id: parentId - } - ]; - } - var path = '/upload/drive/v2/files'; - var method = 'POST'; - if(fileId) { - // If it's an update - path += "/" + fileId; - method = 'PUT'; - } - var headers = { - 'Content-Type': 'multipart/mixed; boundary="' + boundary + '"', - }; + var authuser = 0; + var immediate; - var base64Data = utils.encodeBase64(content); - var multipartRequestBody = [ - delimiter, - 'Content-Type: application/json\r\n\r\n', - JSON.stringify(metadata), - delimiter, - 'Content-Type: ', - contentType, - '\r\n', - 'Content-Transfer-Encoding: base64\r\n', - '\r\n', - base64Data, - close_delim - ].join(""); + function localAuthenticate() { + if(authuser > 5) { + return task.error(new Error('Unable to authenticate user ' + authorizationMgr.getUserId() + ', please sign in with Google.')); + } + if(immediate === false) { + task.timeout = constants.ASYNC_TASK_LONG_TIMEOUT; + } + var scopeList = _.chain(scopeMap).pick(authorizationMgr.getListWithNew(permission)).flatten().value(); + gapi.auth.authorize({ + client_id: constants.GOOGLE_CLIENT_ID, + scope: scopeList, + immediate: immediate, + authuser: immediate === false ? '' : authuser + }, function(authResult) { + newToken = gapi.auth.getToken(); + gapi.auth.setToken(currentToken); + if(!authResult || authResult.error) { + if(connected === true && immediate === true) { + // If immediate did not work retry without immediate + // flag + immediate = false; + task.chain(oauthRedirect); + } + else { + // Error + task.error(new Error("Access to Google account is not authorized.")); + } + } + else { + // Success but we need to check the user id + immediate === true && authuser++; + task.chain(getTokenInfo); + } + }); + } - runWithToken(accountId, function() { - var request = gapi.client.request({ - 'path': path, - 'method': method, - 'params': { - 'uploadType': 'multipart', - }, - 'headers': headers, - 'body': multipartRequestBody, - }); - request.execute(function(response) { - if(response && response.id) { - // Upload success - result = response; - result.content = content; - task.chain(); - return; - } - var error = response.error; - // Handle error - if(error !== undefined && fileId !== undefined) { - if(error.code === 404) { - error = 'File ID "' + fileId + '" not found on Google Drive.|removePublish'; - } - else if(error.code === 412) { - // We may have missed a file update - storage.removeItem(accountId + ".gdrive.lastChangeId"); - error = 'Conflict on file ID "' + fileId + '". Please restart the synchronization.'; - } - } - handleError(error, task); - }); - }); - }); - task.onSuccess(function() { - callback(undefined, result); - }); - task.onError(function(error) { - callback(error); - }); - task.enqueue(); - }; + function oauthRedirect() { + if(immediate === true) { + return task.chain(localAuthenticate); + } + utils.redirectConfirm('You are being redirected to Google authorization page.', function() { + task.chain(localAuthenticate); + }, function() { + task.error(new Error('Operation canceled.')); + }); + } - googleHelper.rename = function(fileId, title, accountId, callback) { - var result; - var task = new AsyncTask(); - connect(task); - authenticate(task, 'gdrive', accountId); - task.onRun(function() { - var body = {'title': title}; - runWithToken(accountId, function() { - var request = gapi.client.drive.files.patch({ - 'fileId': fileId, - 'resource': body - }); - request.execute(function(response) { - if(response && response.id) { - // Rename success - result = response; - task.chain(); - return; - } - var error = response.error; - // Handle error - if(error !== undefined && fileId !== undefined) { - if(error.code === 404) { - error = 'File ID "' + fileId + '" not found on Google Drive.|removePublish'; - } - } - handleError(error, task); - }); - }); - }); - task.onSuccess(function() { - callback(undefined, result); - }); - task.onError(function(error) { - callback(error); - }); - task.enqueue(); - }; + function startAuthenticate() { + immediate = true; + if(authorizationMgr.token && authorizationMgr.isAuthorized(permission)) { + task.chain(); + return; + } + if(!authorizationMgr.getUserId()) { + immediate = false; + } + task.chain(oauthRedirect); + } - googleHelper.checkChanges = function(lastChangeId, accountId, callback) { - var changes = []; - var newChangeId = lastChangeId || 0; - var task = new AsyncTask(); - connect(task); - authenticate(task, 'gdrive', accountId); - task.onRun(function() { - var nextPageToken; - function retrievePageOfChanges() { - runWithToken(accountId, function() { - var request; - if(nextPageToken === undefined) { - request = gapi.client.drive.changes.list({ - 'startChangeId': newChangeId + 1 - }); - } - else { - request = gapi.client.drive.changes.list({ - 'pageToken': nextPageToken - }); - } + startAuthenticate(); + }); + } - request.execute(function(response) { - if(!response || !response.largestChangeId) { - // Handle error - handleError(response.error, task); - return; - } - // Retrieve success - newChangeId = response.largestChangeId; - nextPageToken = response.nextPageToken; - if(response.items !== undefined) { - changes = changes.concat(response.items); - } - if(nextPageToken !== undefined) { - task.chain(retrievePageOfChanges); - } - else { - task.chain(); - } - }); - }); - } - task.chain(retrievePageOfChanges); - }); - task.onSuccess(function() { - callback(undefined, changes, newChangeId); - }); - task.onError(function(error) { - callback(error); - }); - task.enqueue(); - }; + googleHelper.refreshGdriveToken = function(accountId) { + var task = new AsyncTask(); + connect(task); + var authorizationMgr = authorizationMgrMap[accountId]; + authorizationMgr && authorizationMgr.setRefreshFlag(); + authenticate(task, 'gdrive', accountId); + task.enqueue(); + }; - googleHelper.downloadMetadata = function(ids, accountId, callback, skipAuth) { - var result = []; - var task = new AsyncTask(); - connect(task); - if(!skipAuth) { - authenticate(task, 'gdrive', accountId); - } - task.onRun(function() { - function recursiveDownloadMetadata() { - if(ids.length === 0) { - task.chain(); - return; - } - var id = ids[0]; - var headers = {}; - var authorizationMgr = authorizationMgrMap[accountId]; - if(authorizationMgr && authorizationMgr.token) { - headers.Authorization = "Bearer " + authorizationMgr.token.access_token; - } - $.ajax({ - url: "https://www.googleapis.com/drive/v2/files/" + id, - headers: headers, - data: { - key: constants.GOOGLE_API_KEY - }, - dataType: "json", - timeout: constants.AJAX_TIMEOUT - }).done(function(data) { - result.push(data); - ids.shift(); - task.chain(recursiveDownloadMetadata); - }).fail(function(jqXHR) { - var error = { - code: jqXHR.status, - message: jqXHR.statusText - }; - // Handle error - if(error.code === 404) { - error = 'File ID "' + id + '" not found on Google Drive.'; - } - handleError(error, task); - }); - } - task.chain(recursiveDownloadMetadata); - }); - task.onSuccess(function() { - callback(undefined, result); - }); - task.onError(function(error) { - callback(error); - }); - task.enqueue(); - }; + function runWithToken(accountId, functionToRun) { + var currentToken = gapi.auth.getToken(); + var authorizationMgr = authorizationMgrMap[accountId]; + gapi.auth.setToken(authorizationMgr.token); + functionToRun(); + gapi.auth.setToken(currentToken); + } - googleHelper.downloadContent = function(objects, accountId, callback, skipAuth) { - var result = []; - var task = new AsyncTask(); - // Add some time for user to choose his files - task.timeout = constants.ASYNC_TASK_LONG_TIMEOUT; - connect(task); - if(!skipAuth) { - authenticate(task, 'gdrive', accountId); - } - task.onRun(function() { - function recursiveDownloadContent() { - if(objects.length === 0) { - task.chain(); - return; - } - var object = objects[0]; - result.push(object); - var file; - // object may be a file - if(object.kind == "drive#file") { - file = object; - } - // object may be a change - else if(object.kind == "drive#change") { - file = object.file; - } - if(!file) { - objects.shift(); - task.chain(recursiveDownloadContent); - return; - } - var url = file.downloadUrl; - // if file is a real time document - if(file.mimeType.indexOf("application/vnd.google-apps.drive-sdk") === 0) { - file.isRealtime = true; - url = 'https://www.googleapis.com/drive/v2/files/' + file.id + '/realtime'; - } - var headers = {}; - var authorizationMgr = authorizationMgrMap[accountId]; - if(authorizationMgr && authorizationMgr.token) { - headers.Authorization = "Bearer " + authorizationMgr.token.access_token; - } - $.ajax({ - url: url, - headers: headers, - data: { - key: constants.GOOGLE_API_KEY - }, - dataType: file.isRealtime ? 'json' : 'text', - timeout: constants.AJAX_TIMEOUT - }).done(function(data) { - file.content = file.isRealtime ? data.data.value.content.value : data; - objects.shift(); - task.chain(recursiveDownloadContent); - }).fail(function(jqXHR) { - var error = { - code: jqXHR.status, - message: jqXHR.statusText - }; - // Handle error - handleError(error, task); - }); - } - task.chain(recursiveDownloadContent); - }); - task.onSuccess(function() { - callback(undefined, result); - }); - task.onError(function(error) { - callback(error); - }); - task.enqueue(); - }; + googleHelper.upload = function(fileId, parentId, title, content, contentType, etag, accountId, callback) { + var result; + var task = new AsyncTask(); + connect(task); + authenticate(task, 'gdrive', accountId); + task.onRun(function() { + var boundary = '-------314159265358979323846'; + var delimiter = "\r\n--" + boundary + "\r\n"; + var close_delim = "\r\n--" + boundary + "--"; + contentType = contentType || settings.markdownMimeType; + var metadata = { + title: title, + mimeType: contentType + }; + if(parentId) { + // Specify the directory + metadata.parents = [ + { + kind: 'drive#fileLink', + id: parentId + } + ]; + } + var path = '/upload/drive/v2/files'; + var method = 'POST'; + if(fileId) { + // If it's an update + path += "/" + fileId; + method = 'PUT'; + } + var headers = { + 'Content-Type': 'multipart/mixed; boundary="' + boundary + '"' + }; - googleHelper.uploadImg = function(name, content, albumId, callback) { - var accountId = 'google.picasa0'; - var result; - var task = new AsyncTask(); - connect(task); - authenticate(task, 'picasa', accountId); - task.onRun(function() { - var headers = { - "Slug": name - }; - if(name.match(/.jpe?g$/i)) { - headers["Content-Type"] = "image/jpeg"; - } - else if(name.match(/.png$/i)) { - headers["Content-Type"] = "image/png"; - } - else if(name.match(/.gif$/i)) { - headers["Content-Type"] = "image/gif"; - } - var authorizationMgr = authorizationMgrMap[accountId]; - if(authorizationMgr && authorizationMgr.token) { - headers.Authorization = "Bearer " + authorizationMgr.token.access_token; - } + var base64Data = utils.encodeBase64(content); + var multipartRequestBody = [ + delimiter, + 'Content-Type: application/json\r\n\r\n', + JSON.stringify(metadata), + delimiter, + 'Content-Type: ', + contentType, + '\r\n', + 'Content-Transfer-Encoding: base64\r\n', + '\r\n', + base64Data, + close_delim + ].join(""); - $.ajax({ - url: constants.PICASA_PROXY_URL + "upload/" + albumId, - headers: headers, - data: content, - processData: false, - dataType: "xml", - timeout: constants.AJAX_TIMEOUT, - type: "POST" - }).done(function(data) { - result = data; - task.chain(); - }).fail(function(jqXHR) { - var error = { - code: jqXHR.status, - message: jqXHR.statusText - }; - if(error.code == 200) { - error.message = jqXHR.responseText; - } - handleError(error, task); - }); - }); - task.onSuccess(function() { - callback(undefined, result); - }); - task.onError(function(error) { - callback(error); - }); - task.enqueue(); - }; + runWithToken(accountId, function() { + var request = gapi.client.request({ + 'path': path, + 'method': method, + 'params': { + 'uploadType': 'multipart' + }, + 'headers': headers, + 'body': multipartRequestBody + }); + request.execute(function(response) { + if(response && response.id) { + // Upload success + result = response; + result.content = content; + return task.chain(); + } + var error = response.error; + // Handle error + if(error !== undefined && fileId !== undefined) { + if(error.code === 404) { + error = 'File ID "' + fileId + '" not found on Google Drive.|removePublish'; + } + else if(error.code === 412) { + // We may have missed a file update + storage.removeItem(accountId + ".gdrive.lastChangeId"); + error = 'Conflict on file ID "' + fileId + '". Please restart the synchronization.'; + } + } + handleError(error, task); + }); + }); + }); + task.onSuccess(function() { + callback(undefined, result); + }); + task.onError(function(error) { + callback(error); + }); + task.enqueue(); + }; - function handleError(error, task) { - var errorMsg; - if(error) { - logger.error(error); - // Try to analyze the error - if(typeof error === "string") { - errorMsg = error; - } - else { - errorMsg = "Google error (" + error.code + ": " + error.message + ")."; - if(error.code >= 500 && error.code < 600) { - // Retry as described in Google's best practices - task.retry(new Error(errorMsg)); - return; - } - else if(error.code === 401 || error.code === 403 || error.code == "token_refresh_required") { - _.each(authorizationMgrMap, function(authorizationMgr) { - authorizationMgr.setRefreshFlag(); - }); - errorMsg = "Access to Google account is not authorized."; - task.retry(new Error(errorMsg), 1); - return; - } - else if(error.code === 0 || error.code === -1) { - connected = false; - _.each(authorizationMgrMap, function(authorizationMgr) { - authorizationMgr.setRefreshFlag(); - }); - core.setOffline(); - errorMsg = "|stopPublish"; - } - } - } - task.error(new Error(errorMsg)); - } + googleHelper.rename = function(fileId, title, accountId, callback) { + var result; + var task = new AsyncTask(); + connect(task); + authenticate(task, 'gdrive', accountId); + task.onRun(function() { + var body = {'title': title}; + runWithToken(accountId, function() { + var request = gapi.client.drive.files.patch({ + 'fileId': fileId, + 'resource': body + }); + request.execute(function(response) { + if(response && response.id) { + // Rename success + result = response; + return task.chain(); + } + var error = response.error; + // Handle error + if(error !== undefined && fileId !== undefined) { + if(error.code === 404) { + error = 'File ID "' + fileId + '" not found on Google Drive.|removePublish'; + } + } + handleError(error, task); + }); + }); + }); + task.onSuccess(function() { + callback(undefined, result); + }); + task.onError(function(error) { + callback(error); + }); + task.enqueue(); + }; - var pickerLoaded = false; - function loadPicker(task) { - task.onRun(function() { - if(pickerLoaded === true) { - task.chain(); - return; - } - $.ajax({ - url: "//www.google.com/jsapi", - data: { - key: constants.GOOGLE_API_KEY - }, - dataType: "script", - timeout: constants.AJAX_TIMEOUT - }).done(function() { - google.load('picker', '1', { - callback: function() { - task.chain(); - } - }); - pickerLoaded = true; - }).fail(function(jqXHR) { - var error = { - code: jqXHR.status, - message: jqXHR.statusText - }; - handleError(error, task); - }); - }); - } + googleHelper.checkChanges = function(lastChangeId, accountId, callback) { + var changes = []; + var newChangeId = lastChangeId || 0; + var task = new AsyncTask(); + connect(task); + authenticate(task, 'gdrive', accountId); + task.onRun(function() { + var nextPageToken; - googleHelper.picker = function(callback, pickerType, accountId) { - var docs = []; - var picker; - function hidePicker() { - if(picker !== undefined) { - picker.setVisible(false); - $(".modal-backdrop, .picker").remove(); - } - } - var task = new AsyncTask(); - // Add some time for user to choose his files - task.timeout = constants.ASYNC_TASK_LONG_TIMEOUT; - connect(task); - if(pickerType == 'doc' || pickerType == 'folder') { - authenticate(task, 'gdrive', accountId); - } - else { - accountId = 'google.picasa0'; - authenticate(task, 'picasa', accountId); - } - loadPicker(task); - task.onRun(function() { - var authorizationMgr = authorizationMgrMap[accountId]; - var pickerBuilder = new google.picker.PickerBuilder(); - pickerBuilder.setAppId(constants.GOOGLE_DRIVE_APP_ID); - var view; - if(pickerType == 'doc') { - view = new google.picker.DocsView(google.picker.ViewId.DOCS); - view.setParent('root'); - view.setIncludeFolders(true); - view.setMimeTypes([ - "text/x-markdown", - "text/plain", - "application/octet-stream", - "application/vnd.google-apps.drive-sdk." + constants.GOOGLE_DRIVE_APP_ID - ].join(",")); - pickerBuilder.enableFeature(google.picker.Feature.NAV_HIDDEN); - pickerBuilder.enableFeature(google.picker.Feature.MULTISELECT_ENABLED); - pickerBuilder.addView(view); - authorizationMgr && authorizationMgr.token && pickerBuilder.setOAuthToken(authorizationMgr.token.access_token); - } - else if(pickerType == 'folder') { - view = new google.picker.DocsView(google.picker.ViewId.FOLDERS); - view.setParent('root'); - view.setIncludeFolders(true); - view.setSelectFolderEnabled(true); - view.setMimeTypes('application/vnd.google-apps.folder'); - pickerBuilder.enableFeature(google.picker.Feature.NAV_HIDDEN); - pickerBuilder.addView(view); - authorizationMgr && authorizationMgr.token && pickerBuilder.setOAuthToken(authorizationMgr.token.access_token); - } - else if(pickerType == 'img') { - view = new google.picker.PhotosView(); - view.setType('flat'); - pickerBuilder.addView(view); - view = new google.picker.PhotosView(); - view.setType('ofuser'); - pickerBuilder.addView(view); - pickerBuilder.addView(google.picker.ViewId.PHOTO_UPLOAD); - authorizationMgr && authorizationMgr.token && pickerBuilder.setOAuthToken(authorizationMgr.token.access_token); - } - pickerBuilder.setCallback(function(data) { - if(data.action == google.picker.Action.PICKED || data.action == google.picker.Action.CANCEL) { - if(data.action == google.picker.Action.PICKED) { - docs = data.docs; - } - hidePicker(); - task.chain(); - } - }); - picker = pickerBuilder.build(); - $(utils.createBackdrop()).click(function() { - hidePicker(); - task.chain(); - }); - picker.setVisible(true); - }); - task.onSuccess(function() { - callback(undefined, docs); - }); - task.onError(function(error) { - hidePicker(); - callback(error); - }); - task.enqueue(); - }; + function retrievePageOfChanges() { + runWithToken(accountId, function() { + var request; + if(nextPageToken === undefined) { + request = gapi.client.drive.changes.list({ + 'startChangeId': newChangeId + 1 + }); + } + else { + request = gapi.client.drive.changes.list({ + 'pageToken': nextPageToken + }); + } - googleHelper.uploadBlogger = function(blogUrl, blogId, postId, labelList, isDraft, publishDate, title, content, callback) { - var accountId = 'google.blogger0'; - var task = new AsyncTask(); - connect(task); - authenticate(task, 'blogger', accountId); - task.onRun(function() { - var headers = {}; - var authorizationMgr = authorizationMgrMap[accountId]; - if(authorizationMgr && authorizationMgr.token) { - headers.Authorization = "Bearer " + authorizationMgr.token.access_token; - } - function uploadPost() { - var url = "https://www.googleapis.com/blogger/v3/blogs/" + blogId + "/posts/"; - var data = { - kind: "blogger#post", - blog: { - id: blogId - }, - labels: labelList, - title: title, - content: content - }; - if(publishDate) { - data.published = publishDate.toISOString(); - } - var type = "POST"; - // If it's an update - if(postId !== undefined) { - url += postId; - data.id = postId; - type = "PUT"; - } - $.ajax({ - url: url, - data: JSON.stringify(data), - headers: headers, - type: type, - contentType: "application/json", - dataType: "json", - timeout: constants.AJAX_TIMEOUT - }).done(function(post) { - postId = post.id; - task.chain(publish); - }).fail(function(jqXHR) { - var error = { - code: jqXHR.status, - message: jqXHR.statusText - }; - // Handle error - if(error.code === 404 && postId !== undefined) { - error = 'Post ' + postId + ' not found on Blogger.|removePublish'; - } - handleError(error, task); - }); - } - function publish() { - var url = "https://www.googleapis.com/blogger/v3/blogs/" + blogId + "/posts/" + postId; - if(isDraft) { - url += "/revert"; - } - else { - url += "/publish"; - if(publishDate) { - url += '?publishDate=' + publishDate.toISOString(); - } - } - $.ajax({ - url: url, - headers: headers, - type: 'POST', - dataType: "json", - timeout: constants.AJAX_TIMEOUT - }).done(function() { - task.chain(); - }).fail(function(jqXHR) { - var error = { - code: jqXHR.status, - message: jqXHR.statusText - }; - // Handle error - if(error.code === 404) { - error = 'Post ' + postId + ' not found on Blogger.|removePublish'; - } - handleError(error, task); - }); - } - function getBlogId() { - if(blogId !== undefined) { - task.chain(uploadPost); - return; - } - $.ajax({ - url: "https://www.googleapis.com/blogger/v3/blogs/byurl", - data: { - url: blogUrl - }, - headers: headers, - dataType: "json", - timeout: constants.AJAX_TIMEOUT - }).done(function(blog) { - blogId = blog.id; - task.chain(uploadPost); - }).fail(function(jqXHR) { - var error = { - code: jqXHR.status, - message: jqXHR.statusText - }; - // Handle error - if(error.code === 404) { - error = 'Blog "' + blogUrl + '" not found on Blogger.|removePublish'; - } - handleError(error, task); - }); - } - task.chain(getBlogId); - }); - task.onSuccess(function() { - callback(undefined, blogId, postId); - }); - task.onError(function(error) { - callback(error); - }); - task.enqueue(); - }; + request.execute(function(response) { + if(!response || !response.largestChangeId) { + // Handle error + return handleError(response.error, task); + } + // Retrieve success + newChangeId = response.largestChangeId; + nextPageToken = response.nextPageToken; + if(response.items !== undefined) { + changes = changes.concat(response.items); + } + if(nextPageToken !== undefined) { + task.chain(retrievePageOfChanges); + } + else { + task.chain(); + } + }); + }); + } - googleHelper.uploadBloggerPage = function(blogUrl, blogId, pageId, isDraft, publishDate, title, content, callback) { - var accountId = 'google.blogger0'; - var task = new AsyncTask(); - connect(task); - authenticate(task, 'blogger', accountId); - task.onRun(function() { - var headers = {}; - var authorizationMgr = authorizationMgrMap[accountId]; - if(authorizationMgr && authorizationMgr.token) { - headers.Authorization = "Bearer " + authorizationMgr.token.access_token; - } - function uploadPage() { - var url = "https://www.googleapis.com/blogger/v3/blogs/" + blogId + "/pages/"; - var data = { - kind: "blogger#page", - blog: { - id: blogId - }, - title: title, - content: content - }; - var type = "POST"; - // If it's an update - if(pageId !== undefined) { - url += pageId; - data.id = pageId; - type = "PUT"; - } - $.ajax({ - url: url, - data: JSON.stringify(data), - headers: headers, - type: type, - contentType: "application/json", - dataType: "json", - timeout: constants.AJAX_TIMEOUT - }).done(function(page) { - pageId = page.id; - task.chain(); - }).fail(function(jqXHR) { - var error = { - code: jqXHR.status, - message: jqXHR.statusText - }; - // Handle error - if(error.code === 404 && pageId !== undefined) { - error = 'Page ' + pageId + ' not found on Blogger.|removePublish'; - } - handleError(error, task); - }); - } - function getBlogId() { - if(blogId !== undefined) { - task.chain(uploadPage); - return; - } - $.ajax({ - url: "https://www.googleapis.com/blogger/v3/blogs/byurl", - data: { - url: blogUrl - }, - headers: headers, - dataType: "json", - timeout: constants.AJAX_TIMEOUT - }).done(function(blog) { - blogId = blog.id; - task.chain(uploadPage); - }).fail(function(jqXHR) { - var error = { - code: jqXHR.status, - message: jqXHR.statusText - }; - // Handle error - if(error.code === 404) { - error = 'Blog "' + blogUrl + '" not found on Blogger.|removePublish'; - } - handleError(error, task); - }); - } - task.chain(getBlogId); - }); - task.onSuccess(function() { - callback(undefined, blogId, pageId); - }); - task.onError(function(error) { - callback(error); - }); - task.enqueue(); - }; + task.chain(retrievePageOfChanges); + }); + task.onSuccess(function() { + callback(undefined, changes, newChangeId); + }); + task.onError(function(error) { + callback(error); + }); + task.enqueue(); + }; - // Use by Google's client.js - window.delayedFunction = undefined; - window.runDelayedFunction = function() { - if(window.delayedFunction !== undefined) { - window.delayedFunction(); - } - }; + googleHelper.downloadMetadata = function(ids, accountId, callback, skipAuth) { + var result = []; + var task = new AsyncTask(); + connect(task); + if(!skipAuth) { + authenticate(task, 'gdrive', accountId); + } + task.onRun(function() { + function recursiveDownloadMetadata() { + if(ids.length === 0) { + return task.chain(); + } + var id = ids[0]; + var headers = {}; + var authorizationMgr = authorizationMgrMap[accountId]; + if(authorizationMgr && authorizationMgr.token) { + headers.Authorization = "Bearer " + authorizationMgr.token.access_token; + } + $.ajax({ + url: "https://www.googleapis.com/drive/v2/files/" + id, + headers: headers, + data: { + key: constants.GOOGLE_API_KEY + }, + dataType: "json", + timeout: constants.AJAX_TIMEOUT + }).done(function(data) { + result.push(data); + ids.shift(); + task.chain(recursiveDownloadMetadata); + }).fail(function(jqXHR) { + var error = { + code: jqXHR.status, + message: jqXHR.statusText + }; + // Handle error + if(error.code === 404) { + error = 'File ID "' + id + '" not found on Google Drive.'; + } + handleError(error, task); + }); + } - return googleHelper; + task.chain(recursiveDownloadMetadata); + }); + task.onSuccess(function() { + callback(undefined, result); + }); + task.onError(function(error) { + callback(error); + }); + task.enqueue(); + }; + + googleHelper.downloadContent = function(objects, accountId, callback, skipAuth) { + var result = []; + var task = new AsyncTask(); + // Add some time for user to choose his files + task.timeout = constants.ASYNC_TASK_LONG_TIMEOUT; + connect(task); + if(!skipAuth) { + authenticate(task, 'gdrive', accountId); + } + task.onRun(function() { + function recursiveDownloadContent() { + if(objects.length === 0) { + return task.chain(); + } + var object = objects[0]; + result.push(object); + var file; + // object may be a file + if(object.kind == "drive#file") { + file = object; + } + // object may be a change + else if(object.kind == "drive#change") { + file = object.file; + } + if(!file) { + objects.shift(); + return task.chain(recursiveDownloadContent); + } + var url = file.downloadUrl; + // if file is a real time document + if(file.mimeType.indexOf("application/vnd.google-apps.drive-sdk") === 0) { + file.isRealtime = true; + url = 'https://www.googleapis.com/drive/v2/files/' + file.id + '/realtime'; + } + var headers = {}; + var authorizationMgr = authorizationMgrMap[accountId]; + if(authorizationMgr && authorizationMgr.token) { + headers.Authorization = "Bearer " + authorizationMgr.token.access_token; + } + $.ajax({ + url: url, + headers: headers, + data: { + key: constants.GOOGLE_API_KEY + }, + dataType: file.isRealtime ? 'json' : 'text', + timeout: constants.AJAX_TIMEOUT + }).done(function(data) { + file.content = file.isRealtime ? data.data.value.content.value : data; + objects.shift(); + task.chain(recursiveDownloadContent); + }).fail(function(jqXHR) { + var error = { + code: jqXHR.status, + message: jqXHR.statusText + }; + // Handle error + handleError(error, task); + }); + } + + task.chain(recursiveDownloadContent); + }); + task.onSuccess(function() { + callback(undefined, result); + }); + task.onError(function(error) { + callback(error); + }); + task.enqueue(); + }; + + googleHelper.uploadImg = function(name, content, albumId, callback) { + var accountId = 'google.picasa0'; + var result; + var task = new AsyncTask(); + connect(task); + authenticate(task, 'picasa', accountId); + task.onRun(function() { + var headers = { + "Slug": name + }; + if(name.match(/.jpe?g$/i)) { + headers["Content-Type"] = "image/jpeg"; + } + else if(name.match(/.png$/i)) { + headers["Content-Type"] = "image/png"; + } + else if(name.match(/.gif$/i)) { + headers["Content-Type"] = "image/gif"; + } + var authorizationMgr = authorizationMgrMap[accountId]; + if(authorizationMgr && authorizationMgr.token) { + headers.Authorization = "Bearer " + authorizationMgr.token.access_token; + } + + $.ajax({ + url: constants.PICASA_PROXY_URL + "upload/" + albumId, + headers: headers, + data: content, + processData: false, + dataType: "xml", + timeout: constants.AJAX_TIMEOUT, + type: "POST" + }).done(function(data) { + result = data; + task.chain(); + }).fail(function(jqXHR) { + var error = { + code: jqXHR.status, + message: jqXHR.statusText + }; + if(error.code == 200) { + error.message = jqXHR.responseText; + } + handleError(error, task); + }); + }); + task.onSuccess(function() { + callback(undefined, result); + }); + task.onError(function(error) { + callback(error); + }); + task.enqueue(); + }; + + function handleError(error, task) { + var errorMsg; + if(error) { + logger.error(error); + // Try to analyze the error + if(typeof error === "string") { + errorMsg = error; + } + else { + errorMsg = "Google error (" + error.code + ": " + error.message + ")."; + if(error.code >= 500 && error.code < 600) { + // Retry as described in Google's best practices + return task.retry(new Error(errorMsg)); + } + else if(error.code === 401 || error.code === 403 || error.code == "token_refresh_required") { + _.each(authorizationMgrMap, function(authorizationMgr) { + authorizationMgr.setRefreshFlag(); + }); + errorMsg = "Access to Google account is not authorized."; + return task.retry(new Error(errorMsg), 1); + } + else if(error.code === 0 || error.code === -1) { + connected = false; + _.each(authorizationMgrMap, function(authorizationMgr) { + authorizationMgr.setRefreshFlag(); + }); + core.setOffline(); + errorMsg = "|stopPublish"; + } + } + } + task.error(new Error(errorMsg)); + } + + var pickerLoaded = false; + + function loadPicker(task) { + task.onRun(function() { + if(pickerLoaded === true) { + return task.chain(); + } + $.ajax({ + url: "//www.google.com/jsapi", + data: { + key: constants.GOOGLE_API_KEY + }, + dataType: "script", + timeout: constants.AJAX_TIMEOUT + }).done(function() { + google.load('picker', '1', { + callback: function() { + task.chain(); + } + }); + pickerLoaded = true; + }).fail(function(jqXHR) { + var error = { + code: jqXHR.status, + message: jqXHR.statusText + }; + handleError(error, task); + }); + }); + } + + googleHelper.picker = function(callback, pickerType, accountId) { + var docs = []; + var picker; + + function hidePicker() { + if(picker !== undefined) { + picker.setVisible(false); + $(".modal-backdrop, .picker").remove(); + } + } + + var task = new AsyncTask(); + // Add some time for user to choose his files + task.timeout = constants.ASYNC_TASK_LONG_TIMEOUT; + connect(task); + if(pickerType == 'doc' || pickerType == 'folder') { + authenticate(task, 'gdrive', accountId); + } + else { + accountId = 'google.picasa0'; + authenticate(task, 'picasa', accountId); + } + loadPicker(task); + task.onRun(function() { + var authorizationMgr = authorizationMgrMap[accountId]; + var pickerBuilder = new google.picker.PickerBuilder(); + pickerBuilder.setAppId(constants.GOOGLE_DRIVE_APP_ID); + var view; + if(pickerType == 'doc') { + view = new google.picker.DocsView(google.picker.ViewId.DOCS); + view.setParent('root'); + view.setIncludeFolders(true); + view.setMimeTypes([ + "text/x-markdown", + "text/plain", + "application/octet-stream", + "application/vnd.google-apps.drive-sdk." + constants.GOOGLE_DRIVE_APP_ID + ].join(",")); + pickerBuilder.enableFeature(google.picker.Feature.NAV_HIDDEN); + pickerBuilder.enableFeature(google.picker.Feature.MULTISELECT_ENABLED); + pickerBuilder.addView(view); + authorizationMgr && authorizationMgr.token && pickerBuilder.setOAuthToken(authorizationMgr.token.access_token); + } + else if(pickerType == 'folder') { + view = new google.picker.DocsView(google.picker.ViewId.FOLDERS); + view.setParent('root'); + view.setIncludeFolders(true); + view.setSelectFolderEnabled(true); + view.setMimeTypes('application/vnd.google-apps.folder'); + pickerBuilder.enableFeature(google.picker.Feature.NAV_HIDDEN); + pickerBuilder.addView(view); + authorizationMgr && authorizationMgr.token && pickerBuilder.setOAuthToken(authorizationMgr.token.access_token); + } + else if(pickerType == 'img') { + view = new google.picker.PhotosView(); + view.setType('flat'); + pickerBuilder.addView(view); + view = new google.picker.PhotosView(); + view.setType('ofuser'); + pickerBuilder.addView(view); + pickerBuilder.addView(google.picker.ViewId.PHOTO_UPLOAD); + authorizationMgr && authorizationMgr.token && pickerBuilder.setOAuthToken(authorizationMgr.token.access_token); + } + pickerBuilder.setCallback(function(data) { + if(data.action == google.picker.Action.PICKED || data.action == google.picker.Action.CANCEL) { + if(data.action == google.picker.Action.PICKED) { + docs = data.docs; + } + hidePicker(); + task.chain(); + } + }); + picker = pickerBuilder.build(); + $(utils.createBackdrop()).on('click.backdrop', function() { + hidePicker(); + task.chain(); + }); + picker.setVisible(true); + }); + task.onSuccess(function() { + callback(undefined, docs); + }); + task.onError(function(error) { + hidePicker(); + callback(error); + }); + task.enqueue(); + }; + + googleHelper.uploadBlogger = function(blogUrl, blogId, postId, labelList, isDraft, publishDate, title, content, callback) { + var accountId = 'google.blogger0'; + var task = new AsyncTask(); + connect(task); + authenticate(task, 'blogger', accountId); + task.onRun(function() { + var headers = {}; + var authorizationMgr = authorizationMgrMap[accountId]; + if(authorizationMgr && authorizationMgr.token) { + headers.Authorization = "Bearer " + authorizationMgr.token.access_token; + } + function uploadPost() { + var url = "https://www.googleapis.com/blogger/v3/blogs/" + blogId + "/posts/"; + var data = { + kind: "blogger#post", + blog: { + id: blogId + }, + labels: labelList, + title: title, + content: content + }; + if(publishDate) { + data.published = publishDate.toISOString(); + } + var type = "POST"; + // If it's an update + if(postId !== undefined) { + url += postId; + data.id = postId; + type = "PUT"; + } + $.ajax({ + url: url, + data: JSON.stringify(data), + headers: headers, + type: type, + contentType: "application/json", + dataType: "json", + timeout: constants.AJAX_TIMEOUT + }).done(function(post) { + postId = post.id; + task.chain(publish); + }).fail(function(jqXHR) { + var error = { + code: jqXHR.status, + message: jqXHR.statusText + }; + // Handle error + if(error.code === 404 && postId !== undefined) { + error = 'Post ' + postId + ' not found on Blogger.|removePublish'; + } + handleError(error, task); + }); + } + + function publish() { + var url = "https://www.googleapis.com/blogger/v3/blogs/" + blogId + "/posts/" + postId; + if(isDraft) { + url += "/revert"; + } + else { + url += "/publish"; + if(publishDate) { + url += '?publishDate=' + publishDate.toISOString(); + } + } + $.ajax({ + url: url, + headers: headers, + type: 'POST', + dataType: "json", + timeout: constants.AJAX_TIMEOUT + }).done(function() { + task.chain(); + }).fail(function(jqXHR) { + var error = { + code: jqXHR.status, + message: jqXHR.statusText + }; + // Handle error + if(error.code === 404) { + error = 'Post ' + postId + ' not found on Blogger.|removePublish'; + } + handleError(error, task); + }); + } + + function getBlogId() { + if(blogId !== undefined) { + task.chain(uploadPost); + return; + } + $.ajax({ + url: "https://www.googleapis.com/blogger/v3/blogs/byurl", + data: { + url: blogUrl + }, + headers: headers, + dataType: "json", + timeout: constants.AJAX_TIMEOUT + }).done(function(blog) { + blogId = blog.id; + task.chain(uploadPost); + }).fail(function(jqXHR) { + var error = { + code: jqXHR.status, + message: jqXHR.statusText + }; + // Handle error + if(error.code === 404) { + error = 'Blog "' + blogUrl + '" not found on Blogger.|removePublish'; + } + handleError(error, task); + }); + } + + task.chain(getBlogId); + }); + task.onSuccess(function() { + callback(undefined, blogId, postId); + }); + task.onError(function(error) { + callback(error); + }); + task.enqueue(); + }; + + googleHelper.uploadBloggerPage = function(blogUrl, blogId, pageId, isDraft, publishDate, title, content, callback) { + var accountId = 'google.blogger0'; + var task = new AsyncTask(); + connect(task); + authenticate(task, 'blogger', accountId); + task.onRun(function() { + var headers = {}; + var authorizationMgr = authorizationMgrMap[accountId]; + if(authorizationMgr && authorizationMgr.token) { + headers.Authorization = "Bearer " + authorizationMgr.token.access_token; + } + function uploadPage() { + var url = "https://www.googleapis.com/blogger/v3/blogs/" + blogId + "/pages/"; + var data = { + kind: "blogger#page", + blog: { + id: blogId + }, + title: title, + content: content + }; + var type = "POST"; + // If it's an update + if(pageId !== undefined) { + url += pageId; + data.id = pageId; + type = "PUT"; + } + $.ajax({ + url: url, + data: JSON.stringify(data), + headers: headers, + type: type, + contentType: "application/json", + dataType: "json", + timeout: constants.AJAX_TIMEOUT + }).done(function(page) { + pageId = page.id; + task.chain(); + }).fail(function(jqXHR) { + var error = { + code: jqXHR.status, + message: jqXHR.statusText + }; + // Handle error + if(error.code === 404 && pageId !== undefined) { + error = 'Page ' + pageId + ' not found on Blogger.|removePublish'; + } + handleError(error, task); + }); + } + + function getBlogId() { + if(blogId !== undefined) { + task.chain(uploadPage); + return; + } + $.ajax({ + url: "https://www.googleapis.com/blogger/v3/blogs/byurl", + data: { + url: blogUrl + }, + headers: headers, + dataType: "json", + timeout: constants.AJAX_TIMEOUT + }).done(function(blog) { + blogId = blog.id; + task.chain(uploadPage); + }).fail(function(jqXHR) { + var error = { + code: jqXHR.status, + message: jqXHR.statusText + }; + // Handle error + if(error.code === 404) { + error = 'Blog "' + blogUrl + '" not found on Blogger.|removePublish'; + } + handleError(error, task); + }); + } + + task.chain(getBlogId); + }); + task.onSuccess(function() { + callback(undefined, blogId, pageId); + }); + task.onError(function(error) { + callback(error); + }); + task.enqueue(); + }; + + // Use by Google's client.js + window.delayedFunction = undefined; + window.runDelayedFunction = function() { + if(window.delayedFunction !== undefined) { + window.delayedFunction(); + } + }; + + return googleHelper; }); diff --git a/public/res/layout.js b/public/res/layout.js index 1ec59696..24d055d7 100644 --- a/public/res/layout.js +++ b/public/res/layout.js @@ -117,7 +117,7 @@ define([ }; DomObject.prototype.createToggler = function(backdrop) { - var backdropElt; + var $backdropElt; var pushedEvents = 0; this.toggle = function(show) { if(show === this.isOpen) { @@ -127,7 +127,7 @@ define([ if(this.isOpen) { this.$elt.addClass('panel-open').trigger('show.layout.toggle'); if(backdrop) { - $(backdropElt = utils.createBackdrop(wrapperL1.elt)).click(_.bind(function() { + $backdropElt = $(utils.createBackdrop(wrapperL1.elt)).on('click.backdrop', _.bind(function() { this.toggle(false); }, this)); this.$elt.addClass('bring-to-front'); @@ -140,8 +140,11 @@ define([ } else { this.$elt.trigger('hide.layout.toggle'); - backdropElt && backdropElt.removeBackdrop(); - backdropElt = undefined; + if($backdropElt) { + $backdropElt.off('click.backdrop'); + $backdropElt[0].removeBackdrop(); + $backdropElt = undefined; + } transitionEndCallbacks.push(_.bind(function() { if(--pushedEvents === 0) { !this.isOpen && this.$elt.removeClass('panel-open bring-to-front').trigger('hidden.layout.toggle'); diff --git a/public/res/providers/gdriveProviderBuilder.js b/public/res/providers/gdriveProviderBuilder.js index 84cd5faa..b0f5e575 100644 --- a/public/res/providers/gdriveProviderBuilder.js +++ b/public/res/providers/gdriveProviderBuilder.js @@ -91,8 +91,7 @@ define([ var syncIndex = createSyncIndex(doc.id); var fileDesc = fileMgr.getFileFromSyncIndex(syncIndex); if(fileDesc !== undefined) { - eventMgr.onError('"' + fileDesc.title + '" was already imported.'); - return; + return eventMgr.onError('"' + fileDesc.title + '" was already imported.'); } importIds.push(doc.id); }); @@ -109,16 +108,14 @@ define([ var fileDesc = fileMgr.getFileFromSyncIndex(syncIndex); if(fileDesc !== undefined) { eventMgr.onError('File ID is already synchronized with "' + fileDesc.title + '".'); - callback(true); - return; + return callback(true); } } var parentId = utils.getInputTextValue('#input-sync-export-' + providerId + '-parentid'); var data = gdriveProvider.serializeContent(content, discussionListJSON); googleHelper.upload(fileId, parentId, title, data, undefined, undefined, accountId, function(error, result) { if(error) { - callback(error); - return; + return callback(error); } var syncAttributes = createSyncAttributes(result.id, result.etag, content, title, discussionListJSON); callback(undefined, syncAttributes); @@ -128,7 +125,7 @@ define([ gdriveProvider.syncUp = function(content, contentCRC, title, titleCRC, discussionList, discussionListCRC, syncAttributes, callback) { if( (syncAttributes.contentCRC == contentCRC) && // Content CRC hasn't changed - (syncAttributes.titleCRC == titleCRC) && // Content CRC hasn't changed + (syncAttributes.titleCRC == titleCRC) && // Title CRC hasn't changed (syncAttributes.discussionListCRC == discussionListCRC) // Discussion list CRC hasn't changed ) { return callback(undefined, false); @@ -144,8 +141,7 @@ define([ var data = gdriveProvider.serializeContent(content, discussionList); googleHelper.upload(syncAttributes.id, undefined, title, data, undefined, syncAttributes.etag, accountId, function(error, result) { if(error) { - callback(error, true); - return; + return callback(error, true); } syncAttributes.etag = result.etag; // Remove this deprecated flag if any @@ -167,8 +163,7 @@ define([ var lastChangeId = parseInt(storage[accountId + ".gdrive.lastChangeId"], 10); googleHelper.checkChanges(lastChangeId, accountId, function(error, changes, newChangeId) { if(error) { - callback(error); - return; + return callback(error); } var interestingChanges = []; _.each(changes, function(change) { diff --git a/public/res/styles/main.less b/public/res/styles/main.less index 56680572..60f153b4 100644 --- a/public/res/styles/main.less +++ b/public/res/styles/main.less @@ -1336,7 +1336,7 @@ div.dropdown-menu, { color: @link-hover-color; } h1, h2, h3, h4, h5, h6 { - margin: 0.8em 0; + margin: 1em 0; } } diff --git a/public/res/utils.js b/public/res/utils.js index 9b435106..710453f0 100644 --- a/public/res/utils.js +++ b/public/res/utils.js @@ -349,6 +349,36 @@ define([ ].join("")); }; + var $windowElt = $(window); + utils.iframe = function(url, width, height) { + var $backdropElt = $(utils.createBackdrop()); + var result = crel('iframe', { + src: url, + frameborder: 0, + class: 'modal-content modal-iframe' + }); + document.body.appendChild(result); + function placeIframe() { + var actualWidth = window.innerWidth - 20; + actualWidth > width && (actualWidth = width); + var actualHeight = window.innerHeight - 50; + actualHeight > height && (actualHeight = height); + result.setAttribute('width', actualWidth); + result.setAttribute('height', actualHeight); + } + placeIframe(); + $windowElt.on('resize.iframe', placeIframe); + function removeIframe() { + $backdropElt.off('click.backdrop'); + $backdropElt[0].removeBackdrop(); + $windowElt.off('resize.iframe'); + result.parentNode.removeChild(result); + } + result.removeIframe = removeIframe; + $backdropElt.on('click.backdrop', removeIframe); + return result; + }; + // Shows a dialog to force the user to click a button before opening oauth popup var redirectCallbackConfirm; var redirectCallbackCancel;