Browse Source

Add embeddable posts

1570-levenshtein-distance-user-search
Egor Kislitsyn 4 years ago
parent
commit
cb8236cda6
No known key found for this signature in database GPG Key ID: 1B49CB15B71E7805
9 changed files with 384 additions and 1 deletions
  1. +42
    -0
      lib/pleroma/web/embed_controller.ex
  2. +1
    -1
      lib/pleroma/web/endpoint.ex
  3. +2
    -0
      lib/pleroma/web/router.ex
  4. +8
    -0
      lib/pleroma/web/templates/embed/_attachment.html.eex
  5. +76
    -0
      lib/pleroma/web/templates/embed/show.html.eex
  6. +14
    -0
      lib/pleroma/web/templates/layout/embed.html.eex
  7. +83
    -0
      lib/pleroma/web/views/embed_view.ex
  8. +115
    -0
      priv/static/embed.css
  9. +43
    -0
      priv/static/embed.js

+ 42
- 0
lib/pleroma/web/embed_controller.ex View 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

+ 1
- 1
lib/pleroma/web/endpoint.ex View File

@@ -35,7 +35,7 @@ defmodule Pleroma.Web.Endpoint do
at: "/",
from: :pleroma,
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
gzip: true,
cache_control_for_etags: @static_cache_control,


+ 2
- 0
lib/pleroma/web/router.ex View File

@@ -637,6 +637,8 @@ defmodule Pleroma.Web.Router do
post("/auth/password", MastodonAPI.AuthController, :password_reset)

get("/web/*path", MastoFEController, :index)

get("/embed/:id", EmbedController, :show)
end

pipeline :remote_media do


+ 8
- 0
lib/pleroma/web/templates/embed/_attachment.html.eex View 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
- 0
lib/pleroma/web/templates/embed/show.html.eex View 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
- 0
lib/pleroma/web/templates/layout/embed.html.eex View 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
- 0
lib/pleroma/web/views/embed_view.ex View 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
- 0
priv/static/embed.css View 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
- 0
priv/static/embed.js View 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…
Cancel
Save