]> Untitled Git - lemmy.git/commitdiff
Adding a search page
authorDessalines <tyhou13@gmx.com>
Tue, 23 Apr 2019 22:05:50 +0000 (15:05 -0700)
committerDessalines <tyhou13@gmx.com>
Tue, 23 Apr 2019 22:05:50 +0000 (15:05 -0700)
- Fixes # 70

server/src/actions/comment_view.rs
server/src/actions/post_view.rs
server/src/lib.rs
server/src/websocket_server/server.rs
ui/src/components/navbar.tsx
ui/src/components/search.tsx [new file with mode: 0644]
ui/src/index.tsx
ui/src/interfaces.ts
ui/src/services/WebSocketService.ts

index 2e3ae0587573192ab244412cdcf04207f2decd73..85ddf58714041d75844c2186d0fc2f89412599e3 100644 (file)
@@ -3,7 +3,7 @@ use diesel::*;
 use diesel::result::Error;
 use diesel::dsl::*;
 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
 table! {
@@ -60,6 +60,7 @@ impl CommentView {
               sort: &SortType, 
               for_post_id: Option<i32>, 
               for_creator_id: Option<i32>, 
+              search_term: Option<String>,
               my_user_id: Option<i32>, 
               saved_only: bool,
               page: Option<i64>,
@@ -86,6 +87,10 @@ impl CommentView {
     if let Some(for_post_id) = 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 {
       query = query.filter(saved.eq(true));
@@ -353,8 +358,26 @@ mod tests {
       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_with_user = CommentView::list(&conn, &SortType::New, Some(inserted_post.id), None, Some(inserted_user.id), false, None, None).unwrap();
+    let read_comment_views_no_user = CommentView::list(
+      &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 num_deleted = Comment::delete(&conn, inserted_comment.id).unwrap();
     Post::delete(&conn, inserted_post.id).unwrap();
index ccad931782c686861174507e63b4917224c4c4b0..e24b0ed21e2435285f89a79d91c14d6225228bcb 100644 (file)
@@ -3,7 +3,7 @@ use diesel::*;
 use diesel::result::Error;
 use diesel::dsl::*;
 use serde::{Deserialize, Serialize};
-use { SortType, limit_and_offset };
+use { SortType, limit_and_offset, fuzzy_search };
 
 #[derive(EnumString,ToString,Debug, Serialize, Deserialize)]
 pub enum PostListingType {
@@ -74,6 +74,7 @@ impl PostView {
               sort: &SortType, 
               for_community_id: Option<i32>, 
               for_creator_id: Option<i32>, 
+              search_term: Option<String>,
               my_user_id: Option<i32>, 
               saved_only: bool,
               unread_only: bool,
@@ -94,6 +95,10 @@ impl PostView {
       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
     if saved_only {
       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_no_user = PostView::list(&conn, PostListingType::Community, &SortType::New, Some(inserted_community.id), None, None, false, false, None, None).unwrap();
+    let read_post_listings_with_user = PostView::list(&conn, 
+                                                      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_with_user = PostView::read(&conn, inserted_post.id, Some(inserted_user.id)).unwrap();
 
index 8347c23d75ec81ac8c329f40a1469c256de3f448..d8d7f152df053090767e3de2e9829e7eac3b3da3 100644 (file)
@@ -97,6 +97,11 @@ pub enum SortType {
   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> {
   DateTime::<Utc>::from_utc(ndt, Utc)
 }
@@ -121,6 +126,11 @@ pub fn has_slurs(test: &str) -> bool {
   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) {
     let page = page.unwrap_or(1);
     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)]
 mod tests {
-  use {Settings, is_email_regex, remove_slurs, has_slurs};
+  use {Settings, is_email_regex, remove_slurs, has_slurs, fuzzy_search};
   #[test]
   fn test_api() {
     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(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! {
   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();
index c61756e1fe4cbc01aee542f26954797e99c99924..d7b9341636bab83cbd2ae2c7cf2e17a5a9b976f8 100644 (file)
@@ -12,7 +12,7 @@ use std::str::FromStr;
 use diesel::PgConnection;
 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::user::*;
 use actions::post::*;
@@ -27,7 +27,7 @@ use actions::moderator::*;
 
 #[derive(EnumString,ToString,Debug)]
 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)]
@@ -458,6 +458,23 @@ pub struct GetRepliesResponse {
   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
 /// session. implementation is super primitive
 pub struct ChatServer {
@@ -500,6 +517,7 @@ impl ChatServer {
                                Some(community_id), 
                                None,
                                None, 
+                               None,
                                false,
                                false,
                                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)?;
       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);
 
-    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)?;
 
@@ -1537,7 +1559,17 @@ impl Perform for GetPosts {
     let type_ = PostListingType::from_str(&self.type_)?;
     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,
       Err(_e) => {
         return Err(self.error("Couldn't get posts"))?
@@ -2006,15 +2038,52 @@ impl Perform for GetUserDetails {
     let sort = SortType::from_str(&self.sort)?;
 
     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 {
-      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 {
-      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 {
-      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 {
-      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)?;
@@ -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,
+        }
+        )?
+      )
+  }
+}
index 77ffa9af71c266a33c2f2f8893a44c9ba1c8f43d..fb8f5755706c0a019953427f181ac9f08b46ff70 100644 (file)
@@ -76,7 +76,7 @@ export class Navbar extends Component<any, NavbarState> {
               <Link class="nav-link" to="/communities">Forums</Link>
             </li>
             <li class="nav-item">
-              <Link class="nav-link" to="/modlog">Modlog</Link>
+              <Link class="nav-link" to="/search">Search</Link>
             </li>
             <li class="nav-item">
               <Link class="nav-link" to="/create_post">Create Post</Link>
diff --git a/ui/src/components/search.tsx b/ui/src/components/search.tsx
new file mode 100644 (file)
index 0000000..a4b389e
--- /dev/null
@@ -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);
+      
+    }
+  }
+}
+
index 716684dee7cedce4202a9e56d81ef4a7e588afcb..b3b46904a6ee15d293ae9f5748485e7ac2ed8fc2 100644 (file)
@@ -14,6 +14,7 @@ import { User } from './components/user';
 import { Modlog } from './components/modlog';
 import { Setup } from './components/setup';
 import { Inbox } from './components/inbox';
+import { Search } from './components/search';
 import { Symbols } from './components/symbols';
 
 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`} component={Modlog} />
             <Route path={`/setup`} component={Setup} />
+            <Route path={`/search`} component={Search} />
           </Switch>
           <Symbols />
         </div>
index ec111719905539b00083ec0547383a8446b4783d..05987fe3a113c89efc78e278c7e410e1a2ca01ef 100644 (file)
@@ -1,5 +1,5 @@
 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 {
@@ -14,6 +14,10 @@ export enum SortType {
   Hot, New, TopDay, TopWeek, TopMonth, TopYear, TopAll
 }
 
+export enum SearchType {
+  Both, Comments, Posts
+}
+
 export interface User {
   id: number;
   iss: string;
@@ -517,3 +521,18 @@ export interface AddAdminResponse {
   op: string;
   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>;
+}
index ac59631e00fa21982c0715a409238f2ee5f17719..dc06df2867e5360fe361b4031c7a3d3b208fa7ba 100644 (file)
@@ -1,5 +1,5 @@
 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 { Subject } from 'rxjs';
 import { retryWhen, delay, take } from 'rxjs/operators';
@@ -163,10 +163,15 @@ export class WebSocketService {
     this.setAuth(siteForm);
     this.subject.next(this.wsSendWrapper(UserOperation.EditSite, siteForm));
   }
+
   public 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) {
     let send = { op: UserOperation[op], data: data };
     console.log(send);