periodic down synchronization
This commit is contained in:
parent
dffc6e8829
commit
fef879fe50
@ -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:
*
|
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:
*
|
@ -100,7 +100,7 @@
|
|||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<button type="button" class="close" data-dismiss="modal"
|
<button type="button" class="close" data-dismiss="modal"
|
||||||
aria-hidden="true">×</button>
|
aria-hidden="true">×</button>
|
||||||
<h3>Remove file</h3>
|
<h3>Remove</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<p>Are you sure you want to remove "<span class="file-title"></span>"?
|
<p>Are you sure you want to remove "<span class="file-title"></span>"?
|
||||||
|
@ -1,66 +1,101 @@
|
|||||||
|
var ASYNC_TASK_DEFAULT_TIMEOUT = 30000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to run any asynchronous tasks sequentially (ajax mainly)
|
* Used to run any asynchronous tasks sequentially (ajax mainly)
|
||||||
* An asynchronous task must be created with:
|
* 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 onSuccess() function
|
||||||
* - an optional onError() function
|
* - an optional onError() function
|
||||||
* - an optional timeout property
|
* - an optional timeout field (default is 30000)
|
||||||
*/
|
*/
|
||||||
var asyncTaskRunner = (function() {
|
var asyncTaskRunner = (function() {
|
||||||
var asyncTaskRunner = {};
|
var asyncTaskRunner = {};
|
||||||
|
|
||||||
var asyncTaskQueue = [];
|
var asyncTaskQueue = [];
|
||||||
var currentTask = undefined;
|
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
|
// Run the next task in the queue if any and no other is running
|
||||||
asyncTaskRunner.runTask = function() {
|
asyncTaskRunner.runTask = function() {
|
||||||
|
|
||||||
// If there is a task currently running
|
// If there is a task currently running
|
||||||
if(currentTask !== undefined) {
|
if(currentTaskRunning !== false) {
|
||||||
// If the current task takes too long
|
// If the current task takes too long
|
||||||
var timeout = currentTask.timeout || 30000;
|
var timeout = currentTask.timeout || ASYNC_TASK_DEFAULT_TIMEOUT;
|
||||||
if(currentTaskStartTime + timeout < currentTime) {
|
if(currentTaskStartTime + timeout < currentTime) {
|
||||||
currentTask.error();
|
currentTask.error();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(currentTask === undefined) {
|
||||||
// If no task in the queue
|
// If no task in the queue
|
||||||
if(asyncTaskQueue.length === 0) {
|
if(asyncTaskQueue.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dequeue an enqueued task
|
||||||
currentTask = asyncTaskQueue.shift();
|
currentTask = asyncTaskQueue.shift();
|
||||||
currentTaskStartTime = currentTime;
|
currentTaskStartTime = currentTime;
|
||||||
showWorkingIndicator(true);
|
showWorkingIndicator(true);
|
||||||
|
|
||||||
// Set task attributes and functions
|
// Set task attributes and functions
|
||||||
currentTask.finished = false;
|
currentTask.finished = false;
|
||||||
|
currentTask.retryCounter = 0;
|
||||||
currentTask.finish = function() {
|
currentTask.finish = function() {
|
||||||
this.finished = true;
|
this.finished = true;
|
||||||
showWorkingIndicator(false);
|
showWorkingIndicator(false);
|
||||||
currentTask = undefined;
|
currentTask = undefined;
|
||||||
|
currentTaskRunning = false;
|
||||||
asyncTaskRunner.runTask();
|
asyncTaskRunner.runTask();
|
||||||
};
|
};
|
||||||
currentTask.success = function() {
|
currentTask.success = function() {
|
||||||
if(this.finished === true) {
|
if(this.finished === true) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
if(this.onSuccess) {
|
if(this.onSuccess) {
|
||||||
this.onSuccess();
|
this.onSuccess();
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
this.finish();
|
this.finish();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
currentTask.error = function() {
|
currentTask.error = function() {
|
||||||
if(this.finished === true) {
|
if(this.finished === true) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
if(this.onError) {
|
if(this.onError) {
|
||||||
this.onError();
|
this.onError();
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
this.finish();
|
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();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the task
|
||||||
|
if(currentTaskStartTime <= currentTime) {
|
||||||
|
currentTaskRunning = true;
|
||||||
currentTask.run();
|
currentTask.run();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add a task in the queue
|
// Add a task in the queue
|
||||||
|
248
js/gdrive.js
248
js/gdrive.js
@ -1,10 +1,11 @@
|
|||||||
var GOOGLE_CLIENT_ID = '241271498917-jpto9lls9fqnem1e4h6ppds9uob8rpvu.apps.googleusercontent.com';
|
var GOOGLE_CLIENT_ID = '241271498917-jpto9lls9fqnem1e4h6ppds9uob8rpvu.apps.googleusercontent.com';
|
||||||
var SCOPES = [ 'https://www.googleapis.com/auth/drive.install',
|
var SCOPES = [ 'https://www.googleapis.com/auth/drive.install',
|
||||||
'https://www.googleapis.com/auth/drive.file' ];
|
'https://www.googleapis.com/auth/drive.file' ];
|
||||||
|
var AUTH_POPUP_TIMEOUT = 90000;
|
||||||
|
|
||||||
var gdriveDelayedFunction = undefined;
|
var gdriveDelayedFunction = undefined;
|
||||||
function runGdriveDelayedFunction() {
|
function runGdriveDelayedFunction() {
|
||||||
if(gdriveDelayedFunction !== undefined) {
|
if (gdriveDelayedFunction !== undefined) {
|
||||||
gdriveDelayedFunction();
|
gdriveDelayedFunction();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -13,7 +14,8 @@ var gdrive = (function($) {
|
|||||||
|
|
||||||
var connected = false;
|
var connected = false;
|
||||||
var authenticated = false;
|
var authenticated = false;
|
||||||
var doNothing = function() {};
|
var doNothing = function() {
|
||||||
|
};
|
||||||
|
|
||||||
var gdrive = {};
|
var gdrive = {};
|
||||||
|
|
||||||
@ -22,19 +24,19 @@ var gdrive = (function($) {
|
|||||||
callback = callback || doNothing;
|
callback = callback || doNothing;
|
||||||
var asyncTask = {};
|
var asyncTask = {};
|
||||||
asyncTask.run = function() {
|
asyncTask.run = function() {
|
||||||
if(connected === true) {
|
if (connected === true) {
|
||||||
asyncTask.success();
|
asyncTask.success();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
gdriveDelayedFunction = function() {
|
gdriveDelayedFunction = function() {
|
||||||
asyncTask.success();
|
asyncTask.success();
|
||||||
};
|
};
|
||||||
$.ajax({
|
$
|
||||||
url: "https://apis.google.com/js/client.js?onload=runGdriveDelayedFunction",
|
.ajax(
|
||||||
dataType: "script",
|
{
|
||||||
timeout: 5000
|
url : "https://apis.google.com/js/client.js?onload=runGdriveDelayedFunction",
|
||||||
})
|
dataType : "script", timeout : AJAX_TIMEOUT }).fail(
|
||||||
.fail(function() {
|
function() {
|
||||||
asyncTask.error();
|
asyncTask.error();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -54,27 +56,31 @@ var gdrive = (function($) {
|
|||||||
// Try to authenticate with Oauth
|
// Try to authenticate with Oauth
|
||||||
function authenticate(callback, immediate) {
|
function authenticate(callback, immediate) {
|
||||||
callback = callback || doNothing;
|
callback = callback || doNothing;
|
||||||
if(immediate === undefined) {
|
if (immediate === undefined) {
|
||||||
immediate = true;
|
immediate = true;
|
||||||
}
|
}
|
||||||
connect(function() {
|
connect(function() {
|
||||||
if(connected === false) {
|
if (connected === false) {
|
||||||
callback();
|
callback();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var asyncTask = {};
|
var asyncTask = {};
|
||||||
// If not immediate we add time for user to enter his credentials
|
// If not immediate we add time for user to enter his credentials
|
||||||
if(immediate === false) {
|
if (immediate === false) {
|
||||||
asyncTask.timeout = 90000;
|
asyncTask.timeout = AUTH_POPUP_TIMEOUT;
|
||||||
}
|
}
|
||||||
asyncTask.run = function() {
|
asyncTask.run = function() {
|
||||||
if(authenticated === true) {
|
if (authenticated === true) {
|
||||||
asyncTask.success();
|
asyncTask.success();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
gapi.auth.authorize({ 'client_id' : GOOGLE_CLIENT_ID, 'scope' : SCOPES,
|
if (immediate === false) {
|
||||||
'immediate' : immediate }, function(authResult) {
|
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) {
|
if (!authResult || authResult.error) {
|
||||||
asyncTask.error();
|
asyncTask.error();
|
||||||
return;
|
return;
|
||||||
@ -90,7 +96,7 @@ var gdrive = (function($) {
|
|||||||
};
|
};
|
||||||
asyncTask.onError = function() {
|
asyncTask.onError = function() {
|
||||||
// If immediate did not work retry without immediate flag
|
// If immediate did not work retry without immediate flag
|
||||||
if(connected === true && immediate === true) {
|
if (connected === true && immediate === true) {
|
||||||
authenticate(callback, false);
|
authenticate(callback, false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -100,10 +106,41 @@ 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) {
|
function upload(fileId, parentId, title, content, callback) {
|
||||||
callback = callback || doNothing;
|
callback = callback || doNothing;
|
||||||
authenticate(function() {
|
authenticate(function() {
|
||||||
if(connected === false) {
|
if (connected === false) {
|
||||||
callback();
|
callback();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -117,13 +154,14 @@ var gdrive = (function($) {
|
|||||||
|
|
||||||
var contentType = 'text/x-markdown';
|
var contentType = 'text/x-markdown';
|
||||||
var metadata = { title : title, mimeType : contentType };
|
var metadata = { title : title, mimeType : contentType };
|
||||||
if (parentId) {
|
if (parentId !== undefined) {
|
||||||
// Specify the directory
|
// Specify the directory
|
||||||
metadata.parents = [ { kind : 'drive#fileLink', id : parentId } ];
|
metadata.parents = [ { kind : 'drive#fileLink',
|
||||||
|
id : parentId } ];
|
||||||
}
|
}
|
||||||
var path = '/upload/drive/v2/files';
|
var path = '/upload/drive/v2/files';
|
||||||
var method = 'POST';
|
var method = 'POST';
|
||||||
if (fileId) {
|
if (fileId !== undefined) {
|
||||||
// If it's an update
|
// If it's an update
|
||||||
path += "/" + fileId;
|
path += "/" + fileId;
|
||||||
method = 'PUT';
|
method = 'PUT';
|
||||||
@ -133,59 +171,151 @@ var gdrive = (function($) {
|
|||||||
var multipartRequestBody = delimiter
|
var multipartRequestBody = delimiter
|
||||||
+ 'Content-Type: application/json\r\n\r\n'
|
+ 'Content-Type: application/json\r\n\r\n'
|
||||||
+ JSON.stringify(metadata) + delimiter + 'Content-Type: '
|
+ JSON.stringify(metadata) + delimiter + 'Content-Type: '
|
||||||
+ contentType + '\r\n' + 'Content-Transfer-Encoding: base64\r\n'
|
+ contentType + '\r\n'
|
||||||
+ '\r\n' + base64Data + close_delim;
|
+ 'Content-Transfer-Encoding: base64\r\n' + '\r\n'
|
||||||
|
+ base64Data + close_delim;
|
||||||
|
|
||||||
var request = gapi.client.request({
|
var request = gapi.client
|
||||||
|
.request({
|
||||||
'path' : path,
|
'path' : path,
|
||||||
'method' : method,
|
'method' : method,
|
||||||
'params' : { 'uploadType' : 'multipart', },
|
'params' : { 'uploadType' : 'multipart', },
|
||||||
'headers' : { 'Content-Type' : 'multipart/mixed; boundary="'
|
'headers' : { 'Content-Type' : 'multipart/mixed; boundary="'
|
||||||
+ boundary + '"', }, 'body' : multipartRequestBody, });
|
+ boundary + '"', }, 'body' : multipartRequestBody, });
|
||||||
request.execute(function(file) {
|
request.execute(function(response) {
|
||||||
if(file.id) {
|
if (response && response.id) {
|
||||||
// Upload success
|
// Upload success
|
||||||
fileIndex = SYNC_PROVIDER_GDRIVE + file.id;
|
fileIndex = SYNC_PROVIDER_GDRIVE + response.id;
|
||||||
localStorage[fileIndex + ".etag"] = file.etag;
|
localStorage[fileIndex + ".etag"] = response.etag;
|
||||||
asyncTask.success();
|
asyncTask.success();
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
// Upload failed, try to analyse
|
// If file has been removed from Google Drive
|
||||||
if(file.error.code === 401) {
|
if(fileId !== undefined && response.error.code === 404) {
|
||||||
showError("Google Drive is not accessible.");
|
showMessage('"' + title + '" has been removed from Google Drive.');
|
||||||
|
fileManager.removeSync(SYNC_PROVIDER_GDRIVE + fileId);
|
||||||
|
fileManager.updateFileTitles();
|
||||||
|
// Avoid error analyzed by handleError
|
||||||
|
response = undefined;
|
||||||
}
|
}
|
||||||
else {
|
// Handle error
|
||||||
connected = false;
|
handleError(response, asyncTask, callback);
|
||||||
authenticated = false;
|
|
||||||
onOffline();
|
|
||||||
}
|
|
||||||
asyncTask.error();
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
asyncTask.onSuccess = function() {
|
asyncTask.onSuccess = function() {
|
||||||
callback(fileIndex);
|
callback(fileIndex);
|
||||||
};
|
};
|
||||||
asyncTask.onError = function() {
|
|
||||||
callback();
|
|
||||||
};
|
|
||||||
asyncTaskRunner.addTask(asyncTask);
|
asyncTaskRunner.addTask(asyncTask);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
;
|
|
||||||
|
|
||||||
gdrive.init = function() {
|
gdrive.checkUpdates = function(lastChangeId, callback) {
|
||||||
try {
|
callback = callback || doNothing;
|
||||||
var state = JSON.parse(decodeURI((/state=(.+?)(&|$)/
|
authenticate(function() {
|
||||||
.exec(location.search) || [ , null ])[1]));
|
if (connected === false) {
|
||||||
if (state.action == 'create') {
|
callback();
|
||||||
upload(undefined, state.folderId,
|
return;
|
||||||
fileManager.currentFile, fileManager.content, function(
|
}
|
||||||
fileIndex) {
|
|
||||||
console.log(fileIndex);
|
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);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
};
|
||||||
|
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.createFile = function(title, content, callback) {
|
gdrive.createFile = function(title, content, callback) {
|
||||||
@ -196,5 +326,19 @@ var gdrive = (function($) {
|
|||||||
upload(id, undefined, 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;
|
return gdrive;
|
||||||
})(jQuery);
|
})(jQuery);
|
||||||
|
255
js/main.js
255
js/main.js
@ -18,6 +18,8 @@ function showWorkingIndicator(show) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var AJAX_TIMEOUT = 5000;
|
||||||
|
var CHECK_ONLINE_PERIOD = 60000;
|
||||||
var offline = false;
|
var offline = false;
|
||||||
var offlineTime = currentTime;
|
var offlineTime = currentTime;
|
||||||
function onOffline() {
|
function onOffline() {
|
||||||
@ -36,21 +38,24 @@ function onOnline() {
|
|||||||
'jGrowl.beforeClose');
|
'jGrowl.beforeClose');
|
||||||
}
|
}
|
||||||
|
|
||||||
function autoClean() {
|
function checkOnline() {
|
||||||
// Try to reconnect if we are offline but we have some network
|
// Try to reconnect if we are offline but we have some network
|
||||||
if (offline === true && navigator.onLine === true
|
if (offline === true && navigator.onLine === true
|
||||||
&& offlineTime + 60000 < currentTime) {
|
&& offlineTime + CHECK_ONLINE_PERIOD < currentTime) {
|
||||||
offlineTime = currentTime;
|
offlineTime = currentTime;
|
||||||
// Try to download anything to test the connection
|
// Try to download anything to test the connection
|
||||||
$.ajax(
|
$.ajax(
|
||||||
{ url : "https://apis.google.com/js/client.js", timeout : 5000,
|
{ url : "https://apis.google.com/js/client.js",
|
||||||
dataType : "script" }).done(function() {
|
timeout : AJAX_TIMEOUT, dataType : "script" }).done(function() {
|
||||||
onOnline();
|
onOnline();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var SYNC_DOWN_PERIOD = 60000;
|
||||||
var SYNC_PROVIDER_GDRIVE = "sync.gdrive.";
|
var SYNC_PROVIDER_GDRIVE = "sync.gdrive.";
|
||||||
|
var syncGoogleDrive = false;
|
||||||
|
|
||||||
var synchronizer = (function($) {
|
var synchronizer = (function($) {
|
||||||
var synchronizer = {};
|
var synchronizer = {};
|
||||||
|
|
||||||
@ -76,14 +81,13 @@ var synchronizer = (function($) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Recursive function to run synchronization of a single file on multiple
|
// Recursive function to upload a single file on multiple locations
|
||||||
// locations
|
function fileUp(fileSyncIndexList, content, title) {
|
||||||
function sync(fileSyncIndexList, content, title) {
|
|
||||||
if (fileSyncIndexList.length === 0) {
|
if (fileSyncIndexList.length === 0) {
|
||||||
localStorage.removeItem("sync.current");
|
localStorage.removeItem("sync.current");
|
||||||
running = false;
|
uploadRunning = false;
|
||||||
// run the next file synchronization
|
// run the next file synchronization
|
||||||
synchronizer.run();
|
synchronizer.syncUp();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var fileSyncIndex = fileSyncIndexList.pop();
|
var fileSyncIndex = fileSyncIndexList.pop();
|
||||||
@ -92,28 +96,28 @@ var synchronizer = (function($) {
|
|||||||
if (fileSyncIndex.indexOf(SYNC_PROVIDER_GDRIVE) === 0) {
|
if (fileSyncIndex.indexOf(SYNC_PROVIDER_GDRIVE) === 0) {
|
||||||
var id = fileSyncIndex.substring(SYNC_PROVIDER_GDRIVE.length);
|
var id = fileSyncIndex.substring(SYNC_PROVIDER_GDRIVE.length);
|
||||||
gdrive.updateFile(id, title, content, function(result) {
|
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
|
// If we detect offline mode we put the fileIndex back in
|
||||||
// the queue
|
// the queue
|
||||||
synchronizer.addFile(localStorage["sync.current"]);
|
synchronizer.addFile(localStorage["sync.current"]);
|
||||||
localStorage.removeItem("sync.current");
|
localStorage.removeItem("sync.current");
|
||||||
running = false;
|
uploadRunning = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
sync(fileSyncIndexList, content, title);
|
fileUp(fileSyncIndexList, content, title);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
sync(fileSyncIndexList, content, title);
|
fileUp(fileSyncIndexList, content, title);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var running = false;
|
var uploadRunning = false;
|
||||||
synchronizer.run = function() {
|
synchronizer.syncUp = function() {
|
||||||
// If synchronization is already running or nothing to synchronize
|
// If syncUp is already running or nothing to synchronize or offline
|
||||||
if (running || syncQueue.length === 1 || offline) {
|
if (uploadRunning || syncQueue.length === 1 || offline) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
running = true;
|
uploadRunning = true;
|
||||||
|
|
||||||
// Dequeue the fileIndex
|
// Dequeue the fileIndex
|
||||||
var separatorPos = syncQueue.indexOf(";", 1);
|
var separatorPos = syncQueue.indexOf(";", 1);
|
||||||
@ -127,7 +131,101 @@ var synchronizer = (function($) {
|
|||||||
|
|
||||||
// Parse the list of synchronized locations associated to the file
|
// Parse the list of synchronized locations associated to the file
|
||||||
var fileSyncIndexList = localStorage[fileIndex + ".sync"].split(";");
|
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;
|
return synchronizer;
|
||||||
@ -143,19 +241,21 @@ var fileManager = (function($) {
|
|||||||
synchronizer.init();
|
synchronizer.init();
|
||||||
fileManager.selectFile();
|
fileManager.selectFile();
|
||||||
|
|
||||||
// Save file automatically and synchronize
|
// Do periodic stuff
|
||||||
window.setInterval(function() {
|
window.setInterval(function() {
|
||||||
currentTime = new Date().getTime();
|
currentTime = new Date().getTime();
|
||||||
fileManager.saveFile();
|
fileManager.saveFile();
|
||||||
synchronizer.run();
|
synchronizer.syncDown();
|
||||||
|
synchronizer.syncUp();
|
||||||
asyncTaskRunner.runTask();
|
asyncTaskRunner.runTask();
|
||||||
autoClean();
|
checkOnline();
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
$(".action-create-file").click(function() {
|
$(".action-create-file").click(function() {
|
||||||
fileManager.saveFile();
|
fileManager.saveFile();
|
||||||
fileManager.createFile();
|
fileManager.createFile();
|
||||||
fileManager.selectFile();
|
fileManager.selectFile();
|
||||||
|
$("#file-title").click();
|
||||||
});
|
});
|
||||||
$(".action-remove-file").click(function() {
|
$(".action-remove-file").click(function() {
|
||||||
fileManager.deleteFile();
|
fileManager.deleteFile();
|
||||||
@ -172,8 +272,7 @@ var fileManager = (function($) {
|
|||||||
var fileIndexTitle = localStorage["file.current"] + ".title";
|
var fileIndexTitle = localStorage["file.current"] + ".title";
|
||||||
if (title != localStorage[fileIndexTitle]) {
|
if (title != localStorage[fileIndexTitle]) {
|
||||||
localStorage[fileIndexTitle] = title;
|
localStorage[fileIndexTitle] = title;
|
||||||
updateFileDescList();
|
fileManager.updateFileTitles();
|
||||||
updateFileTitleUI();
|
|
||||||
save = true;
|
save = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -205,23 +304,18 @@ var fileManager = (function($) {
|
|||||||
localStorage["file.counter"] = 0;
|
localStorage["file.counter"] = 0;
|
||||||
localStorage["file.list"] = ";";
|
localStorage["file.list"] = ";";
|
||||||
}
|
}
|
||||||
updateFileDescList();
|
|
||||||
// If no file create one
|
// If no file create one
|
||||||
if (fileDescList.length === 0) {
|
if (localStorage["file.list"].length === 1) {
|
||||||
this.createFile();
|
this.createFile();
|
||||||
updateFileDescList();
|
|
||||||
}
|
}
|
||||||
// If no default file take first one
|
// Update the file titles
|
||||||
if (!localStorage["file.current"]) {
|
this.updateFileTitles();
|
||||||
localStorage["file.current"] = fileDescList[0].index;
|
// Update the editor
|
||||||
}
|
|
||||||
// Update the editor and the file title
|
|
||||||
var fileIndex = localStorage["file.current"];
|
var fileIndex = localStorage["file.current"];
|
||||||
$("#wmd-input").val(localStorage[fileIndex + ".content"]);
|
$("#wmd-input").val(localStorage[fileIndex + ".content"]);
|
||||||
core.createEditor(function() {
|
core.createEditor(function() {
|
||||||
save = true;
|
save = true;
|
||||||
});
|
});
|
||||||
updateFileTitleUI();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
fileManager.createFile = function(title) {
|
fileManager.createFile = function(title) {
|
||||||
@ -246,8 +340,8 @@ var fileManager = (function($) {
|
|||||||
// Remove synchronized locations
|
// Remove synchronized locations
|
||||||
var fileSyncIndexList = localStorage[fileIndex + ".sync"].split(";");
|
var fileSyncIndexList = localStorage[fileIndex + ".sync"].split(";");
|
||||||
for ( var i = 1; i < fileSyncIndexList.length - 1; i++) {
|
for ( var i = 1; i < fileSyncIndexList.length - 1; i++) {
|
||||||
var sync = fileSyncIndexList[i];
|
var fileSyncIndex = fileSyncIndexList[i];
|
||||||
fileManager.removeSync(sync);
|
fileManager.removeSync(fileSyncIndex);
|
||||||
}
|
}
|
||||||
localStorage.removeItem(fileIndex + ".sync");
|
localStorage.removeItem(fileIndex + ".sync");
|
||||||
|
|
||||||
@ -268,33 +362,7 @@ var fileManager = (function($) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Remove a synchronization location associated to the file
|
fileManager.updateFileTitles = function() {
|
||||||
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() {
|
|
||||||
fileDescList = [];
|
fileDescList = [];
|
||||||
$("#file-selector").empty();
|
$("#file-selector").empty();
|
||||||
var fileIndexList = localStorage["file.list"].split(";");
|
var fileIndexList = localStorage["file.list"].split(";");
|
||||||
@ -310,20 +378,24 @@ var fileManager = (function($) {
|
|||||||
return 1;
|
return 1;
|
||||||
return 0;
|
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) {
|
function composeTitle(fileIndex) {
|
||||||
var result = localStorage[fileIndex + ".title"];
|
var result = localStorage[fileIndex + ".title"];
|
||||||
var sync = localStorage[fileIndex + ".sync"];
|
var sync = localStorage[fileIndex + ".sync"];
|
||||||
if (sync.indexOf(SYNC_PROVIDER_GDRIVE) !== -1) {
|
if (sync.indexOf(";" + SYNC_PROVIDER_GDRIVE) !== -1) {
|
||||||
|
syncGoogleDrive = true;
|
||||||
result = '<i class="icon-gdrive"></i> ' + result;
|
result = '<i class="icon-gdrive"></i> ' + result;
|
||||||
}
|
}
|
||||||
return 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 fileIndex = localStorage["file.current"];
|
||||||
var title = localStorage[fileIndex + ".title"];
|
var title = localStorage[fileIndex + ".title"];
|
||||||
document.title = "StackEdit - " + title;
|
document.title = "StackEdit - " + title;
|
||||||
@ -348,8 +420,37 @@ var fileManager = (function($) {
|
|||||||
}
|
}
|
||||||
$("#file-selector").append(li);
|
$("#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() {
|
function refreshManageSync() {
|
||||||
var fileIndex = localStorage["file.current"];
|
var fileIndex = localStorage["file.current"];
|
||||||
@ -362,23 +463,24 @@ var fileManager = (function($) {
|
|||||||
$(".msg-no-sync").removeClass("hide");
|
$(".msg-no-sync").removeClass("hide");
|
||||||
}
|
}
|
||||||
for ( var i = 1; i < fileSyncIndexList.length - 1; i++) {
|
for ( var i = 1; i < fileSyncIndexList.length - 1; i++) {
|
||||||
var sync = fileSyncIndexList[i];
|
var fileSyncIndex = fileSyncIndexList[i];
|
||||||
(function(sync) {
|
(function(fileSyncIndex) {
|
||||||
var line = $("<div>").addClass("input-append");
|
var line = $("<div>").addClass("input-append");
|
||||||
if (sync.indexOf(SYNC_PROVIDER_GDRIVE) === 0) {
|
if (fileSyncIndex.indexOf(SYNC_PROVIDER_GDRIVE) === 0) {
|
||||||
line.append($("<input>").prop("type", "text").prop(
|
line.append($("<input>").prop("type", "text").prop(
|
||||||
"disabled", true).addClass("span5").val(
|
"disabled", true).addClass("span5").val(
|
||||||
"Google Drive, FileID="
|
"Google Drive, FileID="
|
||||||
+ sync.substring(SYNC_PROVIDER_GDRIVE.length)));
|
+ fileSyncIndex.substring(SYNC_PROVIDER_GDRIVE.length)));
|
||||||
line.append($("<a>").addClass("btn").html(
|
line.append($("<a>").addClass("btn").html(
|
||||||
'<i class="icon-trash"></i>').prop("title", "Remove this synchronized location").click(function() {
|
'<i class="icon-trash"></i>').prop("title",
|
||||||
fileManager.removeSync(sync);
|
"Remove this synchronized location").click(function() {
|
||||||
updateFileTitleUI();
|
fileManager.removeSync(fileSyncIndex);
|
||||||
|
fileManager.updateFileTitles();
|
||||||
refreshManageSync();
|
refreshManageSync();
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
$("#manage-sync-list").append(line);
|
$("#manage-sync-list").append(line);
|
||||||
})(sync);
|
})(fileSyncIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -493,6 +595,7 @@ var core = (function($) {
|
|||||||
|
|
||||||
$(function() {
|
$(function() {
|
||||||
|
|
||||||
|
$.jGrowl.defaults.life = 5000;
|
||||||
$.jGrowl.defaults.closer = false;
|
$.jGrowl.defaults.closer = false;
|
||||||
$.jGrowl.defaults.closeTemplate = '';
|
$.jGrowl.defaults.closeTemplate = '';
|
||||||
$.jGrowl.defaults.position = 'bottom-right';
|
$.jGrowl.defaults.position = 'bottom-right';
|
||||||
|
Loading…
Reference in New Issue
Block a user