Fix onebox loading on every keystroke after a request fails.

This commit is contained in:
Robin Ward 2013-03-05 14:03:50 -05:00
parent 016634d1d9
commit e4277757c4
5 changed files with 158 additions and 89 deletions

View file

@ -1,89 +1,106 @@
/** /**
A helper for looking up oneboxes and displaying them A helper for looking up oneboxes and displaying them
For now it only stores in a var, in future we can change it so it uses localStorage. For now it only stores in a local Javascript Object, in future we can change it so it uses localStorage
or some other mechanism.
@class Notification @class Onebox
@extends Discourse.Model
@namespace Discourse @namespace Discourse
@module Discourse @module Discourse
**/ **/
Discourse.Onebox = (function() { Discourse.Onebox = {
var cache, load, localCache, lookup, lookupCache; // The cache is just a JS Object
localCache = {}; localCache: {},
cache = function(url, contents) { // A cache of failed URLs
localCache[url] = contents; failedCache: {},
return null;
};
lookupCache = function(url) { /**
var cached; Perform a lookup of a onebox based an anchor element. It will insert a loading
cached = localCache[url]; indicator and remove it when the loading is complete or fails.
if (cached && cached.then) {
return null; @method load
} else { @param {HTMLElement} e the anchor element whose onebox we want to look up
return cached; @param {Boolean} refresh true if we want to force a refresh of the onebox
**/
load: function(e, refresh) {
var $elem = $(e);
// If the onebox has loaded, return
if ($elem.data('onebox-loaded')) return;
if ($elem.hasClass('loading-onebox')) return;
var url = e.href;
// Unless we're forcing a refresh...
if (!refresh) {
// If we have it in our cache, return it.
var cached = this.localCache[url];
if (cached) return cached;
// If the request failed, don't do anything
var failed = this.failedCache[url];
if (failed) return;
} }
};
lookup = function(url, refresh, callback) { // Add the loading CSS class
var cached; $elem.addClass('loading-onebox');
cached = localCache[url];
if (refresh && cached && !cached.then) {
cached = null;
}
if (cached) {
if (cached.then) {
cached.then(callback(lookupCache(url)));
} else {
callback(cached);
}
return false;
} else {
cache(url, jQuery.get("/onebox", {
url: url,
refresh: refresh
}, function(html) {
cache(url, html);
return callback(html);
}));
return true;
}
};
load = function(e, refresh) { // Retrieve the onebox
var $elem, loading, url; var promise = $.ajax({
if (!refresh) refresh = false; type: 'GET',
url: "/onebox",
data: { url: url, refresh: refresh }
});
url = e.href; // We can call this when loading is complete
$elem = $(e); var loadingFinished = function() {
if ($elem.data('onebox-loaded')) {
return;
}
loading = lookup(url, refresh, function(html) {
$elem.removeClass('loading-onebox'); $elem.removeClass('loading-onebox');
$elem.data('onebox-loaded'); $elem.data('onebox-loaded');
if (!html) {
return;
} }
if (html.trim().length === 0) {
return; var onebox = this;
} promise.then(function(html) {
return $elem.replaceWith(html);
// successfully loaded onebox
loadingFinished();
onebox.localCache[url] = html;
$elem.replaceWith(html);
}, function() {
// If the request failed log it as such
onebox.failedCache[url] = true;
loadingFinished();
}); });
if (loading) {
return $elem.addClass('loading-onebox'); },
/**
Return the cached contents of a Onebox
@method lookupCache
@param {String} url the url of the onebox
@return {String} the cached contents of the onebox or null if not found
**/
lookupCache: function(url) {
return this.localCache[url];
},
/**
Store the contents of a Onebox in our local cache.
@method cache
@private
@param {String} url the url of the onebox we crawled
@param {String} contents the contents we want to cache
**/
cache: function(url, contents) {
this.localCache[url] = contents;
} }
}; };
return {
load: load,
lookup: lookup,
lookupCache: lookupCache
};
})();

View file

@ -152,19 +152,27 @@ Discourse.ComposerView = Discourse.View.extend({
// Called after the preview renders. Debounced for performance // Called after the preview renders. Debounced for performance
afterRender: Discourse.debounce(function() { afterRender: Discourse.debounce(function() {
var $wmdPreview, refresh, var $wmdPreview = $('#wmd-preview');
_this = this; if ($wmdPreview.length === 0) return;
$wmdPreview = $('#wmd-preview');
if ($wmdPreview.length === 0) {
return;
}
Discourse.SyntaxHighlighting.apply($wmdPreview); Discourse.SyntaxHighlighting.apply($wmdPreview);
refresh = this.get('controller.content.post.id') !== void 0;
var post = this.get('controller.content.post');
var refresh = false;
// If we are editing a post, we'll refresh its contents once. This is a feature that
// allows a user to refresh its contents once.
if (post && post.blank('refreshedPost')) {
refresh = true
post.set('refreshedPost', true);
}
// Load the post processing effects
$('a.onebox', $wmdPreview).each(function(i, e) { $('a.onebox', $wmdPreview).each(function(i, e) {
return Discourse.Onebox.load(e, refresh); Discourse.Onebox.load(e, refresh);
}); });
return $('span.mention', $wmdPreview).each(function(i, e) { $('span.mention', $wmdPreview).each(function(i, e) {
return Discourse.Mention.load(e, refresh); Discourse.Mention.load(e, refresh);
}); });
}, 100), }, 100),

View file

@ -4,7 +4,16 @@ class OneboxController < ApplicationController
def show def show
Oneboxer.invalidate(params[:url]) if params[:refresh].present? Oneboxer.invalidate(params[:url]) if params[:refresh].present?
render text: Oneboxer.preview(params[:url])
result = Oneboxer.preview(params[:url])
result.strip! if result.present?
# If there is no result, return a 404
if result.blank?
render nothing: true, status: 404
else
render text: result
end
end end
end end

View file

@ -2,14 +2,46 @@ require 'spec_helper'
describe OneboxController do describe OneboxController do
it 'asks the oneboxer for the preview' do let(:url) { "http://google.com" }
Oneboxer.expects(:preview).with('http://google.com')
xhr :get, :show, url: 'http://google.com'
end
it 'invalidates the cache if refresh is passed' do it 'invalidates the cache if refresh is passed' do
Oneboxer.expects(:invalidate).with('http://google.com') Oneboxer.expects(:invalidate).with(url)
xhr :get, :show, url: 'http://google.com', refresh: true xhr :get, :show, url: url, refresh: true
end
describe "found onebox" do
let(:body) { "this is the onebox body"}
before do
Oneboxer.expects(:preview).with(url).returns(body)
xhr :get, :show, url: url
end
it 'returns success' do
response.should be_success
end
it 'returns the onebox response in the body' do
response.body.should == body
end
end
describe "missing onebox" do
it "returns 404 if the onebox is nil" do
Oneboxer.expects(:preview).with(url).returns(nil)
xhr :get, :show, url: url
response.response_code.should == 404
end
it "returns 404 if the onebox is an empty string" do
Oneboxer.expects(:preview).with(url).returns(" \t ")
xhr :get, :show, url: url
response.response_code.should == 404
end
end end
end end

View file

@ -2,19 +2,22 @@
describe("Discourse.Onebox", function() { describe("Discourse.Onebox", function() {
var anchor;
beforeEach(function() { beforeEach(function() {
spyOn(jQuery, 'ajax').andCallThrough(); spyOn(jQuery, 'ajax').andCallThrough();
anchor = $("<a href='http://bla.com'></a>")[0];
}); });
it("Stops rapid calls with cache true", function() { it("Stops rapid calls with cache true", function() {
Discourse.Onebox.lookup('http://bla.com', true, function(c) { return c; }); Discourse.Onebox.load(anchor, true);
Discourse.Onebox.lookup('http://bla.com', true, function(c) { return c; }); Discourse.Onebox.load(anchor, true);
expect(jQuery.ajax.calls.length).toBe(1); expect(jQuery.ajax.calls.length).toBe(1);
}); });
it("Stops rapid calls with cache false", function() { it("Stops rapid calls with cache false", function() {
Discourse.Onebox.lookup('http://bla.com/a', false, function(c) { return c; }); Discourse.Onebox.load(anchor, false);
Discourse.Onebox.lookup('http://bla.com/a', false, function(c) { return c; }); Discourse.Onebox.load(anchor, false);
expect(jQuery.ajax.calls.length).toBe(1); expect(jQuery.ajax.calls.length).toBe(1);
}); });