Front end federation names and links for users, posts, and communities.

This commit is contained in:
Dessalines 2020-04-14 19:18:13 -04:00
parent 1336b4ed60
commit fcf1c65fc1
16 changed files with 138 additions and 54 deletions

View file

@ -1,9 +1,8 @@
use super::*; use super::*;
use crate::apub::activities::follow_community; use crate::apub::activities::follow_community;
use crate::apub::{format_community_name, gen_keypair_str, make_apub_endpoint, EndpointType}; use crate::apub::{gen_keypair_str, make_apub_endpoint, EndpointType};
use diesel::PgConnection; use diesel::PgConnection;
use std::str::FromStr; use std::str::FromStr;
use url::Url;
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct GetCommunity { pub struct GetCommunity {
@ -144,7 +143,7 @@ impl Perform<GetCommunityResponse> for Oper<GetCommunity> {
} }
}; };
let mut community_view = match CommunityView::read(&conn, community.id, user_id) { let community_view = match CommunityView::read(&conn, community.id, user_id) {
Ok(community) => community, Ok(community) => community,
Err(_e) => return Err(APIError::err("couldnt_find_community").into()), Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
}; };
@ -160,12 +159,6 @@ impl Perform<GetCommunityResponse> for Oper<GetCommunity> {
let creator_user = admins.remove(creator_index); let creator_user = admins.remove(creator_index);
admins.insert(0, creator_user); admins.insert(0, creator_user);
if !community.local {
let domain = Url::parse(&community.actor_id)?;
community_view.name =
format_community_name(&community_view.name.to_string(), domain.host_str().unwrap());
}
// Return the jwt // Return the jwt
Ok(GetCommunityResponse { Ok(GetCommunityResponse {
community: community_view, community: community_view,

View file

@ -92,16 +92,6 @@ fn vec_bytes_to_str(bytes: Vec<u8>) -> String {
String::from_utf8_lossy(&bytes).into_owned() String::from_utf8_lossy(&bytes).into_owned()
} }
/// If community is on local instance, don't include the @instance part. This is only for displaying
/// to the user and should never be used otherwise.
pub fn format_community_name(name: &str, instance: &str) -> String {
if instance == Settings::get().hostname {
format!("!{}", name)
} else {
format!("!{}@{}", name, instance)
}
}
pub fn get_following_instances() -> Vec<Instance> { pub fn get_following_instances() -> Vec<Instance> {
Settings::get() Settings::get()
.federation .federation

View file

@ -60,7 +60,7 @@ pub struct Database {
pub pool_size: u32, pub pool_size: u32,
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize, Clone)]
pub struct Federation { pub struct Federation {
pub enabled: bool, pub enabled: bool,
pub followed_instances: String, pub followed_instances: String,

View file

@ -113,6 +113,9 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
user={{ user={{
name: admin.name, name: admin.name,
avatar: admin.avatar, avatar: admin.avatar,
id: admin.id,
local: admin.local,
actor_id: admin.actor_id,
}} }}
/> />
</li> </li>
@ -133,6 +136,9 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
user={{ user={{
name: banned.name, name: banned.name,
avatar: banned.avatar, avatar: banned.avatar,
id: banned.id,
local: banned.local,
actor_id: banned.actor_id,
}} }}
/> />
</li> </li>

View file

@ -154,6 +154,9 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
user={{ user={{
name: node.comment.creator_name, name: node.comment.creator_name,
avatar: node.comment.creator_avatar, avatar: node.comment.creator_avatar,
id: node.comment.creator_id,
local: node.comment.creator_local,
actor_id: node.comment.creator_actor_id,
}} }}
/> />
</span> </span>

View file

@ -14,6 +14,7 @@ import {
} from '../interfaces'; } from '../interfaces';
import { WebSocketService } from '../services'; import { WebSocketService } from '../services';
import { wsJsonToRes, toast } from '../utils'; import { wsJsonToRes, toast } from '../utils';
import { CommunityLink } from './community-link';
import { i18n } from '../i18next'; import { i18n } from '../i18next';
declare const Sortable: any; declare const Sortable: any;
@ -104,9 +105,7 @@ export class Communities extends Component<any, CommunitiesState> {
{this.state.communities.map(community => ( {this.state.communities.map(community => (
<tr> <tr>
<td> <td>
<Link to={`/c/${community.name}`}> <CommunityLink community={community} />
{community.name}
</Link>
</td> </td>
<td class="d-none d-lg-table-cell">{community.title}</td> <td class="d-none d-lg-table-cell">{community.title}</td>
<td>{community.category_name}</td> <td>{community.category_name}</td>

35
ui/src/components/community-link.tsx vendored Normal file
View file

@ -0,0 +1,35 @@
import { Component } from 'inferno';
import { Link } from 'inferno-router';
import { Community } from '../interfaces';
import { hostname } from '../utils';
interface CommunityOther {
name: string;
id?: number; // Necessary if its federated
local?: boolean;
actor_id?: string;
}
interface CommunityLinkProps {
community: Community | CommunityOther;
}
export class CommunityLink extends Component<CommunityLinkProps, any> {
constructor(props: any, context: any) {
super(props, context);
}
render() {
let community = this.props.community;
let name_: string, link: string;
let local = community.local == null ? true : community.local;
if (local) {
name_ = community.name;
link = `/c/${community.name}`;
} else {
name_ = `${hostname(community.actor_id)}/${community.name}`;
link = `/community/${community.id}`;
}
return <Link to={link}>{name_}</Link>;
}
}

View file

@ -80,6 +80,11 @@ export class Community extends Component<any, State> {
removed: null, removed: null,
nsfw: false, nsfw: false,
deleted: null, deleted: null,
local: null,
actor_id: null,
last_refreshed_at: null,
creator_actor_id: null,
creator_local: null,
}, },
moderators: [], moderators: [],
admins: [], admins: [],

View file

@ -34,6 +34,7 @@ import { ListingTypeSelect } from './listing-type-select';
import { DataTypeSelect } from './data-type-select'; import { DataTypeSelect } from './data-type-select';
import { SiteForm } from './site-form'; import { SiteForm } from './site-form';
import { UserListing } from './user-listing'; import { UserListing } from './user-listing';
import { CommunityLink } from './community-link';
import { import {
wsJsonToRes, wsJsonToRes,
repoUrl, repoUrl,
@ -190,9 +191,14 @@ export class Main extends Component<any, MainState> {
<ul class="list-inline"> <ul class="list-inline">
{this.state.subscribedCommunities.map(community => ( {this.state.subscribedCommunities.map(community => (
<li class="list-inline-item"> <li class="list-inline-item">
<Link to={`/c/${community.community_name}`}> <CommunityLink
{community.community_name} community={{
</Link> name: community.community_name,
id: community.community_id,
local: community.community_local,
actor_id: community.community_actor_id,
}}
/>
</li> </li>
))} ))}
</ul> </ul>
@ -228,7 +234,7 @@ export class Main extends Component<any, MainState> {
<ul class="list-inline"> <ul class="list-inline">
{this.state.trendingCommunities.map(community => ( {this.state.trendingCommunities.map(community => (
<li class="list-inline-item"> <li class="list-inline-item">
<Link to={`/c/${community.name}`}>{community.name}</Link> <CommunityLink community={community} />
</li> </li>
))} ))}
</ul> </ul>
@ -319,6 +325,9 @@ export class Main extends Component<any, MainState> {
user={{ user={{
name: admin.name, name: admin.name,
avatar: admin.avatar, avatar: admin.avatar,
local: admin.local,
actor_id: admin.actor_id,
id: admin.id,
}} }}
/> />
</li> </li>

View file

@ -35,6 +35,7 @@ import {
setupTribute, setupTribute,
setupTippy, setupTippy,
emojiPicker, emojiPicker,
hostname,
} from '../utils'; } from '../utils';
import autosize from 'autosize'; import autosize from 'autosize';
import Tribute from 'tributejs/src/Tribute.js'; import Tribute from 'tributejs/src/Tribute.js';
@ -331,7 +332,11 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
onInput={linkEvent(this, this.handlePostCommunityChange)} onInput={linkEvent(this, this.handlePostCommunityChange)}
> >
{this.state.communities.map(community => ( {this.state.communities.map(community => (
<option value={community.id}>{community.name}</option> <option value={community.id}>
{community.local
? community.name
: `${hostname(community.actor_id)}/${community.name}`}
</option>
))} ))}
</select> </select>
</div> </div>

View file

@ -20,6 +20,7 @@ import { MomentTime } from './moment-time';
import { PostForm } from './post-form'; import { PostForm } from './post-form';
import { IFramelyCard } from './iframely-card'; import { IFramelyCard } from './iframely-card';
import { UserListing } from './user-listing'; import { UserListing } from './user-listing';
import { CommunityLink } from './community-link';
import { import {
md, md,
mdToHtml, mdToHtml,
@ -420,6 +421,9 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
user={{ user={{
name: post.creator_name, name: post.creator_name,
avatar: post.creator_avatar, avatar: post.creator_avatar,
id: post.creator_id,
local: post.creator_local,
actor_id: post.creator_actor_id,
}} }}
/> />
{this.isMod && ( {this.isMod && (
@ -440,15 +444,14 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
{this.props.showCommunity && ( {this.props.showCommunity && (
<span> <span>
<span> {i18n.t('to')} </span> <span> {i18n.t('to')} </span>
{post.local ? ( <CommunityLink
<Link to={`/c/${post.community_name}`}> community={{
{post.community_name} name: post.community_name,
</Link> id: post.community_id,
) : ( local: post.community_local,
<a href={post.community_actor_id} target="_blank"> actor_id: post.community_actor_id,
{hostname(post.ap_id)}/{post.community_name} }}
</a> />
)}
</span> </span>
)} )}
</li> </li>

View file

@ -135,6 +135,9 @@ export class PrivateMessageForm extends Component<
user={{ user={{
name: this.state.recipient.name, name: this.state.recipient.name,
avatar: this.state.recipient.avatar, avatar: this.state.recipient.avatar,
id: this.state.recipient.id,
local: this.state.recipient.local,
actor_id: this.state.recipient.actor_id,
}} }}
/> />
</div> </div>

View file

@ -8,12 +8,7 @@ import {
UserView, UserView,
} from '../interfaces'; } from '../interfaces';
import { WebSocketService, UserService } from '../services'; import { WebSocketService, UserService } from '../services';
import { import { mdToHtml, getUnixTime, hostname } from '../utils';
mdToHtml,
getUnixTime,
pictshareAvatarThumbnail,
showAvatars,
} from '../utils';
import { CommunityForm } from './community-form'; import { CommunityForm } from './community-form';
import { UserListing } from './user-listing'; import { UserListing } from './user-listing';
import { i18n } from '../i18next'; import { i18n } from '../i18next';
@ -65,6 +60,15 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
sidebar() { sidebar() {
let community = this.props.community; let community = this.props.community;
let name_: string, link: string;
if (community.local) {
name_ = community.name;
link = `/c/${community.name}`;
} else {
name_ = `${hostname(community.actor_id)}/${community.name}`;
link = community.actor_id;
}
return ( return (
<div> <div>
<div class="card border-secondary mb-3"> <div class="card border-secondary mb-3">
@ -82,9 +86,15 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
</small> </small>
)} )}
</h5> </h5>
<Link className="text-muted" to={`/c/${community.name}`}> {community.local ? (
/c/{community.name} <Link className="text-muted" to={link}>
</Link> {name_}
</Link>
) : (
<a className="text-muted" href={link} target="_blank">
{name_}
</a>
)}
<ul class="list-inline mb-1 text-muted font-weight-bold"> <ul class="list-inline mb-1 text-muted font-weight-bold">
{this.canMod && ( {this.canMod && (
<> <>
@ -210,11 +220,15 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
user={{ user={{
name: mod.user_name, name: mod.user_name,
avatar: mod.avatar, avatar: mod.avatar,
id: mod.user_id,
local: mod.user_local,
actor_id: mod.user_actor_id,
}} }}
/> />
</li> </li>
))} ))}
</ul> </ul>
{/* TODO the to= needs to be able to handle community_ids as well, since they're federated */}
<Link <Link
class={`btn btn-sm btn-secondary btn-block mb-3 ${ class={`btn btn-sm btn-secondary btn-block mb-3 ${
(community.deleted || community.removed) && 'no-click' (community.deleted || community.removed) && 'no-click'

View file

@ -1,11 +1,14 @@
import { Component } from 'inferno'; import { Component } from 'inferno';
import { Link } from 'inferno-router'; import { Link } from 'inferno-router';
import { UserView } from '../interfaces'; import { UserView } from '../interfaces';
import { pictshareAvatarThumbnail, showAvatars } from '../utils'; import { pictshareAvatarThumbnail, showAvatars, hostname } from '../utils';
interface UserOther { interface UserOther {
name: string; name: string;
id?: number; // Necessary if its federated
avatar?: string; avatar?: string;
local?: boolean;
actor_id?: string;
} }
interface UserListingProps { interface UserListingProps {
@ -19,8 +22,19 @@ export class UserListing extends Component<UserListingProps, any> {
render() { render() {
let user = this.props.user; let user = this.props.user;
let local = user.local == null ? true : user.local;
let name_: string, link: string;
if (local) {
name_ = user.name;
link = `/u/${user.name}`;
} else {
name_ = `${hostname(user.actor_id)}/${user.name}`;
link = `/user/${user.id}`;
}
return ( return (
<Link className="text-body font-weight-bold" to={`/u/${user.name}`}> <Link className="text-body font-weight-bold" to={link}>
{user.avatar && showAvatars() && ( {user.avatar && showAvatars() && (
<img <img
height="32" height="32"
@ -29,7 +43,7 @@ export class UserListing extends Component<UserListingProps, any> {
class="rounded-circle mr-2" class="rounded-circle mr-2"
/> />
)} )}
<span>{user.name}</span> <span>{name_}</span>
</Link> </Link>
); );
} }

View file

@ -40,6 +40,7 @@ import {
setupTippy, setupTippy,
} from '../utils'; } from '../utils';
import { PostListing } from './post-listing'; import { PostListing } from './post-listing';
import { UserListing } from './user-listing';
import { SortSelect } from './sort-select'; import { SortSelect } from './sort-select';
import { ListingTypeSelect } from './listing-type-select'; import { ListingTypeSelect } from './listing-type-select';
import { CommentNodes } from './comment-nodes'; import { CommentNodes } from './comment-nodes';
@ -81,7 +82,6 @@ export class User extends Component<any, UserState> {
user: { user: {
id: null, id: null,
name: null, name: null,
fedi_name: null,
published: null, published: null,
number_of_posts: null, number_of_posts: null,
post_score: null, post_score: null,
@ -91,6 +91,8 @@ export class User extends Component<any, UserState> {
avatar: null, avatar: null,
show_avatars: null, show_avatars: null,
send_notifications_to_email: null, send_notifications_to_email: null,
actor_id: null,
local: null,
}, },
user_id: null, user_id: null,
username: null, username: null,
@ -399,7 +401,9 @@ export class User extends Component<any, UserState> {
<div class="card-body"> <div class="card-body">
<h5> <h5>
<ul class="list-inline mb-0"> <ul class="list-inline mb-0">
<li className="list-inline-item">{user.name}</li> <li className="list-inline-item">
<UserListing user={user} />
</li>
{user.banned && ( {user.banned && (
<li className="list-inline-item badge badge-danger"> <li className="list-inline-item badge badge-danger">
{i18n.t('banned')} {i18n.t('banned')}
@ -455,8 +459,9 @@ export class User extends Component<any, UserState> {
) : ( ) : (
<> <>
<a <a
className={`btn btn-block btn-secondary mt-3 ${!this.state className={`btn btn-block btn-secondary mt-3 ${
.user.matrix_user_id && 'disabled'}`} !this.state.user.matrix_user_id && 'disabled'
}`}
target="_blank" target="_blank"
href={`https://matrix.to/#/${this.state.user.matrix_user_id}`} href={`https://matrix.to/#/${this.state.user.matrix_user_id}`}
> >

2
ui/src/env.ts vendored
View file

@ -1,6 +1,6 @@
const host = `${window.location.hostname}`; const host = `${window.location.hostname}`;
const port = `${ const port = `${
window.location.port == '4444' ? '8536' : window.location.port window.location.port == '4444' ? '8540' : window.location.port
}`; }`;
const endpoint = `${host}:${port}`; const endpoint = `${host}:${port}`;