From ea77258caceca44c76071b2c604b1f10b8847aaf Mon Sep 17 00:00:00 2001 From: benweet Date: Wed, 9 Apr 2014 00:20:48 +0100 Subject: [PATCH] Implemented fast defer --- public/res/classes/Provider.js | 11 ++-- public/res/core.js | 2 +- public/res/editor.js | 58 +++++++++---------- public/res/eventMgr.js | 4 +- public/res/extensions/buttonHtmlCode.js | 4 +- public/res/extensions/comments.js | 48 +++++++-------- public/res/extensions/documentSelector.js | 8 +-- public/res/extensions/scrollLink.js | 8 +-- public/res/fileMgr.js | 4 +- .../res/html/settingsExtensionsAccordion.html | 2 +- public/res/providers/gdriveProviderBuilder.js | 18 ++---- public/res/styles/main.less | 5 +- public/res/utils.js | 34 +++++++++++ 13 files changed, 112 insertions(+), 94 deletions(-) diff --git a/public/res/classes/Provider.js b/public/res/classes/Provider.js index 8544ee86..f52ea357 100644 --- a/public/res/classes/Provider.js +++ b/public/res/classes/Provider.js @@ -214,9 +214,7 @@ define([ patch = diffMatchPatch.patch_make(oldContent, diffs); var patchResult = diffMatchPatch.patch_apply(patch, remoteContent); newContent = patchResult[0]; - if(patchResult[1].some(function(patchSuccess) { - return !patchSuccess; - })) { + if(!patchResult[1].every(_.identity)) { // Remaining conflicts diffs = diffMatchPatch.diff_main(localContent, newContent); diffs = cleanupDiffs(diffs); @@ -328,8 +326,7 @@ define([ } if(contentChanged || discussionListChanged) { - var self = this; - editor.watcher.noWatch(function() { + editor.watcher.noWatch(_.bind(function() { if(contentChanged) { if(!/\n$/.test(newContent)) { newContent += '\n'; @@ -363,11 +360,11 @@ define([ } editor.undoMgr.currentMode = 'sync'; editor.undoMgr.saveState(); - eventMgr.onMessage('"' + remoteTitle + '" has been updated from ' + self.providerName + '.'); + eventMgr.onMessage('"' + remoteTitle + '" has been updated from ' + this.providerName + '.'); if(conflictList.length) { eventMgr.onMessage('"' + remoteTitle + '" has conflicts that you have to review.'); } - }); + }), this); } // Return remote CRCs diff --git a/public/res/core.js b/public/res/core.js index 22b0bc7c..ba14c2b8 100644 --- a/public/res/core.js +++ b/public/res/core.js @@ -826,7 +826,7 @@ define([ if(openedTooltip && openedTooltip[0] === elt) { return; } - _.defer(function() { + utils.defer(function() { $(document).on("click.close-tooltip", function() { openedTooltip && openedTooltip.tooltip('hide'); openedTooltip = undefined; diff --git a/public/res/editor.js b/public/res/editor.js index 94f8baea..77ff40d1 100644 --- a/public/res/editor.js +++ b/public/res/editor.js @@ -3,6 +3,7 @@ define([ 'jquery', 'underscore', + 'utils', 'settings', 'eventMgr', 'prism-core', @@ -12,7 +13,7 @@ define([ 'rangy', 'MutationObservers', 'libs/prism-markdown' -], function ($, _, settings, eventMgr, Prism, diff_match_patch, jsondiffpatch, crel, rangy) { +], function ($, _, utils, settings, eventMgr, Prism, diff_match_patch, jsondiffpatch, crel, rangy) { var editor = {}; var scrollTop = 0; @@ -213,7 +214,7 @@ define([ } undoMgr.saveSelectionState(); } - var debouncedSave = _.debounce(save, 0); + var debouncedSave = utils.debounce(save); return function(debounced) { debounced ? debouncedSave() : save(); }; @@ -287,12 +288,10 @@ define([ } var selectionMgr = new SelectionMgr(); editor.selectionMgr = selectionMgr; - $(document).on('selectionchange', function() { - selectionMgr.saveSelectionState(true); - }); + $(document).on('selectionchange', _.bind(selectionMgr.saveSelectionState, selectionMgr, true)); var adjustCursorPosition = (function() { - var adjust = _.debounce(function() { + var adjust = utils.debounce(function() { var adjust = inputElt.offsetHeight / 2; if(adjust > 130) { adjust = 130; @@ -305,7 +304,7 @@ define([ else if(selectionMgr.cursorY > cursorMaxY) { inputElt.scrollTop += selectionMgr.cursorY - cursorMaxY; } - }, 0); + }); return function() { if(inputElt === undefined) { return; @@ -365,7 +364,7 @@ define([ }; this.setMode = function() {}; // For compatibility with PageDown this.onButtonStateChange = function() {}; // To be overridden by PageDown - this.saveState = _.debounce(function() { + this.saveState = utils.debounce(function() { redoStack = []; var currentTime = Date.now(); if(this.currentMode == 'comment' || (this.currentMode != lastMode && lastMode != 'newlines') || currentTime - lastTime > 1000) { @@ -391,7 +390,7 @@ define([ lastMode = this.currentMode; this.currentMode = undefined; this.onButtonStateChange(); - }, 0); + }, this); this.saveSelectionState = _.debounce(function() { if(this.currentMode === undefined) { selectionStartBefore = selectionMgr.selectionStart; @@ -404,7 +403,6 @@ define([ this.canRedo = function() { return redoStack.length; }; - var self = this; function restoreState(state, selectionStart, selectionEnd) { // Update editor watcher.noWatch(function() { @@ -439,9 +437,9 @@ define([ selectionStartBefore = selectionStart; selectionEndBefore = selectionEnd; currentState = state; - self.currentMode = undefined; + this.currentMode = undefined; lastMode = undefined; - self.onButtonStateChange(); + this.onButtonStateChange(); adjustCursorPosition(); } this.undo = function() { @@ -450,7 +448,7 @@ define([ return; } redoStack.push(currentState); - restoreState(state, currentState.selectionStartBefore, currentState.selectionEndBefore); + restoreState.call(this, state, currentState.selectionStartBefore, currentState.selectionEndBefore); }; this.redo = function() { var state = redoStack.pop(); @@ -458,7 +456,7 @@ define([ return; } undoStack.push(currentState); - restoreState(state, state.selectionStartAfter, state.selectionEndAfter); + restoreState.call(this, state, state.selectionStartAfter, state.selectionEndAfter); }; this.init = function() { var content = fileDesc.content; @@ -481,7 +479,7 @@ define([ function onComment() { if(watcher.isWatching === true) { - undoMgr.currentMode = 'comment'; + undoMgr.currentMode = undoMgr.currentMode || 'comment'; undoMgr.saveState(); } } @@ -490,19 +488,19 @@ define([ eventMgr.addListener('onCommentsChanged', onComment); function checkContentChange() { - var currentTextContent = inputElt.textContent; + var newTextContent = inputElt.textContent; if(fileChanged === false) { - if(currentTextContent == textContent) { + if(newTextContent == textContent) { return; } - if(!/\n$/.test(currentTextContent)) { - currentTextContent += '\n'; + if(!/\n$/.test(newTextContent)) { + newTextContent += '\n'; } undoMgr.currentMode = undoMgr.currentMode || 'typing'; var discussionList = _.values(fileDesc.discussionList); fileDesc.newDiscussion && discussionList.push(fileDesc.newDiscussion); - var updateDiscussionList = adjustCommentOffsets(textContent, currentTextContent, discussionList); - textContent = currentTextContent; + var updateDiscussionList = adjustCommentOffsets(textContent, newTextContent, discussionList); + textContent = newTextContent; if(updateDiscussionList === true) { fileDesc.discussionList = fileDesc.discussionList; // Write discussionList in localStorage } @@ -513,11 +511,11 @@ define([ undoMgr.saveState(); } else { - if(!/\n$/.test(currentTextContent)) { - currentTextContent += '\n'; - fileDesc.content = currentTextContent; + textContent = newTextContent; + if(!/\n$/.test(textContent)) { + textContent += '\n'; + fileDesc.content = textContent; } - textContent = currentTextContent; selectionMgr.setSelectionStartEnd(fileDesc.editorStart, fileDesc.editorEnd); eventMgr.onFileOpen(fileDesc, textContent); previewElt.scrollTop = fileDesc.previewScrollTop; @@ -575,10 +573,10 @@ define([ } editor.adjustCommentOffsets = adjustCommentOffsets; - editor.init = function(elt1, elt2) { - inputElt = elt1; + editor.init = function(inputEltParam, previewEltParam) { + inputElt = inputEltParam; $inputElt = $(inputElt); - previewElt = elt2; + previewElt = previewEltParam; contentElt = crel('div', { class: 'editor-content', @@ -676,9 +674,7 @@ define([ clearNewline = false; } }) - .on('mouseup', function() { - selectionMgr.saveSelectionState(true); - }) + .on('mouseup', _.bind(selectionMgr.saveSelectionState, selectionMgr, true)) .on('paste', function () { undoMgr.currentMode = 'paste'; adjustCursorPosition(); diff --git a/public/res/eventMgr.js b/public/res/eventMgr.js index ddb2f48f..d1a9edce 100644 --- a/public/res/eventMgr.js +++ b/public/res/eventMgr.js @@ -228,7 +228,7 @@ define([ logger.log("onAsyncPreview"); function recursiveCall(callbackList) { var callback = callbackList.length ? callbackList.shift() : function() { - _.defer(function() { + setTimeout(function() { var html = ""; _.each(previewContentsElt.children, function(elt) { html += elt.innerHTML; @@ -236,7 +236,7 @@ define([ var htmlWithComments = utils.trim(html); var htmlWithoutComments = htmlWithComments.replace(/ .*?<\/span> /g, ''); onPreviewFinished(htmlWithComments, htmlWithoutComments); - }); + }, 10); }; callback(function() { recursiveCall(callbackList); diff --git a/public/res/extensions/buttonHtmlCode.js b/public/res/extensions/buttonHtmlCode.js index 7b22656b..dad62f8f 100644 --- a/public/res/extensions/buttonHtmlCode.js +++ b/public/res/extensions/buttonHtmlCode.js @@ -44,14 +44,14 @@ define([ buttonHtmlCode.onReady = function() { var textareaElt = document.getElementById('input-html-code'); $(".action-html-code").click(function() { - _.defer(function() { + setTimeout(function() { $("#input-html-code").each(function() { if($(this).is(":hidden")) { return; } this.select(); }); - }); + }, 10); }).parent().on('show.bs.dropdown', function() { try { var htmlCode = _.template(buttonHtmlCode.config.template, { diff --git a/public/res/extensions/comments.js b/public/res/extensions/comments.js index d87114c4..20591bbe 100644 --- a/public/res/extensions/comments.js +++ b/public/res/extensions/comments.js @@ -170,7 +170,7 @@ define([ marginElt.appendChild(commentElt); commentEltMap[discussion.discussionIndex] = commentElt; - if(currentContext && currentContext.getDiscussion() == discussion) { + if(currentContext && currentContext.getDiscussion() === discussion) { inputElt.scrollTop += parseInt(commentElt.style.top) - inputElt.scrollTop - inputElt.offsetHeight * 3 / 4; movePopover(commentElt); } @@ -179,6 +179,7 @@ define([ clearTimeout(refreshTimeoutId); refreshTimeoutId = setTimeout(refreshOne, 5); }, 50); + comments.onLayoutResize = refreshDiscussions; comments.onFileOpen = function(fileDesc) { currentFileDesc = fileDesc; @@ -237,10 +238,6 @@ define([ } }; - comments.onLayoutResize = function() { - refreshDiscussions(); - }; - function getDiscussionComments() { var discussion = currentContext.getDiscussion(); var author = storage['author.name']; @@ -310,29 +307,28 @@ define([ closeCurrentPopover(); var context = new Context(evt.target, currentFileDesc); currentContext = context; + + // If it's not an existing discussion + var discussion = context.getDiscussion(); + if(!discussion) { + // Get selected text + var selectionStart = Math.min(selectionMgr.selectionStart, selectionMgr.selectionEnd); + var selectionEnd = Math.max(selectionMgr.selectionStart, selectionMgr.selectionEnd); + if(selectionStart === selectionEnd) { + var offset = selectionMgr.getClosestWordOffset(selectionStart); + selectionStart = offset.start; + selectionEnd = offset.end; + } + discussion = { + selectionStart: selectionStart, + selectionEnd: selectionEnd, + commentList: [] + }; + currentFileDesc.newDiscussion = discussion; + } + context.selectionRange = selectionMgr.setSelectionStartEnd(discussion.selectionStart, discussion.selectionEnd, undefined, true); inputElt.scrollTop += parseInt(evt.target.style.top) - inputElt.scrollTop - inputElt.offsetHeight * 3 / 4; - // If it's an existing discussion - var discussion = context.getDiscussion(); - if(discussion) { - context.selectionRange = selectionMgr.setSelectionStartEnd(discussion.selectionStart, discussion.selectionEnd, undefined, true); - return; - } - - // Get selected text - var selectionStart = Math.min(selectionMgr.selectionStart, selectionMgr.selectionEnd); - var selectionEnd = Math.max(selectionMgr.selectionStart, selectionMgr.selectionEnd); - if(selectionStart === selectionEnd) { - var offset = selectionMgr.getClosestWordOffset(selectionStart); - selectionStart = offset.start; - selectionEnd = offset.end; - } - context.selectionRange = selectionMgr.setSelectionStartEnd(selectionStart, selectionEnd, undefined, true); - currentFileDesc.newDiscussion = { - selectionStart: selectionStart, - selectionEnd: selectionEnd, - commentList: [] - }; }).on('shown.bs.popover', '#wmd-input > .editor-margin', function(evt) { var context = currentContext; var popoverElt = context.getPopoverElt(); diff --git a/public/res/extensions/documentSelector.js b/public/res/extensions/documentSelector.js index 7979d2a2..36c929c9 100644 --- a/public/res/extensions/documentSelector.js +++ b/public/res/extensions/documentSelector.js @@ -130,9 +130,9 @@ define([ liIndex = -1; } selectedLi = liEltList[(liIndex + liEltList.length) % liEltList.length]; - _.defer(function() { + setTimeout(function() { selectedLi.find("a").focus(); - }); + }, 10); return false; }); var shortcutNext = documentSelector.config.shortcutNext.toLowerCase(); @@ -143,9 +143,9 @@ define([ } var liIndex = _.indexOf(liEltList, selectedLi) + 1; selectedLi = liEltList[liIndex % liEltList.length]; - _.defer(function() { + setTimeout(function() { selectedLi.find("a").focus(); - }); + }, 10); return false; }); var delimiter1 = shortcutPrevious.indexOf("+"); diff --git a/public/res/extensions/scrollLink.js b/public/res/extensions/scrollLink.js index 6b0d995b..36aa60f3 100644 --- a/public/res/extensions/scrollLink.js +++ b/public/res/extensions/scrollLink.js @@ -143,9 +143,9 @@ define([ $previewElt.scrollTop(lastPreviewScrollTop); }, done: function() { - _.defer(function() { + setTimeout(function() { isPreviewMoving = false; - }); + }, 10); }, }).dequeue('scrollLinkFx'); @@ -179,9 +179,9 @@ define([ $editorElt.scrollTop(lastEditorScrollTop); }, done: function() { - _.defer(function() { + setTimeout(function() { isEditorMoving = false; - }); + }, 10); }, }).dequeue('scrollLinkFx'); } diff --git a/public/res/fileMgr.js b/public/res/fileMgr.js index 50cb2721..767ec863 100644 --- a/public/res/fileMgr.js +++ b/public/res/fileMgr.js @@ -186,9 +186,9 @@ define([ } $fileTitleElt.addClass('hide'); var fileTitleInput = $fileTitleInputElt.removeClass('hide'); - _.defer(function() { + setTimeout(function() { fileTitleInput.focus().get(0).select(); - }); + }, 10); }); function applyTitle() { $fileTitleInputElt.addClass('hide'); diff --git a/public/res/html/settingsExtensionsAccordion.html b/public/res/html/settingsExtensionsAccordion.html index ff2321ac..2360f0e6 100644 --- a/public/res/html/settingsExtensionsAccordion.html +++ b/public/res/html/settingsExtensionsAccordion.html @@ -8,7 +8,7 @@ - <%= extensionName %> + <%= extensionName %>
<%= settingsBlock %>
diff --git a/public/res/providers/gdriveProviderBuilder.js b/public/res/providers/gdriveProviderBuilder.js index 75ad272f..45f1990e 100644 --- a/public/res/providers/gdriveProviderBuilder.js +++ b/public/res/providers/gdriveProviderBuilder.js @@ -568,8 +568,8 @@ define([ }); // Also listen to "save success" event - doc.addEventListener(gapi.drive.realtime.EventType.DOCUMENT_SAVE_STATE_CHANGED, function(e) { - if(e.isPending === false && e.isSaving === false) { + doc.addEventListener(gapi.drive.realtime.EventType.DOCUMENT_SAVE_STATE_CHANGED, function(evt) { + if(evt.isPending === false && evt.isSaving === false) { updateCRCs(); } }); @@ -587,16 +587,8 @@ define([ setUndoRedoButtonStates = pagedownEditor.uiManager.setUndoRedoButtonStates; // Set temporary actions for undo/redo buttons - pagedownEditor.uiManager.buttons.undo.execute = function() { - if(model.canUndo) { - model.undo(); - } - }; - pagedownEditor.uiManager.buttons.redo.execute = function() { - if(model.canRedo) { - model.redo(); - } - }; + pagedownEditor.uiManager.buttons.undo.execute = model.undo; + pagedownEditor.uiManager.buttons.redo.execute = model.redo; // Add event handler for model's UndoRedoStateChanged events pagedownEditor.uiManager.setUndoRedoButtonStates = _.debounce(function() { @@ -620,7 +612,7 @@ define([ gdriveProvider.stopRealtimeSync(); } else if(err.isFatal) { - eventMgr.onError('An error has forced real time synchronization to stop.'); + eventMgr.onError('Real time synchronization is temporarily unavailable.'); gdriveProvider.stopRealtimeSync(); } }); diff --git a/public/res/styles/main.less b/public/res/styles/main.less index 7c5eaf42..0c6d8bbf 100644 --- a/public/res/styles/main.less +++ b/public/res/styles/main.less @@ -1473,12 +1473,15 @@ input[type="file"] { color: @input-color; } .icon-comment { - font-size: 14px; + font-size: 15px; color: fade(@label-warning-bg, 60%); } .reply .icon-comment { color: fade(@label-danger-bg, 70%); } + .new-comment-block .icon-comment { + color: fade(@tertiary-color, 35%); + } .input-comment-author { border: none; background: none; diff --git a/public/res/utils.js b/public/res/utils.js index d0fcfdf3..b7b3d4bd 100644 --- a/public/res/utils.js +++ b/public/res/utils.js @@ -10,6 +10,40 @@ define([ var utils = {}; + // Faster than setTimeout (see http://dbaron.org/log/20100309-faster-timeouts) + utils.defer = (function() { + var timeouts = []; + var messageName = "delay"; + window.addEventListener("message", function(evt) { + if(evt.source == window && evt.data == messageName) { + evt.stopPropagation(); + if(timeouts.length > 0) { + timeouts.shift()(); + } + } + }, true); + return function(fn) { + timeouts.push(fn); + window.postMessage(messageName, "*"); + }; + })(); + + // Implements underscore debounce using our defer function + utils.debounce = function(func, context) { + var isExpected = false; + function later() { + isExpected = false; + func.call(context); + } + return function() { + if(isExpected === true) { + return; + } + isExpected = true; + utils.defer(later); + }; + }; + // Return a parameter from the URL utils.getURLParameter = function(name) { // Parameter can be either a search parameter (&name=...) or a hash fragment parameter (#!name=...)