From 0bc45e9c45ec92192f23079a3a1a3cb6e8d8c9e8 Mon Sep 17 00:00:00 2001
From: rr- <rr-@sakuya.pl>
Date: Sun, 17 Apr 2016 08:31:46 +0200
Subject: [PATCH] server/search: support -min and -max suffixes

---
 API.md                                             |  3 +++
 client/html/help-search-general.hbs                |  4 ++++
 client/js/views/endless_page_view.js               |  2 +-
 server/szurubooru/search/search_executor.py        | 14 ++++++++++----
 .../tests/search/test_user_search_config.py        |  8 ++++++++
 5 files changed, 26 insertions(+), 5 deletions(-)

diff --git a/API.md b/API.md
index 5dc0381..6407ea1 100644
--- a/API.md
+++ b/API.md
@@ -622,6 +622,9 @@ take following form:
 | `..4`     | will show things that are equal to at most 4.         |
 | `1..4`    | will show things that are equal to 1, 2, 3 or 4.      |
 
+Ranged values can be also supplied by appending `-min` or `-max` to the key,
+for example like this: `score-min:1`.
+
 Date/time values can be of following form:
 
 - `today`
diff --git a/client/html/help-search-general.hbs b/client/html/help-search-general.hbs
index 27ee963..ab21582 100644
--- a/client/html/help-search-general.hbs
+++ b/client/html/help-search-general.hbs
@@ -58,6 +58,10 @@ take following form:</p>
     </tbody>
 </table>
 
+<p>Ranged values can be also supplied by appending <code>-min</code> or
+<code>-max</code> to the key, for example like this:
+<code>score-min:1</code>.</p>
+
 <p>Date/time values can be of following form:</p>
 
 <ul>
diff --git a/client/js/views/endless_page_view.js b/client/js/views/endless_page_view.js
index e0248f0..84cc283 100644
--- a/client/js/views/endless_page_view.js
+++ b/client/js/views/endless_page_view.js
@@ -25,7 +25,6 @@ class EndlessPageView {
         const threshold = window.innerHeight / 3;
 
         if (ctx.state && ctx.state.html) {
-            console.log('Loading from state');
             this.minPageShown = ctx.state.minPageShown;
             this.maxPageShown = ctx.state.maxPageShown;
             this.totalPages = ctx.state.totalPages;
@@ -83,6 +82,7 @@ class EndlessPageView {
         if (ctx.state && ctx.state.html) {
             pagesHolder.innerHTML = ctx.state.html;
             window.scroll(ctx.state.scrollX, ctx.state.scrollY);
+            this.updater();
         } else {
             this.loadPage(pagesHolder, ctx, ctx.searchQuery.page, true);
         }
diff --git a/server/szurubooru/search/search_executor.py b/server/szurubooru/search/search_executor.py
index 203752d..59ccaf2 100644
--- a/server/szurubooru/search/search_executor.py
+++ b/server/szurubooru/search/search_executor.py
@@ -52,8 +52,7 @@ class SearchExecutor(object):
         elif key == 'special':
             return self._handle_special(query, value, negated)
         else:
-            return self._handle_named(
-                query, key, self._create_criterion(value, negated))
+            return self._handle_named(query, key, value, negated)
 
     def _handle_anonymous(self, query, criterion):
         if not self._search_config.anonymous_filter:
@@ -61,7 +60,14 @@ class SearchExecutor(object):
                 'Anonymous tokens are not valid in this context.')
         return self._search_config.anonymous_filter(query, criterion)
 
-    def _handle_named(self, query, key, criterion):
+    def _handle_named(self, query, key, value, negated):
+        if key.endswith('-min'):
+            key = key[:-4]
+            value += '..'
+        elif key.endswith('-max'):
+            key = key[:-4]
+            value = '..' + value
+        criterion = self._create_criterion(value, negated)
         if key in self._search_config.named_filters:
             return self._search_config.named_filters[key](query, criterion)
         raise errors.SearchError(
@@ -113,7 +119,7 @@ class SearchExecutor(object):
 
     def _create_criterion(self, value, negated):
         if '..' in value:
-            low, high = value.split('..')
+            low, high = value.split('..', 1)
             if not low and not high:
                 raise errors.SearchError('Empty ranged value')
             return criteria.RangedSearchCriterion(value, negated, low, high)
diff --git a/server/szurubooru/tests/search/test_user_search_config.py b/server/szurubooru/tests/search/test_user_search_config.py
index 645b9d9..8c4d7c8 100644
--- a/server/szurubooru/tests/search/test_user_search_config.py
+++ b/server/szurubooru/tests/search/test_user_search_config.py
@@ -26,6 +26,8 @@ def verify_unpaged(session, executor):
     ('creation-time:2014-06..2015-01-01', ['u2', 'u3']),
     ('creation-time:2014-06..', ['u2', 'u3']),
     ('creation-time:..2014-06', ['u1', 'u2']),
+    ('creation-time-min:2014-06', ['u2', 'u3']),
+    ('creation-time-max:2014-06', ['u1', 'u2']),
     ('-creation-time:2014..2014-06', ['u3']),
     ('-creation-time:2014-06..2015-01-01', ['u1']),
     ('creation-date:2014..2014-06', ['u1', 'u2']),
@@ -192,6 +194,12 @@ def test_random_order(session, executor, user_factory):
 
 @pytest.mark.parametrize('input,expected_error', [
     ('creation-date:..', errors.SearchError),
+    ('creation-date-min:..', errors.ValidationError),
+    ('creation-date-min:..2014-01-01', errors.ValidationError),
+    ('creation-date-min:2014-01-01..', errors.ValidationError),
+    ('creation-date-max:..2014-01-01', errors.ValidationError),
+    ('creation-date-max:2014-01-01..', errors.ValidationError),
+    ('creation-date-max:yesterday,today', errors.ValidationError),
     ('creation-date:bad..', errors.ValidationError),
     ('creation-date:..bad', errors.ValidationError),
     ('creation-date:bad..bad', errors.ValidationError),