Przeglądaj źródła

Search progress

Zed 7 lat temu
rodzic
commit
4c748b61a5

+ 8 - 1
src/query.nim

@@ -4,13 +4,20 @@ import types
 
 const
   separators = @["AND", "OR"]
-  validFilters = @[
+  validFilters* = @[
     "media", "images", "twimg", "videos",
     "native_video", "consumer_video", "pro_video",
     "links", "news", "quote", "mentions",
     "replies", "retweets", "nativeretweets",
     "verified", "safe"
   ]
+  commonFilters* = @[
+    "media", "videos", "images", "links", "news", "quote"
+  ]
+  advancedFilters* = @[
+    "mentions", "verified", "safe", "twimg", "native_video",
+    "consumer_video", "pro_video"
+  ]
 
 # Experimental, this might break in the future
 # Till then, it results in shorter urls

+ 20 - 7
src/routes/search.nim

@@ -11,23 +11,36 @@ export search
 proc createSearchRouter*(cfg: Config) =
   router search:
     get "/search":
-      if @"text".len == 0 or "." in @"text":
-        resp Http404, showError("Please enter a valid username.", cfg.title)
-
       if @"text".len > 200:
         resp Http400, showError("Search input too long.", cfg.title)
 
-      if "," in @"text":
-        redirect("/" & @"text")
+      let kind = parseEnum[QueryKind](@"kind", custom)
+      var query = Query(kind: kind, text: @"text")
+
+      if @"retweets".len == 0:
+        query.excludes.add "nativeretweets"
+      else:
+        query.includes.add "nativeretweets"
+
+      if @"replies".len == 0:
+        query.excludes.add "replies"
+      else:
+        query.includes.add "replies"
 
-      let query = Query(kind: parseEnum[QueryKind](@"kind", custom), text: @"text")
+      for f in validFilters:
+        if "f-" & f in params(request):
+          query.filters.add f
+        if "e-" & f in params(request):
+          query.excludes.add f
 
       case query.kind
       of users:
+        if "," in @"text":
+          redirect("/" & @"text")
         let users = await getSearch[Profile](query, @"after", getAgent())
         resp renderMain(renderUserSearch(users, Prefs()), Prefs(), path=getPath())
       of custom:
         let tweets = await getSearch[Tweet](query, @"after", getAgent())
         resp renderMain(renderTweetSearch(tweets, Prefs(), getPath()), Prefs(), path=getPath())
       else:
-        resp Http404
+        resp Http404, showError("Invalid search.", cfg.title)

+ 1 - 1
src/routes/timeline.nim

@@ -64,7 +64,7 @@ proc showTimeline*(name, after: string; query: Option[Query];
   else:
     let
       timeline = await fetchMultiTimeline(names, after, agent, query)
-      html = renderTweetSearch(timeline, prefs, path)
+      html = renderTimelineSearch(timeline, prefs, path)
     return renderMain(html, prefs, title, "Multi")
 
 template respTimeline*(timeline: typed) =

+ 104 - 1
src/sass/general.scss

@@ -10,7 +10,7 @@
     @include center-panel($error_red);
 }
 
-.search-panel > form {
+.search-bar > form {
     @include center-panel($darkest-grey);
 
     button {
@@ -35,3 +35,106 @@
         margin-right: 8px;
     }
 }
+
+.search-field {
+    margin: 2px 5px;
+
+    .pref-group.pref-input {
+        display: inline-block;
+        width: calc(90% - 11px);
+    }
+
+    input[type="text"] {
+        width: calc(100% - 8px);
+    }
+
+    .panel-label {
+        background-color: #121212;
+        color: #F8F8F2;
+        border: 1px solid #FF6C6091;
+        padding: 1px 6px 2px 6px;
+        font-size: 14px;
+        cursor: pointer;
+        margin-left: -2px;
+    }
+
+    .panel-label:hover {
+        border: 1px solid #FF6C60;
+    }
+}
+
+
+#panel-toggle {
+    display: none;
+
+    &:checked ~ .search-panel {
+        max-height: 180px;
+    }
+}
+
+.pannel-label {
+    display: inline;
+}
+
+.search-panel {
+    max-height: 0;
+    overflow: hidden;
+    transition: max-height 0.4s;
+
+    margin: 5px;
+    font-weight: initial;
+    text-align: left;
+
+    > div {
+        line-height: 1.7em;
+    }
+
+    .checkbox-container {
+        display: inline;
+        padding-right: unset;
+        margin-left: 23px;
+    }
+
+    .checkbox {
+        right: unset;
+        left: -22px;
+    }
+
+    .checkbox-container .checkbox:after {
+        top: -4px;
+    }
+
+    .search-title {
+        font-weight: bold;
+        min-width: 60px;
+        display: inline-block;
+    }
+
+    .exclude-extras {
+        max-height: 0;
+        overflow: hidden;
+        transition: max-height 0.4s;
+    }
+
+    #exclude-toggle {
+        display: none;
+
+        &:checked ~ .exclude-extras {
+            max-height: 50px;
+        }
+    }
+
+    .filter-extras {
+        max-height: 0;
+        overflow: hidden;
+        transition: max-height 0.4s;
+    }
+
+    #filter-toggle {
+        display: none;
+
+        &:checked ~ .filter-extras {
+            max-height: 50px;
+        }
+    }
+}

+ 8 - 0
src/sass/inputs.scss

@@ -88,6 +88,10 @@ input[type="text"] {
     }
 }
 
+.pref-group {
+    display: inline;
+}
+
 .preferences {
     button {
         margin: 6px 0 3px 0;
@@ -103,6 +107,10 @@ input[type="text"] {
         max-width: 120px;
     }
 
+    .pref-group {
+        display: block;
+    }
+
     .pref-input {
         position: relative;
         margin-bottom: 6px;

+ 1 - 26
src/views/preferences.nim

@@ -1,34 +1,9 @@
-import tables, macros, strformat, strutils, xmltree
+import tables, macros, strutils
 import karax/[karaxdsl, vdom, vstyles]
 
 import renderutils
 import ../types, ../prefs_impl
 
-proc genCheckbox(pref, label: string; state: bool): VNode =
-  buildHtml(tdiv(class="pref-group")):
-    label(class="checkbox-container"):
-      text label
-      if state: input(name=pref, `type`="checkbox", checked="")
-      else: input(name=pref, `type`="checkbox")
-      span(class="checkbox")
-
-proc genSelect(pref, label, state: string; options: seq[string]): VNode =
-  buildHtml(tdiv(class="pref-group")):
-    label(`for`=pref): text label
-    select(name=pref):
-      for opt in options:
-        if opt == state:
-          option(value=opt, selected=""): text opt
-        else:
-          option(value=opt): text opt
-
-proc genInput(pref, label, state, placeholder: string): VNode =
-  let s = xmltree.escape(state)
-  let p = xmltree.escape(placeholder)
-  buildHtml(tdiv(class="pref-group pref-input")):
-    label(`for`=pref): text label
-    verbatim &"<input name={pref} type=\"text\" placeholder=\"{p}\" value=\"{s}\"/>"
-
 macro renderPrefs*(): untyped =
   result = nnkCall.newTree(
     ident("buildHtml"), ident("tdiv"), nnkStmtList.newTree())

+ 33 - 3
src/views/renderutils.nim

@@ -1,4 +1,4 @@
-import strutils
+import strutils, strformat, xmltree
 import karax/[karaxdsl, vdom]
 
 import ../types, ../utils
@@ -39,9 +39,12 @@ proc linkText*(text: string; class=""): VNode =
   buildHtml():
     a(href=url, class=class): text text
 
-proc refererField*(path: string): VNode =
+proc hiddenField*(name, value: string): VNode =
   buildHtml():
-    verbatim "<input name=\"referer\" style=\"display: none\" value=\"$1\"/>" % path
+    verbatim "<input name=\"$1\" style=\"display: none\" value=\"$2\"/>" % [name, value]
+
+proc refererField*(path: string): VNode =
+  hiddenField("referer", path)
 
 proc iconReferer*(icon, action, path: string, title=""): VNode =
   buildHtml(form(`method`="get", action=action, class="icon-button")):
@@ -54,3 +57,30 @@ proc buttonReferer*(action, text, path: string; class=""; `method`="post"): VNod
     refererField path
     button(`type`="submit"):
       text text
+
+proc genCheckbox*(pref, label: string; state: bool): VNode =
+  buildHtml(tdiv(class="pref-group")):
+    label(class="checkbox-container"):
+      text label
+      if state: input(name=pref, `type`="checkbox", checked="")
+      else: input(name=pref, `type`="checkbox")
+      span(class="checkbox")
+
+proc genInput*(pref, label, state, placeholder: string; class=""): VNode =
+  let s = xmltree.escape(state)
+  let p = xmltree.escape(placeholder)
+  buildHtml(tdiv(class=("pref-group pref-input " & class))):
+    if label.len > 0:
+      label(`for`=pref): text label
+    verbatim &"<input name={pref} type=\"text\" placeholder=\"{p}\" value=\"{s}\"/>"
+
+proc genSelect*(pref, label, state: string; options: seq[string]): VNode =
+  buildHtml(tdiv(class="pref-group")):
+    label(`for`=pref): text label
+    select(name=pref):
+      for opt in options:
+        if opt == state:
+          option(value=opt, selected=""): text opt
+        else:
+          option(value=opt): text opt
+

+ 49 - 8
src/views/search.nim

@@ -1,4 +1,4 @@
-import strutils, strformat
+import strutils, strformat, unicode
 import karax/[karaxdsl, vdom, vstyles]
 
 import renderutils, timeline
@@ -6,13 +6,13 @@ import ".."/[types, formatters, query]
 
 proc renderSearch*(): VNode =
   buildHtml(tdiv(class="panel-container")):
-    tdiv(class="search-panel"):
+    tdiv(class="search-bar"):
       form(`method`="get", action="/search"):
-        verbatim "<input name=\"kind\" style=\"display: none\" value=\"users\"/>"
+        hiddenField("kind", "users")
         input(`type`="text", name="text", autofocus="", placeholder="Enter username...")
         button(`type`="submit"): icon "search"
 
-proc renderTweetSearch*(timeline: Timeline; prefs: Prefs; path: string): VNode =
+proc renderTimelineSearch*(timeline: Timeline; prefs: Prefs; path: string): VNode =
   let users =
     if timeline.query.isSome: get(timeline.query).fromUser
     else: @[]
@@ -24,6 +24,45 @@ proc renderTweetSearch*(timeline: Timeline; prefs: Prefs; path: string): VNode =
     renderProfileTabs(timeline.query, users.join(","))
     renderTimelineTweets(timeline, prefs, path)
 
+proc renderTweetSearch*(tweets: Result[Tweet]; prefs: Prefs; path: string): VNode =
+  let query = if tweets.query.isSome: get(tweets.query) else: Query(kind: custom)
+
+  buildHtml(tdiv(class="timeline-container")):
+    tdiv(class="timeline-header"):
+      form(`method`="get", action="/search", class="search-field"):
+        hiddenField("kind", "custom")
+        genInput("text", "", query.text, "Enter search...", class="pref-inline")
+        button(`type`="submit"): icon "search"
+        input(id="panel-toggle", `type`="checkbox")
+        label(`for`="panel-toggle", class="panel-label"):
+          icon "down"
+        tdiv(class="search-panel"):
+          tdiv:
+            span(class="search-title"): text "Include: "
+            genCheckbox("retweets", "Retweets", "nativeretweets" in query.includes)
+            genCheckbox("replies", "Replies", "replies" in query.includes)
+
+          for f in @["filter", "exclude"]:
+            tdiv:
+              span(class="search-title"): text capitalize(f) & ":"
+              for i in commonFilters:
+                let state =
+                  if f == "filter": i in query.filters
+                  else: i in query.excludes
+                genCheckbox(&"{f[0]}-{i}", capitalize(i), state)
+              input(id=(&"{f}-toggle"), `type`="checkbox")
+              label(`for`=(&"{f}-toggle"), class=(&"{f}-label")):
+                icon "down"
+              tdiv(class=(&"{f}-extras")):
+                for i in advancedFilters:
+                  let state =
+                    if f == "filter": i in query.filters
+                    else: i in query.excludes
+                  genCheckbox(&"{f[0]}-{i}", i, state)
+
+    renderSearchTabs(tweets.query)
+    renderTimelineTweets(tweets, prefs, path)
+
 proc renderUserSearch*(users: Result[Profile]; prefs: Prefs): VNode =
   let searchText =
     if users.query.isSome: get(users.query).text
@@ -31,11 +70,13 @@ proc renderUserSearch*(users: Result[Profile]; prefs: Prefs): VNode =
 
   buildHtml(tdiv(class="timeline-container")):
     tdiv(class="timeline-header"):
-      form(`method`="get", action="/search"):
-        verbatim "<input name=\"kind\" style=\"display: none\" value=\"users\"/>"
-        verbatim "<input type=\"text\" name=\"text\" value=\"$1\"/>" % searchText
+      form(`method`="get", action="/search", class="search-field"):
+        hiddenField("kind", "users")
+        genInput("text", "", searchText, "Enter username...", class="pref-inline")
         button(`type`="submit"): icon "search"
+        input(id="panel-toggle", `type`="checkbox")
+        label(`for`="panel-toggle", class="panel-label"):
+          icon "down"
 
     renderSearchTabs(users.query)
-
     renderTimelineUsers(users, prefs)

+ 3 - 0
src/views/timeline.nim

@@ -37,6 +37,9 @@ proc renderSearchTabs*(query: Option[Query]): VNode =
   var q = if query.isSome: get(query) else: Query()
 
   buildHtml(ul(class="tab")):
+    li(class=query.getTabClass("custom")):
+      q.kind = custom
+      a(href=genQueryUrl(q)): text "Tweets"
     li(class=query.getTabClass("users")):
       q.kind = users
       a(href=genQueryUrl(q)): text "Users"