pub mod timeago;
use std::borrow::{Borrow, Cow};
use std::collections::HashMap;
use crate::resp_types::{
RespCommentInfo, RespMinimalAuthorInfo, RespMinimalCommentInfo, RespMinimalCommunityInfo,
RespNotification, RespNotificationInfo, RespPostCommentInfo, RespPostInfo, RespPostListPost,
RespThingComment, RespThingInfo,
};
use crate::util::{abbreviate_link, author_is_me};
use crate::PageBaseData;
pub use timeago::TimeAgo;
#[render::component]
pub fn Comment<'a>(
comment: &'a RespPostCommentInfo<'a>,
base_data: &'a PageBaseData,
lang: &'a crate::Translator,
) {
render::rsx! {
}
}
pub struct CommunityLink<'community> {
pub community: &'community RespMinimalCommunityInfo<'community>,
}
impl<'community> render::Render for CommunityLink<'community> {
fn render_into(self, writer: &mut W) -> std::fmt::Result {
let community = &self.community;
let href = format!("/communities/{}", community.id);
(render::rsx! {
{
(if community.local {
community.name.as_ref().into()
} else {
Cow::Owned(format!("{}@{}", community.name, community.host))
}).as_ref()
}
})
.render_into(writer)
}
}
pub trait HavingContent {
fn content_text(&self) -> Option<&str>;
fn content_html(&self) -> Option<&str>;
}
impl<'a> HavingContent for RespMinimalCommentInfo<'a> {
fn content_text(&self) -> Option<&str> {
self.content_text.as_deref()
}
fn content_html(&self) -> Option<&str> {
self.content_html.as_deref()
}
}
impl<'a> HavingContent for RespThingComment<'a> {
fn content_text(&self) -> Option<&str> {
self.base.content_text()
}
fn content_html(&self) -> Option<&str> {
self.base.content_html()
}
}
impl<'a> HavingContent for RespPostCommentInfo<'a> {
fn content_text(&self) -> Option<&str> {
self.base.content_text()
}
fn content_html(&self) -> Option<&str> {
self.base.content_html()
}
}
impl<'a> HavingContent for RespCommentInfo<'a> {
fn content_text(&self) -> Option<&str> {
self.base.content_text()
}
fn content_html(&self) -> Option<&str> {
self.base.content_html()
}
}
impl<'a> HavingContent for RespPostInfo<'a> {
fn content_text(&self) -> Option<&str> {
self.content_text.as_deref()
}
fn content_html(&self) -> Option<&str> {
self.content_html.as_deref()
}
}
pub struct Content<'a, T: HavingContent + 'a> {
pub src: &'a T,
}
impl<'a, T: HavingContent + 'a> render::Render for Content<'a, T> {
fn render_into(self, writer: &mut W) -> std::fmt::Result {
match self.src.content_html() {
Some(html) => {
let cleaned = ammonia::clean(&html);
writer.write_str("")?;
render::raw!(cleaned.as_ref()).render_into(writer)?;
writer.write_str("
")?;
}
None => {
if let Some(text) = self.src.content_text() {
writer.write_str("")?;
text.render_into(writer)?;
writer.write_str("
")?;
}
}
}
Ok(())
}
}
#[render::component]
pub fn HTPage<'a, Children: render::Render>(
base_data: &'a PageBaseData,
lang: &'a crate::Translator,
title: &'a str,
children: Children,
) {
render::rsx! {
<>
{title}
{children}
>
}
}
#[render::component]
pub fn PostItem<'a>(
post: &'a RespPostListPost<'a>,
in_community: bool,
no_user: bool,
lang: &'a crate::Translator,
) {
render::rsx! {
{post.as_ref().as_ref().title.as_ref()}
{
if let Some(href) = &post.as_ref().href {
Some(render::rsx! {
<>
{" "}
{abbreviate_link(&href)}{" ↗"}
>
})
} else {
None
}
}
{lang.tr("submitted", None)}
{
if no_user {
None
} else {
Some(render::rsx! {
<>
{" "}{lang.tr("by", None)}{" "}
>
})
}
}
{
if !in_community {
Some(render::rsx! {
<>{" "}{lang.tr("to", None)}{" "}>
})
} else {
None
}
}
{" | "}
{lang.tr("post_comments_count", Some(&fluent::fluent_args!["count" => post.replies_count_total])).into_owned()}
}
}
pub struct ThingItem<'a> {
pub lang: &'a crate::Translator,
pub thing: &'a RespThingInfo<'a>,
}
impl<'a> render::Render for ThingItem<'a> {
fn render_into(self, writer: &mut W) -> std::fmt::Result {
let lang = self.lang;
match self.thing {
RespThingInfo::Post(post) => {
(PostItem { post, in_community: false, no_user: true, lang: self.lang }).render_into(writer)
},
RespThingInfo::Comment(comment) => {
(render::rsx! {
{lang.tr("comment", None)}
{" "}{lang.tr("on", None)}{" "}{comment.post.title.as_ref()}{":"}
}).render_into(writer)
}
}
}
}
pub struct UserLink<'user> {
pub user: Option<&'user RespMinimalAuthorInfo<'user>>,
}
impl<'user> render::Render for UserLink<'user> {
fn render_into(self, writer: &mut W) -> std::fmt::Result {
match self.user {
None => "[unknown]".render_into(writer),
Some(user) => {
let href = format!("/users/{}", user.id);
(render::rsx! {
{
(if user.local {
user.username.as_ref().into()
} else {
Cow::Owned(format!("{}@{}", user.username, user.host))
}).as_ref()
}
})
.render_into(writer)
}
}
}
}
pub trait GetIndex {
fn get(&self, key: K) -> Option<&V>;
}
impl + Eq + std::hash::Hash, V, Q: ?Sized + Eq + std::hash::Hash> GetIndex<&Q, V>
for HashMap
{
fn get<'a>(&'a self, key: &Q) -> Option<&'a V> {
HashMap::get(self, key)
}
}
impl GetIndex for serde_json::Value {
fn get(&self, key: I) -> Option<&serde_json::Value> {
self.get(key)
}
}
fn maybe_fill_value<'a, 'b, M: GetIndex<&'b str, serde_json::Value>>(
values: &'a Option<&'a M>,
name: &'b str,
default_value: Option<&'a str>,
) -> &'a str {
values
.and_then(|values| values.get(name))
.and_then(serde_json::Value::as_str)
.or(default_value)
.unwrap_or("")
}
#[render::component]
pub fn MaybeFillInput<'a, M: GetIndex<&'a str, serde_json::Value>>(
values: &'a Option<&'a M>,
r#type: &'a str,
name: &'a str,
required: bool,
id: &'a str,
) {
let value = maybe_fill_value(values, name, None);
if required {
render::rsx! {
}
} else {
render::rsx! {
}
}
}
#[render::component]
pub fn MaybeFillTextArea<'a, M: GetIndex<&'a str, serde_json::Value>>(
values: &'a Option<&'a M>,
name: &'a str,
default_value: Option<&'a str>,
) {
render::rsx! {
}
}
#[render::component]
pub fn BoolSubmitButton<'a>(value: bool, do_text: &'a str, done_text: &'a str) {
if value {
render::rsx! {
}
} else {
render::rsx! {
}
}
}
pub struct NotificationItem<'a> {
pub notification: &'a RespNotification<'a>,
pub lang: &'a crate::Translator,
}
impl<'a> render::Render for NotificationItem<'a> {
fn render_into(self, writer: &mut W) -> std::fmt::Result {
let lang = self.lang;
write!(writer, "")?;
match &self.notification.info {
RespNotificationInfo::Unknown => {
"[unknown notification type]".render_into(writer)?;
}
RespNotificationInfo::PostReply { reply, post } => {
(render::rsx! {
<>
{lang.tr("comment", None)}
{" "}{lang.tr("on_your_post", None)}{" "}{post.title.as_ref()}{":"}
>
}).render_into(writer)?;
}
RespNotificationInfo::CommentReply {
reply,
comment,
post,
} => {
(render::rsx! {
<>
{lang.tr("reply_to", None)}
{" "}
{lang.tr("your_comment", None)}
{
if let Some(post) = post {
Some(render::rsx! { <>{" "}{lang.tr("on", None)}{" "}{post.title.as_ref()}> })
} else {
None
}
}
{":"}
>
}).render_into(writer)?;
}
}
write!(writer, "")
}
}
pub trait IconExt {
fn img(&self) -> render::SimpleElement<()>;
}
impl IconExt for hitide_icons::Icon {
fn img(&self) -> render::SimpleElement<()> {
render::rsx! { }
}
}