From 5e7b2354e2f285ea0ebbcfea9dbb7843c6f33a1a Mon Sep 17 00:00:00 2001
From: alemi
Date: Thu, 4 Jul 2024 02:14:50 +0200
Subject: [PATCH] feat(web): better layout for lemmy posts
image on the side that expands on click, text that "reflows" under the
image, attachments don't overflow etc. also mentions. also refactored a
bit. since i refactored its hard to split these 3 changes so have one
big commit aha
---
web/index.html | 33 ++--
.../activity.rs => activities/item.rs} | 4 +-
web/src/activities/mod.rs | 1 +
web/src/components/mod.rs | 6 -
web/src/lib.rs | 6 +-
web/src/objects/attachment.rs | 100 ++++++++++++
.../{components/object.rs => objects/item.rs} | 145 +++++-------------
web/src/objects/mod.rs | 3 +
web/src/prelude.rs | 11 +-
9 files changed, 178 insertions(+), 131 deletions(-)
rename web/src/{components/activity.rs => activities/item.rs} (95%)
create mode 100644 web/src/activities/mod.rs
create mode 100644 web/src/objects/attachment.rs
rename web/src/{components/object.rs => objects/item.rs} (69%)
diff --git a/web/index.html b/web/index.html
index 596e4386..3304329e 100644
--- a/web/index.html
+++ b/web/index.html
@@ -94,6 +94,9 @@
article p {
margin: 0 0 0 .5em;
}
+ article.float-container {
+ overflow-y: hidden;
+ }
b.displayname {
overflow-wrap: break-word;
}
@@ -132,15 +135,16 @@
position: sticky;
background-color: var(--background);
}
- div.border {
- border: 1px dashed var(--accent);
- }
span.border-button {
border: 1px solid var(--background-dim);
}
span.border-button:hover {
background-color: var(--background-dim);
}
+ div.border,
+ span.border {
+ border: 1px dashed var(--accent);
+ }
div.inline {
display: inline;
}
@@ -203,18 +207,25 @@
min-width: 2em;
max-width: 2em;
}
- div.flex-pic-container {
- flex: 1;
- border: solid 3px #bf616a;
- margin-right: .5em;
+ img.flex-pic {
+ float: left;
+ width: 10em;
+ height: 10em;
+ object-fit: cover;
+ margin-right: 1em;
+ margin-top: .5em;
+ margin-bottom: .5em;
+ margin-left: .5em;
}
- div.flex-pic {
- background-size: cover;
- margin: 5px;
- height: calc(100% - 10px); /* TODO can we avoid this calc() without having this overflow??? */
+ img.flex-pic-expand {
+ width: unset;
+ height: unset;
+ max-width: calc(100% - 1.5em);
+ max-height: 55vh;
}
.box {
border: 3px solid var(--accent);
+ box-sizing: border-box;
}
.cursor {
cursor: pointer;
diff --git a/web/src/components/activity.rs b/web/src/activities/item.rs
similarity index 95%
rename from web/src/components/activity.rs
rename to web/src/activities/item.rs
index 09721f80..d2e09e4f 100644
--- a/web/src/components/activity.rs
+++ b/web/src/activities/item.rs
@@ -55,14 +55,14 @@ pub fn Item(
match item.object_type().unwrap_or(apb::ObjectType::Object) {
// special case for placeholder activities
apb::ObjectType::Note | apb::ObjectType::Document(_) =>
- Some(view! { {sep.clone()} }.into_view()),
+ Some(view! { {sep.clone()} }.into_view()),
// everything else
apb::ObjectType::Activity(t) => {
let object_id = item.object().id().str().unwrap_or_default();
let object = match t {
apb::ActivityType::Create | apb::ActivityType::Announce =>
cache::OBJECTS.get(&object_id).map(|obj| {
- view! { }
+ view! { }
}.into_view()),
apb::ActivityType::Follow =>
cache::OBJECTS.get(&object_id).map(|obj| {
diff --git a/web/src/activities/mod.rs b/web/src/activities/mod.rs
new file mode 100644
index 00000000..a35e98d6
--- /dev/null
+++ b/web/src/activities/mod.rs
@@ -0,0 +1 @@
+pub mod item;
diff --git a/web/src/components/mod.rs b/web/src/components/mod.rs
index c4301994..b331057d 100644
--- a/web/src/components/mod.rs
+++ b/web/src/components/mod.rs
@@ -1,15 +1,9 @@
mod login;
pub use login::*;
-mod activity;
-pub use activity::*;
-
mod navigation;
pub use navigation::*;
-mod object;
-pub use object::*;
-
mod user;
pub use user::*;
diff --git a/web/src/lib.rs b/web/src/lib.rs
index af53682c..cb059e0d 100644
--- a/web/src/lib.rs
+++ b/web/src/lib.rs
@@ -3,12 +3,14 @@ mod app;
mod components;
mod page;
mod config;
-mod objects;
mod actors;
-mod getters;
+mod activities;
+mod objects;
mod timeline;
+mod getters;
+
pub use app::App;
pub use config::Config;
pub use auth::Auth;
diff --git a/web/src/objects/attachment.rs b/web/src/objects/attachment.rs
new file mode 100644
index 00000000..d19a6e2a
--- /dev/null
+++ b/web/src/objects/attachment.rs
@@ -0,0 +1,100 @@
+use leptos::*;
+use crate::{prelude::*, URL_SENSITIVE};
+
+use apb::{field::OptionalString, target::Addressed, ActivityMut, Base, Collection, CollectionMut, Document, Object, ObjectMut};
+
+#[component]
+pub fn Attachment(
+ object: serde_json::Value,
+ #[prop(optional)]
+ sensitive: bool
+) -> impl IntoView {
+ let config = use_context::>().expect("missing config context");
+ let (expand, set_expand) = create_signal(false);
+ let href = object.url().id().str().unwrap_or_default();
+ let media_type = object.media_type()
+ .unwrap_or("link") // TODO make it an Option rather than defaulting to link everywhere
+ .to_string();
+ let mut kind = media_type
+ .split('/')
+ .next()
+ .unwrap_or("link")
+ .to_string();
+
+ // TODO in theory we should match on document_type, but mastodon and misskey send all attachments
+ // as "Documents" regardless of type, so we're forced to ignore the actual AP type and just match
+ // using media_type, uffff
+ //
+ // those who correctly send Image type objects without a media type get shown as links here, this
+ // is a dirty fix to properly display as images
+ if kind == "link" && matches!(object.document_type(), Ok(apb::DocumentType::Image)) {
+ kind = "image".to_string();
+ }
+
+ match kind.as_str() {
+ "image" =>
+ view! {
+
+
+
+ }.into_view(),
+
+ "video" => {
+ let _href = href.clone();
+ view! {
+
+
+ {move || if sensitive && !expand.get() { None } else { Some(view! { }) }}
+ video clip
+
+
+ }.into_view()
+ },
+
+ "audio" =>
+ view! {
+
+
+
+ audio clip
+
+
+ }.into_view(),
+
+ "link" | "text" =>
+ view! {
+
+
+ {Uri::pretty(&href, 50)}
+
+
+ }.into_view(),
+
+ _ =>
+ view! {
+
+
+ {media_type}
+
+ {object.name().map(|name| {
+ view! {
{name.to_string()}
}
+ })}
+
+ }.into_view(),
+ }
+}
+
+
diff --git a/web/src/components/object.rs b/web/src/objects/item.rs
similarity index 69%
rename from web/src/components/object.rs
rename to web/src/objects/item.rs
index 9afaab72..ed27fa6b 100644
--- a/web/src/components/object.rs
+++ b/web/src/objects/item.rs
@@ -1,109 +1,22 @@
use std::sync::Arc;
+use cache::WEBFINGER;
use leptos::*;
-use crate::{prelude::*, URL_SENSITIVE};
+use regex::Regex;
+use crate::prelude::*;
use apb::{field::OptionalString, target::Addressed, ActivityMut, Base, Collection, CollectionMut, Document, Object, ObjectMut};
-#[component]
-pub fn Attachment(
- object: serde_json::Value,
- #[prop(optional)]
- sensitive: bool
-) -> impl IntoView {
- let config = use_context::>().expect("missing config context");
- let (expand, set_expand) = create_signal(false);
- let href = object.url().id().str().unwrap_or_default();
- let media_type = object.media_type()
- .unwrap_or("link") // TODO make it an Option rather than defaulting to link everywhere
- .to_string();
- let mut kind = media_type
- .split('/')
- .next()
- .unwrap_or("link")
- .to_string();
-
- // TODO in theory we should match on document_type, but mastodon and misskey send all attachments
- // as "Documents" regardless of type, so we're forced to ignore the actual AP type and just match
- // using media_type, uffff
- //
- // those who correctly send Image type objects without a media type get shown as links here, this
- // is a dirty fix to properly display as images
- if kind == "link" && matches!(object.document_type(), Ok(apb::DocumentType::Image)) {
- kind = "image".to_string();
- }
-
- match kind.as_str() {
- "image" =>
- view! {
-
-
-
- }.into_view(),
-
- "video" => {
- let _href = href.clone();
- view! {
-
-
- {move || if sensitive && !expand.get() { None } else { Some(view! { }) }}
- video clip
-
-
- }.into_view()
- },
-
- "audio" =>
- view! {
-
-
-
- audio clip
-
-
- }.into_view(),
-
- "link" | "text" =>
- view! {
-
-
-
-
-
- }.into_view(),
-
- _ =>
- view! {
-
-
- {media_type}
-
- {object.name().map(|name| {
- view! {
{name.to_string()}
}
- })}
-
- }.into_view(),
- }
+lazy_static::lazy_static! {
+ static ref REGEX: Regex = regex::Regex::new("@(\\w+)(@\\w+|) ").expect("failed compiling @ regex");
}
-
#[component]
-pub fn Object(object: crate::Object) -> impl IntoView {
+pub fn Object(
+ object: crate::Object,
+ #[prop(optional)] reply: bool,
+) -> impl IntoView {
let oid = object.id().unwrap_or_default().to_string();
- let content = mdhtml::safe_html(object.content().unwrap_or_default());
let author_id = object.attributed_to().id().str().unwrap_or_default();
let author = cache::OBJECTS.get_or(&author_id, serde_json::Value::String(author_id.clone()).into());
let sensitive = object.sensitive().unwrap_or_default();
@@ -127,6 +40,26 @@ pub fn Object(object: crate::Object) -> impl IntoView {
Some(view! {
})
};
+ let mut content = mdhtml::safe_html(object.content().unwrap_or_default());
+
+ let mut results = vec![];
+ for (matched, [id, username, _domain]) in REGEX.captures_iter(&content).map(|c| c.extract()) {
+ // TODO what the fuck mastodon........... why are you putting the fancy url in the A HREF????????
+ let id = id.replace('@', "users/");
+ // TODO ughh ugly on-the-fly html editing, can this be avoided?
+ let to_replace = format!(
+ "@
{} ",
+ Uri::web(U::Actor, &id), id, username
+ );
+ results.push((matched.to_string(), to_replace));
+ }
+
+ for (from, to) in results {
+ content = content.replace(&from, &to);
+ }
+
+
+
let audience_badge = object.audience().id().str()
.map(|x| view! {
@@ -134,7 +67,7 @@ pub fn Object(object: crate::Object) -> impl IntoView {
class="border-button"
title="this is a group: all interactions will be broadcasted to group members!"
>
- &
+ &
{Uri::pretty(&x, 30)}
@@ -142,13 +75,11 @@ pub fn Object(object: crate::Object) -> impl IntoView {
});
- let post_image = object.image().get().and_then(|x| x.url().id().str()).map(|x| view! {
-
+ let post_image = object.image().get().and_then(|x| x.url().id().str()).map(|x| {
+ let (expand, set_expand) = create_signal(false);
+ view! {
+
+ }
});
let post_inner = view! {
@@ -165,9 +96,9 @@ pub fn Object(object: crate::Object) -> impl IntoView {
}.into_view(),
// lemmy with Page, peertube with Video
Ok(apb::ObjectType::Document(t)) => view! {
-
+
{post_image}
-
+
{object.name().unwrap_or_default().to_string()}
@@ -209,7 +140,7 @@ pub fn Object(object: crate::Object) -> impl IntoView {
{post}
- {audience_badge}
+ {if !reply { audience_badge } else { None }}
diff --git a/web/src/objects/mod.rs b/web/src/objects/mod.rs
index 1bedf719..b6f22119 100644
--- a/web/src/objects/mod.rs
+++ b/web/src/objects/mod.rs
@@ -1 +1,4 @@
pub mod view;
+pub mod item;
+
+pub mod attachment;
diff --git a/web/src/prelude.rs b/web/src/prelude.rs
index 0c0186a4..9f6e85f7 100644
--- a/web/src/prelude.rs
+++ b/web/src/prelude.rs
@@ -11,14 +11,19 @@ pub use crate::{
follow::FollowList,
posts::ActorPosts,
},
+ activities::{
+ item::Item,
+ },
+ objects::{
+ view::ObjectView,
+ attachment::Attachment,
+ item::{Object, Summary, LikeButton, RepostButton, ReplyButton},
+ },
timeline::{
Timeline,
feed::Feed,
thread::Thread,
},
- objects::{
- view::ObjectView,
- }
};
pub use uriproxy::UriClass as U;