Add embeddable posts
This commit is contained in:
parent
d74405fc1a
commit
cb8236cda6
42
lib/pleroma/web/embed_controller.ex
Normal file
42
lib/pleroma/web/embed_controller.ex
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.EmbedController do
|
||||||
|
use Pleroma.Web, :controller
|
||||||
|
|
||||||
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.Object
|
||||||
|
alias Pleroma.User
|
||||||
|
|
||||||
|
alias Pleroma.Web.ActivityPub.Visibility
|
||||||
|
|
||||||
|
plug(:put_layout, :embed)
|
||||||
|
|
||||||
|
def show(conn, %{"id" => id}) do
|
||||||
|
with %Activity{local: true} = activity <-
|
||||||
|
Activity.get_by_id_with_object(id),
|
||||||
|
true <- Visibility.is_public?(activity.object) do
|
||||||
|
{:ok, author} = User.get_or_fetch(activity.object.data["actor"])
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> delete_resp_header("x-frame-options")
|
||||||
|
|> delete_resp_header("content-security-policy")
|
||||||
|
|> render("show.html",
|
||||||
|
activity: activity,
|
||||||
|
author: User.sanitize_html(author),
|
||||||
|
counts: get_counts(activity)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp get_counts(%Activity{} = activity) do
|
||||||
|
%Object{data: data} = Object.normalize(activity)
|
||||||
|
|
||||||
|
%{
|
||||||
|
likes: Map.get(data, "like_count", 0),
|
||||||
|
replies: Map.get(data, "repliesCount", 0),
|
||||||
|
announces: Map.get(data, "announcement_count", 0)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
|
@ -35,7 +35,7 @@ defmodule Pleroma.Web.Endpoint do
|
||||||
at: "/",
|
at: "/",
|
||||||
from: :pleroma,
|
from: :pleroma,
|
||||||
only:
|
only:
|
||||||
~w(index.html robots.txt static finmoji emoji packs sounds images instance sw.js sw-pleroma.js favicon.png schemas doc),
|
~w(index.html robots.txt static finmoji emoji packs sounds images instance sw.js sw-pleroma.js favicon.png schemas doc embed.js embed.css),
|
||||||
# credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength
|
# credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength
|
||||||
gzip: true,
|
gzip: true,
|
||||||
cache_control_for_etags: @static_cache_control,
|
cache_control_for_etags: @static_cache_control,
|
||||||
|
|
|
@ -637,6 +637,8 @@ defmodule Pleroma.Web.Router do
|
||||||
post("/auth/password", MastodonAPI.AuthController, :password_reset)
|
post("/auth/password", MastodonAPI.AuthController, :password_reset)
|
||||||
|
|
||||||
get("/web/*path", MastoFEController, :index)
|
get("/web/*path", MastoFEController, :index)
|
||||||
|
|
||||||
|
get("/embed/:id", EmbedController, :show)
|
||||||
end
|
end
|
||||||
|
|
||||||
pipeline :remote_media do
|
pipeline :remote_media do
|
||||||
|
|
8
lib/pleroma/web/templates/embed/_attachment.html.eex
Normal file
8
lib/pleroma/web/templates/embed/_attachment.html.eex
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<%= case @mediaType do %>
|
||||||
|
<% "audio" -> %>
|
||||||
|
<audio src="<%= @url %>" controls="controls"></audio>
|
||||||
|
<% "video" -> %>
|
||||||
|
<video src="<%= @url %>" controls="controls"></video>
|
||||||
|
<% _ -> %>
|
||||||
|
<img src="<%= @url %>" alt="<%= @name %>" title="<%= @name %>">
|
||||||
|
<% end %>
|
76
lib/pleroma/web/templates/embed/show.html.eex
Normal file
76
lib/pleroma/web/templates/embed/show.html.eex
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
<div>
|
||||||
|
<div class="p-author h-card">
|
||||||
|
<a class="u-url" rel="author noopener" href="<%= User.profile_url(@author) %>">
|
||||||
|
<div class="avatar">
|
||||||
|
<img src="<%= User.avatar_url(@author) |> MediaProxy.url %>" width="48" height="48" alt="">
|
||||||
|
</div>
|
||||||
|
<span class="display-name" style="padding-left: 0.5em;">
|
||||||
|
<bdi><%= raw (@author.name |> Formatter.emojify(emoji_for_user(@author))) %></bdi>
|
||||||
|
<span class="nickname"><%= full_nickname(@author) %></span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="activity-content" >
|
||||||
|
<%= if status_title(@activity) != "" do %>
|
||||||
|
<details <%= if open_content?() do %>open<% end %>>
|
||||||
|
<summary><%= raw status_title(@activity) %></summary>
|
||||||
|
<div><%= activity_content(@activity) %></div>
|
||||||
|
</details>
|
||||||
|
<% else %>
|
||||||
|
<div><%= activity_content(@activity) %></div>
|
||||||
|
<% end %>
|
||||||
|
<%= for %{"name" => name, "url" => [url | _]} <- attachments(@activity) do %>
|
||||||
|
<div class="attachment">
|
||||||
|
<%= if sensitive?(@activity) do %>
|
||||||
|
<details class="nsfw">
|
||||||
|
<summary onClick="updateHeight()"><%= Gettext.gettext("sensitive media") %></summary>
|
||||||
|
<div class="nsfw-content">
|
||||||
|
<%= render("_attachment.html", %{name: name, url: url["href"],
|
||||||
|
mediaType: fetch_media_type(url)}) %>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
<% else %>
|
||||||
|
<%= render("_attachment.html", %{name: name, url: url["href"],
|
||||||
|
mediaType: fetch_media_type(url)}) %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<dl class="counts pull-right">
|
||||||
|
<dt><%= Gettext.gettext("replies") %></dt><dd><%= @counts.replies %></dd>
|
||||||
|
<dt><%= Gettext.gettext("announces") %></dt><dd><%= @counts.announces %></dd>
|
||||||
|
<dt><%= Gettext.gettext("likes") %></dt><dd><%= @counts.likes %></dd>
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
<p class="date pull-left">
|
||||||
|
<%= link published(@activity), to: activity_url(@author, @activity) %>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function updateHeight() {
|
||||||
|
window.requestAnimationFrame(function(){
|
||||||
|
var height = document.getElementsByTagName('html')[0].scrollHeight;
|
||||||
|
|
||||||
|
window.parent.postMessage({
|
||||||
|
type: 'setHeightPleromaEmbed',
|
||||||
|
id: window.parentId,
|
||||||
|
height: height,
|
||||||
|
}, '*');
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('message', function(e){
|
||||||
|
var data = e.data || {};
|
||||||
|
|
||||||
|
if (!window.parent || data.type !== 'setHeightPleromaEmbed') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.parentId = data.id
|
||||||
|
|
||||||
|
updateHeight()
|
||||||
|
});
|
||||||
|
</script>
|
14
lib/pleroma/web/templates/layout/embed.html.eex
Normal file
14
lib/pleroma/web/templates/layout/embed.html.eex
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1,minimal-ui" />
|
||||||
|
<title><%= Pleroma.Config.get([:instance, :name]) %></title>
|
||||||
|
<meta content='noindex' name='robots'>
|
||||||
|
<%= Phoenix.HTML.raw(assigns[:meta] || "") %>
|
||||||
|
<link rel="stylesheet" href="/embed.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<%= render @view_module, @view_template, assigns %>
|
||||||
|
</body>
|
||||||
|
</html>
|
83
lib/pleroma/web/views/embed_view.ex
Normal file
83
lib/pleroma/web/views/embed_view.ex
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.EmbedView do
|
||||||
|
use Pleroma.Web, :view
|
||||||
|
|
||||||
|
alias Calendar.Strftime
|
||||||
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.Emoji.Formatter
|
||||||
|
alias Pleroma.Object
|
||||||
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.Gettext
|
||||||
|
alias Pleroma.Web.MediaProxy
|
||||||
|
alias Pleroma.Web.Metadata.Utils
|
||||||
|
alias Pleroma.Web.Router.Helpers
|
||||||
|
|
||||||
|
use Phoenix.HTML
|
||||||
|
|
||||||
|
@media_types ["image", "audio", "video"]
|
||||||
|
|
||||||
|
defp emoji_for_user(%User{} = user) do
|
||||||
|
user.source_data
|
||||||
|
|> Map.get("tag", [])
|
||||||
|
|> Enum.filter(fn %{"type" => t} -> t == "Emoji" end)
|
||||||
|
|> Enum.map(fn %{"icon" => %{"url" => url}, "name" => name} ->
|
||||||
|
{String.trim(name, ":"), url}
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp fetch_media_type(%{"mediaType" => mediaType}) do
|
||||||
|
Utils.fetch_media_type(@media_types, mediaType)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp open_content? do
|
||||||
|
Pleroma.Config.get(
|
||||||
|
[:frontend_configurations, :collapse_message_with_subjects],
|
||||||
|
true
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp full_nickname(user) do
|
||||||
|
%{host: host} = URI.parse(user.ap_id)
|
||||||
|
"@" <> user.nickname <> "@" <> host
|
||||||
|
end
|
||||||
|
|
||||||
|
defp status_title(%Activity{object: %Object{data: %{"name" => name}}}) when is_binary(name),
|
||||||
|
do: name
|
||||||
|
|
||||||
|
defp status_title(%Activity{object: %Object{data: %{"summary" => summary}}})
|
||||||
|
when is_binary(summary),
|
||||||
|
do: summary
|
||||||
|
|
||||||
|
defp status_title(_), do: nil
|
||||||
|
|
||||||
|
defp activity_content(%Activity{object: %Object{data: %{"content" => content}}}) do
|
||||||
|
content |> Pleroma.HTML.filter_tags() |> raw()
|
||||||
|
end
|
||||||
|
|
||||||
|
defp activity_content(_), do: nil
|
||||||
|
|
||||||
|
defp activity_url(%User{local: true}, activity) do
|
||||||
|
Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, activity)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp activity_url(%User{local: false}, %Activity{object: %Object{data: data}}) do
|
||||||
|
data["url"] || data["external_url"] || data["id"]
|
||||||
|
end
|
||||||
|
|
||||||
|
defp attachments(%Activity{object: %Object{data: %{"attachment" => attachments}}}) do
|
||||||
|
attachments
|
||||||
|
end
|
||||||
|
|
||||||
|
defp sensitive?(%Activity{object: %Object{data: %{"sensitive" => sensitive}}}) do
|
||||||
|
sensitive
|
||||||
|
end
|
||||||
|
|
||||||
|
defp published(%Activity{object: %Object{data: %{"published" => published}}}) do
|
||||||
|
published
|
||||||
|
|> NaiveDateTime.from_iso8601!()
|
||||||
|
|> Strftime.strftime!("%B %d, %Y, %l:%M %p")
|
||||||
|
end
|
||||||
|
end
|
115
priv/static/embed.css
Normal file
115
priv/static/embed.css
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
body {
|
||||||
|
background-color: #282c37;
|
||||||
|
font-family: sans-serif;
|
||||||
|
color: white;
|
||||||
|
margin: 0;
|
||||||
|
padding: 1em;
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar img {
|
||||||
|
float: left;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-content {
|
||||||
|
padding-top: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attachment {
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attachment img {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date a,
|
||||||
|
.counts {
|
||||||
|
color: #666;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.counts dt,
|
||||||
|
.counts dd {
|
||||||
|
float: left;
|
||||||
|
margin-left: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h-card {
|
||||||
|
min-height: 48px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h-card a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h-card a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.display-name {
|
||||||
|
padding-top: 4px;
|
||||||
|
display: block;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* keep emoji from being hilariously huge */
|
||||||
|
.display-name img {
|
||||||
|
max-height: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.display-name .nickname {
|
||||||
|
padding-top: 4px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nickname:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pull-right {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapse {
|
||||||
|
margin: 0;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.button {
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: inline-block;
|
||||||
|
color: white;
|
||||||
|
background-color: #419bdd;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: none;
|
||||||
|
padding: 10px;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.button:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
background-color: #61a6d9;
|
||||||
|
}
|
43
priv/static/embed.js
Normal file
43
priv/static/embed.js
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
(function () {
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
var ready = function (loaded) {
|
||||||
|
if (['interactive', 'complete'].indexOf(document.readyState) !== -1) {
|
||||||
|
loaded()
|
||||||
|
} else {
|
||||||
|
document.addEventListener('DOMContentLoaded', loaded)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ready(function () {
|
||||||
|
var iframes = []
|
||||||
|
|
||||||
|
window.addEventListener('message', function (e) {
|
||||||
|
var data = e.data || {}
|
||||||
|
|
||||||
|
if (data.type !== 'setHeightPleromaEmbed' || !iframes[data.id]) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
iframes[data.id].height = data.height
|
||||||
|
});
|
||||||
|
|
||||||
|
[].forEach.call(document.querySelectorAll('iframe.pleroma-embed'), function (iframe) {
|
||||||
|
iframe.scrolling = 'no'
|
||||||
|
iframe.style.overflow = 'hidden'
|
||||||
|
|
||||||
|
iframes.push(iframe)
|
||||||
|
|
||||||
|
var id = iframes.length - 1
|
||||||
|
|
||||||
|
iframe.onload = function () {
|
||||||
|
iframe.contentWindow.postMessage({
|
||||||
|
type: 'setHeightPleromaEmbed',
|
||||||
|
id: id
|
||||||
|
}, '*')
|
||||||
|
}
|
||||||
|
|
||||||
|
iframe.onload()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})()
|
Loading…
Reference in a new issue