From 286df9faf33edb9e58c82f9beca4e104938d2591 Mon Sep 17 00:00:00 2001
From: rr- <rr-@sakuya.pl>
Date: Thu, 14 Apr 2016 19:37:05 +0200
Subject: [PATCH] docs+client/users: document user filtering

---
 API.md                           | 398 +++++++++++++------------
 client/css/forms.styl            |   4 +
 client/css/help.styl             |  16 +-
 client/css/main.styl             |   3 +
 client/css/users.styl            |   7 +-
 client/html/help-search.hbs      | 482 ++++++++++++++++++-------------
 client/html/index.htm            |   2 +-
 client/html/user_list_header.hbs |   1 +
 client/js/util/views.js          |  14 +
 client/js/views/help_view.js     |   2 +
 10 files changed, 526 insertions(+), 403 deletions(-)

diff --git a/API.md b/API.md
index cacf301..ecc9d06 100644
--- a/API.md
+++ b/API.md
@@ -77,260 +77,268 @@ data.
 
 
 ## Listing users
-**Request**
+- **Request**
 
-`GET /users/?page=<page>&pageSize=<page-size>&query=<query>`
+    `GET /users/?page=<page>&pageSize=<page-size>&query=<query>`
 
-**Output**
+- **Output**
 
-```json5
-{
-    "query": "rr-",
-    "users": [
-        <user>,
-        <user>,
-        <user>,
-        <user>,
-        <user>
-    ],
-    "page": 1,
-    "pageSize": 5,
-    "total": 7
-}
-```
-...where `<user>` is an [user resource](#user) and `query` contains standard
-[search query](#search).
+    ```json5
+    {
+        "query": "rr-",
+        "users": [
+            <user>,
+            <user>,
+            <user>,
+            <user>,
+            <user>
+        ],
+        "page": 1,
+        "pageSize": 5,
+        "total": 7
+    }
+    ```
+    ...where `<user>` is an [user resource](#user) and `query` contains standard
+    [search query](#search).
 
-**Errors**
+- **Errors**
 
-- privileges are too low
+    - privileges are too low
 
-**Description**
+- **Description**
 
-Searches for users.
+    Searches for users.
 
-Available search named tokens:
+    **Anonymous tokens**
 
-| name              | ranged? | array? |
-| ----------------- | ------- | ------ |
-| (anonymous)       |         | ✓      |
-| `name`            |         | ✓      |
-| `creation-date`   | ✓       | ✓      |
-| `creation-time`   | ✓       | ✓      |
-| `last-login-date` | ✓       | ✓      |
-| `last-login-time` | ✓       | ✓      |
-| `login-date`      | ✓       | ✓      |
-| `login-time`      | ✓       | ✓      |
+    Same as `name` token.
 
-Anonymous search tokens are equivalent to `name` token.
+    **Named tokens**
 
-Available search orders:
+    | `<value>`         | Description                                      |
+    | ----------------- | ------------------------------------------------ |
+    | `name`            | having given name (doesn't accept wildcards yet) |
+    | `creation-date`   | registered at given date                         |
+    | `creation-time`   | alias of `creation-date`                         |
+    | `last-login-date` | whose most recent login date matches given date  |
+    | `last-login-time` | alias of `last-login-date`                       |
+    | `login-date`      | alias of `last-login-date`                       |
+    | `login-time`      | alias of `last-login-date`                       |
 
-- `random`
-- `name`
-- `creation-date`
-- `creation-time`
-- `last-login-date`
-- `last-login-time`
-- `login-date`
-- `login-time`
+    **Order tokens**
 
+    | `<value>`         | Description                |
+    | ----------------- | -------------------------- |
+    | `random`          | as random as it can get    |
+    | `name`            | A to Z                     |
+    | `creation-date`   | newest to oldest           |
+    | `creation-time`   | alias of `creation-date`   |
+    | `last-login-date` | recently active first      |
+    | `last-login-time` | alias of `last-login-date` |
+    | `login-date`      | alias of `last-login-date` |
+    | `login-time`      | alias of `last-login-date` |
+
+    **Special tokens**
+
+    None.
 
 ## Creating user
-**Request**
+- **Request**
 
-`POST /users`
+    `POST /users`
 
-**Input**
+- **Input**
 
-```json5
-{
-    "name": <user-name>,
-    "password": <user-password>,
-    "email": <email>
-}
-```
+    ```json5
+    {
+        "name": <user-name>,
+        "password": <user-password>,
+        "email": <email>
+    }
+    ```
 
-**Output**
+- **Output**
 
-```json5
-{
-    "user": <user>
-}
-```
-...where `<user>` is an [user resource](#user).
+    ```json5
+    {
+        "user": <user>
+    }
+    ```
+    ...where `<user>` is an [user resource](#user).
 
-**Errors**
+- **Errors**
 
-- such user already exists (names are case insensitive)
-- either user name, password or email are invalid
-- privileges are too low
+    - such user already exists (names are case insensitive)
+    - either user name, password or email are invalid
+    - privileges are too low
 
-**Description**
+- **Description**
 
-Creates a new user using specified parameters. Names and passwords must match
-`user_name_regex` and `password_regex` from server's configuration,
-respectively. Email address is optional. If the user happens to be the first
-user ever created, they're granted highest available rank, becoming an
-administrator. Subsequent users will be given the rank indicated by
-`default_rank` in the server's configuration.
+    Creates a new user using specified parameters. Names and passwords must
+    match `user_name_regex` and `password_regex` from server's configuration,
+    respectively. Email address is optional. If the user happens to be the
+    first user ever created, they're granted highest available rank, becoming
+    an administrator. Subsequent users will be given the rank indicated by
+    `default_rank` in the server's configuration.
 
 
 
 ## Updating user
-**Request**
+- **Request**
 
-`PUT /user/<name>`
+    `PUT /user/<name>`
 
-**Input**
+- **Input**
 
-```json5
-{
-    "name": <user-name>,
-    "password": <user-password>,
-    "email": <email>,
-    "rank": <rank>,
-    "avatarStyle": <avatar-style>
-}
-```
+    ```json5
+    {
+        "name": <user-name>,
+        "password": <user-password>,
+        "email": <email>,
+        "rank": <rank>,
+        "avatarStyle": <avatar-style>
+    }
+    ```
 
-**Files**
+- **Files**
 
-- `avatar` - the content of the new avatar.
+    - `avatar` - the content of the new avatar.
 
-**Output**
+- **Output**
 
-```json5
-{
-    "user": <user>
-}
-```
-...where `<user>` is an [user resource](#user).
+    ```json5
+    {
+        "user": <user>
+    }
+    ```
+    ...where `<user>` is an [user resource](#user).
 
-**Errors**
+- **Errors**
 
-- the user does not exist
-- the user with new name already exists (names are case insensitive)
-- either user name, password, email or rank are invalid
-- the user is trying to update their or someone else's rank to higher than
-  their own
-- privileges are too low
-- avatar is missing for manual avatar style
+    - the user does not exist
+    - the user with new name already exists (names are case insensitive)
+    - either user name, password, email or rank are invalid
+    - the user is trying to update their or someone else's rank to higher than
+      their own
+    - privileges are too low
+    - avatar is missing for manual avatar style
 
-**Description**
+- **Description**
 
-Updates an existing user using specified parameters. Names and passwords must
-match `user_name_regex` and `password_regex` from server's configuration,
-respectively. All fields are optional - update concerns only provided fields.
-To update last login time, see [authentication](#authentication). Avatar style
-can be either `gravatar` or `manual`. `manual` avatar style requires client to
-pass also `avatar` file - see [file uploads](#file-uploads) for details.
+    Updates an existing user using specified parameters. Names and passwords
+    must match `user_name_regex` and `password_regex` from server's
+    configuration, respectively. All fields are optional - update concerns only
+    provided fields. To update last login time, see
+    [authentication](#authentication). Avatar style can be either `gravatar` or
+    `manual`. `manual` avatar style requires client to pass also `avatar`
+    file - see [file uploads](#file-uploads) for details.
 
 
 
 ## Getting user
-**Request**
+- **Request**
 
-`GET /user/<name>`
+    `GET /user/<name>`
 
-**Output**
+- **Output**
 
-```json5
-{
-    "user": <user>
-}
-```
-...where `<user>` is an [user resource](#user).
+    ```json5
+    {
+        "user": <user>
+    }
+    ```
+    ...where `<user>` is an [user resource](#user).
 
-**Errors**
+- **Errors**
 
-- the user does not exist
-- privileges are too low
+    - the user does not exist
+    - privileges are too low
 
-**Description**
+- **Description**
 
-Retrieves information about an existing user.
+    Retrieves information about an existing user.
 
 
 
 ## Removing user
-**Request**
+- **Request**
 
-`DELETE /user/<name>`
+    `DELETE /user/<name>`
 
-**Output**
+- **Output**
 
-```json5
-{}
-```
+    ```json5
+    {}
+    ```
 
-**Errors**
+- **Errors**
 
-- the user does not exist
-- privileges are too low
+    - the user does not exist
+    - privileges are too low
 
-**Description**
+- **Description**
 
-Deletes existing user.
+    Deletes existing user.
 
 
 
 ## Password reset - step 1: mail request
-**Request**
+- **Request**
 
-`GET /password-reset/<email-or-name>`
+    `GET /password-reset/<email-or-name>`
 
-**Output**
+- **Output**
 
-```
-{}
-```
+    ```
+    {}
+    ```
 
-**Errors**
+- **Errors**
 
-- the user does not exist
-- the user hasn't provided an email address
+    - the user does not exist
+    - the user hasn't provided an email address
 
-**Description**
+- **Description**
 
-Sends a confirmation email to given user. The email contains link containing a
-token. The token cannot be guessed, thus using such link proves that the person
-who requested to reset the password also owns the mailbox, which is a strong
-indication they are the rightful owner of the account.
+    Sends a confirmation email to given user. The email contains link
+    containing a token. The token cannot be guessed, thus using such link
+    proves that the person who requested to reset the password also owns the
+    mailbox, which is a strong indication they are the rightful owner of the
+    account.
 
 
 
 ## Password reset - step 2: confirmation
-**Request**
+- **Request**
 
-`POST /password-reset/<email-or-name>`
+    `POST /password-reset/<email-or-name>`
 
-**Input**
+- **Input**
 
-```json5
-{
-    "token": <token-from-email>
-}
-```
+    ```json5
+    {
+        "token": <token-from-email>
+    }
+    ```
 
-**Output**
+- **Output**
 
-```json5
-{
-    "password": <new-password>
-}
-```
+    ```json5
+    {
+        "password": <new-password>
+    }
+    ```
 
-**Errors**
+- **Errors**
 
-- the token is missing
-- the token is invalid
-- the user does not exist
+    - the token is missing
+    - the token is invalid
+    - the user does not exist
 
-**Description**
+- **Description**
 
-Generates a new password for given user. Password is sent as plain-text, so it
-is recommended to connect through HTTPS.
+    Generates a new password for given user. Password is sent as plain-text, so
+    it is recommended to connect through HTTPS.
 
 
 
@@ -354,29 +362,39 @@ is recommended to connect through HTTPS.
 
 # Search
 
-Nomenclature:
+Search queries are built of tokens that are separated by spaces. Each token can
+be of following form:
 
-- Tokens - search terms inside a query, separated by white space.
-- Anonymous tokens - tokens of form `<value>`, used to filter the search
-  results.
-- Named tokens - tokens of form `<key>:<value>`, used to filter the search
-  results.
-- Special tokens - tokens of form `special:<value>`, used to filter the search
-  results.
-- Order tokens - tokens of form `order:<value>`, used to sort the search
-  results.
+| Syntax            | Token type        | Description                                |
+| ----------------- | ----------------- | ------------------------------------------ |
+| `<value>`         | anonymous tokens  | basic filters                              |
+| `<key>:<value>`   | named tokens      | advanced filters                           |
+| `order:<style>`   | order tokens      | sort results                               |
+| `special:<value>` | special tokens    | filters usually tied to the logged in user |
 
-Features:
+Most of anonymous and named tokens support ranged and composite values that
+take following form:
 
-- Most tokens can be negated like so: `-token`. Used with order tokens, it
-  flips the sort direction.
-- Some tokens support multiple values like so: `3,4,5`.
-- Some tokens support ranges like so: `100..`, `..200`, `100..200`.
-- Date token values can contain following values: `today`, `yesterday`,
-  `<year>`, `<year>-<month>`, `<year>-<month>-<day>`.
-- Order token values can be appended with `,asc` and `,desc` suffixes, which
-  control the sort direction.
+| `<value>` | Description                                           |
+| --------- | ----------------------------------------------------- |
+| `a,b,c`   | will show things that satisfy either `a`, `b` or `c`. |
+| `1..`     | will show things that are equal to or greater than 1. |
+| `..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.      |
 
-Example how it works:
+Date/time values can be of following form:
 
-    haruhi -kyon fav-count:3.. order:fav-count,desc -special:liked
+- `today`
+- `yesterday`
+- `<year>`
+- `<year>-<month>`
+- `<year>-<month>-<day>`
+
+**Example**
+
+Searching for posts with following query:
+
+    sea -fav-count:8.. type:swf uploader:Pirate
+
+will show flash files tagged as sea, that were liked by seven people at most,
+uploaded by user Pirate.
diff --git a/client/css/forms.styl b/client/css/forms.styl
index f6c684b..532b153 100644
--- a/client/css/forms.styl
+++ b/client/css/forms.styl
@@ -17,6 +17,10 @@ form
         margin-bottom: 1em
     .buttons
         margin-top: 1em
+        a
+            margin-left: 1em
+            .fa-question-circle-o
+                font-size: 120%
     .input li:first-child label:not(.radio):not(.checkbox):not(.file-dropper),
     .input li:first-child
         padding-top: 0
diff --git a/client/css/help.styl b/client/css/help.styl
index 76db6c1..a2532ac 100644
--- a/client/css/help.styl
+++ b/client/css/help.styl
@@ -1,11 +1,17 @@
+@import colors
+
 #help
     width: 40em
-
     nav
         margin-bottom: 1.5em
-
-    td
-        padding-right: 1em
-
+    td, th
+        padding: 0 0.5em
+        &:first-child
+            white-space: pre
     .section
         margin-top: 2em
+    h1
+        margin-top: 2.5em
+        font-size: 1.6em
+        &:first-child
+            margin-top: 0
diff --git a/client/css/main.styl b/client/css/main.styl
index e35ee62..9f8c478 100644
--- a/client/css/main.styl
+++ b/client/css/main.styl
@@ -12,6 +12,9 @@ h1, h2, h3
     font-weight: normal
     margin-bottom: 1em
 
+th
+    font-weight: normal
+
 a
     color: $main-color
     text-decoration: none
diff --git a/client/css/users.styl b/client/css/users.styl
index 4d783f5..629fcf6 100644
--- a/client/css/users.styl
+++ b/client/css/users.styl
@@ -24,11 +24,6 @@
             margin: 0 0 0.5em 0
 
 
-#login
-    .buttons a
-        margin-left: 1em
-
-
 #user
     width: 30em
     .text-nav
@@ -99,6 +94,6 @@
 .user-list-header
     text-align: left
     form
-        max-width: auto
+        width: auto
     input[name=search-text]
         max-width: 15em
diff --git a/client/html/help-search.hbs b/client/html/help-search.hbs
index e4e373e..7586326 100644
--- a/client/html/help-search.hbs
+++ b/client/html/help-search.hbs
@@ -1,265 +1,345 @@
+<h1>General search syntax</h1>
+
+<p>Search queries are built of tokens that are separated by spaces. Each token
+can be of following form:</p>
+
 <table>
     <thead>
         <tr>
-            <th>Command</th>
+            <th>Syntax</th>
+            <th>Token type</th>
             <th>Description</th>
         </tr>
     </thead>
-
     <tbody>
         <tr>
-            <td><a href='/posts/query=Haruhi'><code>Haruhi</code></a></td>
-            <td>containing tag “Haruhi”</td>
+            <td><code>&lt;value&gt;</code></td>
+            <td>anonymous tokens</td>
+            <td>basic filters</td>
         </tr>
         <tr>
-            <td><a href='/posts/query=-Kyon'><code>-Kyon</code></a></td>
-            <td>not containing tag “Kyon”</td>
+            <td><code>&lt;key&gt;:&lt;value&gt;</code></td>
+            <td>named tokens</td>
+            <td>advanced filters</td>
         </tr>
         <tr>
-            <td><a href='/posts/query=uploader:David'><code>uploader:David</code></a></td>
-            <td>uploaded by user David</td>
+            <td><code>order:&lt;style&gt;</code></td>
+            <td>order tokens</td>
+            <td>sort results</td>
         </tr>
         <tr>
-            <td><a href='/posts/query=comment:David'><code>comment:David</code></a></td>
-            <td>commented by David</td>
+            <td><code>special:&lt;value&gt;</code></td>
+            <td>special tokens</td>
+            <td>filters usually tied to the logged in user</td>
+        </tr>
+    </tbody>
+</table>
+
+<p>Most of anonymous and named tokens support ranged and composite values that
+take following form:</p>
+
+<table>
+    <tbody>
+        <tr>
+            <td><code>a,b,c</code></td>
+            <td>will show things that satisfy either <code>a</code>,
+            <code>b</code> or <code>c</code>.</td>
         </tr>
         <tr>
-            <td><a href='/posts/query=fav:David'><code>fav:David</code></a></td>
-            <td>favorited by David</td>
+            <td><code>1..</code></td>
+            <td>will show things that are equal to or greater than 1.</td>
         </tr>
         <tr>
-            <td><a href='/posts/query=fav-count:4'><code>fav-count:4</code></a></td>
-            <td>favorited by exactly four users</td>
+            <td><code>..4</code></td>
+            <td>will show things that are equal to at most 4.</td>
         </tr>
         <tr>
-            <td><a href='/posts/query=fav-count:4,5'><code>fav-count:4,5</code></a></td>
-            <td>favorited by four or five users</td>
+            <td><code>1..4</code></td>
+            <td>will show things that are equal to 1, 2, 3 or 4.</td>
         </tr>
+    </tbody>
+</table>
+
+<p>Date/time values can be of following form:</p>
+
+<ul>
+    <li><code>today</code></li>
+    <li><code>yesterday</code></li>
+    <li><code>&lt;year&gt;</code></li>
+    <li><code>&lt;year&gt;-&lt;month&gt;</code></li>
+    <li><code>&lt;year&gt;-&lt;month&gt;-&lt;day&gt;</code></li>
+</ul>
+
+<p>All tokens can be negated by prepending them with <code>-</code>.</p>
+
+<p>Order token values can be appended with <code>,asc</code> or
+<code>,desc</code> to control the sort direction, which can be also controlled
+by negating the whole token.</p>
+
+<h1>Example</h1>
+
+<p>Searching for posts with following query:</p>
+
+<pre><code>sea -fav-count:8.. type:swf uploader:Pirate</code></pre>
+
+<p>will show flash files tagged as sea, that were liked by seven people at
+most, uploaded by user Pirate.</p>
+
+<h1 id='post-search-help'>Post search tokens</h1>
+
+<p><strong>Anonymous tokens</strong></p>
+
+<p>Filter posts tagged with given <code>&lt;value&gt;</code>.</p>
+
+<p><strong>Named tokens</strong></p>
+
+<table>
+    <tbody>
         <tr>
-            <td><a href='/posts/query=fav-count:4..'><code>fav-count:4..</code></a></td>
-            <td>favorited by at least four users</td>
-        </tr>
-        <tr>
-            <td><a href='/posts/query=fav-count:..4'><code>fav-count:..4</code></a></td>
-            <td>favorited by at most four users</td>
-        </tr>
-        <tr>
-            <td><a href='/posts/query=fav-count:4..6'><code>fav-count:4..6</code></a></td>
-            <td>favorited by at least four, but no more than six users</td>
-        </tr>
-        <tr>
-            <td><a href='/posts/query=comment-count:3'><code>comment-count:3</code></a></td>
-            <td>having exactly three comments</td>
-        </tr>
-        <tr>
-            <td><a href='/posts/query=score:4'><code>score:4</code></a></td>
-            <td>having score of 4</td>
-        </tr>
-        <tr>
-            <td><a href='/posts/query=tag-count:7'><code>tag-count:7</code></a></td>
-            <td>tagged with exactly seven tags</td>
-        </tr>
-        <tr>
-            <td><a href='/posts/query=note-count:1..'><code>note-count:1..</code></a></td>
-            <td>having at least one post note</td>
-        </tr>
-        <tr>
-            <td><a href='/posts/query=feature-count:1..'><code>feature-count:1..</code></a></td>
-            <td>having been featured at least once</td>
-        </tr>
-        <tr>
-            <td><a href='/posts/query=date:today'><code>date:today</code></a></td>
-            <td>posted today</td>
-        </tr>
-        <tr>
-            <td><a href='/posts/query=date:yesterday'><code>date:yesterday</code></a></td>
-            <td>posted yesterday</td>
-        </tr>
-        <tr>
-            <td><a href='/posts/query=date:2000'><code>date:2000</code></a></td>
-            <td>posted in year 2000</td>
-        </tr>
-        <tr>
-            <td><a href='/posts/query=date:2000-01'><code>date:2000-01</code></a></td>
-            <td>posted in January, 2000</td>
-        </tr>
-        <tr>
-            <td><a href='/posts/query=date:2000-01-01'><code>date:2000-01-01</code></a></td>
-            <td>posted on January 1st, 2000</td>
-        </tr>
-        <tr>
-            <td><a href='/posts/query=id:1'><code>id:1</code></a></td>
+            <td><code>id</code></td>
             <td>having specific post ID</td>
         </tr>
         <tr>
-            <td><a href='/posts/query=name:hash'><code>name:<em>hash</em></code></a></td>
-            <td>having specific post name (hash in full URLs)</td>
+            <td><code>score</code></td>
+            <td>having given score</td>
         </tr>
         <tr>
-            <td><a href='/posts/query=file-size:100..'><code>file-size:100..</code></a></td>
-            <td>having at least 100 bytes</td>
+            <td><code>uploader</code></td>
+            <td>uploaded by given user</td>
         </tr>
         <tr>
-            <td><a href='/posts/query=image-width:100..'><code>image-width:100..</code></a></td>
-            <td>being at least 100 pixels wide</td>
+            <td><code>comment</code></td>
+            <td>commented by given user</td>
         </tr>
         <tr>
-            <td><a href='/posts/query=image-height:100..'><code>image-height:100..</code></a></td>
-            <td>being at least 100 pixels tall</td>
+            <td><code>fav</code></td>
+            <td>favorited by given user</td>
         </tr>
         <tr>
-            <td><a href='/posts/query=image-area:10000..'><code>image-area:10000..</code></a></td>
-            <td>having at least 10000 pixels</td>
+            <td><code>fav-count</code></td>
+            <td>favorited by given number of users</td>
         </tr>
         <tr>
-            <td><a href='/posts/query=type:image'><code>type:image</code></a></td>
-            <td>only image posts</td>
+            <td><code>comment-count</code></td>
+            <td>having given number of comments</td>
         </tr>
         <tr>
-            <td><a href='/posts/query=type:flash'><code>type:flash</code></a></td>
-            <td>only Flash posts</td>
+            <td><code>tag-count</code></td>
+            <td>having given number of tags</td>
         </tr>
         <tr>
-            <td><a href='/posts/query=type:youtube'><code>type:youtube</code></a></td>
-            <td>only Youtube posts</td>
+            <td><code>note-count</code></td>
+            <td>having given number of annotations</td>
         </tr>
         <tr>
-            <td><a href='/posts/query=type:video'><code>type:video</code></a></td>
-            <td>only video posts</td>
+            <td><code>feature-count</code></td>
+            <td>having been featured given number of times</td>
         </tr>
         <tr>
-            <td><a href='/posts/query=special:liked'><code>special:liked</code></a></td>
+            <td><code>date</code></td>
+            <td>posted at given date</td>
+        </tr>
+        <tr>
+            <td><code>file-size</code></td>
+            <td>having given file size (in bytes)</td>
+        </tr>
+        <tr>
+            <td><code>image-width</code></td>
+            <td>having given image width (where applicable)</td>
+        </tr>
+        <tr>
+            <td><code>image-height</code></td>
+            <td>having given image height (where applicable)</td>
+        </tr>
+        <tr>
+            <td><code>image-area</code></td>
+            <td>having given number of pixels (image width * image height)</td>
+        </tr>
+        <tr>
+            <td><code>type</code></td>
+            <td>given type of posts (<code>&lt;value&gt;</code> can be either <code>image</code>, <code>flash</code>/<code>swf</code>, <code>youtube</code>/<code>yt</code>, <code>video</code> or <code>animation</code>)</td>
+        </tr>
+    </tbody>
+</table>
+
+<p><strong>Order tokens</strong></p>
+
+<table>
+    <tbody>
+        <tr>
+            <td><code>random</code></td>
+            <td>as random as it can get</td>
+        </tr>
+        <tr>
+            <td><code>id</code></td>
+            <td>highest to lowest post ID</td>
+        </tr>
+        <tr>
+            <td><code>score</code></td>
+            <td>highest scored</td>
+        </tr>
+        <tr>
+            <td><code>fav-count</code></td>
+            <td>loved by most</td>
+        </tr>
+        <tr>
+            <td><code>comment-count</code></td>
+            <td>most commented first</td>
+        </tr>
+        <tr>
+            <td><code>tag-count</code></td>
+            <td>with most tags</td>
+        </tr>
+        <tr>
+            <td><code>note-count</code></td>
+            <td>with most annotations</td>
+        </tr>
+        <tr>
+            <td><code>file-size</code></td>
+            <td>largest files first</td>
+        </tr>
+        <tr>
+            <td><code>image-width</code></td>
+            <td>widest images first</td>
+        </tr>
+        <tr>
+            <td><code>image-height</code></td>
+            <td>tallest images first</td>
+        </tr>
+        <tr>
+            <td><code>image-area</code></td>
+            <td>largest images first</td>
+        </tr>
+        <tr>
+            <td><code>creation-date</code></td>
+            <td>newest to oldest (pretty much same as <code>id</code>)</td>
+        </tr>
+        <tr>
+            <td><code>edit-date</code></td>
+            <td>like <code>creation-date</code>, only looks at last edit time</td>
+        </tr>
+        <tr>
+            <td><code>fav-date</code></td>
+            <td>recently added to favorites by anyone</td>
+        </tr>
+        <tr>
+            <td><code>comment-date</code></td>
+            <td>recently commented by anyone</td>
+        </tr>
+        <tr>
+            <td><code>feature-date</code></td>
+            <td>recently featured</td>
+        </tr>
+        <tr>
+            <td><code>feature-count</code></td>
+            <td>most often featured</td>
+        </tr>
+    </tbody>
+</table>
+
+<p><strong>Special tokens</strong></p>
+
+<table>
+    <tbody>
+        <tr>
+            <td><code>liked</code></td>
             <td>posts liked by currently logged in user</td>
         </tr>
         <tr>
-            <td><a href='/posts/query=special:disliked'><code>special:disliked</code></a></td>
+            <td><code>disliked</code></td>
             <td>posts disliked by currently logged in user</td>
         </tr>
         <tr>
-            <td><a href='/posts/query=special:fav'><code>special:fav</code></a></td>
+            <td><code>fav</code></td>
             <td>posts added to favorites by currently logged in user</td>
         </tr>
         <tr>
-            <td><a href='/posts/query=special:tumbleweed'><code>special:tumbleweed</code></a></td>
+            <td><code>tumbleweed</code></td>
             <td>posts with score of 0, without comments and without favorites</td>
         </tr>
     </tbody>
 </table>
 
-<p>Most of the commands support ranged and composites values, e.g.
-<code>id:<em>number</em></code> operator supports respectively
-<a href='/posts/query=id:5..7'><code>id:5..7</code></a> and
-<a href='/posts/query=id:5,10,15'><code>id:5,10,15</code></a>.
-You can combine tags and negate any of them for interesting results.
-<a href='/posts/query=sea -fav-count:..8 type:flash uploader:Pirate'><code>sea -fav-count:8.. type:swf uploader:Pirate</code></a>
-will show you flash files tagged as sea, that were liked by seven people at
-most, uploaded by user Pirate.</p>
+<h1 id='user-search-help'>User search tokens</h1>
 
-<p>All of the above can be sorted using additional tag in form of
-<code>order:<em>keyword</em></code>:</p>
+<p><strong>Anonymous tokens</strong></p>
+
+<p>Same as <code>name</code> token.</p>
+
+<p><strong>Named tokens</strong></p>
 
 <table>
-    <thead>
-        <tr>
-            <th>Command</th>
-            <th>Description</th>
-        </tr>
-    </thead>
-
     <tbody>
-    <tr>
-        <td><a href='/posts/query=order:random'><code>order:random</code></a></td>
-        <td>as random as it can get</td>
-    </tr>
-
-    <tr>
-        <td><a href='/posts/query=order:id'><code>order:id</code></a></td>
-        <td>highest to lowest post ID (default browse view)</td>
-    </tr>
-
-    <tr>
-        <td><a href='/posts/query=order:creation-date'><code>order:creation-date</code></a></td>
-        <td>newest to oldest (pretty much same as above)</td>
-    </tr>
-
-    <tr>
-        <td><a href='/posts/query=-order:creation-date'><code>-order:creation-date</code></a></td>
-        <td>oldest to newest</td>
-    </tr>
-
-    <tr>
-        <td><a href='/posts/query=order:creation-date,asc'><code>order:creation-date,asc</code></a></td>
-        <td>oldest to newest (ascending order, default = descending)</td>
-    </tr>
-
-    <tr>
-        <td><a href='/posts/query=order:edit-date'><code>order:edit-date</code></a></td>
-        <td>like <code>creation-date</code>, only looks at last edit time</td>
-    </tr>
-
-    <tr>
-        <td><a href='/posts/query=order:score'><code>order:score</code></a></td>
-        <td>highest scored</td>
-    </tr>
-
-    <tr>
-        <td><a href='/posts/query=order:file-size'><code>order:file-size</code></a></td>
-        <td>largest files first</td>
-    </tr>
-
-    <tr>
-        <td><a href='/posts/query=order:image-width'><code>order:image-width</code></a></td>
-        <td>widest images first</td>
-    </tr>
-
-    <tr>
-        <td><a href='/posts/query=order:image-height'><code>order:image-height</code></a></td>
-        <td>tallest images first</td>
-    </tr>
-
-    <tr>
-        <td><a href='/posts/query=order:image-area'><code>order:image-area</code></a></td>
-        <td>largest images first</td>
-    </tr>
-
-    <tr>
-        <td><a href='/posts/query=order:tag-count'><code>order:tag-count</code></a></td>
-        <td>with most tags</td>
-    </tr>
-
-    <tr>
-        <td><a href='/posts/query=order:fav-count'><code>order:fav-count</code></a></td>
-        <td>loved by most</td>
-    </tr>
-
-    <tr>
-        <td><a href='/posts/query=order:comment-count'><code>order:comment-count</code></a></td>
-        <td>most commented first</td>
-    </tr>
-
-    <tr>
-        <td><a href='/posts/query=order:fav-date'><code>order:fav-date</code></a></td>
-        <td>recently added to favorites</td>
-    </tr>
-
-    <tr>
-        <td><a href='/posts/query=order:comment-date'><code>order:comment-date</code></a></td>
-        <td>recently commented</td>
-    </tr>
-
-    <tr>
-        <td><a href='/posts/query=order:feature-date'><code>order:feature-date</code></a></td>
-        <td>recently featured</td>
-    </tr>
-
-    <tr>
-        <td><a href='/posts/query=order:feature-count'><code>order:feature-count</code></a></td>
-        <td>most often featured</td>
-    </tr>
+        <tr>
+            <td><code>name</code></td>
+            <td>having given name (doesn't accept wildcards yet)</td>
+        </tr>
+        <tr>
+            <td><code>creation-date</code></td>
+            <td>registered at given date</td>
+        </tr>
+        <tr>
+            <td><code>creation-time</code></td>
+            <td>alias of <code>creation-date</code></td>
+        </tr>
+        <tr>
+            <td><code>last-login-date</code>
+            <td>whose most recent login date matches given date</td>
+        </tr>
+        <tr>
+            <td><code>last-login-time</code>
+            <td>alias of <code>last-login-date</code>
+        </tr>
+        <tr>
+            <td><code>login-date</code>
+            <td>alias of <code>last-login-date</code>
+        </tr>
+        <tr>
+            <td><code>login-time</code></td>
+            <td>alias of <code>last-login-date</code>
+        </tr>
     </tbody>
 </table>
 
-<p>As shown with <a
-href='/posts/query=-order:creation-date'><code>-order:creation-date</code></a>,
-any of them can be reversed in the same way as negating other tags: by placing
-a dash before the tag.</p>
+<p><strong>Order tokens</strong></p>
+
+<table>
+    <tbody>
+        <tr>
+            <td><code>random</code></td>
+            <td>as random as it can get</td>
+        </tr>
+        <tr>
+            <td><code>name</code></td>
+            <td>A to Z</td>
+        </tr>
+        <tr>
+            <td><code>creation-date</code></td>
+            <td>newest to oldest</td>
+        </tr>
+        <tr>
+            <td><code>creation-time</code></td>
+            <td>alias of <code>creation-date</code></td>
+        </tr>
+        <tr>
+            <td><code>last-login-date</code></td>
+            <td>recently active first</td>
+        </tr>
+        <tr>
+            <td><code>last-login-time</code></td>
+            <td>alias of <code>last-login-date</code></td>
+        </tr>
+        <tr>
+            <td><code>login-date</code></td>
+            <td>alias of <code>last-login-date</code></td>
+        </tr>
+        <tr>
+            <td><code>login-time</code></td>
+            <td>alias of <code>last-login-date</code></td>
+        </tr>
+    </tbody>
+</table>
+
+<p><strong>Special tokens</strong></p>
+
+<p>None.</p>
diff --git a/client/html/index.htm b/client/html/index.htm
index 1509bb2..dbbd34a 100644
--- a/client/html/index.htm
+++ b/client/html/index.htm
@@ -5,7 +5,7 @@
     <meta name='viewport' content='width=device-width, initial-scale=1.0'>
     <title><!-- confiured in config file --></title>
     <link href='/bundle.min.css' rel='stylesheet' type='text/css'/>
-    <link href='//maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css' rel='stylesheet' type='text/css'/>
+    <link href='//maxcdn.bootstrapcdn.com/font-awesome/4.6.0/css/font-awesome.min.css' rel='stylesheet' type='text/css'/>
     <link href='//fonts.googleapis.com/css?family=Droid+Sans' rel='stylesheet' type='text/css'/>
     <link rel='shortcut icon' type='image/png' href='/favicon.png'/>
 </head>
diff --git a/client/html/user_list_header.hbs b/client/html/user_list_header.hbs
index b340f02..27c6ed2 100644
--- a/client/html/user_list_header.hbs
+++ b/client/html/user_list_header.hbs
@@ -9,6 +9,7 @@
         </div>
         <div class='buttons'>
             <input type='submit' value='Search'/>
+            <a href='/help/search#user-search-help'><i class='fa fa-question-circle-o'></i></a>
         </div>
     </form>
 </div>
diff --git a/client/js/util/views.js b/client/js/util/views.js
index a7d9888..c7c297f 100644
--- a/client/js/util/views.js
+++ b/client/js/util/views.js
@@ -126,6 +126,19 @@ function showView(target, source) {
     }
 }
 
+function scrollToHash() {
+    window.setTimeout(() => {
+        if (!window.location.hash) {
+            return;
+        }
+        const el = document.getElementById(
+            window.location.hash.replace(/#/, ''));
+        if (el) {
+            el.scrollIntoView();
+        }
+    }, 10);
+}
+
 module.exports = {
     htmlToDom: htmlToDom,
     getTemplate: getTemplate,
@@ -137,4 +150,5 @@ module.exports = {
     decorateValidator: decorateValidator,
     makeVoidElement: makeVoidElement,
     makeNonVoidElement: makeNonVoidElement,
+    scrollToHash: scrollToHash,
 };
diff --git a/client/js/views/help_view.js b/client/js/views/help_view.js
index 2a5c4ec..12439d2 100644
--- a/client/js/views/help_view.js
+++ b/client/js/views/help_view.js
@@ -38,6 +38,8 @@ class HelpView {
 
         views.listenToMessages(target);
         views.showView(target, source);
+
+        views.scrollToHash();
     }
 }