mirror of
https://github.com/philomena-dev/philomena.git
synced 2024-11-27 13:47:58 +01:00
image container spoilering
This commit is contained in:
parent
127aa542f4
commit
f9c9f1a921
8 changed files with 145 additions and 85 deletions
|
@ -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 {
|
||||||
|
|
46
assets/js/imagesclientside.js
Normal file
46
assets/js/imagesclientside.js
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
|
|
@ -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
|
||||||
[] ->
|
[] ->
|
||||||
|
|
|
@ -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()
|
||||||
|
|
30
lib/philomena_web/templates/image/_filter_info.html.slime
Normal file
30
lib/philomena_web/templates/image/_filter_info.html.slime
Normal 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
|
|
@ -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
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue