Merge branch 'next-minor' into develop

This commit is contained in:
Benjamin Neff 2018-02-17 19:26:15 +01:00
commit 0b921c6657
No known key found for this signature in database
GPG key ID: 971464C3F1A90194
16 changed files with 240 additions and 20 deletions

View file

@ -14,6 +14,7 @@
## Bug fixes ## Bug fixes
## Features ## Features
* Add basic html5 audio/video embedding support [#6418](https://github.com/diaspora/diaspora/pull/6418)
# 0.7.3.1 # 0.7.3.1

View file

@ -26,7 +26,7 @@ gem "json-schema", "2.8.0"
# Authentication # Authentication
gem "devise", "4.3.0" gem "devise", "4.4.1"
gem "devise_lastseenable", "0.0.6" gem "devise_lastseenable", "0.0.6"
# Captcha # Captcha
@ -122,6 +122,8 @@ source "https://rails-assets.org" do
gem "rails-assets-perfect-scrollbar", "0.6.16" gem "rails-assets-perfect-scrollbar", "0.6.16"
end end
gem "markdown-it-html5-embed", "1.0.0"
# Localization # Localization
gem "http_accept_language", "2.1.1" gem "http_accept_language", "2.1.1"
@ -186,7 +188,7 @@ gem "typhoeus", "1.3.0"
# Views # Views
gem "gon", "6.1.0" gem "gon", "6.1.0"
gem "hamlit", "2.8.4" gem "hamlit", "2.8.6"
gem "mobile-fu", "1.4.0" gem "mobile-fu", "1.4.0"
gem "rails-timeago", "2.16.0" gem "rails-timeago", "2.16.0"
gem "will_paginate", "3.1.6" gem "will_paginate", "3.1.6"

View file

@ -133,7 +133,7 @@ GEM
tins (~> 1.6) tins (~> 1.6)
crack (0.4.3) crack (0.4.3)
safe_yaml (~> 1.0.0) safe_yaml (~> 1.0.0)
crass (1.0.2) crass (1.0.3)
cucumber (2.4.0) cucumber (2.4.0)
builder (>= 2.1.2) builder (>= 2.1.2)
cucumber-core (~> 1.5.0) cucumber-core (~> 1.5.0)
@ -156,7 +156,7 @@ GEM
railties (>= 4, < 5.2) railties (>= 4, < 5.2)
cucumber-wire (0.0.1) cucumber-wire (0.0.1)
database_cleaner (1.6.1) database_cleaner (1.6.1)
devise (4.3.0) devise (4.4.1)
bcrypt (~> 3.0) bcrypt (~> 3.0)
orm_adapter (~> 0.1) orm_adapter (~> 0.1)
railties (>= 4.1.0, < 5.2) railties (>= 4.1.0, < 5.2)
@ -187,7 +187,7 @@ GEM
entypo-rails (3.0.0) entypo-rails (3.0.0)
railties (>= 4.1, < 6) railties (>= 4.1, < 6)
equalizer (0.0.11) equalizer (0.0.11)
erubi (1.6.1) erubi (1.7.0)
eslintrb (2.1.0) eslintrb (2.1.0)
execjs execjs
multi_json (>= 1.3) multi_json (>= 1.3)
@ -273,7 +273,7 @@ GEM
guard-rubocop (1.3.0) guard-rubocop (1.3.0)
guard (~> 2.0) guard (~> 2.0)
rubocop (~> 0.20) rubocop (~> 0.20)
haml (5.0.3) haml (5.0.4)
temple (>= 0.8.0) temple (>= 0.8.0)
tilt tilt
haml_lint (0.26.0) haml_lint (0.26.0)
@ -282,7 +282,7 @@ GEM
rake (>= 10, < 13) rake (>= 10, < 13)
rubocop (>= 0.49.0) rubocop (>= 0.49.0)
sysexits (~> 1.1) sysexits (~> 1.1)
hamlit (2.8.4) hamlit (2.8.6)
temple (>= 0.8.0) temple (>= 0.8.0)
thor thor
tilt tilt
@ -306,7 +306,8 @@ GEM
httparty (0.15.6) httparty (0.15.6)
multi_xml (>= 0.5.2) multi_xml (>= 0.5.2)
httpclient (2.8.3) httpclient (2.8.3)
i18n (0.8.6) i18n (0.9.5)
concurrent-ruby (~> 1.0)
i18n-inflector (2.6.7) i18n-inflector (2.6.7)
i18n (>= 0.4.1) i18n (>= 0.4.1)
i18n-inflector-rails (1.0.7) i18n-inflector-rails (1.0.7)
@ -359,7 +360,7 @@ GEM
multi_json (~> 1.10) multi_json (~> 1.10)
logging-rails (0.6.0) logging-rails (0.6.0)
logging (>= 1.8) logging (>= 1.8)
loofah (2.1.1) loofah (2.2.0)
crass (~> 1.0.2) crass (~> 1.0.2)
nokogiri (>= 1.5.9) nokogiri (>= 1.5.9)
lumberjack (1.0.12) lumberjack (1.0.12)
@ -367,6 +368,7 @@ GEM
systemu (~> 2.6.2) systemu (~> 2.6.2)
mail (2.6.6) mail (2.6.6)
mime-types (>= 1.16, < 4) mime-types (>= 1.16, < 4)
markdown-it-html5-embed (1.0.0)
markerb (1.1.0) markerb (1.1.0)
memoizable (0.4.2) memoizable (0.4.2)
thread_safe (~> 0.3, >= 0.3.1) thread_safe (~> 0.3, >= 0.3.1)
@ -377,7 +379,7 @@ GEM
mini_magick (4.8.0) mini_magick (4.8.0)
mini_mime (0.1.4) mini_mime (0.1.4)
mini_portile2 (2.3.0) mini_portile2 (2.3.0)
minitest (5.10.3) minitest (5.11.3)
mobile-fu (1.4.0) mobile-fu (1.4.0)
rack-mobile-detect rack-mobile-detect
rails rails
@ -473,7 +475,7 @@ GEM
byebug (~> 9.1) byebug (~> 9.1)
pry (~> 0.10) pry (~> 0.10)
public_suffix (3.0.0) public_suffix (3.0.0)
rack (2.0.3) rack (2.0.4)
rack-cors (1.0.1) rack-cors (1.0.1)
rack-google-analytics (1.2.0) rack-google-analytics (1.2.0)
actionpack actionpack
@ -492,7 +494,7 @@ GEM
rack-rewrite (1.5.1) rack-rewrite (1.5.1)
rack-ssl (1.4.1) rack-ssl (1.4.1)
rack rack
rack-test (0.7.0) rack-test (0.8.2)
rack (>= 1.0, < 3) rack (>= 1.0, < 3)
rails (5.1.4) rails (5.1.4)
actioncable (= 5.1.4) actioncable (= 5.1.4)
@ -576,7 +578,7 @@ GEM
rainbow (2.2.2) rainbow (2.2.2)
rake rake
raindrops (0.19.0) raindrops (0.19.0)
rake (12.1.0) rake (12.3.0)
rb-fsevent (0.10.2) rb-fsevent (0.10.2)
rb-inotify (0.9.10) rb-inotify (0.9.10)
ffi (>= 0.5.0, < 2) ffi (>= 0.5.0, < 2)
@ -712,7 +714,7 @@ GEM
unf (~> 0.1.0) unf (~> 0.1.0)
typhoeus (1.3.0) typhoeus (1.3.0)
ethon (>= 0.9.0) ethon (>= 0.9.0)
tzinfo (1.2.3) tzinfo (1.2.5)
thread_safe (~> 0.1) thread_safe (~> 0.1)
uglifier (3.2.0) uglifier (3.2.0)
execjs (>= 0.3.0, < 3) execjs (>= 0.3.0, < 3)
@ -780,7 +782,7 @@ DEPENDENCIES
cucumber-api-steps (= 0.13) cucumber-api-steps (= 0.13)
cucumber-rails (= 1.5.0) cucumber-rails (= 1.5.0)
database_cleaner (= 1.6.1) database_cleaner (= 1.6.1)
devise (= 4.3.0) devise (= 4.4.1)
devise_lastseenable (= 0.0.6) devise_lastseenable (= 0.0.6)
diaspora-prosody-config (= 0.0.7) diaspora-prosody-config (= 0.0.7)
diaspora_federation-json_schema (= 0.2.3) diaspora_federation-json_schema (= 0.2.3)
@ -801,7 +803,7 @@ DEPENDENCIES
guard-rspec (= 4.7.3) guard-rspec (= 4.7.3)
guard-rubocop (= 1.3.0) guard-rubocop (= 1.3.0)
haml_lint (= 0.26.0) haml_lint (= 0.26.0)
hamlit (= 2.8.4) hamlit (= 2.8.6)
handlebars_assets (= 0.23.2) handlebars_assets (= 0.23.2)
http_accept_language (= 2.1.1) http_accept_language (= 2.1.1)
i18n-inflector-rails (= 1.0.7) i18n-inflector-rails (= 1.0.7)
@ -815,6 +817,7 @@ DEPENDENCIES
json-schema-rspec (= 0.0.4) json-schema-rspec (= 0.0.4)
leaflet-rails (= 1.2.0) leaflet-rails (= 1.2.0)
logging-rails (= 0.6.0) logging-rails (= 0.6.0)
markdown-it-html5-embed (= 1.0.0)
markerb (= 1.1.0) markerb (= 1.1.0)
mini_magick (= 4.8.0) mini_magick (= 4.8.0)
minitest minitest

View file

@ -1,6 +1,11 @@
// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later // @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later
(function(){ (function(){
app.helpers.allowedEmbedsMime = function(mimetype) {
var v = document.createElement(mimetype[1]);
return v.canPlayType && v.canPlayType(mimetype[0]) !== "";
};
app.helpers.textFormatter = function(text, mentions) { app.helpers.textFormatter = function(text, mentions) {
mentions = mentions ? mentions : []; mentions = mentions ? mentions : [];
@ -83,6 +88,30 @@
// Bootstrap table markup // Bootstrap table markup
md.renderer.rules.table_open = function () { return "<table class=\"table table-striped\">\n"; }; md.renderer.rules.table_open = function () { return "<table class=\"table table-striped\">\n"; };
var html5medialPlugin = window.markdownitHTML5Embed;
md.use(html5medialPlugin, {html5embed: {
inline: false,
autoAppend: true,
renderFn: function handleBarsRenderFn(parsed, mediaAttributes) {
var attributes = mediaAttributes[parsed.mediaType];
return HandlebarsTemplates["media-embed_tpl"]({
mediaType: parsed.mediaType,
attributes: attributes,
mimetype: parsed.mimeType,
sourceURL: parsed.url,
title: parsed.title,
fallback: parsed.fallback,
needsCover: parsed.mediaType === "video"
});
},
attributes: {
"audio": "controls preload=none",
"video": "preload=none"
},
isAllowedMimeType: app.helpers.allowedEmbedsMime
}});
return md.render(text); return md.render(text);
}; };
})(); })();

View file

@ -63,7 +63,29 @@ app.views.Content = app.views.Base.extend({
} }
}, },
// This function is called when user clicks cover for HTML5 embedded video
onVideoThumbClick: function(evt) {
var clickedThumb;
if ($(evt.target).hasClass("thumb")) {
clickedThumb = $(evt.target);
} else {
clickedThumb = $(evt.target).parent(".thumb");
}
clickedThumb.find(".video-overlay").addClass("hidden");
clickedThumb.parents(".collapsed").children(".expander").click();
var video = clickedThumb.find("video");
video.attr("controls", "");
video.get(0).load();
video.get(0).play();
clickedThumb.unbind("click");
},
bindMediaEmbedThumbClickEvent: function() {
this.$(".media-embed .thumb").bind("click", this.onVideoThumbClick);
},
postRenderTemplate : function(){ postRenderTemplate : function(){
this.bindMediaEmbedThumbClickEvent();
_.defer(_.bind(this.collapseOversized, this)); _.defer(_.bind(this.collapseOversized, this));
// run collapseOversized again after all contained images are loaded // run collapseOversized again after all contained images are loaded
@ -93,6 +115,8 @@ app.views.StatusMessage = app.views.Content.extend({
app.views.ExpandedStatusMessage = app.views.StatusMessage.extend({ app.views.ExpandedStatusMessage = app.views.StatusMessage.extend({
postRenderTemplate : function(){ postRenderTemplate : function(){
this.bindMediaEmbedThumbClickEvent();
var photoAttachments = this.$(".photo-attachments"); var photoAttachments = this.$(".photo-attachments");
if(photoAttachments.length > 0) { if(photoAttachments.length > 0) {
new app.views.Gallery({ el: photoAttachments }); new app.views.Gallery({ el: photoAttachments });

View file

@ -26,6 +26,7 @@
//= require markdown-it-sanitizer //= require markdown-it-sanitizer
//= require markdown-it-sub //= require markdown-it-sub
//= require markdown-it-sup //= require markdown-it-sup
//= require markdown-it-html5-embed
//= require highlightjs //= require highlightjs
//= require clear-form //= require clear-form
//= require corejs-typeahead //= require corejs-typeahead

View file

@ -88,6 +88,7 @@
@import 'chat'; @import 'chat';
@import 'markdown-content'; @import 'markdown-content';
@import 'oembed'; @import 'oembed';
@import 'media-embed';
@import 'post-content'; @import 'post-content';
// contacts // contacts

View file

@ -0,0 +1,21 @@
$stub-bg-color: #ddd;
.media-embed {
margin-top: 5px;
.thumb {
@include video-overlay;
background-color: $stub-bg-color;
video {
min-height: 60%;
vertical-align: middle;
width: 100%;
}
}
audio {
width: 100%;
}
}

View file

@ -0,0 +1,19 @@
<div class="media-embed">
{{#if needsCover}}
<div class="thumb">
{{/if}}
<{{mediaType}} {{{attributes}}}>
<source type="{{mimetype}}" src="{{sourceURL}}" />
{{title}}
</{{mediaType}}>
{{#if needsCover}}
<div class="video-overlay">
<div class="video-info">
<div class="title">{{title}}</div>
</div>
</div>
</div>
{{/if}}
</div>

View file

@ -9,11 +9,11 @@ screenshot_opts = "--require features --format pretty"
%> %>
# 'normal' test runs # 'normal' test runs
default: <%= std_opts %> -r features default: <%= std_opts %> -r features --tags ~@nophantomjs
wip: -r features --tags @wip:3 --wip features wip: -r features --tags @wip:3 --wip features
rerun: <%= rerun_opts %> --format rerun --out rerun.txt --strict --tags ~@wip --tags ~@screenshots rerun: <%= rerun_opts %> --format rerun --out rerun.txt --strict --tags ~@wip --tags ~@screenshots
# screenshot feature # screenshot feature
ref_screens: "<%= screenshot_opts %> --tags @reference-screenshots" ref_screens: "<%= screenshot_opts %> --tags @reference-screenshots"
cmp_screens: "<%= screenshot_opts %> --tags @comparison-screenshots" cmp_screens: "<%= screenshot_opts %> --tags @comparison-screenshots"
all_screens: "<%= screenshot_opts %> --tags @screenshots" all_screens: "<%= screenshot_opts %> --tags @screenshots"

View file

@ -436,7 +436,7 @@ en:
size_of_images_q: "Can I customize the size of images in posts or comments?" size_of_images_q: "Can I customize the size of images in posts or comments?"
size_of_images_a: "No. Images are resized automatically to fit the stream or single-post view. Markdown does not have a code for specifying the size of an image." size_of_images_a: "No. Images are resized automatically to fit the stream or single-post view. Markdown does not have a code for specifying the size of an image."
embed_multimedia_q: "How do I embed a video, audio, or other multimedia content into a post?" embed_multimedia_q: "How do I embed a video, audio, or other multimedia content into a post?"
embed_multimedia_a: "You can usually just paste the URL (e.g. http://www.youtube.com/watch?v=nnnnnnnnnnn ) into your post and the video or audio will be embedded automatically. The sites supported include: YouTube, Vimeo, SoundCloud, Flickr and a few more. diaspora* uses oEmbed for this feature. Were supporting more media sources all the time. Remember to always post simple, full links no shortened links; no operators after the base URL and give it a little time before you refresh the page after posting for seeing the preview." embed_multimedia_a: "You can usually just paste the URL (e.g. http://www.youtube.com/watch?v=nnnnnnnnnnn ) into your post and the video or audio will be embedded automatically. The sites supported include: YouTube, Vimeo, SoundCloud, Flickr and a few more. diaspora* uses oEmbed for this feature. If you post a direct link to an audio or video file, diaspora* will embed it using standard HTML5 player. Were supporting more media sources all the time. Remember to always post simple, full links no shortened links; no operators after the base URL and give it a little time before you refresh the page after posting for seeing the preview."
post_location_q: "How do I add my location to a post?" post_location_q: "How do I add my location to a post?"
post_location_a: "Click the pin icon next to the camera in the publisher. This will insert your location from OpenStreetMap. You can edit your location you might only want to include the city youre in rather than the specific street address." post_location_a: "Click the pin icon next to the camera in the publisher. This will insert your location from OpenStreetMap. You can edit your location you might only want to include the city youre in rather than the specific street address."
post_poll_q: "How do I add a poll to my post?" post_poll_q: "How do I add a poll to my post?"

View file

@ -0,0 +1,22 @@
# We can create a separate cucumber profile that will run these tests with Selenium
@nophantomjs
@javascript
Feature: oembed
In order to make videos easy accessible
As a user
I want the media links in my posts be replaced by an embedded player
Background:
Given following user exists:
| username | email |
| Alice Smith | alice@alice.alice |
And I sign in as "alice@alice.alice"
Scenario: Post a video link
When I click the publisher and post "[title](http://example.com/file.ogv)"
Then I should see a HTML5 video player
Scenario: Post an audio link
When I click the publisher and post "[title](http://example.com/file.ogg)"
Then I should see a HTML5 audio player

View file

@ -0,0 +1,6 @@
# frozen_string_literal: true
Then /^I should see a HTML5 (video|audio) player$/ do |type|
find(".post-content .media-embed")
find(".stream-container").should have_css(".post-content .media-embed #{type}")
end

View file

@ -26,13 +26,17 @@ module PublishingCukeHelpers
submit_publisher submit_publisher
end end
def visible_text_from_markdown(text)
CGI.unescapeHTML(ActionController::Base.helpers.strip_tags(Diaspora::MessageRenderer.new(text).markdownified.strip))
end
def submit_publisher def submit_publisher
txt = find("#publisher #status_message_text").value txt = find("#publisher #status_message_text").value
find("#publisher .btn-primary").click find("#publisher .btn-primary").click
# wait for the publisher to be closed # wait for the publisher to be closed
expect(find("#publisher")["class"]).to include("closed") expect(find("#publisher")["class"]).to include("closed")
# wait for the content to appear # wait for the content to appear
expect(find("#main-stream")).to have_content(txt) expect(find("#main-stream")).to have_content(visible_text_from_markdown(txt))
end end
def click_and_post(text) def click_and_post(text)

View file

@ -347,6 +347,54 @@ describe("app.helpers.textFormatter", function(){
} }
}); });
}); });
context("media embed", function() {
beforeEach(function() {
spyOn(app.helpers, "allowedEmbedsMime").and.returnValue(true);
});
it("embeds audio", function() {
var html =
'<p><a href="https://example.org/file.mp3" target="_blank" rel="noopener noreferrer">title</a></p>\n' +
'<div class="media-embed">\n' +
"\n" +
" <audio controls preload=none>\n" +
' <source type="audio/mpeg" src="https://example.org/file.mp3" />\n' +
" title\n" +
" </audio>\n" +
"\n" +
"</div>\n";
var content = "[title](https://example.org/file.mp3)";
var parsed = this.formatter(content);
expect(parsed).toContain(html);
});
it("embeds video", function() {
var html =
'<p><a href="https://example.org/file.mp4" target="_blank" rel="noopener noreferrer">title</a></p>\n' +
'<div class="media-embed">\n' +
' <div class="thumb">\n' +
"\n" +
" <video preload=none>\n" +
' <source type="video/mp4" src="https://example.org/file.mp4" />\n' +
" title\n" +
" </video>\n" +
"\n" +
' <div class="video-overlay">\n' +
' <div class="video-info">\n' +
' <div class="title">title</div>\n' +
" </div>\n" +
" </div>\n" +
" </div>\n" +
"</div>\n";
var content = "[title](https://example.org/file.mp4)";
var parsed = this.formatter(content);
expect(parsed).toContain(html);
});
});
}); });
context("real world examples", function(){ context("real world examples", function(){

View file

@ -36,4 +36,43 @@ describe("app.views.Content", function(){
expect(this.view.presenter().location).toEqual(factory.location()); expect(this.view.presenter().location).toEqual(factory.location());
}); });
}); });
// These tests don't work in PhantomJS because it doesn't support HTML5 <video>.
if (/PhantomJS/.exec(navigator.userAgent) === null) {
describe("onVideoThumbClick", function() {
beforeEach(function() {
this.post = new app.models.StatusMessage({text: "[title](https://www.w3schools.com/html/mov_bbb.mp4)"});
this.view = new app.views.StatusMessage({model: this.post});
this.view.render();
});
afterEach(function() {
this.view.$("video").stop();
});
it("hides video overlay", function() {
expect(this.view.$(".video-overlay").length).toBe(1);
this.view.$(".media-embed .thumb").click();
expect(this.view.$(".video-overlay")).toHaveClass("hidden");
});
it("expands posts on click", function() {
this.view.$(".collapsible").height(500);
this.view.collapseOversized();
expect(this.view.$(".collapsed").length).toBe(1);
this.view.$(".media-embed .thumb").click();
expect(this.view.$(".opened").length).toBe(1);
});
it("plays video", function(done) {
this.view.$("video").on("playing", function() {
done();
});
this.view.$(".media-embed .thumb").click();
});
});
}
}); });