From 906f34ff2ddcaacc130700cfb9bcc269826016a2 Mon Sep 17 00:00:00 2001 From: Dessalines Date: Wed, 29 Apr 2020 12:55:54 -0400 Subject: [PATCH 1/3] Adding shorthand federated object searching. --- server/src/apub/fetcher.rs | 14 +++++++++++--- ui/src/api_tests/api.spec.ts | 4 +++- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/server/src/apub/fetcher.rs b/server/src/apub/fetcher.rs index e07e410ba..2164dec8b 100644 --- a/server/src/apub/fetcher.rs +++ b/server/src/apub/fetcher.rs @@ -45,11 +45,19 @@ pub enum SearchAcceptedObjects { /// Attempt to parse the query as URL, and fetch an ActivityPub object from it. /// /// Some working examples for use with the docker/federation/ setup: -/// http://lemmy_alpha:8540/c/main -/// http://lemmy_alpha:8540/u/lemmy_alpha +/// http://lemmy_alpha:8540/c/main, or /c/main@lemmy_alpha:8540 +/// http://lemmy_alpha:8540/u/lemmy_alpha, or /u/lemmy_alpha@lemmy_alpha:8540 /// http://lemmy_alpha:8540/p/3 pub fn search_by_apub_id(query: &str, conn: &PgConnection) -> Result { - let query_url = Url::parse(&query)?; + // Parse the shorthand query url + let query_url = if query.contains('@') { + let split = query.split('@').collect::>(); + let url = format!("{}://{}{}", get_apub_protocol_string(), split[1], split[0]); + Url::parse(&url)? + } else { + Url::parse(&query)? + }; + let mut response = SearchResponse { type_: SearchType::All.to_string(), comments: vec![], diff --git a/ui/src/api_tests/api.spec.ts b/ui/src/api_tests/api.spec.ts index ffc33888b..7a810d9a0 100644 --- a/ui/src/api_tests/api.spec.ts +++ b/ui/src/api_tests/api.spec.ts @@ -95,7 +95,9 @@ describe('main', () => { describe('follow_accept', () => { test('/u/lemmy_alpha follows and accepts lemmy_beta/c/main', async () => { // Make sure lemmy_beta/c/main is cached on lemmy_alpha - let searchUrl = `${lemmyAlphaApiUrl}/search?q=http://lemmy_beta:8550/c/main&type_=All&sort=TopAll`; + // Use short-hand search url + let searchUrl = `${lemmyAlphaApiUrl}/search?q=/c/main@lemmy_beta:8550&type_=All&sort=TopAll`; + let searchResponse: SearchResponse = await fetch(searchUrl, { method: 'GET', }).then(d => d.json()); From 58b38424b7adeaff6428a30fdaa6f53d3b78a34a Mon Sep 17 00:00:00 2001 From: Dessalines Date: Wed, 29 Apr 2020 13:05:38 -0400 Subject: [PATCH 2/3] Fix search form clearing out. --- ui/src/components/search.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/ui/src/components/search.tsx b/ui/src/components/search.tsx index c14f2448e..a02f035ff 100644 --- a/ui/src/components/search.tsx +++ b/ui/src/components/search.tsx @@ -109,7 +109,6 @@ export class Search extends Component { nextProps.history.action == 'POP' || nextProps.history.action == 'PUSH' ) { - this.state = this.emptyState; this.state.q = this.getSearchQueryFromProps(nextProps); this.state.type_ = this.getSearchTypeFromProps(nextProps); this.state.sort = this.getSortTypeFromProps(nextProps); From d7d7beaa747073676b86d537504b6a982f342c97 Mon Sep 17 00:00:00 2001 From: Dessalines Date: Thu, 30 Apr 2020 11:45:12 -0400 Subject: [PATCH 3/3] Changing federated community and user links and searching. - Search: !community@instance, @user@instance - Representation: community@instance, user@instance --- README.md | 2 +- docs/src/about_guide.md | 2 +- server/src/apub/fetcher.rs | 23 ++++++++++++++++++++--- ui/src/api_tests/api.spec.ts | 2 +- ui/src/components/community-link.tsx | 7 +++++-- ui/src/components/sidebar.tsx | 13 +++---------- ui/src/components/user-listing.tsx | 5 +++-- ui/src/components/user.tsx | 2 +- ui/src/utils.ts | 28 ++++++++++++++++++++++------ 9 files changed, 57 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 723d535ff..a4a69cab0 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,7 @@ Each lemmy server can set its own moderation policy; appointing site-wide admins - Full vote scores `(+/-)` like old reddit. - Themes, including light, dark, and solarized. - Emojis with autocomplete support. Start typing `:` - - User tagging using `@`, Community tagging using `#`. + - User tagging using `@`, Community tagging using `!`. - Integrated image uploading in both posts and comments. - A post can consist of a title and any combination of self text, a URL, or nothing else. - Notifications, on comment replies and when you're tagged. diff --git a/docs/src/about_guide.md b/docs/src/about_guide.md index 16788ab93..6f709b580 100644 --- a/docs/src/about_guide.md +++ b/docs/src/about_guide.md @@ -3,7 +3,7 @@ Start typing... - `@a_user_name` to get a list of usernames. -- `#a_community` to get a list of communities. +- `!a_community` to get a list of communities. - `:emoji` to get a list of emojis. ## Sorting diff --git a/server/src/apub/fetcher.rs b/server/src/apub/fetcher.rs index 2164dec8b..8e82a2f79 100644 --- a/server/src/apub/fetcher.rs +++ b/server/src/apub/fetcher.rs @@ -45,14 +45,31 @@ pub enum SearchAcceptedObjects { /// Attempt to parse the query as URL, and fetch an ActivityPub object from it. /// /// Some working examples for use with the docker/federation/ setup: -/// http://lemmy_alpha:8540/c/main, or /c/main@lemmy_alpha:8540 -/// http://lemmy_alpha:8540/u/lemmy_alpha, or /u/lemmy_alpha@lemmy_alpha:8540 +/// http://lemmy_alpha:8540/c/main, or !main@lemmy_alpha:8540 +/// http://lemmy_alpha:8540/u/lemmy_alpha, or @lemmy_alpha@lemmy_alpha:8540 /// http://lemmy_alpha:8540/p/3 pub fn search_by_apub_id(query: &str, conn: &PgConnection) -> Result { // Parse the shorthand query url let query_url = if query.contains('@') { + debug!("{}", query); let split = query.split('@').collect::>(); - let url = format!("{}://{}{}", get_apub_protocol_string(), split[1], split[0]); + + // User type will look like ['', username, instance] + // Community will look like [!community, instance] + let (name, instance) = if split.len() == 3 { + (format!("/u/{}", split[1]), split[2]) + } else if split.len() == 2 { + if split[0].contains('!') { + let split2 = split[0].split('!').collect::>(); + (format!("/c/{}", split2[1]), split[1]) + } else { + return Err(format_err!("Invalid search query: {}", query)); + } + } else { + return Err(format_err!("Invalid search query: {}", query)); + }; + + let url = format!("{}://{}{}", get_apub_protocol_string(), instance, name); Url::parse(&url)? } else { Url::parse(&query)? diff --git a/ui/src/api_tests/api.spec.ts b/ui/src/api_tests/api.spec.ts index 7a810d9a0..7121d038b 100644 --- a/ui/src/api_tests/api.spec.ts +++ b/ui/src/api_tests/api.spec.ts @@ -96,7 +96,7 @@ describe('main', () => { test('/u/lemmy_alpha follows and accepts lemmy_beta/c/main', async () => { // Make sure lemmy_beta/c/main is cached on lemmy_alpha // Use short-hand search url - let searchUrl = `${lemmyAlphaApiUrl}/search?q=/c/main@lemmy_beta:8550&type_=All&sort=TopAll`; + let searchUrl = `${lemmyAlphaApiUrl}/search?q=!main@lemmy_beta:8550&type_=All&sort=TopAll`; let searchResponse: SearchResponse = await fetch(searchUrl, { method: 'GET', diff --git a/ui/src/components/community-link.tsx b/ui/src/components/community-link.tsx index bcfa05344..eb55400e1 100644 --- a/ui/src/components/community-link.tsx +++ b/ui/src/components/community-link.tsx @@ -12,6 +12,7 @@ interface CommunityOther { interface CommunityLinkProps { community: Community | CommunityOther; + realLink?: boolean; } export class CommunityLink extends Component { @@ -27,8 +28,10 @@ export class CommunityLink extends Component { name_ = community.name; link = `/c/${community.name}`; } else { - name_ = `${hostname(community.actor_id)}/${community.name}`; - link = `/community/${community.id}`; + name_ = `${community.name}@${hostname(community.actor_id)}`; + link = !this.props.realLink + ? `/community/${community.id}` + : community.actor_id; } return {name_}; } diff --git a/ui/src/components/sidebar.tsx b/ui/src/components/sidebar.tsx index 517669230..dc2376d7c 100644 --- a/ui/src/components/sidebar.tsx +++ b/ui/src/components/sidebar.tsx @@ -11,6 +11,7 @@ import { WebSocketService, UserService } from '../services'; import { mdToHtml, getUnixTime, hostname } from '../utils'; import { CommunityForm } from './community-form'; import { UserListing } from './user-listing'; +import { CommunityLink } from './community-link'; import { i18n } from '../i18next'; interface SidebarProps { @@ -66,7 +67,7 @@ export class Sidebar extends Component { name_ = community.name; link = `/c/${community.name}`; } else { - name_ = `${hostname(community.actor_id)}/${community.name}`; + name_ = `${community.name}@${hostname(community.actor_id)}`; link = community.actor_id; } return ( @@ -86,15 +87,7 @@ export class Sidebar extends Component { )} - {community.local ? ( - - {name_} - - ) : ( - - {name_} - - )} +
    {this.canMod && ( <> diff --git a/ui/src/components/user-listing.tsx b/ui/src/components/user-listing.tsx index 356c4d484..903aa1a75 100644 --- a/ui/src/components/user-listing.tsx +++ b/ui/src/components/user-listing.tsx @@ -13,6 +13,7 @@ interface UserOther { interface UserListingProps { user: UserView | UserOther; + realLink?: boolean; } export class UserListing extends Component { @@ -29,8 +30,8 @@ export class UserListing extends Component { name_ = user.name; link = `/u/${user.name}`; } else { - name_ = `${hostname(user.actor_id)}/${user.name}`; - link = `/user/${user.id}`; + name_ = `${user.name}@${hostname(user.actor_id)}`; + link = !this.props.realLink ? `/user/${user.id}` : user.actor_id; } return ( diff --git a/ui/src/components/user.tsx b/ui/src/components/user.tsx index b3e4542f5..51b9582fe 100644 --- a/ui/src/components/user.tsx +++ b/ui/src/components/user.tsx @@ -402,7 +402,7 @@ export class User extends Component {
    • - +
    • {user.banned && (
    • diff --git a/ui/src/utils.ts b/ui/src/utils.ts index 480b41c7c..277da9874 100644 --- a/ui/src/utils.ts +++ b/ui/src/utils.ts @@ -501,7 +501,10 @@ export function setupTribute(): Tribute { { trigger: '@', selectTemplate: (item: any) => { - return `[/u/${item.original.key}](/u/${item.original.key})`; + let link = item.original.local + ? `[@${item.original.key}](/u/${item.original.key})` + : `[@${item.original.key}](/user/${item.original.id})`; + return link; }, values: (text: string, cb: any) => { userSearch(text, (users: any) => cb(users)); @@ -514,9 +517,12 @@ export function setupTribute(): Tribute { // Communities { - trigger: '#', + trigger: '!', selectTemplate: (item: any) => { - return `[/c/${item.original.key}](/c/${item.original.key})`; + let link = item.original.local + ? `[!${item.original.key}](/c/${item.original.key})` + : `[!${item.original.key}](/community/${item.original.id})`; + return link; }, values: (text: string, cb: any) => { communitySearch(text, (communities: any) => cb(communities)); @@ -559,7 +565,12 @@ function userSearch(text: string, cb: any) { if (res.op == UserOperation.Search) { let data = res.data as SearchResponse; let users = data.users.map(u => { - return { key: u.name }; + let name_ = u.local ? u.name : `${u.name}@${hostname(u.actor_id)}`; + return { + key: name_, + local: u.local, + id: u.id, + }; }); cb(users); this.userSub.unsubscribe(); @@ -590,8 +601,13 @@ function communitySearch(text: string, cb: any) { let res = wsJsonToRes(msg); if (res.op == UserOperation.Search) { let data = res.data as SearchResponse; - let communities = data.communities.map(u => { - return { key: u.name }; + let communities = data.communities.map(c => { + let name_ = c.local ? c.name : `${c.name}@${hostname(c.actor_id)}`; + return { + key: name_, + local: c.local, + id: c.id, + }; }); cb(communities); this.communitySub.unsubscribe();