Compare commits
No commits in common. "5e7b2354e2f285ea0ebbcfea9dbb7843c6f33a1a" and "9f81116ba32c5d8372c5890f8a51f0492c5a72ec" have entirely different histories.
5e7b2354e2
...
9f81116ba3
10 changed files with 133 additions and 183 deletions
|
@ -51,15 +51,12 @@ impl TokenSink for Sink {
|
|||
}
|
||||
},
|
||||
"a" => {
|
||||
let mut any_attr = !tag.attrs.is_empty();
|
||||
let any_attr = !tag.attrs.is_empty();
|
||||
for attr in tag.attrs {
|
||||
match attr.name.local.as_ref() {
|
||||
"href" => self.buffer.push_str(&format!(" href=\"{}\"", attr.value.as_ref())),
|
||||
"title" => self.buffer.push_str(&format!(" title=\"{}\"", attr.value.as_ref())),
|
||||
"class" => if attr.value.as_ref() == "u-url mention" {
|
||||
any_attr = false;
|
||||
self.buffer.push_str(" class=\"u-url mention\"")
|
||||
},
|
||||
"class" => if attr.value.as_ref() == "u-url mention" { self.buffer.push_str(" class=\"u-url mention\"") },
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -94,9 +94,6 @@
|
|||
article p {
|
||||
margin: 0 0 0 .5em;
|
||||
}
|
||||
article.float-container {
|
||||
overflow-y: hidden;
|
||||
}
|
||||
b.displayname {
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
@ -135,16 +132,15 @@
|
|||
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;
|
||||
}
|
||||
|
@ -207,25 +203,18 @@
|
|||
min-width: 2em;
|
||||
max-width: 2em;
|
||||
}
|
||||
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-container {
|
||||
flex: 1;
|
||||
border: solid 3px #bf616a;
|
||||
margin-right: .5em;
|
||||
}
|
||||
img.flex-pic-expand {
|
||||
width: unset;
|
||||
height: unset;
|
||||
max-width: calc(100% - 1.5em);
|
||||
max-height: 55vh;
|
||||
div.flex-pic {
|
||||
background-size: cover;
|
||||
margin: 5px;
|
||||
height: calc(100% - 10px); /* TODO can we avoid this calc() without having this overflow??? */
|
||||
}
|
||||
.box {
|
||||
border: 3px solid var(--accent);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.cursor {
|
||||
cursor: pointer;
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
pub mod item;
|
|
@ -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! { <Object object=item.clone() reply=replies />{sep.clone()} }.into_view()),
|
||||
Some(view! { <Object object=item.clone() />{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! { <Object object=obj reply=replies /> }
|
||||
view! { <Object object=obj /> }
|
||||
}.into_view()),
|
||||
apb::ActivityType::Follow =>
|
||||
cache::OBJECTS.get(&object_id).map(|obj| {
|
|
@ -1,9 +1,15 @@
|
|||
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::*;
|
||||
|
||||
|
|
|
@ -1,22 +1,109 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use cache::WEBFINGER;
|
||||
use leptos::*;
|
||||
use regex::Regex;
|
||||
use crate::prelude::*;
|
||||
use crate::{prelude::*, URL_SENSITIVE};
|
||||
|
||||
use apb::{field::OptionalString, target::Addressed, ActivityMut, Base, Collection, CollectionMut, Document, Object, ObjectMut};
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref REGEX: Regex = regex::Regex::new("<a href=\"(.+)\" class=\"u-url mention\">@(\\w+)(@\\w+|)</a>").expect("failed compiling @ regex");
|
||||
#[component]
|
||||
pub fn Attachment(
|
||||
object: serde_json::Value,
|
||||
#[prop(optional)]
|
||||
sensitive: bool
|
||||
) -> impl IntoView {
|
||||
let config = use_context::<Signal<crate::Config>>().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! {
|
||||
<p class="center">
|
||||
<img
|
||||
class="w-100 attachment"
|
||||
class:expand=expand
|
||||
src={move || if sensitive && !expand.get() {
|
||||
URL_SENSITIVE.to_string()
|
||||
} else {
|
||||
href.clone()
|
||||
}}
|
||||
title={object.name().unwrap_or_default().to_string()}
|
||||
on:click=move |_| set_expand.set(!expand.get())
|
||||
/>
|
||||
</p>
|
||||
}.into_view(),
|
||||
|
||||
"video" => {
|
||||
let _href = href.clone();
|
||||
view! {
|
||||
<div class="center cursor box ml-1"
|
||||
on:click=move |_| set_expand.set(!expand.get())
|
||||
title={object.name().unwrap_or_default().to_string()}
|
||||
>
|
||||
<video controls class="attachment" class:expand=expand prop:loop=move || config.get().loop_videos >
|
||||
{move || if sensitive && !expand.get() { None } else { Some(view! { <source src={_href.clone()} type={media_type.clone()} /> }) }}
|
||||
<a href={href.clone()} target="_blank">video clip</a>
|
||||
</video>
|
||||
</div>
|
||||
}.into_view()
|
||||
},
|
||||
|
||||
"audio" =>
|
||||
view! {
|
||||
<p class="center">
|
||||
<audio controls class="w-100" prop:loop=move || config.get().loop_videos >
|
||||
<source src={href.clone()} type={media_type} />
|
||||
<a href={href} target="_blank">audio clip</a>
|
||||
</audio>
|
||||
</p>
|
||||
}.into_view(),
|
||||
|
||||
"link" | "text" =>
|
||||
view! {
|
||||
<p class="center mt-s mb-s">
|
||||
<a href={href.clone()} title={href.clone()} rel="noreferrer nofollow" target="_blank">
|
||||
<input style="max-width: 100%" type="submit" class="w-100" value={Uri::pretty(&href, 20)} title={object.name().unwrap_or_else(|_| href.as_str()).to_string()} />
|
||||
</a>
|
||||
</p>
|
||||
}.into_view(),
|
||||
|
||||
_ =>
|
||||
view! {
|
||||
<p class="center box">
|
||||
<code class="cw color center">
|
||||
<a href={href} target="_blank">{media_type}</a>
|
||||
</code>
|
||||
{object.name().map(|name| {
|
||||
view! { <p class="tiny-text"><small>{name.to_string()}</small></p> }
|
||||
})}
|
||||
</p>
|
||||
}.into_view(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[component]
|
||||
pub fn Object(
|
||||
object: crate::Object,
|
||||
#[prop(optional)] reply: bool,
|
||||
) -> impl IntoView {
|
||||
pub fn Object(object: crate::Object) -> 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();
|
||||
|
@ -40,26 +127,6 @@ pub fn Object(
|
|||
Some(view! { <div class="pb-1"></div> })
|
||||
};
|
||||
|
||||
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!(
|
||||
"<a class=\"clean dim\" href=\"{}\" title=\"{}\"><span class=\"border-button mr-s\"><code class=\"color mr-s\">@</code>{}</span></a>",
|
||||
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! {
|
||||
<a class="clean dim" href={Uri::web(U::Actor, &x)} rel="nofollow noreferrer">
|
||||
|
@ -67,7 +134,7 @@ pub fn Object(
|
|||
class="border-button"
|
||||
title="this is a group: all interactions will be broadcasted to group members!"
|
||||
>
|
||||
<code class="color mr-s">&</code>
|
||||
<code class="color">&</code>
|
||||
<small>
|
||||
{Uri::pretty(&x, 30)}
|
||||
</small>
|
||||
|
@ -75,11 +142,13 @@ pub fn Object(
|
|||
</a>
|
||||
});
|
||||
|
||||
let post_image = object.image().get().and_then(|x| x.url().id().str()).map(|x| {
|
||||
let (expand, set_expand) = create_signal(false);
|
||||
view! {
|
||||
<img src={x} class="flex-pic box cursor" class:flex-pic-expand=expand on:click=move|_| set_expand.set(!expand.get()) />
|
||||
}
|
||||
let post_image = object.image().get().and_then(|x| x.url().id().str()).map(|x| view! {
|
||||
<div class="flex-pic-container">
|
||||
<a href={x.clone()} target="_blank">
|
||||
<div class="flex-pic" style={format!("background-image: url('{x}')")}>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
});
|
||||
|
||||
let post_inner = view! {
|
||||
|
@ -96,9 +165,9 @@ pub fn Object(
|
|||
}.into_view(),
|
||||
// lemmy with Page, peertube with Video
|
||||
Ok(apb::ObjectType::Document(t)) => view! {
|
||||
<article class="float-container ml-1 mr-1" >
|
||||
<article class="ml-1 mr-1" style="display: flex">
|
||||
{post_image}
|
||||
<div>
|
||||
<div style="flex: 3">
|
||||
<h4 class="mt-s mb-1" title={t.as_ref().to_string()}>
|
||||
<b>{object.name().unwrap_or_default().to_string()}</b>
|
||||
</h4>
|
||||
|
@ -140,7 +209,7 @@ pub fn Object(
|
|||
</table>
|
||||
{post}
|
||||
<div class="mt-s ml-1 rev">
|
||||
{if !reply { audience_badge } else { None }}
|
||||
{audience_badge}
|
||||
<ReplyButton n=comments target=oid.clone() />
|
||||
<LikeButton n=likes liked=already_liked target=oid.clone() author=author_id private=!public />
|
||||
<RepostButton n=shares target=oid />
|
|
@ -3,13 +3,11 @@ mod app;
|
|||
mod components;
|
||||
mod page;
|
||||
mod config;
|
||||
mod objects;
|
||||
|
||||
mod actors;
|
||||
mod activities;
|
||||
mod objects;
|
||||
mod timeline;
|
||||
|
||||
mod getters;
|
||||
mod timeline;
|
||||
|
||||
pub use app::App;
|
||||
pub use config::Config;
|
||||
|
|
|
@ -1,100 +0,0 @@
|
|||
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::<Signal<crate::Config>>().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! {
|
||||
<p class="center">
|
||||
<img
|
||||
class="w-100 attachment"
|
||||
class:expand=expand
|
||||
src={move || if sensitive && !expand.get() {
|
||||
URL_SENSITIVE.to_string()
|
||||
} else {
|
||||
href.clone()
|
||||
}}
|
||||
title={object.name().unwrap_or_default().to_string()}
|
||||
on:click=move |_| set_expand.set(!expand.get())
|
||||
/>
|
||||
</p>
|
||||
}.into_view(),
|
||||
|
||||
"video" => {
|
||||
let _href = href.clone();
|
||||
view! {
|
||||
<div class="center cursor box ml-1"
|
||||
on:click=move |_| set_expand.set(!expand.get())
|
||||
title={object.name().unwrap_or_default().to_string()}
|
||||
>
|
||||
<video controls class="attachment" class:expand=expand prop:loop=move || config.get().loop_videos >
|
||||
{move || if sensitive && !expand.get() { None } else { Some(view! { <source src={_href.clone()} type={media_type.clone()} /> }) }}
|
||||
<a href={href.clone()} target="_blank">video clip</a>
|
||||
</video>
|
||||
</div>
|
||||
}.into_view()
|
||||
},
|
||||
|
||||
"audio" =>
|
||||
view! {
|
||||
<p class="center">
|
||||
<audio controls class="w-100" prop:loop=move || config.get().loop_videos >
|
||||
<source src={href.clone()} type={media_type} />
|
||||
<a href={href} target="_blank">audio clip</a>
|
||||
</audio>
|
||||
</p>
|
||||
}.into_view(),
|
||||
|
||||
"link" | "text" =>
|
||||
view! {
|
||||
<p class="mt-s mb-s">
|
||||
<a title={href.clone()} href={href.clone()} rel="noreferrer nofollow" target="_blank">
|
||||
{Uri::pretty(&href, 50)}
|
||||
</a>
|
||||
</p>
|
||||
}.into_view(),
|
||||
|
||||
_ =>
|
||||
view! {
|
||||
<p class="center box">
|
||||
<code class="cw color center">
|
||||
<a href={href} target="_blank">{media_type}</a>
|
||||
</code>
|
||||
{object.name().map(|name| {
|
||||
view! { <p class="tiny-text"><small>{name.to_string()}</small></p> }
|
||||
})}
|
||||
</p>
|
||||
}.into_view(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,4 +1 @@
|
|||
pub mod view;
|
||||
pub mod item;
|
||||
|
||||
pub mod attachment;
|
||||
|
|
|
@ -11,19 +11,14 @@ 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;
|
||||
|
|
Loading…
Reference in a new issue