mirror of
https://github.com/discourse/discourse.git
synced 2025-04-22 20:54:33 +08:00
UX: Lightbox support for image uploader. (#7034)
This commit is contained in:
parent
3cb676bf42
commit
58b0e945bd
app
assets
javascripts/discourse
stylesheets/common/base
controllers
models
serializers
config
lib
spec/requests
test/javascripts
@ -1,9 +1,25 @@
|
|||||||
import computed from "ember-addons/ember-computed-decorators";
|
import {
|
||||||
|
default as computed,
|
||||||
|
observes
|
||||||
|
} from "ember-addons/ember-computed-decorators";
|
||||||
|
|
||||||
import UploadMixin from "discourse/mixins/upload";
|
import UploadMixin from "discourse/mixins/upload";
|
||||||
|
import lightbox from "discourse/lib/lightbox";
|
||||||
|
import { ajax } from "discourse/lib/ajax";
|
||||||
|
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||||
|
|
||||||
export default Ember.Component.extend(UploadMixin, {
|
export default Ember.Component.extend(UploadMixin, {
|
||||||
classNames: ["image-uploader"],
|
classNames: ["image-uploader"],
|
||||||
infoHidden: true,
|
|
||||||
|
init() {
|
||||||
|
this._super(...arguments);
|
||||||
|
this._applyLightbox();
|
||||||
|
},
|
||||||
|
|
||||||
|
willDestroyElement() {
|
||||||
|
this._super(...arguments);
|
||||||
|
$("a.lightbox").magnificPopup("close");
|
||||||
|
},
|
||||||
|
|
||||||
@computed("imageUrl")
|
@computed("imageUrl")
|
||||||
backgroundStyle(imageUrl) {
|
backgroundStyle(imageUrl) {
|
||||||
@ -20,11 +36,6 @@ export default Ember.Component.extend(UploadMixin, {
|
|||||||
return imageUrl.split("/").slice(-1)[0];
|
return imageUrl.split("/").slice(-1)[0];
|
||||||
},
|
},
|
||||||
|
|
||||||
@computed("infoHidden", "imageBaseName")
|
|
||||||
showInfo(infoHidden, imageBaseName) {
|
|
||||||
return !infoHidden && imageBaseName;
|
|
||||||
},
|
|
||||||
|
|
||||||
@computed("backgroundStyle")
|
@computed("backgroundStyle")
|
||||||
hasBackgroundStyle(backgroundStyle) {
|
hasBackgroundStyle(backgroundStyle) {
|
||||||
return !Ember.isEmpty(backgroundStyle.string);
|
return !Ember.isEmpty(backgroundStyle.string);
|
||||||
@ -35,16 +46,51 @@ export default Ember.Component.extend(UploadMixin, {
|
|||||||
},
|
},
|
||||||
|
|
||||||
uploadDone(upload) {
|
uploadDone(upload) {
|
||||||
this.setProperties({ imageUrl: upload.url, imageId: upload.id });
|
this.setProperties({
|
||||||
|
imageUrl: upload.url,
|
||||||
|
imageId: upload.id,
|
||||||
|
imageFilesize: upload.human_filesize,
|
||||||
|
imageFilename: upload.original_filename,
|
||||||
|
imageWidth: upload.width,
|
||||||
|
imageHeight: upload.height
|
||||||
|
});
|
||||||
|
|
||||||
|
this._applyLightbox();
|
||||||
|
|
||||||
if (this.onUploadDone) {
|
if (this.onUploadDone) {
|
||||||
this.onUploadDone(upload);
|
this.onUploadDone(upload);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_openLightbox() {
|
||||||
|
Ember.run.next(() => this.$("a.lightbox").magnificPopup("open"));
|
||||||
|
},
|
||||||
|
|
||||||
|
_applyLightbox() {
|
||||||
|
if (this.get("imageUrl")) Ember.run.next(() => lightbox(this.$()));
|
||||||
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
toggleInfo() {
|
toggleLightbox() {
|
||||||
this.toggleProperty("infoHidden");
|
if (this.get("imageFilename")) {
|
||||||
|
this._openLightbox();
|
||||||
|
} else {
|
||||||
|
ajax(`/uploads/lookup-metadata`, {
|
||||||
|
type: "POST",
|
||||||
|
data: { url: this.get("imageUrl") }
|
||||||
|
})
|
||||||
|
.then(json => {
|
||||||
|
this.setProperties({
|
||||||
|
imageFilename: json.original_filename,
|
||||||
|
imageFilesize: json.human_filesize,
|
||||||
|
imageWidth: json.width,
|
||||||
|
imageHeight: json.height
|
||||||
|
});
|
||||||
|
|
||||||
|
this._openLightbox();
|
||||||
|
})
|
||||||
|
.catch(popupAjaxError);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
trash() {
|
trash() {
|
||||||
|
@ -9,18 +9,28 @@
|
|||||||
<button {{action "trash"}} class="btn btn-danger pad-left no-text">{{d-icon "far-trash-alt"}}</button>
|
<button {{action "trash"}} class="btn btn-danger pad-left no-text">{{d-icon "far-trash-alt"}}</button>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{#if imageBaseName}}
|
{{#if imageUrl}}
|
||||||
{{d-button icon="info-circle" class="btn image-uploader-info-btn no-text"
|
{{d-button icon="discourse-expand"
|
||||||
title="upload_selector.filename"
|
title='expand'
|
||||||
action=(action "toggleInfo")}}
|
class="btn image-uploader-lightbox-btn no-text"
|
||||||
|
action=(action "toggleLightbox")}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
<span class="btn {{unless uploading 'hidden'}}">{{i18n 'upload_selector.uploading'}} {{uploadProgress}}%</span>
|
<span class="btn {{unless uploading 'hidden'}}">{{i18n 'upload_selector.uploading'}} {{uploadProgress}}%</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{#if showInfo}}
|
|
||||||
<div class="image-uploader-info">
|
{{#if imageUrl}}
|
||||||
<a href={{imageUrl}} target="_blank">{{imageBaseName}}</a>
|
<a class="lightbox"
|
||||||
</div>
|
href={{imageUrl}}
|
||||||
|
title={{imageFilename}}
|
||||||
|
rel="nofollow noopener">
|
||||||
|
|
||||||
|
<div class="meta">
|
||||||
|
<span class="informations">
|
||||||
|
{{imageWidth}}x{{imageHeight}} {{imageFilesize}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,26 +3,6 @@
|
|||||||
background-size: cover;
|
background-size: cover;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
.image-uploader-info {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
width: 100%;
|
|
||||||
background: $primary;
|
|
||||||
text-align: center;
|
|
||||||
overflow: hidden;
|
|
||||||
white-space: nowrap;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
opacity: 0.6;
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: $secondary;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.image-upload-controls {
|
.image-upload-controls {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
@ -30,7 +10,7 @@
|
|||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.image-uploader-info-btn {
|
.image-uploader-lightbox-btn {
|
||||||
background: none;
|
background: none;
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
|
@ -86,6 +86,19 @@ class UploadsController < ApplicationController
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def metadata
|
||||||
|
params.require(:url)
|
||||||
|
upload = Upload.get_from_url(params[:url])
|
||||||
|
raise Discourse::NotFound unless upload
|
||||||
|
|
||||||
|
render json: {
|
||||||
|
original_filename: upload.original_filename,
|
||||||
|
width: upload.width,
|
||||||
|
height: upload.height,
|
||||||
|
human_filesize: upload.human_filesize
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
def render_404
|
def render_404
|
||||||
|
@ -7,6 +7,8 @@ require_dependency "file_store/local_store"
|
|||||||
require_dependency "base62"
|
require_dependency "base62"
|
||||||
|
|
||||||
class Upload < ActiveRecord::Base
|
class Upload < ActiveRecord::Base
|
||||||
|
include ActionView::Helpers::NumberHelper
|
||||||
|
|
||||||
SHA1_LENGTH = 40
|
SHA1_LENGTH = 40
|
||||||
|
|
||||||
belongs_to :user
|
belongs_to :user
|
||||||
@ -208,6 +210,10 @@ class Upload < ActiveRecord::Base
|
|||||||
upload || Upload.find_by("url LIKE ?", "%#{data[1]}")
|
upload || Upload.find_by("url LIKE ?", "%#{data[1]}")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def human_filesize
|
||||||
|
number_to_human_size(self.filesize)
|
||||||
|
end
|
||||||
|
|
||||||
def self.migrate_to_new_scheme(limit = nil)
|
def self.migrate_to_new_scheme(limit = nil)
|
||||||
problems = []
|
problems = []
|
||||||
|
|
||||||
|
@ -9,5 +9,6 @@ class UploadSerializer < ApplicationSerializer
|
|||||||
:thumbnail_height,
|
:thumbnail_height,
|
||||||
:extension,
|
:extension,
|
||||||
:short_url,
|
:short_url,
|
||||||
:retain_hours
|
:retain_hours,
|
||||||
|
:human_filesize
|
||||||
end
|
end
|
||||||
|
@ -206,6 +206,7 @@ en:
|
|||||||
us_west_2: "US West (Oregon)"
|
us_west_2: "US West (Oregon)"
|
||||||
|
|
||||||
edit: "edit the title and category of this topic"
|
edit: "edit the title and category of this topic"
|
||||||
|
expand: "Expand"
|
||||||
not_implemented: "That feature hasn't been implemented yet, sorry!"
|
not_implemented: "That feature hasn't been implemented yet, sorry!"
|
||||||
no_value: "No"
|
no_value: "No"
|
||||||
yes_value: "Yes"
|
yes_value: "Yes"
|
||||||
@ -1569,7 +1570,6 @@ en:
|
|||||||
select_file: "Select File"
|
select_file: "Select File"
|
||||||
image_link: "link your image will point to"
|
image_link: "link your image will point to"
|
||||||
default_image_alt_text: image
|
default_image_alt_text: image
|
||||||
filename: "Filename"
|
|
||||||
|
|
||||||
search:
|
search:
|
||||||
sort_by: "Sort by"
|
sort_by: "Sort by"
|
||||||
|
@ -461,6 +461,7 @@ Discourse::Application.routes.draw do
|
|||||||
get "stylesheets/:name.css" => "stylesheets#show", constraints: { name: /[-a-z0-9_]+/ }
|
get "stylesheets/:name.css" => "stylesheets#show", constraints: { name: /[-a-z0-9_]+/ }
|
||||||
get "theme-javascripts/:digest.js" => "theme_javascripts#show", constraints: { digest: /\h{40}/ }
|
get "theme-javascripts/:digest.js" => "theme_javascripts#show", constraints: { digest: /\h{40}/ }
|
||||||
|
|
||||||
|
post "uploads/lookup-metadata" => "uploads#metadata"
|
||||||
post "uploads" => "uploads#create"
|
post "uploads" => "uploads#create"
|
||||||
post "uploads/lookup-urls" => "uploads#lookup_urls"
|
post "uploads/lookup-urls" => "uploads#lookup_urls"
|
||||||
|
|
||||||
|
@ -6,8 +6,6 @@ require_dependency 'pretty_text'
|
|||||||
require_dependency 'quote_comparer'
|
require_dependency 'quote_comparer'
|
||||||
|
|
||||||
class CookedPostProcessor
|
class CookedPostProcessor
|
||||||
include ActionView::Helpers::NumberHelper
|
|
||||||
|
|
||||||
INLINE_ONEBOX_LOADING_CSS_CLASS = "inline-onebox-loading"
|
INLINE_ONEBOX_LOADING_CSS_CLASS = "inline-onebox-loading"
|
||||||
INLINE_ONEBOX_CSS_CLASS = "inline-onebox"
|
INLINE_ONEBOX_CSS_CLASS = "inline-onebox"
|
||||||
LOADING_SIZE = 10
|
LOADING_SIZE = 10
|
||||||
@ -422,7 +420,7 @@ class CookedPostProcessor
|
|||||||
|
|
||||||
filename = get_filename(upload, img["src"])
|
filename = get_filename(upload, img["src"])
|
||||||
informations = "#{original_width}×#{original_height}"
|
informations = "#{original_width}×#{original_height}"
|
||||||
informations << " #{number_to_human_size(upload.filesize)}" if upload
|
informations << " #{upload.human_filesize}" if upload
|
||||||
|
|
||||||
a["title"] = CGI.escapeHTML(img["title"] || filename)
|
a["title"] = CGI.escapeHTML(img["title"] || filename)
|
||||||
|
|
||||||
|
@ -283,4 +283,51 @@ describe UploadsController do
|
|||||||
expect(result[0]["url"]).to eq(upload.url)
|
expect(result[0]["url"]).to eq(upload.url)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#metadata' do
|
||||||
|
let(:upload) { Fabricate(:upload) }
|
||||||
|
|
||||||
|
describe 'when url is missing' do
|
||||||
|
it 'should return the right response' do
|
||||||
|
post "/uploads/lookup-metadata.json"
|
||||||
|
|
||||||
|
expect(response.status).to eq(403)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'when not signed in' do
|
||||||
|
it 'should return the right response' do
|
||||||
|
post "/uploads/lookup-metadata.json", params: { url: upload.url }
|
||||||
|
|
||||||
|
expect(response.status).to eq(403)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'when signed in' do
|
||||||
|
before do
|
||||||
|
sign_in(Fabricate(:user))
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'when url is invalid' do
|
||||||
|
it 'should return the right response' do
|
||||||
|
post "/uploads/lookup-metadata.json", params: { url: 'abc' }
|
||||||
|
|
||||||
|
expect(response.status).to eq(404)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should return the right response" do
|
||||||
|
post "/uploads/lookup-metadata.json", params: { url: upload.url }
|
||||||
|
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
|
||||||
|
result = JSON.parse(response.body)
|
||||||
|
|
||||||
|
expect(result["original_filename"]).to eq(upload.original_filename)
|
||||||
|
expect(result["width"]).to eq(upload.width)
|
||||||
|
expect(result["height"]).to eq(upload.height)
|
||||||
|
expect(result["human_filesize"]).to eq(upload.human_filesize)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -17,18 +17,12 @@ componentTest("with image", {
|
|||||||
"it displays the trash icon"
|
"it displays the trash icon"
|
||||||
);
|
);
|
||||||
|
|
||||||
assert.equal(
|
await click(".image-uploader-lightbox-btn");
|
||||||
this.$(".image-uploader-info").length,
|
|
||||||
0,
|
|
||||||
"it does not display the image info"
|
|
||||||
);
|
|
||||||
|
|
||||||
await click(".image-uploader-info-btn");
|
|
||||||
|
|
||||||
assert.equal(
|
assert.equal(
|
||||||
this.$(".image-uploader-info").length,
|
$(".mfp-container").length,
|
||||||
1,
|
1,
|
||||||
"it displays the image info"
|
"it displays the image lightbox"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -50,9 +44,9 @@ componentTest("without image", {
|
|||||||
);
|
);
|
||||||
|
|
||||||
assert.equal(
|
assert.equal(
|
||||||
this.$(".image-uploader-info-btn").length,
|
this.$(".image-uploader-lightbox-btn").length,
|
||||||
0,
|
0,
|
||||||
"it does not display the image info button toggle"
|
"it does not display the button to open image lightbox"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -536,6 +536,15 @@ export default function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.post("/uploads/lookup-metadata", () => {
|
||||||
|
return response(200, {
|
||||||
|
imageFilename: "somefile.png",
|
||||||
|
imageFilesize: "10 KB",
|
||||||
|
imageWidth: "1",
|
||||||
|
imageHeight: "1"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
this.get("/inline-onebox", request => {
|
this.get("/inline-onebox", request => {
|
||||||
if (
|
if (
|
||||||
request.queryParams.urls.includes(
|
request.queryParams.urls.includes(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user