diff --git a/Changelog.md b/Changelog.md
index 1220370b9..54c972751 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -14,6 +14,7 @@
## Bug fixes
## Features
+* Add basic html5 audio/video embedding support [#6418](https://github.com/diaspora/diaspora/pull/6418)
# 0.7.3.1
diff --git a/Gemfile b/Gemfile
index c87f86b7b..b74d7524a 100644
--- a/Gemfile
+++ b/Gemfile
@@ -26,7 +26,7 @@ gem "json-schema", "2.8.0"
# Authentication
-gem "devise", "4.3.0"
+gem "devise", "4.4.1"
gem "devise_lastseenable", "0.0.6"
# Captcha
@@ -122,6 +122,8 @@ source "https://rails-assets.org" do
gem "rails-assets-perfect-scrollbar", "0.6.16"
end
+gem "markdown-it-html5-embed", "1.0.0"
+
# Localization
gem "http_accept_language", "2.1.1"
@@ -186,7 +188,7 @@ gem "typhoeus", "1.3.0"
# Views
gem "gon", "6.1.0"
-gem "hamlit", "2.8.4"
+gem "hamlit", "2.8.6"
gem "mobile-fu", "1.4.0"
gem "rails-timeago", "2.16.0"
gem "will_paginate", "3.1.6"
diff --git a/Gemfile.lock b/Gemfile.lock
index 054f1b156..1bfe6a95e 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -133,7 +133,7 @@ GEM
tins (~> 1.6)
crack (0.4.3)
safe_yaml (~> 1.0.0)
- crass (1.0.2)
+ crass (1.0.3)
cucumber (2.4.0)
builder (>= 2.1.2)
cucumber-core (~> 1.5.0)
@@ -156,7 +156,7 @@ GEM
railties (>= 4, < 5.2)
cucumber-wire (0.0.1)
database_cleaner (1.6.1)
- devise (4.3.0)
+ devise (4.4.1)
bcrypt (~> 3.0)
orm_adapter (~> 0.1)
railties (>= 4.1.0, < 5.2)
@@ -187,7 +187,7 @@ GEM
entypo-rails (3.0.0)
railties (>= 4.1, < 6)
equalizer (0.0.11)
- erubi (1.6.1)
+ erubi (1.7.0)
eslintrb (2.1.0)
execjs
multi_json (>= 1.3)
@@ -273,7 +273,7 @@ GEM
guard-rubocop (1.3.0)
guard (~> 2.0)
rubocop (~> 0.20)
- haml (5.0.3)
+ haml (5.0.4)
temple (>= 0.8.0)
tilt
haml_lint (0.26.0)
@@ -282,7 +282,7 @@ GEM
rake (>= 10, < 13)
rubocop (>= 0.49.0)
sysexits (~> 1.1)
- hamlit (2.8.4)
+ hamlit (2.8.6)
temple (>= 0.8.0)
thor
tilt
@@ -306,7 +306,8 @@ GEM
httparty (0.15.6)
multi_xml (>= 0.5.2)
httpclient (2.8.3)
- i18n (0.8.6)
+ i18n (0.9.5)
+ concurrent-ruby (~> 1.0)
i18n-inflector (2.6.7)
i18n (>= 0.4.1)
i18n-inflector-rails (1.0.7)
@@ -359,7 +360,7 @@ GEM
multi_json (~> 1.10)
logging-rails (0.6.0)
logging (>= 1.8)
- loofah (2.1.1)
+ loofah (2.2.0)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
lumberjack (1.0.12)
@@ -367,6 +368,7 @@ GEM
systemu (~> 2.6.2)
mail (2.6.6)
mime-types (>= 1.16, < 4)
+ markdown-it-html5-embed (1.0.0)
markerb (1.1.0)
memoizable (0.4.2)
thread_safe (~> 0.3, >= 0.3.1)
@@ -377,7 +379,7 @@ GEM
mini_magick (4.8.0)
mini_mime (0.1.4)
mini_portile2 (2.3.0)
- minitest (5.10.3)
+ minitest (5.11.3)
mobile-fu (1.4.0)
rack-mobile-detect
rails
@@ -473,7 +475,7 @@ GEM
byebug (~> 9.1)
pry (~> 0.10)
public_suffix (3.0.0)
- rack (2.0.3)
+ rack (2.0.4)
rack-cors (1.0.1)
rack-google-analytics (1.2.0)
actionpack
@@ -492,7 +494,7 @@ GEM
rack-rewrite (1.5.1)
rack-ssl (1.4.1)
rack
- rack-test (0.7.0)
+ rack-test (0.8.2)
rack (>= 1.0, < 3)
rails (5.1.4)
actioncable (= 5.1.4)
@@ -576,7 +578,7 @@ GEM
rainbow (2.2.2)
rake
raindrops (0.19.0)
- rake (12.1.0)
+ rake (12.3.0)
rb-fsevent (0.10.2)
rb-inotify (0.9.10)
ffi (>= 0.5.0, < 2)
@@ -712,7 +714,7 @@ GEM
unf (~> 0.1.0)
typhoeus (1.3.0)
ethon (>= 0.9.0)
- tzinfo (1.2.3)
+ tzinfo (1.2.5)
thread_safe (~> 0.1)
uglifier (3.2.0)
execjs (>= 0.3.0, < 3)
@@ -780,7 +782,7 @@ DEPENDENCIES
cucumber-api-steps (= 0.13)
cucumber-rails (= 1.5.0)
database_cleaner (= 1.6.1)
- devise (= 4.3.0)
+ devise (= 4.4.1)
devise_lastseenable (= 0.0.6)
diaspora-prosody-config (= 0.0.7)
diaspora_federation-json_schema (= 0.2.3)
@@ -801,7 +803,7 @@ DEPENDENCIES
guard-rspec (= 4.7.3)
guard-rubocop (= 1.3.0)
haml_lint (= 0.26.0)
- hamlit (= 2.8.4)
+ hamlit (= 2.8.6)
handlebars_assets (= 0.23.2)
http_accept_language (= 2.1.1)
i18n-inflector-rails (= 1.0.7)
@@ -815,6 +817,7 @@ DEPENDENCIES
json-schema-rspec (= 0.0.4)
leaflet-rails (= 1.2.0)
logging-rails (= 0.6.0)
+ markdown-it-html5-embed (= 1.0.0)
markerb (= 1.1.0)
mini_magick (= 4.8.0)
minitest
diff --git a/app/assets/javascripts/app/helpers/text_formatter.js b/app/assets/javascripts/app/helpers/text_formatter.js
index 3fc051404..753590664 100644
--- a/app/assets/javascripts/app/helpers/text_formatter.js
+++ b/app/assets/javascripts/app/helpers/text_formatter.js
@@ -1,6 +1,11 @@
// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later
(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) {
mentions = mentions ? mentions : [];
@@ -83,6 +88,30 @@
// Bootstrap table markup
md.renderer.rules.table_open = function () { return "
\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);
};
})();
diff --git a/app/assets/javascripts/app/views/content_view.js b/app/assets/javascripts/app/views/content_view.js
index 78367563b..a0a04b661 100644
--- a/app/assets/javascripts/app/views/content_view.js
+++ b/app/assets/javascripts/app/views/content_view.js
@@ -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(){
+ this.bindMediaEmbedThumbClickEvent();
_.defer(_.bind(this.collapseOversized, this));
// 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({
postRenderTemplate : function(){
+ this.bindMediaEmbedThumbClickEvent();
+
var photoAttachments = this.$(".photo-attachments");
if(photoAttachments.length > 0) {
new app.views.Gallery({ el: photoAttachments });
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js
index a59460b96..ee4fc5e7f 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -26,6 +26,7 @@
//= require markdown-it-sanitizer
//= require markdown-it-sub
//= require markdown-it-sup
+//= require markdown-it-html5-embed
//= require highlightjs
//= require clear-form
//= require corejs-typeahead
diff --git a/app/assets/stylesheets/_application.scss b/app/assets/stylesheets/_application.scss
index 169bf1e1c..983ea5151 100644
--- a/app/assets/stylesheets/_application.scss
+++ b/app/assets/stylesheets/_application.scss
@@ -88,6 +88,7 @@
@import 'chat';
@import 'markdown-content';
@import 'oembed';
+@import 'media-embed';
@import 'post-content';
// contacts
diff --git a/app/assets/stylesheets/media-embed.scss b/app/assets/stylesheets/media-embed.scss
new file mode 100644
index 000000000..1083cd358
--- /dev/null
+++ b/app/assets/stylesheets/media-embed.scss
@@ -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%;
+ }
+}
diff --git a/app/assets/templates/media-embed_tpl.jst.hbs b/app/assets/templates/media-embed_tpl.jst.hbs
new file mode 100644
index 000000000..4c9b3b83c
--- /dev/null
+++ b/app/assets/templates/media-embed_tpl.jst.hbs
@@ -0,0 +1,19 @@
+
diff --git a/config/cucumber.yml b/config/cucumber.yml
index cace674ba..977c9d5ae 100644
--- a/config/cucumber.yml
+++ b/config/cucumber.yml
@@ -9,11 +9,11 @@ screenshot_opts = "--require features --format pretty"
%>
# 'normal' test runs
-default: <%= std_opts %> -r features
+default: <%= std_opts %> -r features --tags ~@nophantomjs
wip: -r features --tags @wip:3 --wip features
rerun: <%= rerun_opts %> --format rerun --out rerun.txt --strict --tags ~@wip --tags ~@screenshots
# screenshot feature
ref_screens: "<%= screenshot_opts %> --tags @reference-screenshots"
cmp_screens: "<%= screenshot_opts %> --tags @comparison-screenshots"
-all_screens: "<%= screenshot_opts %> --tags @screenshots"
\ No newline at end of file
+all_screens: "<%= screenshot_opts %> --tags @screenshots"
diff --git a/config/locales/diaspora/en.yml b/config/locales/diaspora/en.yml
index 58393959b..6c6c3f394 100644
--- a/config/locales/diaspora/en.yml
+++ b/config/locales/diaspora/en.yml
@@ -436,7 +436,7 @@ en:
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."
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. We’re 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. We’re 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_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 you’re in rather than the specific street address."
post_poll_q: "How do I add a poll to my post?"
diff --git a/features/desktop/media-embed.feature b/features/desktop/media-embed.feature
new file mode 100644
index 000000000..07efef81d
--- /dev/null
+++ b/features/desktop/media-embed.feature
@@ -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
+
diff --git a/features/step_definitions/media_embed_steps.rb b/features/step_definitions/media_embed_steps.rb
new file mode 100644
index 000000000..356a2ed6d
--- /dev/null
+++ b/features/step_definitions/media_embed_steps.rb
@@ -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
diff --git a/features/support/publishing_cuke_helpers.rb b/features/support/publishing_cuke_helpers.rb
index 13ed9834c..3fe3e4b59 100644
--- a/features/support/publishing_cuke_helpers.rb
+++ b/features/support/publishing_cuke_helpers.rb
@@ -26,13 +26,17 @@ module PublishingCukeHelpers
submit_publisher
end
+ def visible_text_from_markdown(text)
+ CGI.unescapeHTML(ActionController::Base.helpers.strip_tags(Diaspora::MessageRenderer.new(text).markdownified.strip))
+ end
+
def submit_publisher
txt = find("#publisher #status_message_text").value
find("#publisher .btn-primary").click
# wait for the publisher to be closed
expect(find("#publisher")["class"]).to include("closed")
# 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
def click_and_post(text)
diff --git a/spec/javascripts/app/helpers/text_formatter_spec.js b/spec/javascripts/app/helpers/text_formatter_spec.js
index c2e8e7bcf..fdd73d305 100644
--- a/spec/javascripts/app/helpers/text_formatter_spec.js
+++ b/spec/javascripts/app/helpers/text_formatter_spec.js
@@ -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 =
+ 'title
\n' +
+ '\n";
+ var content = "[title](https://example.org/file.mp3)";
+ var parsed = this.formatter(content);
+
+ expect(parsed).toContain(html);
+ });
+
+ it("embeds video", function() {
+ var html =
+ 'title
\n' +
+ '\n";
+
+ var content = "[title](https://example.org/file.mp4)";
+ var parsed = this.formatter(content);
+
+ expect(parsed).toContain(html);
+ });
+ });
});
context("real world examples", function(){
diff --git a/spec/javascripts/app/views/content_view_spec.js b/spec/javascripts/app/views/content_view_spec.js
index 2315b4473..a10a3cfc6 100644
--- a/spec/javascripts/app/views/content_view_spec.js
+++ b/spec/javascripts/app/views/content_view_spec.js
@@ -36,4 +36,43 @@ describe("app.views.Content", function(){
expect(this.view.presenter().location).toEqual(factory.location());
});
});
+
+ // These tests don't work in PhantomJS because it doesn't support HTML5