diff --git a/cache.manifest b/cache.manifest index 810bbceb..4bb14d3a 100644 --- a/cache.manifest +++ b/cache.manifest @@ -1 +1 @@ -CACHE MANIFEST # v4 CACHE: index.html css/bootstrap.css css/jgrowl.css css/main.css js/async-runner.js js/base64.js js/bootstrap.js js/gdrive.js js/jquery.jgrowl.js js/jquery.js js/jquery.layout.js js/jquery-ui.custom.js js/main.js js/Markdown.Converter.js js/Markdown.Editor.js js/Markdown.Sanitizer.js img/ajax-loader.gif img/gdrive.png img/glyphicons-halflings.png img/glyphicons-halflings-white.png img/stackedit-16.png img/stackedit-32.ico NETWORK: * \ No newline at end of file +CACHE MANIFEST # v5 CACHE: index.html css/bootstrap.css css/jgrowl.css css/main.css js/async-runner.js js/base64.js js/bootstrap.js js/gdrive.js js/jquery.jgrowl.js js/jquery.js js/jquery.layout.js js/jquery-ui.custom.js js/main.js js/Markdown.Converter.js js/Markdown.Editor.js js/Markdown.Sanitizer.js img/ajax-loader.gif img/gdrive.png img/glyphicons-halflings.png img/glyphicons-halflings-white.png img/stackedit-16.png img/stackedit-32.ico NETWORK: * \ No newline at end of file diff --git a/index.html b/index.html index 3ab8df59..f9b84d49 100644 --- a/index.html +++ b/index.html @@ -100,7 +100,7 @@
Are you sure you want to remove ""? diff --git a/js/async-runner.js b/js/async-runner.js index 7ea7a821..3e855acb 100644 --- a/js/async-runner.js +++ b/js/async-runner.js @@ -1,66 +1,101 @@ +var ASYNC_TASK_DEFAULT_TIMEOUT = 30000; + /** * Used to run any asynchronous tasks sequentially (ajax mainly) * An asynchronous task must be created with: - * - a required run() function that may call success() or error() + * - a required run() function that may call success(), error() or retry() * - an optional onSuccess() function * - an optional onError() function - * - an optional timeout property + * - an optional timeout field (default is 30000) */ var asyncTaskRunner = (function() { var asyncTaskRunner = {}; var asyncTaskQueue = []; var currentTask = undefined; - var currentTaskStartTime = new Date().getTime(); + var currentTaskRunning = false; + var currentTaskStartTime = currentTime; // Run the next task in the queue if any and no other is running asyncTaskRunner.runTask = function() { // If there is a task currently running - if(currentTask !== undefined) { + if(currentTaskRunning !== false) { // If the current task takes too long - var timeout = currentTask.timeout || 30000; + var timeout = currentTask.timeout || ASYNC_TASK_DEFAULT_TIMEOUT; if(currentTaskStartTime + timeout < currentTime) { currentTask.error(); } return; } - // If no task in the queue - if(asyncTaskQueue.length === 0) { - return; + if(currentTask === undefined) { + // If no task in the queue + if(asyncTaskQueue.length === 0) { + return; + } + + // Dequeue an enqueued task + currentTask = asyncTaskQueue.shift(); + currentTaskStartTime = currentTime; + showWorkingIndicator(true); + + // Set task attributes and functions + currentTask.finished = false; + currentTask.retryCounter = 0; + currentTask.finish = function() { + this.finished = true; + showWorkingIndicator(false); + currentTask = undefined; + currentTaskRunning = false; + asyncTaskRunner.runTask(); + }; + currentTask.success = function() { + if(this.finished === true) { + return; + } + try { + if(this.onSuccess) { + this.onSuccess(); + } + } finally { + this.finish(); + } + }; + currentTask.error = function() { + if(this.finished === true) { + return; + } + try { + if(this.onError) { + this.onError(); + } + } finally { + this.finish(); + } + }; + currentTask.retry = function() { + if(this.finished === true) { + return; + } + if(currentTask.retryCounter === 5) { + this.error(); + return; + } + // Implement an exponential backoff + var delay = (Math.pow(2, currentTask.retryCounter++) + Math.random()) * 1000; + console.log(delay); + currentTaskStartTime = currentTime + delay; + currentTaskRunning = false; + asyncTaskRunner.runTask(); + }; } - currentTask = asyncTaskQueue.shift(); - currentTaskStartTime = currentTime; - showWorkingIndicator(true); - // Set task attributes and functions - currentTask.finished = false; - currentTask.finish = function() { - this.finished = true; - showWorkingIndicator(false); - currentTask = undefined; - asyncTaskRunner.runTask(); - }; - currentTask.success = function() { - if(this.finished === true) { - return; - } - if(this.onSuccess) { - this.onSuccess(); - } - this.finish(); - }; - currentTask.error = function() { - if(this.finished === true) { - return; - } - if(this.onError) { - this.onError(); - } - this.finish(); - }; - currentTask.run(); + // Run the task + if(currentTaskStartTime <= currentTime) { + currentTaskRunning = true; + currentTask.run(); + } }; // Add a task in the queue diff --git a/js/gdrive.js b/js/gdrive.js index 2c8c4818..74c763c3 100644 --- a/js/gdrive.js +++ b/js/gdrive.js @@ -1,10 +1,11 @@ var GOOGLE_CLIENT_ID = '241271498917-jpto9lls9fqnem1e4h6ppds9uob8rpvu.apps.googleusercontent.com'; var SCOPES = [ 'https://www.googleapis.com/auth/drive.install', 'https://www.googleapis.com/auth/drive.file' ]; +var AUTH_POPUP_TIMEOUT = 90000; var gdriveDelayedFunction = undefined; function runGdriveDelayedFunction() { - if(gdriveDelayedFunction !== undefined) { + if (gdriveDelayedFunction !== undefined) { gdriveDelayedFunction(); } } @@ -13,30 +14,31 @@ var gdrive = (function($) { var connected = false; var authenticated = false; - var doNothing = function() {}; + var doNothing = function() { + }; var gdrive = {}; - + // Try to connect Gdrive by downloading client.js function connect(callback) { callback = callback || doNothing; var asyncTask = {}; asyncTask.run = function() { - if(connected === true) { + if (connected === true) { asyncTask.success(); return; } gdriveDelayedFunction = function() { asyncTask.success(); }; - $.ajax({ - url: "https://apis.google.com/js/client.js?onload=runGdriveDelayedFunction", - dataType: "script", - timeout: 5000 - }) - .fail(function() { - asyncTask.error(); - }); + $ + .ajax( + { + url : "https://apis.google.com/js/client.js?onload=runGdriveDelayedFunction", + dataType : "script", timeout : AJAX_TIMEOUT }).fail( + function() { + asyncTask.error(); + }); }; asyncTask.onSuccess = function() { gdriveDelayedFunction = undefined; @@ -50,31 +52,35 @@ var gdrive = (function($) { }; asyncTaskRunner.addTask(asyncTask); } - + // Try to authenticate with Oauth function authenticate(callback, immediate) { callback = callback || doNothing; - if(immediate === undefined) { + if (immediate === undefined) { immediate = true; } connect(function() { - if(connected === false) { + if (connected === false) { callback(); return; } - + var asyncTask = {}; // If not immediate we add time for user to enter his credentials - if(immediate === false) { - asyncTask.timeout = 90000; + if (immediate === false) { + asyncTask.timeout = AUTH_POPUP_TIMEOUT; } asyncTask.run = function() { - if(authenticated === true) { + if (authenticated === true) { asyncTask.success(); return; } - gapi.auth.authorize({ 'client_id' : GOOGLE_CLIENT_ID, 'scope' : SCOPES, - 'immediate' : immediate }, function(authResult) { + if (immediate === false) { + showMessage("Please make sure the Google authorization popup is not blocked by your browser..."); + } + gapi.auth.authorize({ 'client_id' : GOOGLE_CLIENT_ID, + 'scope' : SCOPES, 'immediate' : immediate }, function( + authResult) { if (!authResult || authResult.error) { asyncTask.error(); return; @@ -90,7 +96,7 @@ var gdrive = (function($) { }; asyncTask.onError = function() { // If immediate did not work retry without immediate flag - if(connected === true && immediate === true) { + if (connected === true && immediate === true) { authenticate(callback, false); return; } @@ -100,14 +106,45 @@ var gdrive = (function($) { }); } + function handleError(response, asyncTask, callback) { + var errorMsg = undefined; + if (response && response.error) { + var error = response.error; + // Try to analyze the error + if (error.code >= 500 && error.code < 600) { + errorMsg = "Google Drive is not accessible."; + // Retry as described in Google's best practices + asyncTask.retry(); + return; + } else if (error.code === 401) { + authenticated = false; + errorMsg = "Access to Google Drive is not authorized."; + } else if (error.code === -1) { + connected = false; + authenticated = false; + onOffline(); + } else { + errorMsg = "Google Drive error (" + error.code + ": " + + error.message + ")."; + } + } + asyncTask.onError = function() { + if (errorMsg !== undefined) { + showError(errorMsg); + } + callback(); + }; + asyncTask.error(); + } + function upload(fileId, parentId, title, content, callback) { callback = callback || doNothing; authenticate(function() { - if(connected === false) { + if (connected === false) { callback(); return; } - + var fileIndex = undefined; var asyncTask = {}; asyncTask.run = function() { @@ -117,13 +154,14 @@ var gdrive = (function($) { var contentType = 'text/x-markdown'; var metadata = { title : title, mimeType : contentType }; - if (parentId) { + if (parentId !== undefined) { // Specify the directory - metadata.parents = [ { kind : 'drive#fileLink', id : parentId } ]; + metadata.parents = [ { kind : 'drive#fileLink', + id : parentId } ]; } var path = '/upload/drive/v2/files'; var method = 'POST'; - if (fileId) { + if (fileId !== undefined) { // If it's an update path += "/" + fileId; method = 'PUT'; @@ -133,59 +171,151 @@ var gdrive = (function($) { 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; + + contentType + '\r\n' + + 'Content-Transfer-Encoding: base64\r\n' + '\r\n' + + base64Data + close_delim; - var request = gapi.client.request({ - 'path' : path, - 'method' : method, - 'params' : { 'uploadType' : 'multipart', }, - 'headers' : { 'Content-Type' : 'multipart/mixed; boundary="' - + boundary + '"', }, 'body' : multipartRequestBody, }); - request.execute(function(file) { - if(file.id) { + var request = gapi.client + .request({ + 'path' : path, + 'method' : method, + 'params' : { 'uploadType' : 'multipart', }, + 'headers' : { 'Content-Type' : 'multipart/mixed; boundary="' + + boundary + '"', }, 'body' : multipartRequestBody, }); + request.execute(function(response) { + if (response && response.id) { // Upload success - fileIndex = SYNC_PROVIDER_GDRIVE + file.id; - localStorage[fileIndex + ".etag"] = file.etag; + fileIndex = SYNC_PROVIDER_GDRIVE + response.id; + localStorage[fileIndex + ".etag"] = response.etag; asyncTask.success(); - return + return; } - // Upload failed, try to analyse - if(file.error.code === 401) { - showError("Google Drive is not accessible."); + // If file has been removed from Google Drive + if(fileId !== undefined && response.error.code === 404) { + showMessage('"' + title + '" has been removed from Google Drive.'); + fileManager.removeSync(SYNC_PROVIDER_GDRIVE + fileId); + fileManager.updateFileTitles(); + // Avoid error analyzed by handleError + response = undefined; } - else { - connected = false; - authenticated = false; - onOffline(); - } - asyncTask.error(); + // Handle error + handleError(response, asyncTask, callback); }); }; asyncTask.onSuccess = function() { callback(fileIndex); }; - asyncTask.onError = function() { + asyncTaskRunner.addTask(asyncTask); + }); + } + + gdrive.checkUpdates = function(lastChangeId, callback) { + callback = callback || doNothing; + authenticate(function() { + if (connected === false) { callback(); + return; + } + + var changes = []; + var newChangeId = lastChangeId || 0; + function retrievePageOfChanges(request) { + var nextPageToken = undefined; + var asyncTask = {}; + asyncTask.run = function() { + request + .execute(function(response) { + if (response && response.largestChangeId) { + // Retrieve success + newChangeId = response.largestChangeId; + nextPageToken = response.nextPageToken; + if (response.items !== undefined) { + for ( var i = 0; i < response.items.length; i++) { + var item = response.items[i]; + var etag = localStorage[SYNC_PROVIDER_GDRIVE + + item.fileId + ".etag"]; + if (etag + && (item.deleted === true || item.file.etag != etag)) { + changes.push(item); + } + } + } + asyncTask.success(); + return; + } + // Handle error + handleError(response, asyncTask, callback); + }); + }; + asyncTask.onSuccess = function() { + if (nextPageToken !== undefined) { + request = gapi.client.drive.changes + .list({ 'pageToken' : nextPageToken }); + retrievePageOfChanges(request); + } else { + callback(changes, newChangeId); + } + }; + asyncTaskRunner.addTask(asyncTask); + } + var initialRequest = gapi.client.drive.changes + .list({ 'startChangeId' : newChangeId + 1 }); + retrievePageOfChanges(initialRequest); + }); + }; + + gdrive.downloadContent = function(objects, callback, result) { + callback = callback || doNothing; + result = result || []; + if(objects.length === 0) { + callback(result); + return; + } + + var object = objects.pop(); + result.push(object); + var file = undefined; + // 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 === undefined) { + this.downloadContent(objects, callback, result); + return; + } + + authenticate(function() { + if (connected === false) { + callback(); + return; + } + + var asyncTask = {}; + asyncTask.run = function() { + var accessToken = gapi.auth.getToken().access_token; + $ + .ajax( + { + url : file.downloadUrl, + headers : { "Authorization" : "Bearer " + + accessToken }, dataType : "text", + timeout : AJAX_TIMEOUT }).done( + function(data, textStatus, jqXHR) { + file.content = data; + asyncTask.success(); + }).fail(function() { + asyncTask.error(); + }); + }; + asyncTask.onSuccess = function() { + gdrive.downloadContent(objects, callback, result); }; asyncTaskRunner.addTask(asyncTask); - }); - } - ; - - gdrive.init = function() { - try { - var state = JSON.parse(decodeURI((/state=(.+?)(&|$)/ - .exec(location.search) || [ , null ])[1])); - if (state.action == 'create') { - upload(undefined, state.folderId, - fileManager.currentFile, fileManager.content, function( - fileIndex) { - console.log(fileIndex); - }); - } - } catch (e) { - } + }); }; gdrive.createFile = function(title, content, callback) { @@ -195,6 +325,20 @@ var gdrive = (function($) { gdrive.updateFile = function(id, title, content, callback) { upload(id, undefined, title, content, callback); }; - + + gdrive.init = function() { + try { + var state = JSON.parse(decodeURI((/state=(.+?)(&|$)/ + .exec(location.search) || [ , null ])[1])); + if (state.action == 'create') { + upload(undefined, state.folderId, fileManager.currentFile, + fileManager.content, function(fileIndex) { + console.log(fileIndex); + }); + } + } catch (e) { + } + }; + return gdrive; })(jQuery); diff --git a/js/main.js b/js/main.js index cfad4566..4db5f43a 100644 --- a/js/main.js +++ b/js/main.js @@ -18,6 +18,8 @@ function showWorkingIndicator(show) { } } +var AJAX_TIMEOUT = 5000; +var CHECK_ONLINE_PERIOD = 60000; var offline = false; var offlineTime = currentTime; function onOffline() { @@ -36,21 +38,24 @@ function onOnline() { 'jGrowl.beforeClose'); } -function autoClean() { +function checkOnline() { // Try to reconnect if we are offline but we have some network if (offline === true && navigator.onLine === true - && offlineTime + 60000 < currentTime) { + && offlineTime + CHECK_ONLINE_PERIOD < currentTime) { offlineTime = currentTime; // Try to download anything to test the connection $.ajax( - { url : "https://apis.google.com/js/client.js", timeout : 5000, - dataType : "script" }).done(function() { + { url : "https://apis.google.com/js/client.js", + timeout : AJAX_TIMEOUT, dataType : "script" }).done(function() { onOnline(); }); } } +var SYNC_DOWN_PERIOD = 60000; var SYNC_PROVIDER_GDRIVE = "sync.gdrive."; +var syncGoogleDrive = false; + var synchronizer = (function($) { var synchronizer = {}; @@ -76,14 +81,13 @@ var synchronizer = (function($) { } }; - // Recursive function to run synchronization of a single file on multiple - // locations - function sync(fileSyncIndexList, content, title) { + // Recursive function to upload a single file on multiple locations + function fileUp(fileSyncIndexList, content, title) { if (fileSyncIndexList.length === 0) { localStorage.removeItem("sync.current"); - running = false; + uploadRunning = false; // run the next file synchronization - synchronizer.run(); + synchronizer.syncUp(); return; } var fileSyncIndex = fileSyncIndexList.pop(); @@ -92,28 +96,28 @@ var synchronizer = (function($) { if (fileSyncIndex.indexOf(SYNC_PROVIDER_GDRIVE) === 0) { var id = fileSyncIndex.substring(SYNC_PROVIDER_GDRIVE.length); gdrive.updateFile(id, title, content, function(result) { - if (!result && offline) { + if (result === undefined && offline === true) { // If we detect offline mode we put the fileIndex back in // the queue synchronizer.addFile(localStorage["sync.current"]); localStorage.removeItem("sync.current"); - running = false; + uploadRunning = false; return; } - sync(fileSyncIndexList, content, title); + fileUp(fileSyncIndexList, content, title); }); } else { - sync(fileSyncIndexList, content, title); + fileUp(fileSyncIndexList, content, title); } } - var running = false; - synchronizer.run = function() { - // If synchronization is already running or nothing to synchronize - if (running || syncQueue.length === 1 || offline) { + var uploadRunning = false; + synchronizer.syncUp = function() { + // If syncUp is already running or nothing to synchronize or offline + if (uploadRunning || syncQueue.length === 1 || offline) { return; } - running = true; + uploadRunning = true; // Dequeue the fileIndex var separatorPos = syncQueue.indexOf(";", 1); @@ -127,7 +131,101 @@ var synchronizer = (function($) { // Parse the list of synchronized locations associated to the file var fileSyncIndexList = localStorage[fileIndex + ".sync"].split(";"); - sync(fileSyncIndexList, content, title); + fileUp(fileSyncIndexList, content, title); + }; + + function syncDownGdrive(callback) { + if (syncGoogleDrive === false) { + callback(); + return; + } + var lastChangeId = parseInt(localStorage[SYNC_PROVIDER_GDRIVE + + "lastChangeId"]); + gdrive.checkUpdates(lastChangeId, function(changes, newChangeId) { + if (changes === undefined) { + callback(); + return; + } + gdrive.downloadContent(changes, function(changes) { + if (changes === undefined) { + callback(); + return; + } + var updateFileTitles = false; + for ( var i = 0; i < changes.length; i++) { + var change = changes[i]; + var fileSyncIndex = SYNC_PROVIDER_GDRIVE + change.fileId; + var fileIndexList = localStorage["file.list"].split(";"); + var fileIndex = undefined; + // Look for local file associated to this synchronized location + for ( var i = 1; i < fileIndexList.length - 1; i++) { + var tempFileIndex = fileIndexList[i]; + var sync = localStorage[tempFileIndex + ".sync"]; + if (sync.indexOf(";" + fileSyncIndex + ";") !== -1) { + fileIndex = tempFileIndex; + break; + } + } + // No file corresponding (this should never happen...) + if(fileIndex === undefined) { + // We can remove the stored etag + localStorage.removeItem(fileSyncIndex + ".etag"); + continue; + } + var title = localStorage[fileIndex + ".title"]; + // File deleted + if (change.deleted === true) { + fileManager.removeSync(fileSyncIndex); + updateFileTitles = true; + showMessage('"' + title + '" has been removed from Google Drive.'); + continue; + } + var content = localStorage[fileIndex + ".content"]; + var file = change.file; + // File title changed + if(title != file.title) { + localStorage[fileIndex + ".title"] = file.title; + updateFileTitles = true; + showMessage('"' + title + '" has been renamed to "' + file.title + '" on Google Drive.'); + } + // File content changed + if(content != file.content) { + localStorage[fileIndex + ".content"] = file.content; + showMessage('"' + file.title + '" has been updated from Google Drive.'); + if(fileIndex == localStorage["file.current"]) { + updateFileTitles = false; // Done by next function + fileManager.selectFile(); + } + } + // Update file etag + localStorage[fileSyncIndex + ".etag"] = file.etag; + // Synchronize file to others locations + synchronizer.addFile(fileIndex); + } + if(updateFileTitles) { + fileManager.updateFileTitles(); + } + localStorage[SYNC_PROVIDER_GDRIVE + + "lastChangeId"] = newChangeId; + callback(); + }); + }); + } + + var downloadRunning = false; + var lastSyncDown = 0; + synchronizer.syncDown = function() { + // If syncDown is already running or timeout is not reached or offline + if (downloadRunning || lastSyncDown + SYNC_DOWN_PERIOD > currentTime + || offline) { + return; + } + downloadRunning = true; + lastSyncDown = currentTime; + + syncDownGdrive(function() { + downloadRunning = false; + }); }; return synchronizer; @@ -143,19 +241,21 @@ var fileManager = (function($) { synchronizer.init(); fileManager.selectFile(); - // Save file automatically and synchronize + // Do periodic stuff window.setInterval(function() { currentTime = new Date().getTime(); fileManager.saveFile(); - synchronizer.run(); + synchronizer.syncDown(); + synchronizer.syncUp(); asyncTaskRunner.runTask(); - autoClean(); + checkOnline(); }, 1000); $(".action-create-file").click(function() { fileManager.saveFile(); fileManager.createFile(); fileManager.selectFile(); + $("#file-title").click(); }); $(".action-remove-file").click(function() { fileManager.deleteFile(); @@ -172,8 +272,7 @@ var fileManager = (function($) { var fileIndexTitle = localStorage["file.current"] + ".title"; if (title != localStorage[fileIndexTitle]) { localStorage[fileIndexTitle] = title; - updateFileDescList(); - updateFileTitleUI(); + fileManager.updateFileTitles(); save = true; } } @@ -205,23 +304,18 @@ var fileManager = (function($) { localStorage["file.counter"] = 0; localStorage["file.list"] = ";"; } - updateFileDescList(); // If no file create one - if (fileDescList.length === 0) { + if (localStorage["file.list"].length === 1) { this.createFile(); - updateFileDescList(); } - // If no default file take first one - if (!localStorage["file.current"]) { - localStorage["file.current"] = fileDescList[0].index; - } - // Update the editor and the file title + // Update the file titles + this.updateFileTitles(); + // Update the editor var fileIndex = localStorage["file.current"]; $("#wmd-input").val(localStorage[fileIndex + ".content"]); core.createEditor(function() { save = true; }); - updateFileTitleUI(); }; fileManager.createFile = function(title) { @@ -242,18 +336,18 @@ var fileManager = (function($) { fileManager.deleteFile = function() { var fileIndex = localStorage["file.current"]; - + // Remove synchronized locations var fileSyncIndexList = localStorage[fileIndex + ".sync"].split(";"); for ( var i = 1; i < fileSyncIndexList.length - 1; i++) { - var sync = fileSyncIndexList[i]; - fileManager.removeSync(sync); - } + var fileSyncIndex = fileSyncIndexList[i]; + fileManager.removeSync(fileSyncIndex); + } localStorage.removeItem(fileIndex + ".sync"); localStorage.removeItem("file.current"); localStorage["file.list"] = localStorage["file.list"].replace(";" - + fileIndex + ";", ";"); + + fileIndex + ";", ";"); localStorage.removeItem(fileIndex + ".title"); localStorage.removeItem(fileIndex + ".content"); }; @@ -267,34 +361,8 @@ var fileManager = (function($) { save = false; } }; - - // Remove a synchronization location associated to the file - fileManager.removeSync = function(sync) { - var fileIndexSync = localStorage["file.current"] + ".sync"; - localStorage[fileIndexSync] = localStorage[fileIndexSync].replace(";" - + sync + ";", ";"); - - localStorage.removeItem(sync + ".etag"); - }; - - function uploadGdrive() { - $(".file-sync-indicator").removeClass("hide"); - var fileIndex = localStorage["file.current"]; - var content = localStorage[fileIndex + ".content"]; - var title = localStorage[fileIndex + ".title"]; - gdrive.createFile(title, content, function(fileSyncIndex) { - if (fileSyncIndex) { - localStorage[fileIndex + ".sync"] += fileSyncIndex + ";"; - updateFileTitleUI(); - showMessage('The file "' + title - + '" will now be synchronized on Google Drive.'); - } else { - showError("Error while creating file on Google Drive."); - } - }); - } - - function updateFileDescList() { + + fileManager.updateFileTitles = function() { fileDescList = []; $("#file-selector").empty(); var fileIndexList = localStorage["file.list"].split(";"); @@ -310,20 +378,24 @@ var fileManager = (function($) { return 1; return 0; }); - } - ; - function updateFileTitleUI() { + // If no default file take first one + if (!localStorage["file.current"]) { + localStorage["file.current"] = fileDescList[0].index; + } + + syncGoogleDrive = false; function composeTitle(fileIndex) { var result = localStorage[fileIndex + ".title"]; var sync = localStorage[fileIndex + ".sync"]; - if (sync.indexOf(SYNC_PROVIDER_GDRIVE) !== -1) { + if (sync.indexOf(";" + SYNC_PROVIDER_GDRIVE) !== -1) { + syncGoogleDrive = true; result = ' ' + result; } return result; } - // Update the editor and the file title + // Update the the file title and the file selector var fileIndex = localStorage["file.current"]; var title = localStorage[fileIndex + ".title"]; document.title = "StackEdit - " + title; @@ -348,8 +420,37 @@ var fileManager = (function($) { } $("#file-selector").append(li); } + }; + + // Remove a synchronized location + fileManager.removeSync = function(fileSyncIndex) { + var fileIndexList = localStorage["file.list"].split(";"); + // Look for local files associated to this synchronized location + for ( var i = 1; i < fileIndexList.length - 1; i++) { + var fileIndexSync = fileIndexList[i] + ".sync"; + localStorage[fileIndexSync] = localStorage[fileIndexSync].replace(";" + + fileSyncIndex + ";", ";"); + } + // Remove etag + localStorage.removeItem(fileSyncIndex + ".etag"); + }; + + function uploadGdrive() { + $(".file-sync-indicator").removeClass("hide"); + var fileIndex = localStorage["file.current"]; + var content = localStorage[fileIndex + ".content"]; + var title = localStorage[fileIndex + ".title"]; + gdrive.createFile(title, content, function(fileSyncIndex) { + if (fileSyncIndex) { + localStorage[fileIndex + ".sync"] += fileSyncIndex + ";"; + fileManager.updateFileTitles(); + showMessage('The file "' + title + + '" will now be synchronized on Google Drive.'); + } else { + showError("Error while creating file on Google Drive."); + } + }); } - ; function refreshManageSync() { var fileIndex = localStorage["file.current"]; @@ -362,23 +463,24 @@ var fileManager = (function($) { $(".msg-no-sync").removeClass("hide"); } for ( var i = 1; i < fileSyncIndexList.length - 1; i++) { - var sync = fileSyncIndexList[i]; - (function(sync) { + var fileSyncIndex = fileSyncIndexList[i]; + (function(fileSyncIndex) { var line = $("