Google Realtime synchronization
This commit is contained in:
parent
d1cb3db557
commit
7eaeb45806
@ -134,6 +134,7 @@ input::-webkit-input-placeholder,textarea::-webkit-input-placeholder {
|
||||
}
|
||||
|
||||
.navbar-inner .btn.disabled,
|
||||
.navbar-inner .btn.blocked,
|
||||
.navbar-inner .btn[disabled] {
|
||||
color: #333333;
|
||||
background-color: #ddd;
|
||||
|
@ -33,7 +33,7 @@
|
||||
<script src="js/libs/require.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="navbar" class="navbar navbar-fixed-top ui-layout-north">
|
||||
<div class="navbar navbar-fixed-top ui-layout-north">
|
||||
<div class="navbar-inner">
|
||||
|
||||
<ul class="nav">
|
||||
@ -303,7 +303,8 @@
|
||||
</div>
|
||||
<br /> <br />
|
||||
<p>
|
||||
<label class="checkbox"> <input id="input-sync-export-gdrive-realtime" type="checkbox">
|
||||
<label class="checkbox"> <input
|
||||
id="input-sync-export-gdrive-realtime" type="checkbox">
|
||||
Create a real time collaborative document
|
||||
</label>
|
||||
</p>
|
||||
@ -314,7 +315,9 @@
|
||||
your root folder.</li>
|
||||
<li>You can move or rename the file afterwards within Google
|
||||
Drive.</li>
|
||||
<li>Real time collaborative document can not have multiple
|
||||
<li>Real time collaborative documents can't be open outside
|
||||
StackEdit</li>
|
||||
<li>Real time collaborative documents can't have multiple
|
||||
synchronized locations.</li>
|
||||
</ul>
|
||||
</blockquote>
|
||||
|
30
js/core.js
30
js/core.js
@ -197,7 +197,7 @@ define([
|
||||
south__minSize: 200
|
||||
}));
|
||||
}
|
||||
$("#navbar").click(function() {
|
||||
$(".navbar").click(function() {
|
||||
layout.allowOverflow('north');
|
||||
});
|
||||
$(".ui-layout-toggler-north").addClass("btn").append($("<b>").addClass("caret"));
|
||||
@ -212,7 +212,6 @@ define([
|
||||
var editor = undefined;
|
||||
var fileDesc = undefined;
|
||||
var documentContent = undefined;
|
||||
var undoManager = undefined;
|
||||
core.initEditor = function(fileDescParam) {
|
||||
if(fileDesc !== undefined) {
|
||||
extensionMgr.onFileClosed(fileDesc);
|
||||
@ -224,7 +223,7 @@ define([
|
||||
editorElt.val(initDocumentContent);
|
||||
if(editor !== undefined) {
|
||||
// If the editor is already created
|
||||
undoManager.reinit(initDocumentContent, fileDesc.editorStart, fileDesc.editorEnd, fileDesc.editorScrollTop);
|
||||
editor.undoManager.reinit(initDocumentContent, fileDesc.editorStart, fileDesc.editorEnd, fileDesc.editorScrollTop);
|
||||
editor.refreshPreview();
|
||||
extensionMgr.onFileOpen(fileDesc);
|
||||
return;
|
||||
@ -310,9 +309,8 @@ define([
|
||||
}
|
||||
extensionMgr.onEditorConfigure(editor);
|
||||
editor.hooks.chain("onPreviewRefresh", extensionMgr.onAsyncPreview);
|
||||
undoManager = editor.run(previewWrapper);
|
||||
undoManager.reinit(initDocumentContent, fileDesc.editorStart, fileDesc.editorEnd, fileDesc.editorScrollTop);
|
||||
extensionMgr.onFileOpen(fileDesc);
|
||||
editor.run(previewWrapper);
|
||||
editor.undoManager.reinit(initDocumentContent, fileDesc.editorStart, fileDesc.editorEnd, fileDesc.editorScrollTop);
|
||||
|
||||
// Hide default buttons
|
||||
$(".wmd-button-row").addClass("btn-group").find("li:not(.wmd-spacer)").addClass("btn").css("left", 0).find("span").hide();
|
||||
@ -328,8 +326,15 @@ define([
|
||||
$("#wmd-ulist-button").append($("<i>").addClass("icon-list"));
|
||||
$("#wmd-heading-button").append($("<i>").addClass("icon-text-height"));
|
||||
$("#wmd-hr-button").append($("<i>").addClass("icon-hr"));
|
||||
$("#wmd-undo-button").append($("<i>").addClass("icon-undo"));
|
||||
$("#wmd-redo-button").append($("<i>").addClass("icon-share-alt"));
|
||||
// Create additional undo/redo button for real time synchronization
|
||||
var realtimeUndoButton = $('<li class="btn hide" id="wmd-undo-button-realtime" title="Undo - Ctrl+Z" style="left: 0px;">');
|
||||
realtimeUndoButton.append($("<i>").addClass("icon-undo"));
|
||||
$("#wmd-undo-button").append($("<i>").addClass("icon-undo")).after(realtimeUndoButton);
|
||||
var realtimeRedoButton = $('<li class="btn hide" id="wmd-redo-button-realtime" title="Redo - Ctrl+Shift+Z" style="left: 0px;">');
|
||||
realtimeRedoButton.append($("<i>").addClass("icon-share-alt"));
|
||||
$("#wmd-redo-button").append($("<i>").addClass("icon-share-alt")).after(realtimeRedoButton);
|
||||
|
||||
extensionMgr.onFileOpen(fileDesc);
|
||||
};
|
||||
|
||||
// Used to lock the editor from the user interaction during asynchronous tasks
|
||||
@ -337,14 +342,7 @@ define([
|
||||
core.lockUI = function(param) {
|
||||
uiLocked = param;
|
||||
$("#wmd-input").prop("disabled", uiLocked);
|
||||
$(".btn").each(function() {
|
||||
var classes = $(this).attr("class");
|
||||
if(uiLocked) {
|
||||
$(this).attr("class", classes + " disabled");
|
||||
} else {
|
||||
$(this).attr("class", classes.replace(" disabled", ""));
|
||||
}
|
||||
});
|
||||
$(".navbar-inner .btn").toggleClass("blocked", uiLocked);
|
||||
if(uiLocked) {
|
||||
$(".lock-ui").removeClass("hide");
|
||||
}
|
||||
|
@ -65,15 +65,17 @@ define([
|
||||
});
|
||||
};
|
||||
}
|
||||
extensionMgr.addHookCallback = function(hookName, callback) {
|
||||
hookCallbackList[hookName].push(callback);
|
||||
};
|
||||
|
||||
|
||||
// Add a Hook to the extensionMgr
|
||||
function addHook(hookName, noLog) {
|
||||
extensionMgr[hookName] = createHook(hookName, noLog);
|
||||
}
|
||||
|
||||
// Used by external modules to listen to extension events
|
||||
extensionMgr.addHookCallback = function(hookName, callback) {
|
||||
hookCallbackList[hookName].push(callback);
|
||||
};
|
||||
|
||||
// Set extension config
|
||||
extensionSettings = settings.extensionSettings || {};
|
||||
_.each(extensionList, function(extension) {
|
||||
|
@ -12,6 +12,11 @@ define([
|
||||
dialogManageSynchronization.onExtensionMgrCreated = function(extensionMgrParameter) {
|
||||
extensionMgr = extensionMgrParameter;
|
||||
};
|
||||
|
||||
var synchronizer = undefined;
|
||||
dialogManageSynchronization.onSynchronizerCreated = function(synchronizerParameter) {
|
||||
synchronizer = synchronizerParameter;
|
||||
};
|
||||
|
||||
var fileDesc = undefined;
|
||||
var removeButtonTemplate = '<a class="btn" title="Remove this location"><i class="icon-trash"></i></a>';
|
||||
@ -36,6 +41,7 @@ define([
|
||||
syncDesc: syncDesc
|
||||
}));
|
||||
lineElement.append($(removeButtonTemplate).click(function() {
|
||||
synchronizer.tryStopRealtimeSync();
|
||||
fileDesc.removeSyncLocation(syncAttributes);
|
||||
extensionMgr.onSyncRemoved(fileDesc, syncAttributes);
|
||||
}));
|
||||
|
@ -180,7 +180,7 @@ define([
|
||||
});
|
||||
task.enqueue();
|
||||
};
|
||||
|
||||
|
||||
googleHelper.createRealtimeFile = function(parentId, title, callback) {
|
||||
var result = undefined;
|
||||
var task = new AsyncTask();
|
||||
@ -189,7 +189,7 @@ define([
|
||||
task.onRun(function() {
|
||||
var metadata = {
|
||||
title: title,
|
||||
mimeType : 'application/vnd.google-apps.drive-sdk',
|
||||
mimeType: 'application/vnd.google-apps.drive-sdk',
|
||||
};
|
||||
if(parentId !== undefined) {
|
||||
// Specify the directory
|
||||
@ -201,7 +201,7 @@ define([
|
||||
];
|
||||
}
|
||||
var request = gapi.client.drive.files.insert({
|
||||
'resource' : metadata
|
||||
'resource': metadata
|
||||
});
|
||||
request.execute(function(response) {
|
||||
if(response && response.id) {
|
||||
@ -413,6 +413,14 @@ define([
|
||||
task.chain(recursiveDownloadContent);
|
||||
return;
|
||||
}
|
||||
// if file is a real time document
|
||||
if(file.mimeType.indexOf("application/vnd.google-apps.drive-sdk") === 0) {
|
||||
file.content = "";
|
||||
file.isRealtime = true;
|
||||
objects.shift();
|
||||
task.chain(recursiveDownloadContent);
|
||||
return;
|
||||
}
|
||||
var headers = {};
|
||||
var token = gapi.auth.getToken();
|
||||
if(token) {
|
||||
@ -449,7 +457,7 @@ define([
|
||||
});
|
||||
task.enqueue();
|
||||
};
|
||||
|
||||
|
||||
googleHelper.loadRealtime = function(fileId, content, callback) {
|
||||
var doc = undefined;
|
||||
var task = new AsyncTask();
|
||||
@ -469,9 +477,9 @@ define([
|
||||
handleError({
|
||||
code: err.type
|
||||
}, task);
|
||||
});
|
||||
task.onSuccess(function() {
|
||||
callback(undefined, doc);
|
||||
});
|
||||
task.onSuccess(function() {
|
||||
callback(undefined, doc);
|
||||
});
|
||||
});
|
||||
task.onError(function(error) {
|
||||
@ -560,7 +568,12 @@ define([
|
||||
pickerBuilder.setAppId(GOOGLE_DRIVE_APP_ID);
|
||||
if(!isImagePicker) {
|
||||
var view = new google.picker.View(google.picker.ViewId.DOCS);
|
||||
view.setMimeTypes("text/x-markdown,text/plain,application/octet-stream");
|
||||
view.setMimeTypes([
|
||||
"text/x-markdown",
|
||||
"text/plain",
|
||||
"application/octet-stream",
|
||||
"application/vnd.google-apps.drive-sdk." + GOOGLE_DRIVE_APP_ID
|
||||
].join(","));
|
||||
pickerBuilder.enableFeature(google.picker.Feature.NAV_HIDDEN);
|
||||
pickerBuilder.enableFeature(google.picker.Feature.MULTISELECT_ENABLED);
|
||||
pickerBuilder.addView(view);
|
||||
|
@ -148,7 +148,8 @@
|
||||
|
||||
//Not necessary
|
||||
//forceRefresh();
|
||||
return undoManager;
|
||||
that.undoManager = undoManager;
|
||||
that.uiManager = uiManager;
|
||||
};
|
||||
|
||||
}
|
||||
@ -663,7 +664,7 @@
|
||||
}
|
||||
};
|
||||
|
||||
util.addEvent(panels.input, "keydown", handleCtrlYZ);
|
||||
//util.addEvent(panels.input, "keydown", handleCtrlYZ);
|
||||
util.addEvent(panels.input, "keydown", handleModeChange);
|
||||
util.addEvent(panels.input, "mousedown", function () {
|
||||
setMode("moving");
|
||||
@ -694,6 +695,7 @@
|
||||
inputStateObj.setInputAreaSelection();
|
||||
saveState();
|
||||
};
|
||||
this.setMode = setMode;
|
||||
|
||||
init();
|
||||
}
|
||||
@ -1251,7 +1253,7 @@
|
||||
util.addEvent(inputBox, keyEvent, function (key) {
|
||||
|
||||
// Check to see if we have a button key and, if so execute the callback.
|
||||
if ((key.ctrlKey || key.metaKey) && !key.altKey && !key.shiftKey) {
|
||||
if ((key.ctrlKey || key.metaKey) && !key.altKey) {
|
||||
|
||||
var keyCode = key.charCode || key.keyCode;
|
||||
var keyCodeStr = String.fromCharCode(keyCode).toLowerCase();
|
||||
@ -1298,6 +1300,12 @@
|
||||
doClick(buttons.undo);
|
||||
}
|
||||
break;
|
||||
case "v":
|
||||
undoManager.setMode("typing");
|
||||
return;
|
||||
case "x":
|
||||
undoManager.setMode("deleting");
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
@ -1547,6 +1555,8 @@
|
||||
};
|
||||
|
||||
this.setUndoRedoButtonStates = setUndoRedoButtonStates;
|
||||
this.buttons = buttons;
|
||||
this.setButtonState = setupButton;
|
||||
|
||||
}
|
||||
|
||||
|
@ -42,16 +42,18 @@ define([
|
||||
return;
|
||||
}
|
||||
var fileDescList = [];
|
||||
var fileDesc = undefined;
|
||||
_.each(result, function(file) {
|
||||
var syncAttributes = createSyncAttributes(file.id, file.etag, file.content, file.title);
|
||||
syncAttributes.isRealtime = file.isRealtime;
|
||||
var syncLocations = {};
|
||||
syncLocations[syncAttributes.syncIndex] = syncAttributes;
|
||||
var fileDesc = fileMgr.createFile(file.title, file.content, syncLocations);
|
||||
fileMgr.selectFile(fileDesc);
|
||||
fileDesc = fileMgr.createFile(file.title, file.content, syncLocations);
|
||||
fileDescList.push(fileDesc);
|
||||
});
|
||||
if(fileDescList.length !== 0) {
|
||||
if(fileDesc !== undefined) {
|
||||
extensionMgr.onSyncImportSuccess(fileDescList, gdriveProvider);
|
||||
fileMgr.selectFile(fileDesc);
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -154,7 +156,9 @@ define([
|
||||
_.each(changes, function(change) {
|
||||
var syncIndex = createSyncIndex(change.fileId);
|
||||
var syncAttributes = fileMgr.getSyncAttributes(syncIndex);
|
||||
if(syncAttributes === undefined) {
|
||||
// If file is not synchronized or it's a real time synchronized location
|
||||
if(syncAttributes === undefined || syncAttributes.isRealtime === true) {
|
||||
// Skip it
|
||||
return;
|
||||
}
|
||||
// Store syncAttributes to avoid 2 times searching
|
||||
@ -261,31 +265,75 @@ define([
|
||||
});
|
||||
|
||||
// Start realtime synchronization
|
||||
var binding = undefined;
|
||||
gdriveProvider.startSync = function(content, syncAttributes, callback) {
|
||||
var realtimeDocument = undefined;
|
||||
var realtimeBinding = undefined;
|
||||
var undoExecute = undefined;
|
||||
var redoExecute = undefined;
|
||||
gdriveProvider.startRealtimeSync = function(content, syncAttributes, callback) {
|
||||
logger.log("Starting Google Drive realtime synchronization");
|
||||
googleHelper.loadRealtime(syncAttributes.id, content, function(err, doc) {
|
||||
if(err || !doc) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
var string = doc.getModel().getRoot().get('content');
|
||||
binding = gapi.drive.realtime.databinding.bindString(string, $("#wmd-input")[0]);
|
||||
// Listen to
|
||||
realtimeDocument = doc;
|
||||
var model = realtimeDocument.getModel();
|
||||
var string = model.getRoot().get('content');
|
||||
realtimeBinding = gapi.drive.realtime.databinding.bindString(string, $("#wmd-input")[0]);
|
||||
|
||||
// Listen to text changed events
|
||||
var debouncedRefreshPreview = _.debounce(editor.refreshPreview, 100);
|
||||
string.addEventListener(gapi.drive.realtime.EventType.TEXT_INSERTED, debouncedRefreshPreview);
|
||||
string.addEventListener(gapi.drive.realtime.EventType.TEXT_DELETED, debouncedRefreshPreview);
|
||||
debouncedRefreshPreview();
|
||||
|
||||
// Add event handler for UndoRedoStateChanged events.
|
||||
undoExecute = editor.uiManager.buttons.undo.execute;
|
||||
redoExecute = editor.uiManager.buttons.redo.execute;
|
||||
|
||||
// var undoButton = $('#wmd-undo-button-realtime').removeClass("hide");
|
||||
// var redoButton = $('#wmd-redo-button-realtime').removeClass("hide");
|
||||
// $('#wmd-undo-button').addClass("hide");
|
||||
// $('#wmd-redo-button').addClass("hide");
|
||||
editor.uiManager.buttons.undo.execute = function() {
|
||||
model.canUndo && model.undo();
|
||||
};
|
||||
editor.uiManager.buttons.redo.execute = function() {
|
||||
model.canRedo && model.redo();
|
||||
};
|
||||
function setUndoRedoState() {
|
||||
editor.uiManager.setButtonState(editor.uiManager.buttons.undo, model.canUndo);
|
||||
editor.uiManager.setButtonState(editor.uiManager.buttons.redo, model.canRedo);
|
||||
}
|
||||
model.addEventListener(
|
||||
gapi.drive.realtime.EventType.UNDO_REDO_STATE_CHANGED,
|
||||
setUndoRedoState);
|
||||
setUndoRedoState();
|
||||
|
||||
callback();
|
||||
});
|
||||
};
|
||||
|
||||
// Stop realtime synchronization
|
||||
gdriveProvider.stopSync = function(syncAttributes) {
|
||||
gdriveProvider.stopRealtimeSync = function() {
|
||||
logger.log("Stopping Google Drive realtime synchronization");
|
||||
if(binding !== undefined) {
|
||||
binding.unbind();
|
||||
binding = undefined;
|
||||
if(realtimeBinding !== undefined) {
|
||||
realtimeBinding.unbind();
|
||||
realtimeBinding = undefined;
|
||||
}
|
||||
if(realtimeDocument !== undefined) {
|
||||
realtimeDocument.close();
|
||||
realtimeDocument = undefined;
|
||||
}
|
||||
|
||||
editor.uiManager.buttons.undo.execute = undoExecute;
|
||||
editor.uiManager.buttons.redo.execute = redoExecute;
|
||||
editor.uiManager.setUndoRedoButtonStates();
|
||||
|
||||
// $('#wmd-undo-button-realtime').off('click').addClass("hide");
|
||||
// $('#wmd-redo-button-realtime').off('click').addClass("hide");
|
||||
// $('#wmd-undo-button').removeClass("hide");
|
||||
// $('#wmd-redo-button').removeClass("hide");
|
||||
};
|
||||
|
||||
core.onReady(function() {
|
||||
|
@ -46,6 +46,10 @@ define([
|
||||
});
|
||||
});
|
||||
|
||||
/***************************************************************************
|
||||
* Standard synchronization
|
||||
**************************************************************************/
|
||||
|
||||
// Recursive function to upload a single file on multiple locations
|
||||
var uploadSyncAttributesList = [];
|
||||
var uploadContent = undefined;
|
||||
@ -63,6 +67,12 @@ define([
|
||||
// Dequeue a synchronized location
|
||||
var syncAttributes = uploadSyncAttributesList.pop();
|
||||
|
||||
// Skip real time synchronized location
|
||||
if(syncAttributes.isRealtime === true) {
|
||||
locationUp(callback);
|
||||
return;
|
||||
}
|
||||
|
||||
// Use the specified provider to perform the upload
|
||||
syncAttributes.provider.syncUp(uploadContent, uploadContentCRC, uploadTitle, uploadTitleCRC, syncAttributes, function(error, uploadFlag) {
|
||||
if(uploadFlag === true) {
|
||||
@ -188,30 +198,65 @@ define([
|
||||
return true;
|
||||
};
|
||||
|
||||
// Used for realtime synchronization
|
||||
/***************************************************************************
|
||||
* Realtime synchronization
|
||||
**************************************************************************/
|
||||
|
||||
var realtimeFileDesc = undefined;
|
||||
var realtimeSyncAttributes = undefined;
|
||||
var isOnline = true;
|
||||
|
||||
// Determines if open file has real time sync location and tries to start
|
||||
// real time sync
|
||||
function onFileOpen(fileDesc) {
|
||||
_.each(fileDesc.syncLocations, function(syncAttributes) {
|
||||
if(syncAttributes.isRealtime) {
|
||||
core.lockUI(true);
|
||||
syncAttributes.provider.startSync(fileDesc.content, syncAttributes, function() {
|
||||
core.lockUI(false);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
function onFileClosed(fileDesc) {
|
||||
_.each(fileDesc.syncLocations, function(syncAttributes) {
|
||||
if(syncAttributes.isRealtime) {
|
||||
syncAttributes.provider.stopSync(syncAttributes);
|
||||
}
|
||||
});
|
||||
}
|
||||
// Enable realtime synchronization
|
||||
if(viewerMode === false) {
|
||||
extensionMgr.addHookCallback("onFileOpen", onFileOpen);
|
||||
extensionMgr.addHookCallback("onFileClosed", onFileClosed);
|
||||
realtimeFileDesc = _.some(fileDesc.syncLocations, function(syncAttributes) {
|
||||
realtimeSyncAttributes = syncAttributes;
|
||||
return syncAttributes.isRealtime;
|
||||
}) ? fileDesc : undefined;
|
||||
tryStartRealtimeSync();
|
||||
}
|
||||
|
||||
// Tries to start/stop real time sync on online/offline event
|
||||
function onOfflineChanged(isOfflineParam) {
|
||||
if(isOfflineParam === false) {
|
||||
isOnline = true;
|
||||
tryStartRealtimeSync();
|
||||
}
|
||||
else {
|
||||
synchronizer.tryStopRealtimeSync();
|
||||
isOnline = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Starts real time synchronization if:
|
||||
// 1. current file has real time sync location
|
||||
// 2. we are online
|
||||
function tryStartRealtimeSync() {
|
||||
if(realtimeFileDesc !== undefined && isOnline === true) {
|
||||
core.lockUI(true);
|
||||
realtimeSyncAttributes.provider.startRealtimeSync(realtimeFileDesc.content, realtimeSyncAttributes, function() {
|
||||
core.lockUI(false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Stops previously started synchronization if any
|
||||
synchronizer.tryStopRealtimeSync = function() {
|
||||
if(realtimeFileDesc !== undefined && isOnline === true) {
|
||||
realtimeSyncAttributes.provider.stopRealtimeSync();
|
||||
}
|
||||
};
|
||||
|
||||
// Triggers realtime synchronization from extensionMgr events
|
||||
if(viewerMode === false) {
|
||||
extensionMgr.addHookCallback("onFileOpen", onFileOpen);
|
||||
extensionMgr.addHookCallback("onFileClosed", synchronizer.tryStopRealtimeSync);
|
||||
extensionMgr.addHookCallback("onOfflineChanged", onOfflineChanged);
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
* Initialize module
|
||||
**************************************************************************/
|
||||
|
||||
// Initialize the export dialog
|
||||
function initExportDialog(provider) {
|
||||
@ -250,7 +295,7 @@ define([
|
||||
if(_.size(fileDesc.syncLocations) > 0) {
|
||||
extensionMgr.onError("Realtime collaboration document can't be synchronized with multiple locations");
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Perform the provider's real time export
|
||||
provider.exportRealtimeFile(event, fileDesc.title, fileDesc.content, function(error, syncAttributes) {
|
||||
if(error) {
|
||||
@ -259,13 +304,18 @@ define([
|
||||
syncAttributes.isRealtime = true;
|
||||
fileDesc.addSyncLocation(syncAttributes);
|
||||
extensionMgr.onSyncExportSuccess(fileDesc, syncAttributes);
|
||||
|
||||
// Start the real time sync
|
||||
realtimeFileDesc = fileDesc;
|
||||
realtimeSyncAttributes = syncAttributes;
|
||||
tryStartRealtimeSync();
|
||||
});
|
||||
}
|
||||
else {
|
||||
if(_.size(fileDesc.syncLocations) > 0 && _.first(_.values(obj)).isRealtime) {
|
||||
extensionMgr.onError("Realtime collaboration document can't be synchronized with multiple locations");
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Perform the provider's standard export
|
||||
provider.exportFile(event, fileDesc.title, fileDesc.content, function(error, syncAttributes) {
|
||||
if(error) {
|
||||
@ -290,7 +340,7 @@ define([
|
||||
if(_.size(fileDesc.syncLocations) > 0 && _.first(_.values(obj)).isRealtime) {
|
||||
extensionMgr.onError("Realtime collaboration document can't be synchronized with multiple locations");
|
||||
return;
|
||||
}
|
||||
}
|
||||
provider.exportManual(event, fileDesc.title, fileDesc.content, function(error, syncAttributes) {
|
||||
if(error) {
|
||||
return;
|
||||
|
@ -44,6 +44,7 @@ textarea[disabled],
|
||||
}
|
||||
|
||||
.navbar-inner .btn.disabled,
|
||||
.navbar-inner .btn.blocked,
|
||||
.navbar-inner .btn[disabled] {
|
||||
background-color: #ced5de;
|
||||
}
|
||||
|
@ -111,6 +111,7 @@ blockquote {
|
||||
}
|
||||
|
||||
.navbar-inner .btn.disabled,
|
||||
.navbar-inner .btn.blocked,
|
||||
.navbar-inner .btn[disabled] {
|
||||
background-color: #444;
|
||||
}
|
||||
|
@ -28,7 +28,7 @@
|
||||
<script src="js/libs/require.js"></script>
|
||||
</head>
|
||||
<body class="viewer">
|
||||
<div id="navbar" class="navbar navbar-fixed-top ui-layout-north">
|
||||
<div class="navbar navbar-fixed-top ui-layout-north">
|
||||
<div class="navbar-inner">
|
||||
|
||||
<ul class="nav pull-right hide" id="menu-bar">
|
||||
|
Loading…
Reference in New Issue
Block a user