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 e07e410ba..8e82a2f79 100644 --- a/server/src/apub/fetcher.rs +++ b/server/src/apub/fetcher.rs @@ -45,11 +45,36 @@ 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 !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 { - let query_url = Url::parse(&query)?; + // Parse the shorthand query url + let query_url = if query.contains('@') { + debug!("{}", query); + let split = query.split('@').collect::>(); + + // 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)? + }; + 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 e6f7bd864..e3e205f0d 100644 --- a/ui/src/api_tests/api.spec.ts +++ b/ui/src/api_tests/api.spec.ts @@ -98,7 +98,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=!main@lemmy_beta:8550&type_=All&sort=TopAll`; + let searchResponse: SearchResponse = await fetch(searchUrl, { method: 'GET', }).then(d => d.json()); 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/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); 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();