Renders profile info

main
Ashelyn Dawn 1 year ago
parent f1c975e51a
commit 0e04ed4373

1
.gitignore vendored

@ -1,6 +1,7 @@
test-archive.tar.gz
out/
dist/
temp/
# Added by cargo
/target

@ -3,7 +3,24 @@ release = false
dist = "dist"
public_url = "/"
[watch]
ignore = ["temp"]
[serve]
address = "0.0.0.0"
port = 3000
[[hooks]]
stage = "pre_build"
command = "sh"
command_arguments = ["-c", "mkdir -p ./temp && tar -xzf ./test-archive.tar.gz -C ./temp"]
[[hooks]]
stage = "pre_build"
command = "cp"
command_arguments = ["-r", "./temp/", "./dist/.stage/media" ]
[[hooks]]
stage = "pre_build"
command = "cp"
command_arguments = ["-r", "./resources/", "./dist/.stage/resources" ]

@ -3,7 +3,8 @@
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>Trunk | Yew | YBC</title>
<title>Mastodon Export</title>
<link rel="stylesheet" href="/resources/style.css" />
<base data-trunk-public-url/>
</head>
@ -11,3 +12,6 @@
<link data-trunk rel="rust" href="Cargo.toml" />
</body>
</html>
<!-- Masto UI: Roboto Regular -->
<!-- Masto Body: Roboto Regular -->

@ -0,0 +1,126 @@
/* cyrillic-ext */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(./roboto/KFOmCnqEu92Fr1Mu72xKOzY.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(./roboto/KFOmCnqEu92Fr1Mu5mxKOzY.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(./roboto/KFOmCnqEu92Fr1Mu7mxKOzY.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(./roboto/KFOmCnqEu92Fr1Mu4WxKOzY.woff2) format('woff2');
unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(./roboto/KFOmCnqEu92Fr1Mu7WxKOzY.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(./roboto/KFOmCnqEu92Fr1Mu7GxKOzY.woff2) format('woff2');
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(./roboto/KFOmCnqEu92Fr1Mu4mxK.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(./roboto/KFOlCnqEu92Fr1MmEU9fCRc4EsA.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(./roboto/KFOlCnqEu92Fr1MmEU9fABc4EsA.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(./roboto/KFOlCnqEu92Fr1MmEU9fCBc4EsA.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(./roboto/KFOlCnqEu92Fr1MmEU9fBxc4EsA.woff2) format('woff2');
unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(./roboto/KFOlCnqEu92Fr1MmEU9fCxc4EsA.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(./roboto/KFOlCnqEu92Fr1MmEU9fChc4EsA.woff2) format('woff2');
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(./roboto/KFOlCnqEu92Fr1MmEU9fBBc4.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@ -0,0 +1,99 @@
@import url('./fonts/roboto.css');
:root {
--page-background: lightgray;
--body-background: white;
--body-width: 600px;
--body-padding: 15px;
--profile-size: 120px;
--cover-size: 250px;
}
html, body {
margin: 0;
padding: 0;
background: var(--page-background);
font-family: 'Roboto';
}
body {
max-width: var(--body-width);
min-height: 100vh;
margin: 0 auto;
background: var(--body-background);
box-sizing: border-box;
padding: var(--body-padding);
}
img.banner {
width: calc(100% + 2 * var(--body-padding));
object-fit: cover;
object-position: center;
max-height: var(--cover-size);
margin: calc(-1 * var(--body-padding));
}
img.avatar {
width: var(--profile-size);
height: var(--profile-size);
border: solid 2px white;
border-radius: var(--body-padding);
margin-top: calc(var(--profile-size) / -2 + var(--body-padding));
display: inline-block;
}
h1.name {
font-size: 16px;
line-height: 24px;
font-weight: 500;
margin-bottom: 0;
}
.username, .archived_on {
font-size: 14px;
line-height: 24px;
font-weight: 400px;
margin: 0;
display: block;
color: inherit;
text-decoration: none;
}
.archived_on {
font-style: italic;
opacity: .8;
margin-top: -4px;
}
.profile-grid {
display: grid;
grid-template-columns: 120px 1fr;
margin: var(--body-padding) calc(var(--body-padding) * -1);
font-size: 14px;
}
.profile_property {
display: contents;
border-top: solid 1px gray;
}
.profile_property .property_name {
background: lightgray;
}
.profile_property > span {
border-top: solid 1px gray;
padding: var(--body-padding);
}
.profile_property:last-child > span {
border-bottom: solid 1px gray;
}
.profile_property .invisible {
display: none;
}
.bio {
font-size: 14px;
}

@ -1,3 +1,5 @@
use std::str::FromStr;
use serde::Deserialize;
#[derive(Deserialize, Debug, PartialEq, Clone)]
@ -20,6 +22,33 @@ pub struct Person {
pub attachments: Option<Vec<Attachment>>,
}
impl Person {
pub fn full_username(&self) -> Option<String> {
let url = &self.url;
let domain_separator = url.find("//")?;
if domain_separator + 2 >= url.len() {
return None;
}
let mut parts = url.get((domain_separator + 2)..)?.split("/");
let domain = parts.next()?;
let user = parts.next()?;
if parts.count() > 0 {
return None;
}
let mut username = String::new();
username.push_str(user);
username.push('@');
username.push_str(domain);
Some(username)
}
}
#[derive(Deserialize, Debug, PartialEq, Clone)]
#[serde(tag = "type")]
pub enum Item {

@ -1,14 +1,14 @@
use chrono::DateTime;
use futures::executor;
use yew::{function_component, html, Html, Renderer, ServerRenderer};
use yew::{function_component, html, Html, Renderer, ServerRenderer, AttrValue};
#[cfg(debug_assertions)]
use console_error_panic_hook::set_once as set_panic_hook;
#[cfg(debug_assertions)]
use wasm_bindgen::prelude::*;
use yew::prelude::*;
use crate::{
data::{Outbox, Person},
data::{Attachment, Outbox, Person},
error::Ærror,
};
@ -76,10 +76,60 @@ fn Layout(props: &Props) -> Html {
#[function_component]
fn ProfileHeader(props: &Props) -> Html {
html! {
<p>
{"Generated on "}
{props.archive_time.to_rfc2822()}
</p>
<div class="header">
<img class="banner" src="/media/header.png"/>
<img class="avatar" src="/media/avatar.png"/>
<h1 class="name">{props.author.name.as_str()}</h1>
<a href={props.author.url.clone()} class="username" target="_blank">
{props.author.full_username().unwrap().as_str()}
</a>
{ if props.author.attachments.is_some() {
html! {
<div class="profile-grid">
{
props.author.clone().attachments.unwrap().into_iter().filter_map(|attach| match attach {
Attachment::Property {name, value} => Some((name, value)),
_ => None,
}).map(|(name, value)| {
html! {
<div key={name.clone()} class="profile_property">
<span class="property_name">{name}</span>
<span class="property_value">{
if value.find("<a").is_some() {
Html::from_html_unchecked(AttrValue::from(value))
} else {
Html::from(value)
}
}</span>
</div>
}
}).collect::<Html>()
}
</div>
}
} else {
html! {
<></>
}
}}
<div class="bio">
{Html::from_html_unchecked(
AttrValue::from(
props.author.summary.replace("class=\"u-url mention\"", "class=\"mention\" target=\"_blank\"")
)
)}
</div>
<span class="archived_on">
{"Archived "}
{props.archive_time.format("%b %e, %Y")}
</span>
</div>
}
}
#[function_component]
fn Posts(props: &Props) -> Html {
html! {
<p>{"Posts"}</p>
}
}

Loading…
Cancel
Save