diff --git a/catalog/search/views.py b/catalog/search/views.py
index f9ee2b7c..af50aa7c 100644
--- a/catalog/search/views.py
+++ b/catalog/search/views.py
@@ -141,7 +141,7 @@ def search(request):
         {
             "items": items,
             "dup_items": dup_items,
-            "pagination": PageLinksGenerator(PAGE_LINK_NUMBER, p, num_pages),
+            "pagination": PageLinksGenerator(p, num_pages, request.GET),
             "sites": SiteName.labels,
             "hide_category": hide_category,
         },
diff --git a/catalog/views.py b/catalog/views.py
index 11ed0da2..164bdc9e 100644
--- a/catalog/views.py
+++ b/catalog/views.py
@@ -157,7 +157,7 @@ def mark_list(request, item_path, item_uuid, following_only=False):
     paginator = Paginator(queryset, NUM_REVIEWS_ON_LIST_PAGE)
     page_number = request.GET.get("page", default=1)
     marks = paginator.get_page(page_number)
-    pagination = PageLinksGenerator(PAGE_LINK_NUMBER, page_number, paginator.num_pages)
+    pagination = PageLinksGenerator(page_number, paginator.num_pages, request.GET)
     return render(
         request,
         "item_mark_list.html",
@@ -179,7 +179,7 @@ def review_list(request, item_path, item_uuid):
     paginator = Paginator(queryset, NUM_REVIEWS_ON_LIST_PAGE)
     page_number = request.GET.get("page", default=1)
     reviews = paginator.get_page(page_number)
-    pagination = PageLinksGenerator(PAGE_LINK_NUMBER, page_number, paginator.num_pages)
+    pagination = PageLinksGenerator(page_number, paginator.num_pages, request.GET)
     return render(
         request,
         "item_review_list.html",
diff --git a/common/static/scss/_common.scss b/common/static/scss/_common.scss
index 39790539..c8e41878 100644
--- a/common/static/scss/_common.scss
+++ b/common/static/scss/_common.scss
@@ -184,3 +184,26 @@ form img {
     margin: 0 0.5em;
   }
 }
+
+.pagination {
+  text-align: center;
+  width: 100%;
+  margin-top: 1em;
+  a {
+    margin: 0 0.3em;
+  }
+  .s, .prev, .next {
+    font-size: 1.4em;
+  }
+  .prev {
+    margin-right: 0.5em;
+  }
+  .next {
+    margin-left: 0.5em;
+  }
+  .current {
+    font-weight: bold;
+    font-size: 1.1em;
+    color: var(--pico-secondary);
+  }
+}
diff --git a/common/static/scss/_legacy.sass b/common/static/scss/_legacy.sass
index 45093622..ed1e6771 100644
--- a/common/static/scss/_legacy.sass
+++ b/common/static/scss/_legacy.sass
@@ -954,57 +954,10 @@ $panel-padding : 0
 	padding: 0
 	list-style: none
 
-.pagination
-    // position: absolute
-    // bottom: 30px
-    // left: 50%
-    // transform: translateX(-50%)
-    text-align: center
-    width: 100%
-
-    & &__page-link
-        font-weight: normal
-        margin: 0 5px
-
-        &--current
-            font-weight: bold
-            font-size: 1.2em
-            // text-decoration: underline
-            color: $color-secondary
-
-    & &__nav-link
-        font-size: 1.4em
-        margin: 0 2px
-
-        $nav-link-edge-margin-width: 18px
-
-        &--right-margin
-            margin-right: $nav-link-edge-margin-width
-
-        &--left-margin
-            margin-left: $nav-link-edge-margin-width
-
-        &--hidden
-            display: none
-
 
 // Small devices (landscape phones, 576px and up)
 @media (max-width: $small-devices)
-    .pagination
-        & &__page-link
-            margin: 0 3px
-
-        & &__nav-link
-            font-size: 1.4em
-            margin: 0 2px
-
-            $nav-link-edge-margin-width: 10px
-
-            &--right-margin
-                margin-right: $nav-link-edge-margin-width
-
-            &--left-margin
-                margin-left: $nav-link-edge-margin-width
+	pass
 // Medium devices (tablets, 768px and up)
 @media (max-width: $medium-devices)
 	pass
diff --git a/common/templates/_pagination.html b/common/templates/_pagination.html
index 892dcb18..729d61f3 100644
--- a/common/templates/_pagination.html
+++ b/common/templates/_pagination.html
@@ -1,23 +1,20 @@
 <div class="pagination">
   {% if pagination.has_prev %}
-    <a href="?{% if request.GET.t %}t={{ request.GET.t }}&{% endif %}page=1"
-       class="pagination__nav-link pagination__nav-link">&laquo;</a>
-    <a href="?{% if request.GET.t %}t={{ request.GET.t }}&{% endif %}page={{ pagination.previous_page }}"
-       class="pagination__nav-link pagination__nav-link--right-margin pagination__nav-link">&lsaquo;</a>
+    <a href="?{{ pagination.query_string }}page=1" class="s">&laquo;</a>
+    <a href="?{{ pagination.query_string }}page={{ pagination.previous_page }}"
+       class="prev">&lsaquo;</a>
   {% endif %}
   {% for page in pagination.page_range %}
     {% if page == pagination.current_page %}
-      <a href="?{% if request.GET.t %}t={{ request.GET.t }}&{% endif %}page={{ page }}"
-         class="pagination__page-link pagination__page-link--current">{{ page }}</a>
+      <a href="?{{ pagination.query_string }}page={{ page }}" class="current">{{ page }}</a>
     {% else %}
-      <a href="?{% if request.GET.t %}t={{ request.GET.t }}&{% endif %}page={{ page }}"
-         class="pagination__page-link">{{ page }}</a>
+      <a href="?{{ pagination.query_string }}page={{ page }}">{{ page }}</a>
     {% endif %}
   {% endfor %}
   {% if pagination.has_next %}
-    <a href="?{% if request.GET.t %}t={{ request.GET.t }}&{% endif %}page={{ pagination.next_page }}"
-       class="pagination__nav-link pagination__nav-link--left-margin">&rsaquo;</a>
-    <a href="?{% if request.GET.t %}t={{ request.GET.t }}&{% endif %}page={{ pagination.last_page }}"
-       class="pagination__nav-link">&raquo;</a>
+    <a href="?{{ pagination.query_string }}page={{ pagination.next_page }}"
+       class="next">&rsaquo;</a>
+    <a href="?{{ pagination.query_string }}page={{ pagination.last_page }}"
+       class="s">&raquo;</a>
   {% endif %}
 </div>
diff --git a/common/utils.py b/common/utils.py
index b42de4fb..8c6d6191 100644
--- a/common/utils.py
+++ b/common/utils.py
@@ -1,11 +1,15 @@
 import functools
+import re
 import uuid
 from typing import TYPE_CHECKING
 
-from django.http import Http404, HttpRequest, HttpResponseRedirect
+from django.db.models import query
+from django.http import Http404, HttpRequest, HttpResponseRedirect, QueryDict
 from django.utils import timezone
 from django.utils.baseconv import base62
 
+from .config import PAGE_LINK_NUMBER
+
 if TYPE_CHECKING:
     from users.models import APIdentity, User
 
@@ -82,8 +86,19 @@ class PageLinksGenerator:
     length -- the number of page links in pagination
     """
 
-    def __init__(self, length: int, current_page: int, total_pages: int):
+    def __init__(
+        self, current_page: int, total_pages: int, query: QueryDict | None = None
+    ):
+        length = PAGE_LINK_NUMBER
         current_page = int(current_page)
+        self.query_string = ""
+        if query:
+            q = query.copy()
+            if q.get("page"):
+                q.pop("page")
+            self.query_string = q.urlencode()
+        if self.query_string:
+            self.query_string += "&"
         self.current_page = current_page
         self.previous_page = current_page - 1 if current_page > 1 else None
         self.next_page = current_page + 1 if current_page < total_pages else None
diff --git a/journal/models/rating.py b/journal/models/rating.py
index d5813d6d..5bbc94fe 100644
--- a/journal/models/rating.py
+++ b/journal/models/rating.py
@@ -1,11 +1,12 @@
 from datetime import datetime
+from typing import Any
 
-from django.core.validators import MaxValueValidator, MinValueValidator, RegexValidator
-from django.db import connection, models
+from django.core.validators import MaxValueValidator, MinValueValidator
+from django.db import models
 from django.db.models import Avg, Count, Q
 from django.utils.translation import gettext_lazy as _
 
-from catalog.models import Item, ItemCategory
+from catalog.models import Item
 from users.models import APIdentity
 
 from .common import Content
@@ -116,24 +117,22 @@ class Rating(Content):
 
     @staticmethod
     def update_item_rating(
-        item: Item, owner: APIdentity, rating_grade: int | None, visibility: int = 0
+        item: Item,
+        owner: APIdentity,
+        rating_grade: int | None,
+        visibility: int = 0,
+        created_time: datetime | None = None,
     ):
         if rating_grade and (rating_grade < 1 or rating_grade > 10):
             raise ValueError(f"Invalid rating grade: {rating_grade}")
-        rating = Rating.objects.filter(owner=owner, item=item).first()
         if not rating_grade:
-            if rating:
-                rating.delete()
-                rating = None
-        elif rating is None:
-            rating = Rating.objects.create(
-                owner=owner, item=item, grade=rating_grade, visibility=visibility
-            )
-        elif rating.grade != rating_grade or rating.visibility != visibility:
-            rating.visibility = visibility
-            rating.grade = rating_grade
-            rating.save()
-        return rating
+            Rating.objects.filter(owner=owner, item=item).delete()
+        else:
+            d: dict[str, Any] = {"grade": rating_grade, "visibility": visibility}
+            if created_time:
+                d["created_time"] = created_time
+            r, _ = Rating.objects.update_or_create(owner=owner, item=item, defaults=d)
+            return r
 
     @staticmethod
     def get_item_rating(item: Item, owner: APIdentity) -> int | None:
diff --git a/journal/models/shelf.py b/journal/models/shelf.py
index a01db0fe..8d9ced04 100644
--- a/journal/models/shelf.py
+++ b/journal/models/shelf.py
@@ -259,6 +259,15 @@ class ShelfManager:
         else:
             return qs
 
+    def get_members(
+        self, shelf_type: ShelfType, item_category: ItemCategory | None = None
+    ):
+        qs = self.shelf_list[shelf_type].members.all()
+        if item_category:
+            return qs.filter(q_item_in_category(item_category))
+        else:
+            return qs
+
     # def get_items_on_shelf(self, item_category, shelf_type):
     #     shelf = (
     #         self.owner.shelf_set.all()
diff --git a/journal/templates/_sidebar_user_mark_list.html b/journal/templates/_sidebar_user_mark_list.html
index 88339cb6..6cacad7b 100644
--- a/journal/templates/_sidebar_user_mark_list.html
+++ b/journal/templates/_sidebar_user_mark_list.html
@@ -5,52 +5,65 @@
 <section class="filter">
   <script>
 		function filter() {
-			location = '{{ user.identity.url }}' + $('#shelf')[0].value + '/' + $('#category')[0].value + '/' + ( $('#year')[0].value ? $('#year')[0].value + '/' : '' );
+      var q = [];
+      if ($('#year')[0].value) {
+        q.push('year=' + $('#year')[0].value)
+      }
+      if ($('#sort')[0].value) {
+        q.push('sort=' + $('#sort')[0].value)
+      }
+      location = '{{ user.identity.url }}' + $('#shelf')[0].value + '/' + $('#category')[0].value + '/' + (q.length ? '?' + q.join('&') : '')
 		}
-
   </script>
-  <form role="search" method="get" action="">
-    <select name="year" id="year" onchange="filter()">
-      <option value="">全部</option>
-      {% for y in years %}
-        <option value="{{ y }}" {% if y == year %}selected{% endif %}>{{ y }}</option>
-      {% endfor %}
-    </select>
-    <select name="shelf" id="shelf" onchange="filter()">
-      {% for typ, label in shelf_labels %}
-        {% if label %}
-          <option {% if typ in request.path %}selected{% endif %} value="{{ typ }}">{{ label }}</option>
+  <form method="get" action="">
+    <fieldset role="search" style="width: 100%; padding;">
+      <select name="shelf" id="shelf" onchange="filter()">
+        {% for typ, label in shelf_labels %}
+          {% if label %}
+            <option {% if typ in request.path %}selected{% endif %} value="{{ typ }}">{{ label }}</option>
+          {% endif %}
+        {% endfor %}
+        <option {% if '/reviews/' in request.path %}selected{% endif %}
+                value="reviews">评论</option>
+      </select>
+      <select name="category" id="category" onchange="filter()">
+        {% visible_categories as cats %}
+        {% if 'book' in cats %}
+          <option {% if '/book/' in request.path %}selected{% endif %} value="book">书籍</option>
         {% endif %}
-      {% endfor %}
-      <option {% if '/reviews/' in request.path %}selected{% endif %}
-              value="reviews">评论</option>
-    </select>
-    <select name="category" id="category" onchange="filter()">
-      {% visible_categories as cats %}
-      {% if 'book' in cats %}
-        <option {% if '/book/' in request.path %}selected{% endif %} value="book">书籍</option>
-      {% endif %}
-      {% if 'movie' in cats %}
-        <option {% if '/movie/' in request.path %}selected{% endif %} value="movie">电影</option>
-      {% endif %}
-      {% if 'tv' in cats %}
-        <option {% if '/tv/' in request.path %}selected{% endif %} value="tv">剧集</option>
-      {% endif %}
-      {% if 'podcast' in cats %}
-        <option {% if '/podcast/' in request.path %}selected{% endif %}
-                value="podcast">播客</option>
-      {% endif %}
-      {% if 'music' in cats %}
-        <option {% if '/music/' in request.path %}selected{% endif %} value="music">音乐</option>
-      {% endif %}
-      {% if 'game' in cats %}
-        <option {% if '/game/' in request.path %}selected{% endif %} value="game">游戏</option>
-      {% endif %}
-      {% if 'performance' in cats %}
-        <option {% if '/performance/' in request.path %}selected{% endif %}
-                value="performance">演出</option>
-      {% endif %}
-    </select>
+        {% if 'movie' in cats %}
+          <option {% if '/movie/' in request.path %}selected{% endif %} value="movie">电影</option>
+        {% endif %}
+        {% if 'tv' in cats %}
+          <option {% if '/tv/' in request.path %}selected{% endif %} value="tv">剧集</option>
+        {% endif %}
+        {% if 'podcast' in cats %}
+          <option {% if '/podcast/' in request.path %}selected{% endif %}
+                  value="podcast">播客</option>
+        {% endif %}
+        {% if 'music' in cats %}
+          <option {% if '/music/' in request.path %}selected{% endif %} value="music">音乐</option>
+        {% endif %}
+        {% if 'game' in cats %}
+          <option {% if '/game/' in request.path %}selected{% endif %} value="game">游戏</option>
+        {% endif %}
+        {% if 'performance' in cats %}
+          <option {% if '/performance/' in request.path %}selected{% endif %}
+                  value="performance">演出</option>
+        {% endif %}
+      </select>
+      <select name="year" id="year" onchange="filter()">
+        <option value="">全部</option>
+        {% for y in years %}
+          <option value="{{ y }}" {% if y == year %}selected{% endif %}>{{ y }}</option>
+        {% endfor %}
+      </select>
+      <select name="sort" id="sort" onchange="filter()">
+        <option value="" disabled>排列顺序</option>
+        <option value="">时间</option>
+        <option value="rating" {% if 'rating' == sort %}selected{% endif %}>评分</option>
+      </select>
+    </fieldset>
   </form>
   <!--
   <style type="text/css">
diff --git a/journal/urls.py b/journal/urls.py
index 630072fd..841bb831 100644
--- a/journal/urls.py
+++ b/journal/urls.py
@@ -101,15 +101,6 @@ urlpatterns = [
         user_mark_list,
         name="user_mark_list",
     ),
-    re_path(
-        r"^users/(?P<user_name>[~A-Za-z0-9_\-.@]+)/(?P<shelf_type>"
-        + _get_all_shelf_types()
-        + ")/(?P<item_category>"
-        + _get_all_categories()
-        + r")/(?P<year>\d+)/$",
-        user_mark_list,
-        name="user_mark_list_year",
-    ),
     re_path(
         r"^users/(?P<user_name>[~A-Za-z0-9_\-.@]+)/reviews/(?P<item_category>"
         + _get_all_categories()
@@ -117,13 +108,6 @@ urlpatterns = [
         user_review_list,
         name="user_review_list",
     ),
-    re_path(
-        r"^users/(?P<user_name>[~A-Za-z0-9_\-.@]+)/reviews/(?P<item_category>"
-        + _get_all_categories()
-        + r")/(?P<year>\d+)/$",
-        user_review_list,
-        name="user_review_list_year",
-    ),
     re_path(
         r"^users/(?P<user_name>[~A-Za-z0-9_\-.@]+)/tags/(?P<tag_title>.+)/$",
         user_tag_member_list,
diff --git a/journal/views/common.py b/journal/views/common.py
index 69cb4e94..46ec9f6c 100644
--- a/journal/views/common.py
+++ b/journal/views/common.py
@@ -3,7 +3,7 @@ import datetime
 from django.contrib.auth.decorators import login_required
 from django.core.exceptions import BadRequest, ObjectDoesNotExist, PermissionDenied
 from django.core.paginator import Paginator
-from django.db.models import Min
+from django.db.models import F, Min, OuterRef, Subquery
 from django.shortcuts import get_object_or_404, redirect, render
 from django.urls import reverse
 from django.utils.translation import gettext_lazy as _
@@ -57,12 +57,15 @@ def render_list(
     item_category=None,
     tag_title=None,
     year=None,
+    sort="time",
 ):
     target = request.target_identity
     viewer = request.user.identity
     tag = None
+    sort = request.GET.get("sort")
+    year = request.GET.get("year")
     if type == "mark" and shelf_type:
-        queryset = target.shelf_manager.get_latest_members(shelf_type, item_category)
+        queryset = target.shelf_manager.get_members(shelf_type, item_category)
     elif type == "tagmember":
         tag = Tag.objects.filter(owner=target, title=tag_title).first()
         if not tag:
@@ -74,6 +77,15 @@ def render_list(
         queryset = Review.objects.filter(q_item_in_category(item_category))
     else:
         raise BadRequest()
+    if sort == "rating":
+        rating = Rating.objects.filter(
+            owner_id=OuterRef("owner_id"), item_id=OuterRef("item_id")
+        )
+        queryset = queryset.alias(
+            rating_grade=Subquery(rating.values("grade"))
+        ).order_by(F("rating_grade").desc(nulls_last=True))
+    else:
+        queryset = queryset.order_by("-created_time")
     start_date = queryset.aggregate(Min("created_time"))["created_time__min"]
     if start_date:
         start_year = start_date.year
@@ -81,16 +93,14 @@ def render_list(
         years = reversed(range(start_year, current_year + 1))
     else:
         years = []
-    queryset = queryset.filter(
-        q_owned_piece_visible_to_user(request.user, target)
-    ).order_by("-created_time")
+    queryset = queryset.filter(q_owned_piece_visible_to_user(request.user, target))
     if year:
         year = int(year)
         queryset = queryset.filter(created_time__year=year)
     paginator = Paginator(queryset, PAGE_SIZE)
     page_number = int(request.GET.get("page", default=1))
     members = paginator.get_page(page_number)
-    pagination = PageLinksGenerator(PAGE_SIZE, page_number, paginator.num_pages)
+    pagination = PageLinksGenerator(page_number, paginator.num_pages, request.GET)
     shelf_labels = get_shelf_labels_for_category(item_category) if item_category else []
     return render(
         request,
@@ -103,6 +113,7 @@ def render_list(
             "pagination": pagination,
             "years": years,
             "year": year,
+            "sort": sort,
             "shelf": shelf_type,
             "shelf_labels": shelf_labels,
             "category": item_category,
diff --git a/journal/views/mark.py b/journal/views/mark.py
index ab97ee46..a09e749e 100644
--- a/journal/views/mark.py
+++ b/journal/views/mark.py
@@ -11,7 +11,7 @@ from django.utils.dateparse import parse_datetime
 from django.utils.translation import gettext_lazy as _
 
 from catalog.models import *
-from common.utils import AuthedHttpRequest, PageLinksGenerator, get_uuid_or_404
+from common.utils import AuthedHttpRequest, get_uuid_or_404
 from mastodon.api import boost_toot_later, share_comment
 from takahe.utils import Takahe
 
@@ -184,14 +184,7 @@ def comment(request: AuthedHttpRequest, item_uuid):
     raise BadRequest()
 
 
-def user_mark_list(
-    request: AuthedHttpRequest, user_name, shelf_type, item_category, year=None
-):
+def user_mark_list(request: AuthedHttpRequest, user_name, shelf_type, item_category):
     return render_list(
-        request,
-        user_name,
-        "mark",
-        shelf_type=shelf_type,
-        item_category=item_category,
-        year=year,
+        request, user_name, "mark", shelf_type=shelf_type, item_category=item_category
     )
diff --git a/journal/views/post.py b/journal/views/post.py
index 13ac4322..f6959b90 100644
--- a/journal/views/post.py
+++ b/journal/views/post.py
@@ -5,12 +5,7 @@ from django.urls import reverse
 from django.utils.translation import gettext_lazy as _
 from django.views.decorators.http import require_http_methods
 
-from common.utils import (
-    AuthedHttpRequest,
-    PageLinksGenerator,
-    get_uuid_or_404,
-    target_identity_required,
-)
+from common.utils import AuthedHttpRequest, get_uuid_or_404, target_identity_required
 from mastodon.api import boost_toot_later
 from takahe.utils import Takahe
 
diff --git a/journal/views/review.py b/journal/views/review.py
index 24e75a08..4ce97a90 100644
--- a/journal/views/review.py
+++ b/journal/views/review.py
@@ -13,7 +13,7 @@ from django.utils.translation import gettext_lazy as _
 from django.views.decorators.http import require_http_methods
 
 from catalog.models import *
-from common.utils import AuthedHttpRequest, PageLinksGenerator, get_uuid_or_404
+from common.utils import AuthedHttpRequest, get_uuid_or_404
 from journal.models.renderers import convert_leading_space_in_md, render_md
 from users.models.apidentity import APIdentity
 
@@ -97,10 +97,8 @@ def review_edit(request: AuthedHttpRequest, item_uuid, review_uuid=None):
         raise BadRequest()
 
 
-def user_review_list(request, user_name, item_category, year=None):
-    return render_list(
-        request, user_name, "review", item_category=item_category, year=None
-    )
+def user_review_list(request, user_name, item_category):
+    return render_list(request, user_name, "review", item_category=item_category)
 
 
 MAX_ITEM_PER_TYPE = 10
diff --git a/misc/www/robots.txt b/misc/www/robots.txt
index 80eef49a..dc2450ff 100644
--- a/misc/www/robots.txt
+++ b/misc/www/robots.txt
@@ -1,2 +1,14 @@
+User-agent: CCBot
+Disallow: /review/
+
 User-agent: GPTBot
 Disallow: /review/
+
+User-agent: Google-Extended
+Disallow: /review/
+
+User-agent: FacebookBot
+Disallow: /review/
+
+User-agent: Omgilibot
+Disallow: /review/