Unverified Commit b32c8443 authored by Senya's avatar Senya Committed by Benjamin Neff

Support for embedding HTML5 media links

Use markdown-it-html5-embed plugin so user can embed audio and
video using the markdown link syntax []() in the HTML5 way.
parent 258b502c
......@@ -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"
......
......@@ -368,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)
......@@ -816,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
......
// @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 "<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);
};
})();
......
......@@ -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 });
......
......@@ -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
......
......@@ -88,6 +88,7 @@
@import 'chat';
@import 'markdown-content';
@import 'oembed';
@import 'media-embed';
@import 'post-content';
// contacts
......
$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%;
}
}
<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>
......@@ -421,7 +421,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?"
......
@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
# 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
......@@ -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)
......
......@@ -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(){
......
......@@ -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 <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();
});
});
}
});
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment