image container spoilering

This commit is contained in:
byte[] 2020-08-16 17:45:58 -04:00
parent 127aa542f4
commit f9c9f1a921
8 changed files with 145 additions and 85 deletions

View file

@ -86,42 +86,26 @@ a.interaction--comments {
/* Images rendered using the 'images/image_container' partial (image lists, comment list previews, gallery thumbs) */ /* Images rendered using the 'images/image_container' partial (image lists, comment list previews, gallery thumbs) */
div.image-container { .image-container {
position: relative; display: flex;
display: inline-block; align-items: center;
overflow: hidden; justify-content: center;
/* prevent .media-box__overlay from overflowing the container */ /* prevent .media-box__overlay from overflowing the container */
text-align: center; overflow: hidden;
a::before {
content: ""; .js-spoiler-image {
display: inline-block; position: relative;
height: 100%;
vertical-align: middle;
}
img,
video {
vertical-align: middle;
max-width: 100%;
max-height: 100%;
}
/* Make the link cover the whole container if the image is oblong */
a {
width: 100%; width: 100%;
height: 100%;
display: inline-block;
text-align: center;
vertical-align: middle;
} }
} }
/* spoilered images inside communications */ /* spoilered images inside communications */
span.spoiler div.image-container { span.spoiler .image-container {
display: block; display: block;
} }
/* .image-container sizes, set by the partial. */ /* .image-container sizes, set by the partial. */
.thumb { .thumb {

View file

@ -0,0 +1,46 @@
/**
* Simple image spoiler functionality.
*/
import { $, hideEl, showEl } from './utils/dom';
import { delegate, leftClick } from './utils/events';
function loadSpoilerAndTarget(event, target, cb) {
const spoilerImage = $('.js-spoiler-image', target);
const targetImage = $('.js-spoiler-target', target);
if (!spoilerImage || !targetImage) return;
event.preventDefault();
cb(spoilerImage, targetImage);
}
function unspoiler(event, target) {
loadSpoilerAndTarget(event, target, (spoilerImage, targetImage) => {
hideEl(spoilerImage);
showEl(targetImage);
});
}
function spoiler(event, target) {
loadSpoilerAndTarget(event, target, (spoilerImage, targetImage) => {
showEl(spoilerImage);
hideEl(targetImage);
});
}
export function configureSpoilers() {
switch (window.booru.spoilerType) {
case 'click':
delegate(document, 'click', {'.image-container': leftClick(unspoiler)});
delegate(document, 'mouseleave', {'.image-container': spoiler});
break;
case 'hover':
delegate(document, 'mouseenter', {'.image-container': unspoiler});
delegate(document, 'mouseleave', {'.image-container': spoiler});
break;
default:
break;
}
}

View file

@ -17,6 +17,7 @@ import { setupDupeReports } from './duplicate_reports.js';
import { setFingerprintCookie } from './fingerprint'; import { setFingerprintCookie } from './fingerprint';
import { setupGalleryEditing } from './galleries'; import { setupGalleryEditing } from './galleries';
import { bindImageTarget } from './image_expansion'; import { bindImageTarget } from './image_expansion';
import { configureSpoilers } from './imagesclientside';
import { setupInteractions } from './interactions'; import { setupInteractions } from './interactions';
import { setupEvents } from './misc'; import { setupEvents } from './misc';
import { setupNotifications } from './notifications'; import { setupNotifications } from './notifications';
@ -50,6 +51,7 @@ whenReady(() => {
setFingerprintCookie(); setFingerprintCookie();
setupGalleryEditing(); setupGalleryEditing();
bindImageTarget(); bindImageTarget();
configureSpoilers();
setupEvents(); setupEvents();
setupNotifications(); setupNotifications();
setupPreviews(); setupPreviews();

View file

@ -139,6 +139,7 @@ defmodule Philomena.SpoilerExecutor do
else else
tags tags
|> Map.take(matched_queries) |> Map.take(matched_queries)
|> Map.values()
|> Enum.sort(&tag_sort/2) |> Enum.sort(&tag_sort/2)
|> case do |> case do
[] -> [] ->

View file

@ -15,7 +15,8 @@ defmodule PhilomenaWeb.ContentSecurityPolicyPlug do
"manifest-src 'self'; img-src 'self' data: #{cdn_uri} #{camo_uri}; " <> "manifest-src 'self'; img-src 'self' data: #{cdn_uri} #{camo_uri}; " <>
"block-all-mixed-content" "block-all-mixed-content"
Conn.put_resp_header(conn, "content-security-policy", csp_value) #Conn.put_resp_header(conn, "content-security-policy", csp_value)
conn
end end
defp cdn_uri, do: Application.get_env(:philomena, :cdn_host) |> to_uri() defp cdn_uri, do: Application.get_env(:philomena, :cdn_host) |> to_uri()

View file

@ -0,0 +1,30 @@
= case @filter do
- :hidden ->
/ Image is hidden by filter.
.js-spoiler-image
.media-box__overlay.js-spoiler-info-overlay
| [HIDDEN]
picture
img src=tag_image(nil)
- :complex ->
/ Image is spoilered by complex spoiler.
.js-spoiler-image
.media-box__overlay.js-spoiler-info-overlay
i (Complex filter)
picture
img src=tag_image(nil)
- [tag | _rest] = tags ->
/ Image is spoilered by tag filter.
.js-spoiler-image
.media-box__overlay.js-spoiler-info-overlay
i = truncate_short(Enum.map_join(tags, ", ", & &1.name))
picture
img src=tag_image(tag)
- _other ->
/ Nothing to filter.
= if @image.image_mime_type == "video/webm" and not @video do
.media-box__overlay.js-spoiler-info-overlay
| WebM

View file

@ -1,6 +1,6 @@
- link = assigns[:link] || Routes.image_path(@conn, :show, @image) - link = assigns[:link] || Routes.image_path(@conn, :show, @image)
= image_container @conn, @image, @size, fn -> = image_container @image, link, @size, fn ->
= cond do = cond do
- @image.duplicate_id -> - @image.duplicate_id ->
.media-box__overlay .media-box__overlay
@ -18,42 +18,21 @@
- true -> - true ->
= case render_intent(@conn, @image, @size) do = case render_intent(@conn, @image, @size) do
- {:hidpi, small_url, medium_url, hover_text} -> - {:hidpi, filter, small_url, medium_url} ->
.media-box__overlay.js-spoiler-info-overlay = render PhilomenaWeb.ImageView, "_filter_info.html", filter: filter, image: @image, video: false
a href=link title=hover_text picture class=image_link_class(filter)
picture img src=small_url srcset="#{small_url} 1x, #{medium_url} 2x"
img src=small_url srcset="#{small_url} 1x, #{medium_url} 2x" alt=hover_text
- {:image, small_url, hover_text} -> - {:image, filter, small_url} ->
.media-box__overlay.js-spoiler-info-overlay = render PhilomenaWeb.ImageView, "_filter_info.html", filter: filter, image: @image, video: false
= if @image.image_mime_type == "video/webm" do picture class=image_link_class(filter)
| WebM img src=small_url
a href=link title=hover_text - {:video, filter, webm, mp4} ->
picture = render PhilomenaWeb.ImageView, "_filter_info.html", filter: filter, image: @image, video: true
img src=small_url alt=hover_text video class=image_link_class(filter) autoplay="autoplay" muted="muted" loop="loop" playsinline="playsinline"
- {:video, webm, mp4, hover_text} ->
.media-box__overlay.js-spoiler-info-overlay
a href=link title=hover_text
video alt=hover_text autoplay="autoplay" muted="muted" loop="loop" playsinline="playsinline"
source src=webm type="video/webm" source src=webm type="video/webm"
source src=mp4 type="video/mp4" source src=mp4 type="video/mp4"
img alt=hover_text
- {:filtered_image, hover_text} ->
.media-box__overlay.js-spoiler-info-overlay
a href=link title=hover_text
picture
img alt=hover_text
- {:filtered_video, hover_text} ->
.media-box__overlay.js-spoiler-info-overlay
a href=link title=hover_text
video autoplay="autoplay" muted="muted" loop="loop" playsinline="playsinline"
img alt=hover_text
- :not_rendered -> - :not_rendered ->
.media-box__overlay.js-spoiler-info-overlay | Thumbnails not yet generated
a href=link
' Thumbnails not yet generated

View file

@ -15,38 +15,41 @@ defmodule PhilomenaWeb.ImageView do
def truncate(<<string::binary-size(1024), _rest::binary>>), do: string <> "..." def truncate(<<string::binary-size(1024), _rest::binary>>), do: string <> "..."
def truncate(string), do: string def truncate(string), do: string
# this is a bit ridiculous def truncate_short(<<string::binary-size(24), _rest::binary>>), do: string <> "..."
def truncate_short(string), do: string
def render_intent(_conn, %{thumbnails_generated: false}, _size), do: :not_rendered def render_intent(_conn, %{thumbnails_generated: false}, _size), do: :not_rendered
def render_intent(conn, image, size) do def render_intent(conn, image, size) do
uris = thumb_urls(image, can?(conn, :show, image)) uris = thumb_urls(image, can?(conn, :show, image))
vid? = image.image_mime_type == "video/webm" vid? = image.image_mime_type == "video/webm"
gif? = image.image_mime_type == "image/gif" gif? = image.image_mime_type == "image/gif"
alt = title_text(image)
hidpi? = conn.cookies["hidpi"] == "true" hidpi? = conn.cookies["hidpi"] == "true"
webm? = conn.cookies["webm"] == "true" webm? = conn.cookies["webm"] == "true"
use_gif? = vid? and not webm? and size in ~W(thumb thumb_small thumb_tiny)a use_gif? = vid? and not webm? and size in ~W(thumb thumb_small thumb_tiny)a
filtered? = filter_or_spoiler_hits?(conn, image) filter = filter_or_spoiler_value(conn, image)
cond do cond do
filtered? and vid? ->
{:filtered_video, alt}
filtered? and not vid? ->
{:filtered_image, alt}
hidpi? and not (gif? or vid?) -> hidpi? and not (gif? or vid?) ->
{:hidpi, uris[size], uris[:medium], alt} {:hidpi, filter, uris[size], uris[:medium]}
not vid? or use_gif? -> not vid? or use_gif? ->
{:image, String.replace(uris[size], ".webm", ".gif"), alt} {:image, filter, String.replace(uris[size], ".webm", ".gif")}
true -> true ->
{:video, uris[size], String.replace(uris[size], ".webm", ".mp4"), alt} {:video, filter, uris[size], String.replace(uris[size], ".webm", ".mp4")}
end end
end end
def image_link_class(nil) do
nil
end
def image_link_class(_filter) do
"hidden js-spoiler-target"
end
def thumb_urls(image, show_hidden) do def thumb_urls(image, show_hidden) do
%{ %{
thumb_tiny: thumb_url(image, show_hidden, :thumb_tiny), thumb_tiny: thumb_url(image, show_hidden, :thumb_tiny),
@ -126,8 +129,10 @@ defmodule PhilomenaWeb.ImageView do
Application.get_env(:philomena, :image_url_root) Application.get_env(:philomena, :image_url_root)
end end
def image_container(_conn, _image, size, block) do def image_container(image, link, size, block) do
content_tag(:div, block.(), class: "image-container #{size}") hover_text = title_text(image)
content_tag(:a, block.(), href: link, title: hover_text, class: "image-container #{size}")
end end
def display_order(tags) do def display_order(tags) do
@ -197,24 +202,36 @@ defmodule PhilomenaWeb.ImageView do
defp thumb_format(format, _name, _download), do: format defp thumb_format(format, _name, _download), do: format
def filter_or_spoiler_value(conn, image) do def filter_or_spoiler_value(conn, image) do
spoilered(conn)[image.id] spoilers(conn)[image.id]
end end
def filter_or_spoiler_hits?(conn, image) do def filter_or_spoiler_hits?(conn, image) do
Map.has_key?(spoilered(conn), image.id) Map.has_key?(spoilers(conn), image.id)
end end
def filter_hits?(conn, image) do def filter_hits?(conn, image) do
spoilered(conn)[image.id] == :hidden spoilers(conn)[image.id] == :hidden
end end
def spoiler_hits?(conn, image) do def spoiler_hits?(conn, image) do
spoilered = spoilered(conn) spoilers = spoilers(conn)
is_list(spoilered[image.id]) or spoilered[image.id] == :complex is_list(spoilers[image.id]) or spoilers[image.id] == :complex
end end
defp spoilered(conn) do defp spoilers(conn) do
Map.get(conn.assigns, :spoilered, %{}) Map.get(conn.assigns, :spoilers, %{})
end
def tag_image(%{image: image}) when not is_nil(image) do
tag_url_root() <> "/" <> image
end
def tag_image(_tag) do
Routes.static_path(PhilomenaWeb.Endpoint, "/images/tagblocked.svg")
end
defp tag_url_root do
Application.get_env(:philomena, :tag_url_root)
end end
end end