- 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.
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
/// 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<SearchResponse, Error> {
// Parse the shorthand query url
let query_url = if query.contains('@') {
+ debug!("{}", query);
let split = query.split('@').collect::<Vec<&str>>();
- 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::<Vec<&str>>();
+ (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)?
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',
interface CommunityLinkProps {
community: Community | CommunityOther;
+ realLink?: boolean;
}
export class CommunityLink extends Component<CommunityLinkProps, any> {
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 <Link to={link}>{name_}</Link>;
}
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 {
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 (
</small>
)}
</h5>
- {community.local ? (
- <Link className="text-muted" to={link}>
- {name_}
- </Link>
- ) : (
- <a className="text-muted" href={link} target="_blank">
- {name_}
- </a>
- )}
+ <CommunityLink community={community} realLink />
<ul class="list-inline mb-1 text-muted font-weight-bold">
{this.canMod && (
<>
interface UserListingProps {
user: UserView | UserOther;
+ realLink?: boolean;
}
export class UserListing extends Component<UserListingProps, any> {
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 (
<h5>
<ul class="list-inline mb-0">
<li className="list-inline-item">
- <UserListing user={user} />
+ <UserListing user={user} realLink />
</li>
{user.banned && (
<li className="list-inline-item badge badge-danger">
{
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));
// 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));
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();
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();