FEATURE: image uploads now have short urls

Shorten all image uploads to use short urls, this is the client
side implementation.
This commit is contained in:
Sam 2017-08-22 16:40:01 -04:00
parent 605653a369
commit d7a2584c6e
9 changed files with 97 additions and 7 deletions

View File

@ -13,6 +13,9 @@ import { tinyAvatar,
displayErrorForUpload,
getUploadMarkdown,
validateUploadedFiles } from 'discourse/lib/utilities';
import { lookupCachedUploadUrl,
lookupUncachedUploadUrls,
cacheShortUploadUrl } from 'pretty-text/image-short-url';
export default Ember.Component.extend({
classNames: ['wmd-controls'],
@ -191,6 +194,24 @@ export default Ember.Component.extend({
$oneboxes.each((_, o) => load(o, refresh, ajax, this.currentUser.id));
},
_loadShortUrls($images) {
const urls = _.map($images, img => $(img).data('orig-src'));
lookupUncachedUploadUrls(urls, ajax).then(() => this._loadCachedShortUrls($images));
},
_loadCachedShortUrls($images) {
$images.each((idx, image) => {
let $image = $(image);
let url = lookupCachedUploadUrl($image.data('orig-src'));
if (url) {
$image.removeAttr('data-orig-src');
if (url !== "missing") {
$image.attr('src', url);
}
}
});
},
_warnMentionedGroups($preview) {
Ember.run.scheduleOnce('afterRender', () => {
var found = this.get('warnedGroupMentions') || [];
@ -312,6 +333,7 @@ export default Ember.Component.extend({
if (upload && upload.url) {
if (!this._xhr || !this._xhr._userCancelled) {
const markdown = getUploadMarkdown(upload);
cacheShortUploadUrl(upload.short_url, upload.url);
this.appEvents.trigger('composer:replace-text', uploadPlaceholder, markdown);
this._resetUpload(false);
} else {
@ -579,6 +601,19 @@ export default Ember.Component.extend({
Ember.run.debounce(this, this._loadOneboxes, $oneboxes, 450);
}
// Short upload urls
let $shortUploadUrls = $('img[data-orig-src]');
if ($shortUploadUrls.length > 0) {
this._loadCachedShortUrls($shortUploadUrls);
$shortUploadUrls = $('img[data-orig-src]');
if ($shortUploadUrls.length > 0) {
// this is carefully batched so we can do an leading debounce (trigger right away)
Ember.run.debounce(this, this._loadShortUrls, $shortUploadUrls, 450, true);
}
}
let inline = {};
$('a.inline-onebox-loading', $preview).each(function(index, link) {
let $link = $(link);

View File

@ -298,7 +298,7 @@ export function getUploadMarkdown(upload) {
if (isAnImage(upload.original_filename)) {
const split = upload.original_filename.split('.');
const name = split[split.length-2];
return `![${name}|${upload.width}x${upload.height}](${upload.url})`;
return `![${name}|${upload.width}x${upload.height}](${upload.short_url || upload.url})`;
} else if (!Discourse.SiteSettings.prevent_anons_from_downloading_files && (/\.(mov|mp4|webm|ogv|mp3|ogg|wav|m4a)$/i).test(upload.original_filename)) {
return uploadLocation(upload.url);
} else {

View File

@ -10,3 +10,4 @@
//= require ./pretty-text/sanitizer
//= require ./pretty-text/oneboxer
//= require ./pretty-text/inline-oneboxer
//= require ./pretty-text/image-short-url

View File

@ -35,7 +35,8 @@ function rule(state) {
if (images.length > 0) {
let srcList = images.map(([token, srcIndex]) => token.attrs[srcIndex][1]);
let longUrls = state.md.options.discourse.lookupImageUrls(srcList);
let lookup = state.md.options.discourse.lookupImageUrls;
let longUrls = (lookup && lookup(srcList)) || {};
images.forEach(([token, srcIndex]) => {
let origSrc = token.attrs[srcIndex][1];

View File

@ -0,0 +1,18 @@
let _cache = {};
export function lookupCachedUploadUrl(shortUrl) {
return _cache[shortUrl];
}
export function lookupUncachedUploadUrls(urls, ajax) {
return ajax('/uploads/lookup-urls', { method: 'POST', data: { short_urls: urls } })
.then(uploads => {
uploads.forEach(upload => _cache[upload.short_url] = upload.url);
urls.forEach(url => _cache[url] = _cache[url] || "missing");
return uploads;
});
}
export function cacheShortUploadUrl(shortUrl, url) {
_cache[shortUrl] = url;
}

View File

@ -20,19 +20,32 @@ class UploadsController < ApplicationController
if params[:synchronous] && (current_user.staff? || is_api?)
data = create_upload(file, url, type, for_private_message, pasted)
render json: data.as_json
render json: serialize_upload(data)
else
Scheduler::Defer.later("Create Upload") do
begin
data = create_upload(file, url, type, for_private_message, pasted)
ensure
MessageBus.publish("/uploads/#{type}", (data || {}).as_json, client_ids: [params[:client_id]])
MessageBus.publish("/uploads/#{type}", serialize_upload(data), client_ids: [params[:client_id]])
end
end
render json: success_json
end
end
def lookup_urls
params.permit(short_urls: [])
uploads = []
if (params[:short_urls] && params[:short_urls].length > 0)
PrettyText::Helpers.lookup_image_urls(params[:short_urls]).each do |short_url, url|
uploads << { short_url: short_url, url: url }
end
end
render json: uploads.to_json
end
def show
return render_404 if !RailsMultisite::ConnectionManagement.has_db?(params[:site])
@ -57,6 +70,13 @@ class UploadsController < ApplicationController
protected
def serialize_upload(data)
# as_json.as_json is not a typo... as_json in AM serializer returns keys as symbols, we need them
# as strings here
serialized = UploadSerializer.new(data, root: nil).as_json.as_json if Upload === data
serialized ||= (data || {}).as_json
end
def render_404
raise Discourse::NotFound
end

View File

@ -1,5 +1,11 @@
class UploadSerializer < ApplicationSerializer
attributes :id, :url, :original_filename, :filesize, :width, :height
attributes :id,
:url,
:original_filename,
:filesize,
:width,
:height,
:extension,
:short_url,
:retain_hours
end

View File

@ -414,6 +414,7 @@ Discourse::Application.routes.draw do
get "stylesheets/:name.css" => "stylesheets#show", constraints: { name: /[-a-z0-9_]+/ }
post "uploads" => "uploads#create"
post "uploads/lookup-urls" => "uploads#lookup_urls"
# used to download original images
get "uploads/:site/:sha(.:extension)" => "uploads#show", constraints: { site: /\w+/, sha: /\h{40}/, extension: /[a-z0-9\.]+/i }

View File

@ -36,6 +36,13 @@ describe UploadsController do
xhr :post, :create, file: logo, type: "super \# long \//\\ type with \\. $%^&*( chars" * 5
end
it 'can look up long urls' do
upload = Fabricate(:upload)
xhr :post, :lookup_urls, short_urls: [upload.short_url]
result = JSON.parse(response.body)
expect(result[0]["url"]).to eq(upload.url)
end
it 'is successful with an image' do
Jobs.expects(:enqueue).with(:create_avatar_thumbnails, anything)
@ -78,6 +85,7 @@ describe UploadsController do
expect(response.status).to eq 200
expect(json["id"]).to be
expect(json["short_url"]).to eq("upload://qUm0DGR49PAZshIi7HxMd3cAlzn.png")
end
it 'correctly sets retain_hours for admins' do