Adding a search page

- Fixes # 70
This commit is contained in:
Dessalines 2019-04-23 15:05:50 -07:00
parent c96aa4819c
commit 73b9266580
9 changed files with 513 additions and 18 deletions

View file

@ -3,7 +3,7 @@ use diesel::*;
use diesel::result::Error; use diesel::result::Error;
use diesel::dsl::*; use diesel::dsl::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use { SortType, limit_and_offset }; use { SortType, limit_and_offset, fuzzy_search };
// The faked schema since diesel doesn't do views // The faked schema since diesel doesn't do views
table! { table! {
@ -60,6 +60,7 @@ impl CommentView {
sort: &SortType, sort: &SortType,
for_post_id: Option<i32>, for_post_id: Option<i32>,
for_creator_id: Option<i32>, for_creator_id: Option<i32>,
search_term: Option<String>,
my_user_id: Option<i32>, my_user_id: Option<i32>,
saved_only: bool, saved_only: bool,
page: Option<i64>, page: Option<i64>,
@ -86,6 +87,10 @@ impl CommentView {
if let Some(for_post_id) = for_post_id { if let Some(for_post_id) = for_post_id {
query = query.filter(post_id.eq(for_post_id)); query = query.filter(post_id.eq(for_post_id));
}; };
if let Some(search_term) = search_term {
query = query.filter(content.ilike(fuzzy_search(&search_term)));
};
if saved_only { if saved_only {
query = query.filter(saved.eq(true)); query = query.filter(saved.eq(true));
@ -353,8 +358,26 @@ mod tests {
saved: None, saved: None,
}; };
let read_comment_views_no_user = CommentView::list(&conn, &SortType::New, Some(inserted_post.id), None, None, false, None, None).unwrap(); let read_comment_views_no_user = CommentView::list(
let read_comment_views_with_user = CommentView::list(&conn, &SortType::New, Some(inserted_post.id), None, Some(inserted_user.id), false, None, None).unwrap(); &conn,
&SortType::New,
Some(inserted_post.id),
None,
None,
None,
false,
None,
None).unwrap();
let read_comment_views_with_user = CommentView::list(
&conn,
&SortType::New,
Some(inserted_post.id),
None,
None,
Some(inserted_user.id),
false,
None,
None).unwrap();
let like_removed = CommentLike::remove(&conn, &comment_like_form).unwrap(); let like_removed = CommentLike::remove(&conn, &comment_like_form).unwrap();
let num_deleted = Comment::delete(&conn, inserted_comment.id).unwrap(); let num_deleted = Comment::delete(&conn, inserted_comment.id).unwrap();
Post::delete(&conn, inserted_post.id).unwrap(); Post::delete(&conn, inserted_post.id).unwrap();

View file

@ -3,7 +3,7 @@ use diesel::*;
use diesel::result::Error; use diesel::result::Error;
use diesel::dsl::*; use diesel::dsl::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use { SortType, limit_and_offset }; use { SortType, limit_and_offset, fuzzy_search };
#[derive(EnumString,ToString,Debug, Serialize, Deserialize)] #[derive(EnumString,ToString,Debug, Serialize, Deserialize)]
pub enum PostListingType { pub enum PostListingType {
@ -74,6 +74,7 @@ impl PostView {
sort: &SortType, sort: &SortType,
for_community_id: Option<i32>, for_community_id: Option<i32>,
for_creator_id: Option<i32>, for_creator_id: Option<i32>,
search_term: Option<String>,
my_user_id: Option<i32>, my_user_id: Option<i32>,
saved_only: bool, saved_only: bool,
unread_only: bool, unread_only: bool,
@ -94,6 +95,10 @@ impl PostView {
query = query.filter(creator_id.eq(for_creator_id)); query = query.filter(creator_id.eq(for_creator_id));
}; };
if let Some(search_term) = search_term {
query = query.filter(name.ilike(fuzzy_search(&search_term)));
};
// TODO these are wrong, bc they'll only show saved for your logged in user, not theirs // TODO these are wrong, bc they'll only show saved for your logged in user, not theirs
if saved_only { if saved_only {
query = query.filter(saved.eq(true)); query = query.filter(saved.eq(true));
@ -295,8 +300,27 @@ mod tests {
}; };
let read_post_listings_with_user = PostView::list(&conn, PostListingType::Community, &SortType::New, Some(inserted_community.id), None, Some(inserted_user.id), false, false, None, None).unwrap(); let read_post_listings_with_user = PostView::list(&conn,
let read_post_listings_no_user = PostView::list(&conn, PostListingType::Community, &SortType::New, Some(inserted_community.id), None, None, false, false, None, None).unwrap(); PostListingType::Community,
&SortType::New, Some(inserted_community.id),
None,
None,
Some(inserted_user.id),
false,
false,
None,
None).unwrap();
let read_post_listings_no_user = PostView::list(&conn,
PostListingType::Community,
&SortType::New,
Some(inserted_community.id),
None,
None,
None,
false,
false,
None,
None).unwrap();
let read_post_listing_no_user = PostView::read(&conn, inserted_post.id, None).unwrap(); let read_post_listing_no_user = PostView::read(&conn, inserted_post.id, None).unwrap();
let read_post_listing_with_user = PostView::read(&conn, inserted_post.id, Some(inserted_user.id)).unwrap(); let read_post_listing_with_user = PostView::read(&conn, inserted_post.id, Some(inserted_user.id)).unwrap();

View file

@ -97,6 +97,11 @@ pub enum SortType {
Hot, New, TopDay, TopWeek, TopMonth, TopYear, TopAll Hot, New, TopDay, TopWeek, TopMonth, TopYear, TopAll
} }
#[derive(EnumString,ToString,Debug, Serialize, Deserialize)]
pub enum SearchType {
Both, Comments, Posts
}
pub fn to_datetime_utc(ndt: NaiveDateTime) -> DateTime<Utc> { pub fn to_datetime_utc(ndt: NaiveDateTime) -> DateTime<Utc> {
DateTime::<Utc>::from_utc(ndt, Utc) DateTime::<Utc>::from_utc(ndt, Utc)
} }
@ -121,6 +126,11 @@ pub fn has_slurs(test: &str) -> bool {
SLUR_REGEX.is_match(test) SLUR_REGEX.is_match(test)
} }
pub fn fuzzy_search(q: &str) -> String {
let replaced = q.replace(" ", "%");
format!("%{}%", replaced)
}
pub fn limit_and_offset(page: Option<i64>, limit: Option<i64>) -> (i64, i64) { pub fn limit_and_offset(page: Option<i64>, limit: Option<i64>) -> (i64, i64) {
let page = page.unwrap_or(1); let page = page.unwrap_or(1);
let limit = limit.unwrap_or(10); let limit = limit.unwrap_or(10);
@ -130,7 +140,7 @@ pub fn limit_and_offset(page: Option<i64>, limit: Option<i64>) -> (i64, i64) {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use {Settings, is_email_regex, remove_slurs, has_slurs}; use {Settings, is_email_regex, remove_slurs, has_slurs, fuzzy_search};
#[test] #[test]
fn test_api() { fn test_api() {
assert_eq!(Settings::get().api_endpoint(), "http://0.0.0.0/api/v1"); assert_eq!(Settings::get().api_endpoint(), "http://0.0.0.0/api/v1");
@ -148,9 +158,15 @@ mod tests {
assert!(has_slurs(&test)); assert!(has_slurs(&test));
assert!(!has_slurs(slur_free)); assert!(!has_slurs(slur_free));
} }
#[test] fn test_fuzzy_search() {
let test = "This is a fuzzy search";
assert_eq!(fuzzy_search(test), "%This%is%a%fuzzy%search%".to_string());
}
} }
lazy_static! { lazy_static! {
static ref EMAIL_REGEX: Regex = Regex::new(r"^[a-zA-Z0-9.!#$%&*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$").unwrap(); static ref EMAIL_REGEX: Regex = Regex::new(r"^[a-zA-Z0-9.!#$%&*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$").unwrap();
static ref SLUR_REGEX: Regex = Regex::new(r"(fag(g|got|tard)?|maricos?|cock\s?sucker(s|ing)?|\bnig(\b|g?(a|er)?s?)\b|dindu(s?)|mudslime?s?|kikes?|mongoloids?|towel\s*heads?|\bspi(c|k)s?\b|\bchinks?|niglets?|beaners?|\bnips?\b|\bcoons?\b|jungle\s*bunn(y|ies?)|jigg?aboo?s?|\bpakis?\b|rag\s*heads?|gooks?|cunts?|bitch(es|ing|y)?|puss(y|ies?)|twats?|feminazis?|whor(es?|ing)|\bslut(s|t?y)?|\btrann?(y|ies?)|ladyboy(s?))").unwrap(); static ref SLUR_REGEX: Regex = Regex::new(r"(fag(g|got|tard)?|maricos?|cock\s?sucker(s|ing)?|\bnig(\b|g?(a|er)?s?)\b|dindu(s?)|mudslime?s?|kikes?|mongoloids?|towel\s*heads?|\bspi(c|k)s?\b|\bchinks?|niglets?|beaners?|\bnips?\b|\bcoons?\b|jungle\s*bunn(y|ies?)|jigg?aboo?s?|\bpakis?\b|rag\s*heads?|gooks?|cunts?|bitch(es|ing|y)?|puss(y|ies?)|twats?|feminazis?|whor(es?|ing)|\bslut(s|t?y)?|\btrann?(y|ies?)|ladyboy(s?))").unwrap();

View file

@ -12,7 +12,7 @@ use std::str::FromStr;
use diesel::PgConnection; use diesel::PgConnection;
use failure::Error; use failure::Error;
use {Crud, Joinable, Likeable, Followable, Bannable, Saveable, establish_connection, naive_now, naive_from_unix, SortType, has_slurs, remove_slurs}; use {Crud, Joinable, Likeable, Followable, Bannable, Saveable, establish_connection, naive_now, naive_from_unix, SortType, SearchType, has_slurs, remove_slurs};
use actions::community::*; use actions::community::*;
use actions::user::*; use actions::user::*;
use actions::post::*; use actions::post::*;
@ -27,7 +27,7 @@ use actions::moderator::*;
#[derive(EnumString,ToString,Debug)] #[derive(EnumString,ToString,Debug)]
pub enum UserOperation { pub enum UserOperation {
Login, Register, CreateCommunity, CreatePost, ListCommunities, ListCategories, GetPost, GetCommunity, CreateComment, EditComment, SaveComment, CreateCommentLike, GetPosts, CreatePostLike, EditPost, SavePost, EditCommunity, FollowCommunity, GetFollowedCommunities, GetUserDetails, GetReplies, GetModlog, BanFromCommunity, AddModToCommunity, CreateSite, EditSite, GetSite, AddAdmin, BanUser Login, Register, CreateCommunity, CreatePost, ListCommunities, ListCategories, GetPost, GetCommunity, CreateComment, EditComment, SaveComment, CreateCommentLike, GetPosts, CreatePostLike, EditPost, SavePost, EditCommunity, FollowCommunity, GetFollowedCommunities, GetUserDetails, GetReplies, GetModlog, BanFromCommunity, AddModToCommunity, CreateSite, EditSite, GetSite, AddAdmin, BanUser, Search
} }
#[derive(Fail, Debug)] #[derive(Fail, Debug)]
@ -458,6 +458,23 @@ pub struct GetRepliesResponse {
replies: Vec<ReplyView>, replies: Vec<ReplyView>,
} }
#[derive(Serialize, Deserialize)]
pub struct Search {
q: String,
type_: String,
community_id: Option<i32>,
sort: String,
page: Option<i64>,
limit: Option<i64>,
}
#[derive(Serialize, Deserialize)]
pub struct SearchResponse {
op: String,
comments: Vec<CommentView>,
posts: Vec<PostView>,
}
/// `ChatServer` manages chat rooms and responsible for coordinating chat /// `ChatServer` manages chat rooms and responsible for coordinating chat
/// session. implementation is super primitive /// session. implementation is super primitive
pub struct ChatServer { pub struct ChatServer {
@ -500,6 +517,7 @@ impl ChatServer {
Some(community_id), Some(community_id),
None, None,
None, None,
None,
false, false,
false, false,
None, None,
@ -703,6 +721,10 @@ fn parse_json_message(chat: &mut ChatServer, msg: StandardMessage) -> Result<Str
let get_replies: GetReplies = serde_json::from_str(data)?; let get_replies: GetReplies = serde_json::from_str(data)?;
get_replies.perform(chat, msg.id) get_replies.perform(chat, msg.id)
}, },
UserOperation::Search => {
let search: Search = serde_json::from_str(data)?;
search.perform(chat, msg.id)
},
} }
} }
@ -1106,7 +1128,7 @@ impl Perform for GetPost {
chat.rooms.get_mut(&self.id).unwrap().insert(addr); chat.rooms.get_mut(&self.id).unwrap().insert(addr);
let comments = CommentView::list(&conn, &SortType::New, Some(self.id), None, user_id, false, None, Some(9999))?; let comments = CommentView::list(&conn, &SortType::New, Some(self.id), None, None, user_id, false, None, Some(9999))?;
let community = CommunityView::read(&conn, post_view.community_id, user_id)?; let community = CommunityView::read(&conn, post_view.community_id, user_id)?;
@ -1537,7 +1559,17 @@ impl Perform for GetPosts {
let type_ = PostListingType::from_str(&self.type_)?; let type_ = PostListingType::from_str(&self.type_)?;
let sort = SortType::from_str(&self.sort)?; let sort = SortType::from_str(&self.sort)?;
let posts = match PostView::list(&conn, type_, &sort, self.community_id, None, user_id, false, false, self.page, self.limit) { let posts = match PostView::list(&conn,
type_,
&sort,
self.community_id,
None,
None,
user_id,
false,
false,
self.page,
self.limit) {
Ok(posts) => posts, Ok(posts) => posts,
Err(_e) => { Err(_e) => {
return Err(self.error("Couldn't get posts"))? return Err(self.error("Couldn't get posts"))?
@ -2006,15 +2038,52 @@ impl Perform for GetUserDetails {
let sort = SortType::from_str(&self.sort)?; let sort = SortType::from_str(&self.sort)?;
let user_view = UserView::read(&conn, self.user_id)?; let user_view = UserView::read(&conn, self.user_id)?;
// If its saved only, you don't care what creator it was
let posts = if self.saved_only { let posts = if self.saved_only {
PostView::list(&conn, PostListingType::All, &sort, self.community_id, None, Some(self.user_id), self.saved_only, false, self.page, self.limit)? PostView::list(&conn,
PostListingType::All,
&sort,
self.community_id,
None,
None,
Some(self.user_id),
self.saved_only,
false,
self.page,
self.limit)?
} else { } else {
PostView::list(&conn, PostListingType::All, &sort, self.community_id, Some(self.user_id), None, self.saved_only, false, self.page, self.limit)? PostView::list(&conn,
PostListingType::All,
&sort,
self.community_id,
Some(self.user_id),
None,
None,
self.saved_only,
false,
self.page,
self.limit)?
}; };
let comments = if self.saved_only { let comments = if self.saved_only {
CommentView::list(&conn, &sort, None, None, Some(self.user_id), self.saved_only, self.page, self.limit)? CommentView::list(&conn,
&sort,
None,
None,
None,
Some(self.user_id),
self.saved_only,
self.page,
self.limit)?
} else { } else {
CommentView::list(&conn, &sort, None, Some(self.user_id), None, self.saved_only, self.page, self.limit)? CommentView::list(&conn,
&sort,
None,
Some(self.user_id),
None,
None,
self.saved_only,
self.page,
self.limit)?
}; };
let follows = CommunityFollowerView::for_user(&conn, self.user_id)?; let follows = CommunityFollowerView::for_user(&conn, self.user_id)?;
@ -2539,3 +2608,81 @@ impl Perform for BanUser {
} }
} }
impl Perform for Search {
fn op_type(&self) -> UserOperation {
UserOperation::Search
}
fn perform(&self, _chat: &mut ChatServer, _addr: usize) -> Result<String, Error> {
let conn = establish_connection();
let sort = SortType::from_str(&self.sort)?;
let type_ = SearchType::from_str(&self.type_)?;
let mut posts = Vec::new();
let mut comments = Vec::new();
match type_ {
SearchType::Posts => {
posts = PostView::list(&conn,
PostListingType::All,
&sort,
self.community_id,
None,
Some(self.q.to_owned()),
None,
false,
false,
self.page,
self.limit)?;
},
SearchType::Comments => {
comments = CommentView::list(&conn,
&sort,
None,
None,
Some(self.q.to_owned()),
None,
false,
self.page,
self.limit)?;
},
SearchType::Both => {
posts = PostView::list(&conn,
PostListingType::All,
&sort,
self.community_id,
None,
Some(self.q.to_owned()),
None,
false,
false,
self.page,
self.limit)?;
comments = CommentView::list(&conn,
&sort,
None,
None,
Some(self.q.to_owned()),
None,
false,
self.page,
self.limit)?;
}
};
// Return the jwt
Ok(
serde_json::to_string(
&SearchResponse {
op: self.op_type().to_string(),
comments: comments,
posts: posts,
}
)?
)
}
}

View file

@ -76,7 +76,7 @@ export class Navbar extends Component<any, NavbarState> {
<Link class="nav-link" to="/communities">Forums</Link> <Link class="nav-link" to="/communities">Forums</Link>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<Link class="nav-link" to="/modlog">Modlog</Link> <Link class="nav-link" to="/search">Search</Link>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<Link class="nav-link" to="/create_post">Create Post</Link> <Link class="nav-link" to="/create_post">Create Post</Link>

View file

@ -0,0 +1,259 @@
import { Component, linkEvent } from 'inferno';
import { Subscription } from "rxjs";
import { retryWhen, delay, take } from 'rxjs/operators';
import { UserOperation, Post, Comment, SortType, SearchForm, SearchResponse, SearchType } from '../interfaces';
import { WebSocketService } from '../services';
import { msgOp, fetchLimit } from '../utils';
import { PostListing } from './post-listing';
import { CommentNodes } from './comment-nodes';
interface SearchState {
q: string,
type_: SearchType,
sort: SortType,
page: number,
searchResponse: SearchResponse;
loading: boolean;
}
export class Search extends Component<any, SearchState> {
private subscription: Subscription;
private emptyState: SearchState = {
q: undefined,
type_: SearchType.Both,
sort: SortType.TopAll,
page: 1,
searchResponse: {
op: null,
posts: [],
comments: [],
},
loading: false,
}
constructor(props: any, context: any) {
super(props, context);
this.state = this.emptyState;
this.subscription = WebSocketService.Instance.subject
.pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
.subscribe(
(msg) => this.parseMessage(msg),
(err) => console.error(err),
() => console.log('complete')
);
}
componentWillUnmount() {
this.subscription.unsubscribe();
}
componentDidMount() {
document.title = "Search - Lemmy";
}
render() {
return (
<div class="container">
<div class="row">
<div class="col-12">
<h5>Search</h5>
{this.selects()}
{this.searchForm()}
{this.state.type_ == SearchType.Both &&
this.both()
}
{this.state.type_ == SearchType.Comments &&
this.comments()
}
{this.state.type_ == SearchType.Posts &&
this.posts()
}
{this.noResults()}
{this.paginator()}
</div>
</div>
</div>
)
}
searchForm() {
return (
<form class="form-inline" onSubmit={linkEvent(this, this.handleSearchSubmit)}>
<input type="text" class="form-control mr-2" value={this.state.q} placeholder="Search..." onInput={linkEvent(this, this.handleQChange)} required minLength={3} />
<button type="submit" class="btn btn-secondary mr-2">
{this.state.loading ?
<svg class="icon icon-spinner spin"><use xlinkHref="#icon-spinner"></use></svg> :
<span>Search</span>
}
</button>
</form>
)
}
selects() {
return (
<div className="mb-2">
<select value={this.state.type_} onChange={linkEvent(this, this.handleTypeChange)} class="custom-select w-auto">
<option disabled>Type</option>
<option value={SearchType.Both}>Both</option>
<option value={SearchType.Comments}>Comments</option>
<option value={SearchType.Posts}>Posts</option>
</select>
<select value={this.state.sort} onChange={linkEvent(this, this.handleSortChange)} class="custom-select w-auto ml-2">
<option disabled>Sort Type</option>
<option value={SortType.New}>New</option>
<option value={SortType.TopDay}>Top Day</option>
<option value={SortType.TopWeek}>Week</option>
<option value={SortType.TopMonth}>Month</option>
<option value={SortType.TopYear}>Year</option>
<option value={SortType.TopAll}>All</option>
</select>
</div>
)
}
both() {
let combined: Array<{type_: string, data: Comment | Post}> = [];
let comments = this.state.searchResponse.comments.map(e => {return {type_: "comments", data: e}});
let posts = this.state.searchResponse.posts.map(e => {return {type_: "posts", data: e}});
combined.push(...comments);
combined.push(...posts);
// Sort it
if (this.state.sort == SortType.New) {
combined.sort((a, b) => b.data.published.localeCompare(a.data.published));
} else {
combined.sort((a, b) => b.data.score - a.data.score);
}
return (
<div>
{combined.map(i =>
<div>
{i.type_ == "posts"
? <PostListing post={i.data as Post} showCommunity viewOnly />
: <CommentNodes nodes={[{comment: i.data as Comment}]} viewOnly noIndent />
}
</div>
)
}
</div>
)
}
comments() {
return (
<div>
{this.state.searchResponse.comments.map(comment =>
<CommentNodes nodes={[{comment: comment}]} noIndent viewOnly />
)}
</div>
);
}
posts() {
return (
<div>
{this.state.searchResponse.posts.map(post =>
<PostListing post={post} showCommunity viewOnly />
)}
</div>
);
}
paginator() {
return (
<div class="mt-2">
{this.state.page > 1 &&
<button class="btn btn-sm btn-secondary mr-1" onClick={linkEvent(this, this.prevPage)}>Prev</button>
}
<button class="btn btn-sm btn-secondary" onClick={linkEvent(this, this.nextPage)}>Next</button>
</div>
);
}
noResults() {
let res = this.state.searchResponse;
return (
<div>
{res && res.op && res.posts.length == 0 && res.comments.length == 0 &&
<span>No Results</span>
}
</div>
)
}
nextPage(i: Search) {
i.state.page++;
i.setState(i.state);
i.search();
}
prevPage(i: Search) {
i.state.page--;
i.setState(i.state);
i.search();
}
search() {
// TODO community
let form: SearchForm = {
q: this.state.q,
type_: SearchType[this.state.type_],
sort: SortType[this.state.sort],
page: this.state.page,
limit: fetchLimit,
};
WebSocketService.Instance.search(form);
}
handleSortChange(i: Search, event: any) {
i.state.sort = Number(event.target.value);
i.state.page = 1;
i.setState(i.state);
i.search();
}
handleTypeChange(i: Search, event: any) {
i.state.type_ = Number(event.target.value);
i.state.page = 1;
i.setState(i.state);
i.search();
}
handleSearchSubmit(i: Search, event: any) {
event.preventDefault();
i.state.loading = true;
i.search();
i.setState(i.state);
}
handleQChange(i: Search, event: any) {
i.state.q = event.target.value;
i.setState(i.state);
}
parseMessage(msg: any) {
console.log(msg);
let op: UserOperation = msgOp(msg);
if (msg.error) {
alert(msg.error);
return;
} else if (op == UserOperation.Search) {
let res: SearchResponse = msg;
this.state.searchResponse = res;
this.state.loading = false;
document.title = `Search - ${this.state.q} - Lemmy`;
this.setState(this.state);
}
}
}

View file

@ -14,6 +14,7 @@ import { User } from './components/user';
import { Modlog } from './components/modlog'; import { Modlog } from './components/modlog';
import { Setup } from './components/setup'; import { Setup } from './components/setup';
import { Inbox } from './components/inbox'; import { Inbox } from './components/inbox';
import { Search } from './components/search';
import { Symbols } from './components/symbols'; import { Symbols } from './components/symbols';
import './css/bootstrap.min.css'; import './css/bootstrap.min.css';
@ -52,6 +53,7 @@ class Index extends Component<any, any> {
<Route path={`/modlog/community/:community_id`} component={Modlog} /> <Route path={`/modlog/community/:community_id`} component={Modlog} />
<Route path={`/modlog`} component={Modlog} /> <Route path={`/modlog`} component={Modlog} />
<Route path={`/setup`} component={Setup} /> <Route path={`/setup`} component={Setup} />
<Route path={`/search`} component={Search} />
</Switch> </Switch>
<Symbols /> <Symbols />
</div> </div>

View file

@ -1,5 +1,5 @@
export enum UserOperation { export enum UserOperation {
Login, Register, CreateCommunity, CreatePost, ListCommunities, ListCategories, GetPost, GetCommunity, CreateComment, EditComment, SaveComment, CreateCommentLike, GetPosts, CreatePostLike, EditPost, SavePost, EditCommunity, FollowCommunity, GetFollowedCommunities, GetUserDetails, GetReplies, GetModlog, BanFromCommunity, AddModToCommunity, CreateSite, EditSite, GetSite, AddAdmin, BanUser Login, Register, CreateCommunity, CreatePost, ListCommunities, ListCategories, GetPost, GetCommunity, CreateComment, EditComment, SaveComment, CreateCommentLike, GetPosts, CreatePostLike, EditPost, SavePost, EditCommunity, FollowCommunity, GetFollowedCommunities, GetUserDetails, GetReplies, GetModlog, BanFromCommunity, AddModToCommunity, CreateSite, EditSite, GetSite, AddAdmin, BanUser, Search
} }
export enum CommentSortType { export enum CommentSortType {
@ -14,6 +14,10 @@ export enum SortType {
Hot, New, TopDay, TopWeek, TopMonth, TopYear, TopAll Hot, New, TopDay, TopWeek, TopMonth, TopYear, TopAll
} }
export enum SearchType {
Both, Comments, Posts
}
export interface User { export interface User {
id: number; id: number;
iss: string; iss: string;
@ -517,3 +521,18 @@ export interface AddAdminResponse {
op: string; op: string;
admins: Array<UserView>; admins: Array<UserView>;
} }
export interface SearchForm {
q: string;
type_: string;
community_id?: number;
sort: string;
page?: number;
limit?: number;
}
export interface SearchResponse {
op: string;
posts?: Array<Post>;
comments?: Array<Comment>;
}

View file

@ -1,5 +1,5 @@
import { wsUri } from '../env'; import { wsUri } from '../env';
import { LoginForm, RegisterForm, UserOperation, CommunityForm, PostForm, SavePostForm, CommentForm, SaveCommentForm, CommentLikeForm, GetPostsForm, CreatePostLikeForm, FollowCommunityForm, GetUserDetailsForm, ListCommunitiesForm, GetModlogForm, BanFromCommunityForm, AddModToCommunityForm, AddAdminForm, BanUserForm, SiteForm, Site, UserView, GetRepliesForm } from '../interfaces'; import { LoginForm, RegisterForm, UserOperation, CommunityForm, PostForm, SavePostForm, CommentForm, SaveCommentForm, CommentLikeForm, GetPostsForm, CreatePostLikeForm, FollowCommunityForm, GetUserDetailsForm, ListCommunitiesForm, GetModlogForm, BanFromCommunityForm, AddModToCommunityForm, AddAdminForm, BanUserForm, SiteForm, Site, UserView, GetRepliesForm, SearchForm } from '../interfaces';
import { webSocket } from 'rxjs/webSocket'; import { webSocket } from 'rxjs/webSocket';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { retryWhen, delay, take } from 'rxjs/operators'; import { retryWhen, delay, take } from 'rxjs/operators';
@ -163,10 +163,15 @@ export class WebSocketService {
this.setAuth(siteForm); this.setAuth(siteForm);
this.subject.next(this.wsSendWrapper(UserOperation.EditSite, siteForm)); this.subject.next(this.wsSendWrapper(UserOperation.EditSite, siteForm));
} }
public getSite() { public getSite() {
this.subject.next(this.wsSendWrapper(UserOperation.GetSite, {})); this.subject.next(this.wsSendWrapper(UserOperation.GetSite, {}));
} }
public search(form: SearchForm) {
this.subject.next(this.wsSendWrapper(UserOperation.Search, form));
}
private wsSendWrapper(op: UserOperation, data: any) { private wsSendWrapper(op: UserOperation, data: any) {
let send = { op: UserOperation[op], data: data }; let send = { op: UserOperation[op], data: data };
console.log(send); console.log(send);