diff --git a/.gitignore b/.gitignore index a0403515..cf53e58f 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ tox.ini # generated css /common/static/css/boofilsic.min.css /common/static/css/boofilsic.css +/common/static/scss/neodb.css # debug log file /log diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b9a6102f..61edcd73 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,3 +10,10 @@ repos: hooks: - id: black language_version: python3.11 +repos: + - repo: https://github.com/Riverside-Healthcare/djLint + rev: v1.28.0 + hooks: + - id: djlint-reformat-django + - id: djlint-django + diff --git a/boofilsic/settings.py b/boofilsic/settings.py index 88ef1297..85800e71 100644 --- a/boofilsic/settings.py +++ b/boofilsic/settings.py @@ -12,6 +12,7 @@ https://docs.djangoproject.com/en/3.0/ref/settings/ import os +PROJECT_ROOT = os.path.abspath(os.path.dirname(__name__)) # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) @@ -52,6 +53,7 @@ INSTALLED_APPS = [ "django_rq", "django_bleach", "tz_detect", + "sass_processor", "simple_history", "markdownx", "polymorphic", @@ -210,6 +212,11 @@ STATIC_URL = "/static/" STATIC_ROOT = os.path.join(BASE_DIR, "static/") STATICFILES_STORAGE = "django.contrib.staticfiles.storage.ManifestStaticFilesStorage" +STATICFILES_FINDERS = [ + "django.contrib.staticfiles.finders.FileSystemFinder", + "django.contrib.staticfiles.finders.AppDirectoriesFinder", + "sass_processor.finders.CssFinder", +] AUTH_USER_MODEL = "users.User" @@ -221,9 +228,9 @@ SILENCED_SYSTEM_CHECKS = [ MEDIA_URL = "/media/" MEDIA_ROOT = os.path.join(BASE_DIR, "media/") -PROJECT_ROOT = os.path.abspath(os.path.dirname(__name__)) SITE_INFO = { "site_name": "NiceDB", + "site_url": "https://nicedb.org", "support_link": "https://github.com/doubaniux/boofilsic/issues", "social_link": "https://donotban.com/@testie", "donation_link": "https://patreon.com/tertius", @@ -237,6 +244,7 @@ CLIENT_NAME = os.environ.get("APP_NAME", "NiceDB") SITE_INFO["site_name"] = os.environ.get("APP_NAME", "NiceDB") APP_WEBSITE = os.environ.get("APP_URL", "https://nicedb.org") REDIRECT_URIS = APP_WEBSITE + "/users/OAuth2_login/" +SITE_INFO["site_url"] = APP_WEBSITE # Path to save report related images, ends with slash @@ -408,3 +416,5 @@ MAINTENANCE_MODE_IGNORE_ADMIN_SITE = True MAINTENANCE_MODE_IGNORE_SUPERUSER = True MAINTENANCE_MODE_IGNORE_ANONYMOUS_USER = True MAINTENANCE_MODE_IGNORE_URLS = (r"^/users/connect/", r"^/users/OAuth2_login/") + +DISCORD_WEBHOOKS = {} diff --git a/catalog/common/downloaders.py b/catalog/common/downloaders.py index 69288186..8d7caa47 100644 --- a/catalog/common/downloaders.py +++ b/catalog/common/downloaders.py @@ -277,9 +277,9 @@ class MockResponse: return json.load(StringIO(self.text)) def html(self): - return html.fromstring( + return html.fromstring( # may throw exception unexpectedly due to OS bug, see https://github.com/neodb-social/neodb/issues/5 self.text - ) # may throw exception unexpectedly due to OS bug, see https://github.com/neodb-social/neodb/issues/5 + ) @property def headers(self): diff --git a/catalog/management/commands/discover.py b/catalog/management/commands/discover.py index 02980c9f..d9bc0a2d 100644 --- a/catalog/management/commands/discover.py +++ b/catalog/management/commands/discover.py @@ -8,6 +8,9 @@ from django.utils import timezone from django.db.models import Count +MAX_GALLERY_ITEMS = 42 + + class Command(BaseCommand): help = "catalog app utilities" @@ -18,9 +21,21 @@ class Command(BaseCommand): help="generate discover data", ) + def get_popular_item_ids(self, category, days): + self.stdout.write(f"Generating popular {category} items for {days} days...") + item_ids = [ + m["item_id"] + for m in ShelfMember.objects.filter(query_item_category(category)) + .filter(created_time__gt=timezone.now() - timedelta(days=days)) + .values("item_id") + .annotate(num=Count("item_id")) + .order_by("-num")[:MAX_GALLERY_ITEMS] + ] + return item_ids + def handle(self, *args, **options): if options["update"]: - cache_key = "public_gallery_list" + cache_key = "public_gallery" gallery_categories = [ ItemCategory.Book, ItemCategory.Movie, @@ -31,22 +46,19 @@ class Command(BaseCommand): ] gallery_list = [] for category in gallery_categories: - item_ids = [ - m["item_id"] - for m in ShelfMember.objects.filter(query_item_category(category)) - .filter(created_time__gt=timezone.now() - timedelta(days=42)) - .values("item_id") - .annotate(num=Count("item_id")) - .order_by("-num")[:100] - ] + days = 14 + item_ids = [] + while len(item_ids) < MAX_GALLERY_ITEMS / 2 and days < 150: + item_ids = self.get_popular_item_ids(category, days) + days *= 3 + items = Item.objects.filter(id__in=item_ids) gallery_list.append( { "name": "popular_" + category.value, "title": "热门" + (category.label if category != ItemCategory.Book else "图书"), - "item_ids": item_ids, + "items": items, } ) cache.set(cache_key, gallery_list, timeout=None) - self.stdout.write(self.style.SUCCESS(f"Done.")) diff --git a/catalog/music/models.py b/catalog/music/models.py index 571e6929..9ffddbb0 100644 --- a/catalog/music/models.py +++ b/catalog/music/models.py @@ -75,6 +75,16 @@ class Album(Item): bandcamp_album_id = jsondata.CharField(blank=True, default="", max_length=500) disc_count = jsondata.IntegerField(_("碟片数"), blank=True, default="", max_length=500) + def get_embed_link(self): + for res in self.external_resources.all(): + if res.id_type == IdType.Bandcamp.value and res.metadata.get( + "bandcamp_album_id" + ): + return f"https://bandcamp.com/EmbeddedPlayer/album={res.metadata.get('bandcamp_album_id')}/size=large/bgcol=ffffff/linkcol=19A2CA/artwork=small/transparent=true/" + if res.id_type == IdType.Spotify_Album.value: + return res.url.replace("open.spotify.com/", "open.spotify.com/embed/") + return None + @classmethod def lookup_id_type_choices(cls): id_types = [ diff --git a/catalog/search/external.py b/catalog/search/external.py index 94e957b3..6b72e999 100644 --- a/catalog/search/external.py +++ b/catalog/search/external.py @@ -16,19 +16,22 @@ class SearchResultItem: self, category, source_site, source_url, title, subtitle, brief, cover_url ): self.category = category + self.external_resources = { + "all": [{"url": source_url, "site_name": {"label": source_site}}] + } self.source_site = source_site self.source_url = source_url self.title = title self.subtitle = subtitle self.brief = brief - self.cover_url = cover_url + self.cover_image_url = cover_url @property def verbose_category_name(self): return self.category.label @property - def link(self): + def url(self): return f"/search?q={quote_plus(self.source_url)}" @property @@ -272,7 +275,7 @@ class ExternalSources: results = [] if c == "" or c is None: c = "all" - if c == "all" or c == "movie" or c == "tv": + if c == "all" or c == "movietv": results.extend(TheMovieDatabase.search(q, page)) if c == "all" or c == "book": results.extend(GoogleBooks.search(q, page)) diff --git a/catalog/search/typesense.py b/catalog/search/typesense.py index 3187feb1..ed500fa5 100644 --- a/catalog/search/typesense.py +++ b/catalog/search/typesense.py @@ -236,7 +236,10 @@ class Indexer: def search(cls, q, page=1, category=None, tag=None, sort=None): f = [] if category: - f.append("category:= " + category) + if category == "movietv": + f.append("category:= [movie,tv]") + else: + f.append("category:= " + category) if tag: f.append(f"tags:= '{tag}'") filters = " && ".join(f) @@ -254,6 +257,7 @@ class Indexer: try: r = cls.instance().collections[INDEX_NAME].documents.search(options) + print(r) results.items = list( [ x diff --git a/catalog/search/views.py b/catalog/search/views.py index 2ee372e1..a8b0e0a0 100644 --- a/catalog/search/views.py +++ b/catalog/search/views.py @@ -80,6 +80,7 @@ def fetch(request, url, is_refetch: bool = False, site: AbstractSite = None): "fetch_pending.html", { "site": site, + "sites": SiteName.labels, "job_id": job_id, }, ) @@ -99,6 +100,7 @@ def search(request): "search_results.html", { "items": None, + "sites": SiteName.labels, }, ) @@ -150,7 +152,7 @@ def search(request): ), "categories": ["book", "movie", "music", "game"], "sites": SiteName.labels, - "hide_category": category is not None, + "hide_category": category is not None and category != "movietv", }, ) diff --git a/catalog/sites/bandcamp.py b/catalog/sites/bandcamp.py index b820c862..33a00659 100644 --- a/catalog/sites/bandcamp.py +++ b/catalog/sites/bandcamp.py @@ -14,8 +14,8 @@ _logger = logging.getLogger(__name__) class Bandcamp(AbstractSite): SITE_NAME = SiteName.Bandcamp ID_TYPE = IdType.Bandcamp - URL_PATTERNS = [r"https://([a-z0-9\-]+.bandcamp.com/album/[^?#/]+)"] - URL_PATTERN_FALLBACK = r"https://([a-z0-9\-\.]+/album/[^?#/]+)" + URL_PATTERNS = [r"https://([a-z0-9\-]+.bandcamp.com/album/[^?#/]+).*"] + URL_PATTERN_FALLBACK = r"https://([a-z0-9\-\.]+/album/[^?#/]+).*" WIKI_PROPERTY_ID = "" DEFAULT_MODEL = Album diff --git a/catalog/sites/spotify.py b/catalog/sites/spotify.py index 790fb376..8b2625d9 100644 --- a/catalog/sites/spotify.py +++ b/catalog/sites/spotify.py @@ -24,7 +24,7 @@ spotify_token_expire_time = time.time() class Spotify(AbstractSite): SITE_NAME = SiteName.Spotify ID_TYPE = IdType.Spotify_Album - URL_PATTERNS = [r"\w+://open\.spotify\.com/album/([a-zA-Z0-9]+)"] + URL_PATTERNS = [r"\w+://open\.spotify\.com/album/([a-zA-Z0-9]+).*"] WIKI_PROPERTY_ID = "?" DEFAULT_MODEL = Album diff --git a/catalog/static/podcast.js b/catalog/static/podcast.js new file mode 100644 index 00000000..0285860d --- /dev/null +++ b/catalog/static/podcast.js @@ -0,0 +1,57 @@ +function create_player(audio) { + window.player = new Shikwasa.Player({ + container: () => document.querySelector('.player'), + preload: 'metadata', + autoplay: true, + themeColor: getComputedStyle(document.documentElement).getPropertyValue('--pico-primary'), + fixed: { + type: 'fixed', + position: 'bottom' + }, + audio: audio + }); + // $('.shk-title').on('click', e=>{ + // window.location = "#"; + // }); + $('.footer').attr('style', 'margin-bottom: 120px !important'); +} +function podcast_init(context) { + $('.episode', context).on('click', e=>{ + e.preventDefault(); + var ele = e.currentTarget; + var album = $(ele).data('album'); + var artist = $(ele).data('hosts'); + var title = $(ele).data('title'); + var cover_url = $(ele).data('cover'); + var media_url = $(ele).data('media'); + var position = $(ele).data('position'); + if (!media_url) return; + window.current_item_uuid = $(ele).data('uuid'); + if (!window.player) { + create_player({ + title: title, + cover: cover_url, + src: media_url, + album: album, + artist: artist + }) + } else { + window.player.update({ + title: title, + cover: cover_url, + src: media_url, + album: album, + artist: artist + }) + } + if (position) window.player._initSeek = position; + window.player.play() + }); +} + +$(function() { + document.body.addEventListener('htmx:load', function(evt) { + podcast_init(evt.detail.elt); + }); + podcast_init(document.body); +}); diff --git a/catalog/templates/_item_card.html b/catalog/templates/_item_card.html new file mode 100644 index 00000000..5351f935 --- /dev/null +++ b/catalog/templates/_item_card.html @@ -0,0 +1,121 @@ +{% load humanize %} +{% load i18n %} +{% load highlight %} +{% if item.get_embed_link %} +
+
+ {{ item.title }} + + {% if not hide_category %}[{{ item.category.label }}]{% endif %} + + {% for res in item.external_resources.all %} + {{ res.site_name.label }} + {% endfor %} + + +
+ +
+{% else %} +
+
+ + cover + +
+
+
+
+ + {% if request.GET.q %} + {{ item.title | strip_season | highlight:request.GET.q }} + {% else %} + {{ item.title | strip_season }} + {% endif %} + + + {% if item.season_number %}第{{ item.season_number|apnumber }}季{% endif %} + {% if item.year %}({{ item.year }}){% endif %} + {% if not hide_category %}[{{ item.category.label }}]{% endif %} + + {% for res in item.external_resources.all %} + {{ res.site_name.label }} + {% endfor %} + + +
+ {% comment %} + + {% for res in item.external_resources.all %} + {{ res.site_name.label }} + {% endfor %} + + {% endcomment %} +
+ + {% if item.subtitle %}{{ item.subtitle }}{% endif %} + {% if item.orig_title %} + {{ item.orig_title }} + {% if item.season_number %}Season {{ item.season_number }}{% endif %} + {% endif %} + +
+
+
+ +
+ {% if item.actor %} + {% include '_people.html' with people=item.actor role='主演' max=2 %} +
+ {% endif %} + {% if item.other_title %} + {% include '_people.html' with people=item.other_title role='又名' max=2 %} +
+ {% endif %} + {% if request.GET.q %} + {{ item.brief | linebreaksbr | highlight:request.GET.q }} + {% else %} + {{ item.brief | linebreaksbr }} + {% endif %} +
+ {% if show_tags %} +
+ {% for tag in item.tags %} + {% if forloop.counter <= 5 %} + + {{ tag }} + + {% endif %} + {% endfor %} +
+ {% endif %} +
+
+
+{% endif %} diff --git a/catalog/templates/_people.html b/catalog/templates/_people.html new file mode 100644 index 00000000..8463a0c0 --- /dev/null +++ b/catalog/templates/_people.html @@ -0,0 +1,11 @@ +{% if people %} + {% if role %}{{ role }}:{% endif %} + {% for p in people %} + {% if forloop.counter <= max %} + {% if not forloop.first %}、{% endif %} + {{ p }} + {% elif forloop.last %} + 等 + {% endif %} + {% endfor %} +{% endif %} diff --git a/catalog/templates/album.html b/catalog/templates/album.html index 88faa68e..b41dd6bf 100644 --- a/catalog/templates/album.html +++ b/catalog/templates/album.html @@ -10,114 +10,120 @@ {% load strip_scheme %} {% load thumb %} {% load duration %} - {% block details %} -
-
- {% if item.rating and item.rating_count >= 5 %} - - {{ item.rating | floatformat:1 }} - ({{ item.rating_count }}人评分) - {% else %} - {% trans '评分:评分人数不足' %} - {% endif %} -
-
{% if item.artist %}{% trans '艺术家:' %} - {% for artist in item.artist %} - 5 %}style="display: none;" {% endif %}> - {{ artist }} - {% if not forloop.last %} / {% endif %} - - {% endfor %} - {% if item.artist|length > 5 %} - {% trans '更多' %} - - {% endif %} - {% endif %}
-
{% if item.company %}{% trans '发行方:' %} - {% for company in item.company %} - 5 %}style="display: none;" {% endif %}> - {{ company }} - {% if not forloop.last %} / {% endif %} - - {% endfor %} - {% if item.company|length > 5 %} - {% trans '更多' %} - + {% endif %} + {% endif %} +
+
+ {% if item.company %} + {% trans '发行方:' %} + {% for company in item.company %} + 5 %}style="display: none;"{% endif %}> + {{ company }} + {% if not forloop.last %}/{% endif %} + + {% endfor %} + {% if item.company|length > 5 %} + {% trans '更多' %} + - {% endif %} - {% endif %}
-
{% if item.release_date %} - {% trans '发行日期:' %}{{ item.release_date }} - {% endif %} -
-
{% if item.duration %} - {% trans '时长:' %}{{ item.duration|duration_format:1000 }} - {% endif %} -
-
{% if item.genre %} - {% trans '流派:' %} - {% for genre in item.genre %} - {{ genre }}{% if not forloop.last %} / {% endif %} - {% endfor %} - {% endif %} -
-
{% if item.barcode %} - {% trans '条形码:' %}{{ item.barcode }} - {% endif %} -
- -
-
-
{% if item.other_title %} - {% trans '又名:' %} - {% for t in item.other_title %} - {{ t }}{% if not forloop.last %} / {% endif %} - {% endfor %} - {% endif %} -
-
{% if item.album_type %} - {% trans '专辑类型:' %}{{ item.album_type }} - {% endif %} -
-
{% if item.media %} - {% trans '介质:' %}{{ item.media }} - {% endif %} -
-
{% if item.disc_count %} - {% trans '碟片数:' %}{{ item.disc_count }} - {% endif %} -
- - {% if item.last_editor and item.last_editor.preference.show_last_edit %} -
{% trans '最近编辑者:' %}{{ item.last_editor | default:"" }}
- {% endif %} - -
- {% if user.is_authenticated %} - {% trans '编辑' %}{{ item.demonstrative }} - {% endif %} -
-
+ + + {% endif %} + {% endif %} + +
+ {% if item.release_date %} + {% trans '发行日期:' %}{{ item.release_date }} + {% endif %} +
+
+ {% if item.duration %} + {% trans '时长:' %}{{ item.duration|duration_format:1000 }} + {% endif %} +
+
+ {% if item.genre %} + {% trans '流派:' %} + {% for genre in item.genre %} + {{ genre }} + {% if not forloop.last %}/{% endif %} + {% endfor %} + {% endif %} +
+
+ {% if item.barcode %} + {% trans '条形码:' %}{{ item.barcode }} + {% endif %} +
+
+ {% if item.other_title %} + {% trans '又名:' %} + {% for t in item.other_title %} + {{ t }} + {% if not forloop.last %}/{% endif %} + {% endfor %} + {% endif %} +
+
+ {% if item.album_type %} + {% trans '专辑类型:' %}{{ item.album_type }} + {% endif %} +
+
+ {% if item.media %} + {% trans '介质:' %}{{ item.media }} + {% endif %} +
+
+ {% if item.disc_count %} + {% trans '碟片数:' %}{{ item.disc_count }} + {% endif %} +
{% endblock %} - -{% block sidebar %} -{% if item.get_embed_link %} - -{% endif %} +{% block content %} + {% if item.track_list %} +
+
曲目
+

{{ item.track_list | linebreaksbr }}

+
+ {% endif %} + {% if item.get_embed_link %} +
+
播放
+ +
+ {% endif %} {% endblock %} diff --git a/catalog/templates/catalog_edit.html b/catalog/templates/catalog_edit.html index 30ed0a4d..54b21482 100644 --- a/catalog/templates/catalog_edit.html +++ b/catalog/templates/catalog_edit.html @@ -4,133 +4,148 @@ {% load mastodon %} {% load oauth_token %} {% load truncate %} - - - - - - - {{ site_name }} - {% if form.instance.id %}{% trans '编辑' %} {{ form.instance.title }} {% else %}{% trans '添加' %}{% endif %} - {% include "common_libs.html" with jquery=0 %} - - - -
-
- {% include "partial/_navbar.html" %} - -
-
-
- {% if item %} -
- {% for res in form.instance.external_resources.all %} -
-
{% trans '源网站' %}: {{ res.site_name.label }}
-
-
- {% csrf_token %} - - - -
-
+ + + + + {{ site_name }} - + {% if form.instance.id %} + {% trans '编辑' %} {{ form.instance.title }} + {% else %} + {% trans '添加' %} + {% endif %} + + {% include "common_libs.html" with jquery=0 v2=1 %} + + + + {% include "_header.html" %} +
+
+
+ {% csrf_token %} + {{ form.media }} + {{ form }} +
+ + +
+
+
+
- - {% if item.class_name == "movie" or item.class_name == "tvshow" %} -
-
-
{% trans '切换分类' %}
-
-
- {% csrf_token %} - {% if item.class_name == "movie" %} - - - {% endif %} - {% if item.class_name == "tvshow" %} - - - {% endif %} -
-
-
-
- {% endif %} - -
-
- {% if item.class_name == "tvseason" %} - {% if not item.show or request.user.is_superuser %} -
{% trans '将本季关联到电视剧' %}
-
-
- {% csrf_token %} -
- -
-
+ + {% endfor %} + {% if item.class_name == "movie" or item.class_name == "tvshow" %} +
+ {% trans '切换分类' %} +
+ {% csrf_token %} + {% if item.class_name == "movie" %} + + {% endif %} + {% if item.class_name == "tvshow" %} + + {% endif %} - - {% if item.is_deleted and not request.user.is_superuser %} - 条目已被删除 - {% elif item.merged_to_item and not request.user.is_superuser %} - 条目已被合并 - {% elif not item.deletable and not request.user.is_superuser %} - 条目已被用户标记过 - {% else %} -
{% trans '合并到另一条目' %}
-
- - {% csrf_token %} -
- - -
- -
{% trans '删除' %}
-
-
- {% csrf_token %} - -
-
- {% endif %} -
-
- + + {% endif %} -
-
-
- {% csrf_token %} - {{ form.media }} - {{ form }} -
- - 返回 -
-
- -
-
- {% include "partial/_footer.html" %} -
- - - - + {% if item.class_name == "tvseason" %} + {% if not item.show or request.user.is_superuser %} +
+ {% trans '将本季关联到电视剧' %} +
+ {% csrf_token %} + +
+ +
+
+ {% endif %} + {% endif %} + {% if item.is_deleted and not request.user.is_superuser %} + 条目已被删除 + {% elif item.merged_to_item and not request.user.is_superuser %} + 条目已被合并 + {% elif not item.deletable and not request.user.is_superuser %} + 条目已被用户标记过 + {% else %} +
+ {% trans '合并到另一条目' %} +
+ {% csrf_token %} + +
+ +
+
+
+ {% trans '删除' %} +
+ {% csrf_token %} + +
+
+ {% endif %} + {% endif %} + + + {% include "partial/_footer.html" %} + + diff --git a/catalog/templates/common_libs.html b/catalog/templates/common_libs.html index a0cdae34..fa52c296 100644 --- a/catalog/templates/common_libs.html +++ b/catalog/templates/common_libs.html @@ -1,42 +1,38 @@ {% load static %} +{% load sass_tags %} {% load tz_detect %} -{% tz_detect %} -{% if sentry_dsn %} - - +{% if request.user.is_authenticated %} + {% tz_detect %} {% endif %} {% if jquery %} - + {% else %} - + {% endif %} - + - - - - - - - - + + + + diff --git a/catalog/templates/discover.html b/catalog/templates/discover.html index 1468e3f2..fb072518 100644 --- a/catalog/templates/discover.html +++ b/catalog/templates/discover.html @@ -6,118 +6,111 @@ {% load truncate %} {% load thumb %} - - - - - - {{ site_name }} - {% trans '发现' %} - - - {% include "common_libs.html" with jquery=0 %} - - + + + + + + + + + + + {{ site_name }} - {% trans '发现' %} + + {% include "common_libs.html" with jquery=0 v2=1 %} + + + - -
-
- {% include "partial/_navbar.html" with current="discover" %} - -
-
-
- -
- - {% for gallery in gallery_list %} -
-
- {{ gallery.title }} -
- -
+ {% include "_header.html" with current="discover" %} +
+
+
+ {% for gallery in gallery_list %} +
+
{{ gallery.title }}
+
- - {% if user == request.user %} - -
-
- - {% trans '编辑布局' %} - - - - - - -
- -
- -
- {% csrf_token %} - - -
- - - {% endif %} - - {{ layout|json_script:"layout-data" }} - - + +
+ {% endfor %} +
+ {% if request.user.is_authenticated %} +
+
+ {% trans '编辑布局' %} + + + + + +
+ - - {% include "partial/_sidebar.html" %}
- + +
+ {% csrf_token %} + + +
+ + + {{ layout|json_script:"layout-data" }} + + {% endif %}
- {% include "partial/_footer.html" %} - - - {% if request.user.unread_announcements %} - {% include "partial/_announcement.html" %} - {% endif %} + {% if request.user.is_authenticated %} + {% include "_sidebar.html" with show_progress=1 %} + {% else %} + {% include "partial/_sidebar_anonymous.html" %} + {% endif %} + + {% include "partial/_footer.html" %} diff --git a/catalog/templates/edition.html b/catalog/templates/edition.html index cb814da7..e1dd90af 100644 --- a/catalog/templates/edition.html +++ b/catalog/templates/edition.html @@ -9,105 +9,153 @@ {% load truncate %} {% load strip_scheme %} {% load thumb %} - {% block head %} -{% if item.author %} - -{% endif %} -{% if item.isbn %} - -{% endif %} + {% if item.author %} + + {% endif %} + {% if item.isbn %}{% endif %} {% endblock %} - {% block details %} -
-
- {% if item.rating %} - - {{ item.rating | floatformat:1 }} - ({{ item.rating_count }}人评分) - {% else %} - {% trans '评分:评分人数不足' %} - {% endif %} -
-
{% if item.subtitle %}{% trans '副标题:' %}{{ item.subtitle }}{% endif %}
-
{% if item.isbn %}{% trans 'ISBN:' %}{{ item.isbn }}{% endif %}
-
{% if item.author %}{% trans '作者:' %} - {% for author in item.author %} - {{ author }}{% if not forloop.last %} / {% endif %} - {% endfor %} - {% endif %}
-
{% if item.pub_house %}{% trans '出版社:' %}{{ item.pub_house }}{% endif %}
-
{% if item.orig_title %}{% trans '原作名:' %}{{ item.orig_title }}{% endif %}
-
{% if item.translator %}{% trans '译者:' %} - {% for translator in item.translator %} - {{ translator }}{% if not forloop.last %} / {% endif %} - {% endfor %} - {% endif %}
-
{%if item.pub_year %}{% trans '出版时间:' %}{{ item.pub_year }}{% trans '年' %}{% if item.pub_month %}{{ item.pub_month }}{% trans '月' %}{% endif %}{% endif %}
-
{% if item.series %}{% trans '丛书系列:' %}{{ item.series }}{% endif %}
-
-
-
{% if item.language %}{% trans '语言:' %}{{ item.language }}{% endif %}
-
{% if item.binding %}{% trans '装帧:' %}{{ item.binding }}{% endif %}
-
{% if item.price %}{% trans '定价:' %}{{ item.price }}{% endif %}
-
{% if item.pages %}{% trans '页数:' %}{{ item.pages }}{% endif %}
-
{% if item.imprint %}{% trans '出品方:' %}{{ item.imprint }}{% endif %}
- {% if item.other_info %} - {% for k, v in item.other_info.items %} -
- {{ k }}:{{ v | urlize }} -
- {% endfor %} - {% endif %} - - - {% if item.last_editor and item.last_editor.preference.show_last_edit %} -
{% trans '最近编辑者:' %}{{ item.last_editor | default:"" }}
- {% endif %} - -
- {% if user.is_authenticated %} - {% trans '编辑' %}{{ item.demonstrative }} - {% endif %} -
-
+ +
+ {% if item.isbn %} + {% trans 'ISBN:' %}{{ item.isbn }} + {% endif %} +
+
+ {% if item.author %} + {% trans '作者:' %} + {% for author in item.author %} + {{ author }} + {% if not forloop.last %}/{% endif %} + {% endfor %} + {% endif %} +
+
+ {% if item.translator %} + {% trans '译者:' %} + {% for translator in item.translator %} + {{ translator }} + {% if not forloop.last %}/{% endif %} + {% endfor %} + {% endif %} +
+
+ {% if item.pub_house %} + {% trans '出版社:' %}{{ item.pub_house }} + {% endif %} +
+
+ {% if item.imprint %} + {% trans '出品方:' %}{{ item.imprint }} + {% endif %} +
+
+ {% if item.pub_year %} + {% trans '出版时间:' %}{{ item.pub_year }}{% trans '年' %} + {% if item.pub_month %} + {{ item.pub_month }}{% trans '月' %} + {% endif %} + {% endif %} +
+
+ {% if item.series %} + {% trans '丛书系列:' %}{{ item.series }} + {% endif %} +
+
+ {% if item.language %} + {% trans '语言:' %}{{ item.language }} + {% endif %} +
+
+ {% if item.binding %} + {% trans '装帧:' %}{{ item.binding }} + {% endif %} +
+
+ {% if item.price %} + {% trans '定价:' %}{{ item.price }} + {% endif %} +
+
+ {% if item.pages %} + {% trans '页数:' %}{{ item.pages }} + {% endif %} +
+ {% if item.other_info %} + {% for k, v in item.other_info.items %}
{{ k }}:{{ v|urlizetrunc:24 }}
{% endfor %} + {% endif %} +{% endblock %} +{% block content %} + {% if item.contents %} +
+
目录
+

{{ item.contents | linebreaksbr }}

+
+ {% endif %} {% endblock %} - - {% block sidebar %} -{% with related_books=item.get_related_books %} -{% if related_books.count > 0 %} -
-
-
{% trans '其它版本' %}
-
- {% for b in related_books %} -

- {{ b.title }} - ({{ b.pub_house | default:'' }} {{ b.pub_year | default:'' }}) - {% for res in b.external_resources.all %} - - {{ res.site_name.label }} - - {% endfor %} -

- {% endfor %} -
-
-
-{% endif %} -{% endwith %} - -{% if item.isbn %} -
-
-
{% trans '借阅或购买' %}
- -
-
-{% endif %} + {% with related_books=item.get_related_books %} + {% if related_books.count > 0 %} +
+
{% trans '其它版本' %}
+ {% for b in related_books %} +

+ {{ b.title }} + ({{ b.pub_house | default:'' }} {{ b.pub_year | default:'' }}) + {% comment %} {% for res in b.external_resources.all %} + + {{ res.site_name.label }} + + {% endfor %} {% endcomment %} +

+ {% endfor %} +
+ {% endif %} + {% endwith %} + {% if item.isbn %} +
+
+
{% trans '借阅或购买' %}
+
+ +
+
+
+ {% endif %} {% endblock %} diff --git a/catalog/templates/embed_base.html b/catalog/templates/embed_base.html index b3570479..e20d0154 100644 --- a/catalog/templates/embed_base.html +++ b/catalog/templates/embed_base.html @@ -8,17 +8,14 @@ {% load truncate %} {% load strip_scheme %} {% load thumb %} - - - - - - {% block head %} - {% endblock %} - {{ site_name }} - {% trans item.category.label %} | {{ item.title }} - - - + + + + + {% block head %}{% endblock %} + {{ site_name }} - {% trans item.category.label %} | {{ item.title }} + + diff --git a/catalog/templates/embed_podcast.html b/catalog/templates/embed_podcast.html index 19514e5d..fa720632 100644 --- a/catalog/templates/embed_podcast.html +++ b/catalog/templates/embed_podcast.html @@ -9,10 +9,10 @@ {% load truncate %} {% load strip_scheme %} {% load thumb %} - {% block head %} - - + + -
-
- {% if item.rating and item.rating_count >= 5 %} - - {{ item.rating | floatformat:1 }} - ({{ item.rating_count }}人评分) - {% else %} - {% trans '评分:评分人数不足' %} - {% endif %} -
- -
- {% if item.genre %}{% trans '类型:' %} - {% for genre in item.genre %} - {{ genre }}{% if not forloop.last %} / {% endif %} - {% endfor %} - {% endif %} -
- -
{% if item.official_site %} - {% trans '网站:' %}{{ item.official_site|urlizetrunc:42 }} - {% endif %} -
- -
-
- -
{% if item.hosts %}{% trans '主播:' %} - {% for host in item.hosts %} - 5 %}style="display: none;" {% endif %}> - {{ host }} - {% if not forloop.last %} / {% endif %} - - {% endfor %} - {% if item.hosts|length > 5 %} - {% trans '更多' %} - - {% endif %} - {% endif %} -
- {% if item.last_editor and item.last_editor.preference.show_last_edit %} -
{% trans '最近编辑者:' %}{{ item.last_editor | default:"" }}
- {% endif %} - -
- {% if user.is_authenticated %} - {% trans '编辑' %}{{ item.demonstrative }} - {% endif %} -
-
-{% endblock %} - - -{% block content %} -
-
{% trans '近期节目' %}
-
-
-
-{% endblock %} - - -{% block sidebar %} -
-
-
- -
+ + {% endif %} + {% endif %}
-
- - - + + {% endblock %} diff --git a/catalog/templates/podcast_episode_data.html b/catalog/templates/podcast_episode_data.html index fb9c3728..74695cfa 100644 --- a/catalog/templates/podcast_episode_data.html +++ b/catalog/templates/podcast_episode_data.html @@ -3,44 +3,39 @@ {% load l10n %} {% load humanize %} {% for ep in episodes %} -
- -
- - - {{ ep.title }} - {{ ep.pub_date|date }} -
-
-

{{ ep.brief | linebreaksbr }}

-
-
- {% if request.user.is_authenticated %} - - - - {% endif %} - - - - -
- -{% if forloop.last %} - -{% endif %} - +

+

+ +   + {% if request.user.is_authenticated %} + + {% endif %} + + {{ ep.title }} + {{ ep.pub_date|date }} +
+ {{ ep.brief | linebreaksbr }} +

+ {% if forloop.last %} + + {% endif %} {% empty %} -
{% trans '目前没有更多内容了' %}
+
{% trans '目前没有更多内容了' %}
{% endfor %} diff --git a/catalog/templates/podcastepisode.html b/catalog/templates/podcastepisode.html index 42f6a26f..23f7ee23 100644 --- a/catalog/templates/podcastepisode.html +++ b/catalog/templates/podcastepisode.html @@ -9,15 +9,18 @@ {% load truncate %} {% load strip_scheme %} {% load thumb %} - {% block head %} - - - -{% if item.media_url %} - - - - -{% endif %} + + + + {% if item.media_url %} + + + + + {% endif %} {% endblock %} diff --git a/catalog/templates/search_results.html b/catalog/templates/search_results.html index 2d27683e..72a86534 100644 --- a/catalog/templates/search_results.html +++ b/catalog/templates/search_results.html @@ -9,94 +9,70 @@ {% load highlight %} {% load thumb %} - - - + + {{ site_name }} - {% trans '搜索结果' %} - {% include "common_libs.html" with jquery=0 %} - - - -
-
- {% include 'partial/_navbar.html' %} - -
-
-
-
- -
- {% if request.GET.q %} -
“{{ request.GET.q }}” {% trans '的搜索结果' %}
- {% endif %} - - {% if request.GET.tag %} -
{% trans '含有标签' %} “{{ request.GET.tag }}” {% trans '的结果' %}
- {% endif %} - -
    - {% for item in items %} - {% with "list_item_"|add:item.class_name|add:".html" as template %} - {% include template %} - {% endwith %} - {% empty %} -
  • - {% trans '无站内条目匹配' %} -
  • - {% endfor %} - {% if request.GET.q and user.is_authenticated %} -
  • - {% trans '正在实时搜索站外条目' %} - -
  • - {% endif %} -
-
- -
- -
- - {% include "search_sidebar.html" %} -
-
+ {% include "common_libs.html" with jquery=0 v2=1 %} + + + {% include '_header.html' %} +
+
+
+
+ {% if request.GET.q %} +
“{{ request.GET.q }}” {% trans '的搜索结果' %}
+ {% endif %} + {% if request.GET.tag %} +
{% trans '含有标签' %} “{{ request.GET.tag }}” {% trans '的结果' %}
+ {% endif %} +
    + {% for item in items %} + {% with "list_item_"|add:item.class_name|add:".html" as template %} + {% include template with show_tags=1 %} + {% endwith %} + {% empty %} +
  • {% trans '无站内条目匹配' %}
  • + {% endfor %} + {% if request.GET.q and user.is_authenticated %} +
  • + {% trans '正在实时搜索站外条目' %} + +
  • + {% endif %} +
+
+
- {% include 'partial/_footer.html' %} -
- - - - - - - +
+ {% include "search_sidebar.html" %} + + {% include 'partial/_footer.html' %} + diff --git a/catalog/templates/search_sidebar.html b/catalog/templates/search_sidebar.html index 4b543218..1bda5958 100644 --- a/catalog/templates/search_sidebar.html +++ b/catalog/templates/search_sidebar.html @@ -1,46 +1,42 @@ {% load static %} {% load i18n %} {% load l10n %} - -
-
- -
-
-
- {% trans '没有想要的结果?' %} -
-

- 如果在 - {% for site in sites %} - {{ site }} - {% if not forloop.last %}/{% endif %} - {% endfor %} - 找到了条目,可以在搜索栏中输入完整链接提交。 -

-

- 当然也可以手工创建条目。 -

- - - - - - - - - - - - - - - - - - -
-
- -
-
+ + diff --git a/catalog/templates/sidebar_item.html b/catalog/templates/sidebar_item.html index ab52f96c..973c5e2f 100644 --- a/catalog/templates/sidebar_item.html +++ b/catalog/templates/sidebar_item.html @@ -2,30 +2,26 @@ {% load i18n %} {% load truncate %} {% load thumb %} -
-
-
- -
-
-
{{ item.title }} - {% for res in item.external_resources.all %} +
+
+ + + +
+
+

+ {{ item.title }} + {% for res in item.external_resources.all %} {{ res.site_name.label }} - {% endfor %} -

- - {% if item.isbn %} -
ISBN: {{ item.isbn }}
- {% endif %} - -
{% if item.pub_house %}{% trans '出版社:' %}{{ item.pub_house }}{% endif %}
- {% if item.rating %} - {% trans '评分: ' %} - {{ item.rating | floatformat:1 }} + {% endfor %} +
+ {% if item.isbn %}
ISBN: {{ item.isbn }}
{% endif %} +
+ {% if item.pub_house %} + {% trans '出版社:' %}{{ item.pub_house }} {% endif %}
- -
-
\ No newline at end of file + + diff --git a/catalog/templates/tvseason.html b/catalog/templates/tvseason.html index 2a6221da..3ca6f887 100644 --- a/catalog/templates/tvseason.html +++ b/catalog/templates/tvseason.html @@ -9,178 +9,201 @@ {% load truncate %} {% load strip_scheme %} {% load thumb %} - {% block title %} -
- {% if item.season_number %} - {{ item.title }} {{ item.orig_title }} Season {{ item.season_number }} - - {% if item.year %}({{ item.year }}){% endif %} - - {% else %} - {{ item.title }} {{ item.orig_title }} - - {% if item.year %}({{ item.year }}){% endif %} - - {% endif %} - - {% for res in item.external_resources.all %} - - {{ res.site_name.label }} - - {% endfor %} -
+
+ {% if item.season_number %} + {{ item.title }} {{ item.orig_title }} Season {{ item.season_number }} + + {% if item.year %}({{ item.year }}){% endif %} + + {% else %} + {{ item.title }} {{ item.orig_title }} + + {% if item.year %}({{ item.year }}){% endif %} + + {% endif %} + {% for res in item.external_resources.all %} + + {{ res.site_name.label }} + + {% endfor %} +
{% endblock %} - {% block details %} -
-
- {% if item.rating and item.rating_count >= 5 %} - - {{ item.rating | floatformat:1 }} - ({{ item.rating_count }}人评分) - {% else %} - {% trans '评分:评分人数不足' %} - {% endif %} -
-
{% if item.imdb %} - {% trans 'IMDb:' %}{{ item.imdb }} - {% endif %} -
-
{% if item.director %}{% trans '导演:' %} - {% for director in item.director %} - 5 %}style="display: none;" {% endif %}> - {{ director }} - {% if not forloop.last %} / {% endif %} - - {% endfor %} - {% if item.director|length > 5 %} - {% trans '更多' %} - - {% endif %} - {% endif %}
-
{% if item.playwright %}{% trans '编剧:' %} - {% for playwright in item.playwright %} - 5 %}style="display: none;" {% endif %}> - {{ playwright }} - {% if not forloop.last %} / {% endif %} - - {% endfor %} - {% if item.playwright|length > 5 %} - {% trans '更多' %} - + {% endif %} + {% endif %} +
+
+ {% if item.playwright %} + {% trans '编剧:' %} + {% for playwright in item.playwright %} + 5 %}style="display: none;"{% endif %}> + {{ playwright }} + {% if not forloop.last %}/{% endif %} + + {% endfor %} + {% if item.playwright|length > 5 %} + {% trans '更多' %} + - {% endif %} - {% endif %}
-
{% if item.actor %}{% trans '主演:' %} - {% for actor in item.actor %} - 5 %}style="display: none;"{% endif %}> - {{ actor }} - {% if not forloop.last %} / {% endif %} - - {% endfor %} - {% if item.actor|length > 5 %} - {% trans '更多' %} - + {% endif %} + {% endif %} +
+
+ {% if item.actor %} + {% trans '主演:' %} + {% for actor in item.actor %} + 5 %}style="display: none;"{% endif %}> + {{ actor }} + {% if not forloop.last %}/{% endif %} + + {% endfor %} + {% if item.actor|length > 5 %} + {% trans '更多' %} + - {% endif %} - - {% endif %}
-
{% if item.genre %}{% trans '类型:' %} - {% for genre in item.genre %} - {{ genre }}{% if not forloop.last %} / {% endif %} - {% endfor %} - {% endif %}
-
{% if item.area %}{% trans '制片国家/地区:' %} - {% for area in item.area %} - {{ area }}{% if not forloop.last %} / {% endif %} - {% endfor %} - {% endif %}
-
{% if item.language %}{% trans '语言:' %} - {% for language in item.language %} - {{ language }}{% if not forloop.last %} / {% endif %} - {% endfor %} - {% endif %}
+ + {% endif %} + {% endif %} +
+
+ {% if item.genre %} + {% trans '类型:' %} + {% for genre in item.genre %} + {{ genre }} + {% if not forloop.last %}/{% endif %} + {% endfor %} + {% endif %} +
+
+ {% if item.area %} + {% trans '制片国家/地区:' %} + {% for area in item.area %} + {{ area }} + {% if not forloop.last %}/{% endif %} + {% endfor %} + {% endif %} +
+
+ {% if item.language %} + {% trans '语言:' %} + {% for language in item.language %} + {{ language }} + {% if not forloop.last %}/{% endif %} + {% endfor %} + {% endif %} +
+
+ {% if item.season_number %} + {% trans '本季序号:' %}{{ item.season_number }} + {% endif %} +
+
+ {% if item.episode_count %} + {% trans '本季集数:' %}{{ item.episode_count }} + {% endif %} +
+
+
+ {% if item.show %} + {% trans '所属剧集:' %}{{ item.show.title }} + {% endif %} +
-
-
{% if item.season_number %}{% trans '本季序号:' %}{{ item.season_number }}{% endif %}
-
{% if item.episode_count %}{% trans '本季集数:' %}{{ item.episode_count }}{% endif %}
-
-
{% if item.show %}{% trans '所属剧集:' %}{{ item.show.title }}{% endif %}
-
{% if item.season_count %}{% trans '总季数:' %}{{ item.season_count }}{% endif %}
-
{% if item.single_episode_length %}{% trans '单集长度:' %}{{ item.single_episode_length }}{% endif %}
- {% with item.all_seasons as seasons %} - {% if seasons %} -
- {% trans '本剧所有季:' %} - {% for s in seasons %} - - {{ s.season_number }} - +
+ {% if item.season_count %} + {% trans '总季数:' %}{{ item.season_count }} + {% endif %} +
+
+ {% if item.single_episode_length %} + {% trans '单集长度:' %}{{ item.single_episode_length }} + {% endif %} +
+{% with item.all_seasons as seasons %} + {% if seasons %} +
+ {% trans '本剧所有季:' %} + {% for s in seasons %} + + {{ s.season_number }} + + {% endfor %} +
+ {% endif %} +{% endwith %} +
+ {% if item.showtime %} + {% trans '上映时间:' %} + {% for showtime in item.showtime %} + {% for time, region in showtime.items %} + {{ time }} + {% if region != '' %}({{ region }}){% endif %} + + {% endfor %} + {% if not forloop.last %}/{% endif %} {% endfor %} -
- {% endif %} - {% endwith %} - -
{% if item.showtime %}{% trans '上映时间:' %} - {% for showtime in item.showtime %} - {% for time, region in showtime.items %} - {{ time }}{% if region != '' %}({{ region }}){% endif %} - {% endfor %} - {% if not forloop.last %} / {% endif %} - {% endfor %} - {% endif %}
-
{% if item.other_title %}{% trans '又名:' %} - {% for t in item.other_title %} - {{ t }}{% if not forloop.last %} / {% endif %} - {% endfor %} - {% endif %}
-
{% if item.site %}{% trans '网站:' %} - {{ item.site|strip_scheme }} - {% endif %}
- {% if item.other_info %} - {% for k, v in item.other_info.items %} -
- {{ k }}:{{ v | urlize }} -
- {% endfor %} - {% endif %} - - - {% if item.last_editor and item.last_editor.preference.show_last_edit %} -
{% trans '最近编辑者:' %}{{ item.last_editor | default:"" }}
- {% endif %} - -
- {% if user.is_authenticated %} - {% trans '编辑' %}{{ item.demonstrative }} - {% endif %} -
+ {% endif %}
+
+ {% if item.other_title %} + {% trans '又名:' %} + {% for t in item.other_title %} + {{ t }} + {% if not forloop.last %}/{% endif %} + {% endfor %} + {% endif %} +
+
+ {% if item.site %} + {% trans '网站:' %} + {{ item.site|strip_scheme }} + {% endif %} +
+{% if item.other_info %} + {% for k, v in item.other_info.items %}
{{ k }}:{{ v|urlizetrunc:24 }}
{% endfor %} +{% endif %} {% endblock %} - -{% block sidebar %} -{% endblock %} +{% block sidebar %}{% endblock %} diff --git a/catalog/templates/tvshow.html b/catalog/templates/tvshow.html index ea3e5250..12afc5ac 100644 --- a/catalog/templates/tvshow.html +++ b/catalog/templates/tvshow.html @@ -9,179 +9,167 @@ {% load truncate %} {% load strip_scheme %} {% load thumb %} - -{% block title %} -
- {% if item.season_number %} - {{ item.title }} {% trans '第' %}{{ item.season_number|apnumber }}{% trans '季' %} {{ item.orig_title }} Season {{ item.season_number }} - - {% if item.year %}({{ item.year }}){% endif %} - - {% else %} - {{ item.title }} {{ item.orig_title }} - - {% if item.year %}({{ item.year }}){% endif %} - - {% endif %} - - {% for res in item.external_resources.all %} - - {{ res.site_name.label }} - - {% endfor %} -
-{% endblock %} - {% block details %} -
-
- {% if item.rating and item.rating_count >= 5 %} - - {{ item.rating | floatformat:1 }} - ({{ item.rating_count }}人评分) - {% else %} - {% trans '评分:评分人数不足' %} - {% endif %} -
-
{% if item.imdb %} - {% trans 'IMDb:' %}{{ item.imdb }} - {% endif %} -
-
{% if item.director %}{% trans '导演:' %} - {% for director in item.director %} - 5 %}style="display: none;" {% endif %}> - {{ director }} - {% if not forloop.last %} / {% endif %} - - {% endfor %} - {% if item.director|length > 5 %} - {% trans '更多' %} - - {% endif %} - {% endif %}
-
{% if item.playwright %}{% trans '编剧:' %} - {% for playwright in item.playwright %} - 5 %}style="display: none;" {% endif %}> - {{ playwright }} - {% if not forloop.last %} / {% endif %} - - {% endfor %} - {% if item.playwright|length > 5 %} - {% trans '更多' %} - + {% endif %} + {% endif %} +
+
+ {% if item.playwright %} + {% trans '编剧:' %} + {% for playwright in item.playwright %} + 5 %}style="display: none;"{% endif %}> + {{ playwright }} + {% if not forloop.last %}/{% endif %} + + {% endfor %} + {% if item.playwright|length > 5 %} + {% trans '更多' %} + - {% endif %} - {% endif %}
-
{% if item.actor %}{% trans '主演:' %} - {% for actor in item.actor %} - 5 %}style="display: none;"{% endif %}> - {{ actor }} - {% if not forloop.last %} / {% endif %} - - {% endfor %} - {% if item.actor|length > 5 %} - {% trans '更多' %} - + {% endif %} + {% endif %} +
+
+ {% if item.actor %} + {% trans '主演:' %} + {% for actor in item.actor %} + 5 %}style="display: none;"{% endif %}> + {{ actor }} + {% if not forloop.last %}/{% endif %} + + {% endfor %} + {% if item.actor|length > 5 %} + {% trans '更多' %} + - {% endif %} - {% endif %}
-
{% if item.genre %}{% trans '类型:' %} - {% for genre in item.genre %} - {{ genre }}{% if not forloop.last %} / {% endif %} - {% endfor %} - {% endif %}
-
{% if item.area %}{% trans '制片国家/地区:' %} - {% for area in item.area %} - {{ area }}{% if not forloop.last %} / {% endif %} - {% endfor %} - {% endif %}
-
{% if item.language %}{% trans '语言:' %} - {% for language in item.language %} - {{ language }}{% if not forloop.last %} / {% endif %} - {% endfor %} - {% endif %}
- -
-
-
{% if item.season_count %}{% trans '季数:' %}{{ item.season_count }}{% endif %}
-
{% if item.episode_count %}{% trans '集数:' %}{{ item.episode_count }}{% endif %}
-
{% if item.single_episode_length %}{% trans '单集长度:' %}{{ item.single_episode_length }}{% endif %}
- - {% with item.all_seasons as seasons %} - {% if seasons %} -
- {% trans '本剧所有季:' %} - {% for s in seasons %} - - {{ s.season_number }} - - {% endfor %} -
- {% endif %} - {% endwith %} - -
{% if item.showtime %}{% trans '播出时间:' %} - {% for showtime in item.showtime %} - {% for time, region in showtime.items %} - {{ time }}{% if region != '' %}({{ region }}){% endif %} - {% endfor %} - {% if not forloop.last %} / {% endif %} - {% endfor %} - {% endif %}
-
{% if item.other_title %}{% trans '又名:' %} - {% for t in item.other_title %} - {{ t }}{% if not forloop.last %} / {% endif %} - {% endfor %} - {% endif %}
-
{% if item.site %}{% trans '网站:' %} - {{ item.site|strip_scheme }} - {% endif %}
- {% if item.other_info %} - {% for k, v in item.other_info.items %} -
- {{ k }}:{{ v | urlize }} -
- {% endfor %} - {% endif %} - - - {% if item.last_editor and item.last_editor.preference.show_last_edit %} -
{% trans '最近编辑者:' %}{{ item.last_editor | default:"" }}
- {% endif %} - -
- {% if user.is_authenticated %} - {% trans '编辑' %}{{ item.demonstrative }} - {% endif %} - {% if user.is_staff %} - / {% trans '删除' %} - {% endif %} -
-
-{% endblock %} - - -{% block sidebar %} + + {% endif %} + {% endif %} +
+
+ {% if item.genre %} + {% trans '类型:' %} + {% for genre in item.genre %} + {{ genre }} + {% if not forloop.last %}/{% endif %} + {% endfor %} + {% endif %} +
+
+ {% if item.area %} + {% trans '制片国家/地区:' %} + {% for area in item.area %} + {{ area }} + {% if not forloop.last %}/{% endif %} + {% endfor %} + {% endif %} +
+
+ {% if item.language %} + {% trans '语言:' %} + {% for language in item.language %} + {{ language }} + {% if not forloop.last %}/{% endif %} + {% endfor %} + {% endif %} +
+
+ {% if item.season_count %} + {% trans '季数:' %}{{ item.season_count }} + {% endif %} +
+
+ {% if item.episode_count %} + {% trans '集数:' %}{{ item.episode_count }} + {% endif %} +
+
+ {% if item.single_episode_length %} + {% trans '单集长度:' %}{{ item.single_episode_length }} + {% endif %} +
+ {% with item.all_seasons as seasons %} + {% if seasons %} +
+ {% trans '本剧所有季:' %} + {% for s in seasons %} + + {{ s.season_number }} + + {% endfor %} +
+ {% endif %} + {% endwith %} +
+ {% if item.showtime %} + {% trans '播出时间:' %} + {% for showtime in item.showtime %} + {% for time, region in showtime.items %} + {{ time }} + {% if region != '' %}({{ region }}){% endif %} + + {% endfor %} + {% if not forloop.last %}/{% endif %} + {% endfor %} + {% endif %} +
+
+ {% if item.other_title %} + {% trans '又名:' %} + {% for t in item.other_title %} + {{ t }} + {% if not forloop.last %}/{% endif %} + {% endfor %} + {% endif %} +
+
+ {% if item.site %} + {% trans '网站:' %} + {{ item.site|strip_scheme }} + {% endif %} +
+ {% if item.other_info %} + {% for k, v in item.other_info.items %}
{{ k }}:{{ v|urlizetrunc:24 }}
{% endfor %} + {% endif %} {% endblock %} diff --git a/catalog/templates/work.html b/catalog/templates/work.html index 16df8f0e..d354a06d 100644 --- a/catalog/templates/work.html +++ b/catalog/templates/work.html @@ -9,11 +9,7 @@ {% load truncate %} {% load strip_scheme %} {% load thumb %} - -{% block details %} -{% endblock %} - +{% block details %}{% endblock %} -{% block sidebar %} -{% endblock %} +{% block sidebar %}{% endblock %} diff --git a/catalog/urls.py b/catalog/urls.py index 63f4927a..468861cb 100644 --- a/catalog/urls.py +++ b/catalog/urls.py @@ -72,10 +72,24 @@ urlpatterns = [ recast, name="recast", ), + re_path( + r"^(?P" + + _get_all_url_paths() + + ")/(?P[A-Za-z0-9]{21,22})/comments", + comments, + name="comments", + ), re_path( r"^(?P" + _get_all_url_paths() + ")/(?P[A-Za-z0-9]{21,22})/reviews", + reviews, + name="reviews", + ), + re_path( + r"^(?P" + + _get_all_url_paths() + + ")/(?P[A-Za-z0-9]{21,22})/review_list", review_list, name="review_list", ), diff --git a/catalog/views.py b/catalog/views.py index c52cf8f1..94169dad 100644 --- a/catalog/views.py +++ b/catalog/views.py @@ -11,7 +11,7 @@ from django.core.paginator import Paginator from catalog.common.models import ExternalResource, IdealIdTypes from .models import * from django.views.decorators.clickjacking import xframe_options_exempt -from journal.models import Mark, ShelfMember, Review, query_item_category +from journal.models import Mark, ShelfMember, Review, Comment, query_item_category from journal.models import ( query_visible, query_following, @@ -85,32 +85,28 @@ def retrieve(request, item_path, item_uuid): ) mark = None review = None - mark_list = None - review_list = None + my_collections = [] collection_list = [] shelf_types = [(n[1], n[2]) for n in iter(ShelfTypeNames) if n[0] == item.category] if request.user.is_authenticated: visible = query_visible(request.user) mark = Mark(request.user, item) review = mark.review + my_collections = item.collections.all().filter(owner=request.user) collection_list = ( item.collections.all() + .exclude(owner=request.user) .filter(visible) .annotate(like_counts=Count("likes")) .order_by("-like_counts") ) - mark_query = ( - ShelfMember.objects.filter(item=item) - .filter(visible) - .order_by("-created_time") + else: + collection_list = ( + item.collections.all() + .filter(visibility=0) + .annotate(like_counts=Count("likes")) + .order_by("-like_counts") ) - mark_list = [member.mark for member in mark_query[:NUM_REVIEWS_ON_ITEM_PAGE]] - review_list = ( - Review.objects.filter(item=item) - .filter(visible) - .order_by("-created_time")[:NUM_REVIEWS_ON_ITEM_PAGE] - ) - return render( request, item.class_name + ".html", @@ -119,8 +115,7 @@ def retrieve(request, item_path, item_uuid): "focus_item": focus_item, "mark": mark, "review": review, - "mark_list": mark_list, - "review_list": review_list, + "my_collections": my_collections, "collection_list": collection_list, "shelf_types": shelf_types, }, @@ -273,7 +268,7 @@ def episode_data(request, item_uuid): if request.GET.get("last"): qs = qs.filter(pub_date__lt=request.GET.get("last")) return render( - request, "podcast_episode_data.html", {"item": item, "episodes": qs[:10]} + request, "podcast_episode_data.html", {"item": item, "episodes": qs[:5]} ) @@ -326,27 +321,62 @@ def review_list(request, item_path, item_uuid): @login_required +def comments(request, item_path, item_uuid): + item = get_object_or_404(Item, uid=get_uuid_or_404(item_uuid)) + if not item: + raise Http404() + queryset = Comment.objects.filter(item=item).order_by("-created_time") + queryset = queryset.filter(query_visible(request.user)) + before_time = request.GET.get("last") + if before_time: + queryset = queryset.filter(created_time__lte=before_time) + return render( + request, + "item_comments.html", + { + "comments": queryset[:11], + }, + ) + + +@login_required +def reviews(request, item_path, item_uuid): + item = get_object_or_404(Item, uid=get_uuid_or_404(item_uuid)) + if not item: + raise Http404() + queryset = Review.objects.filter(item=item).order_by("-created_time") + queryset = queryset.filter(query_visible(request.user)) + before_time = request.GET.get("last") + if before_time: + queryset = queryset.filter(created_time__lte=before_time) + return render( + request, + "item_reviews.html", + { + "reviews": queryset[:11], + }, + ) + + def discover(request): if request.method != "GET": raise BadRequest() user = request.user if user.is_authenticated: layout = user.get_preference().discover_layout - top_tags = user.tag_manager.all_tags[:10] else: layout = [] - top_tags = [] - cache_key = "public_gallery_list" + cache_key = "public_gallery" gallery_list = cache.get(cache_key, []) - for gallery in gallery_list: - ids = ( - random.sample(gallery["item_ids"], 10) - if len(gallery["item_ids"]) > 10 - else gallery["item_ids"] - ) - gallery["items"] = Item.objects.filter(id__in=ids) + # for gallery in gallery_list: + # ids = ( + # random.sample(gallery["item_ids"], 10) + # if len(gallery["item_ids"]) > 10 + # else gallery["item_ids"] + # ) + # gallery["items"] = Item.objects.filter(id__in=ids) if user.is_authenticated: podcast_ids = [ @@ -355,41 +385,39 @@ def discover(request): ShelfType.PROGRESS, ItemCategory.Podcast ) ] - episodes = PodcastEpisode.objects.filter(program_id__in=podcast_ids).order_by( - "-pub_date" - )[:5] - gallery_list.insert( - 0, - { - "name": "my_recent_podcasts", - "title": "在听播客的近期更新", - "items": episodes, - }, + recent_podcast_episodes = PodcastEpisode.objects.filter( + program_id__in=podcast_ids + ).order_by("-pub_date")[:10] + books_in_progress = Edition.objects.filter( + id__in=[ + p.item_id + for p in user.shelf_manager.get_members( + ShelfType.PROGRESS, ItemCategory.Book + ).order_by("-created_time")[:10] + ] ) - # books = Edition.objects.filter( - # id__in=[ - # p.item_id - # for p in user.shelf_manager.get_members( - # ShelfType.PROGRESS, ItemCategory.Book - # ).order_by("-created_time")[:10] - # ] - # ) - # gallery_list.insert( - # 0, - # { - # "name": "my_books_inprogress", - # "title": "正在读的书", - # "items": books, - # }, - # ) + tvshows_in_progress = Item.objects.filter( + id__in=[ + p.item_id + for p in user.shelf_manager.get_members( + ShelfType.PROGRESS, ItemCategory.TV + ).order_by("-created_time")[:10] + ] + ) + else: + recent_podcast_episodes = [] + books_in_progress = [] + tvshows_in_progress = [] return render( request, "discover.html", { "user": user, - "top_tags": top_tags, "gallery_list": gallery_list, + "recent_podcast_episodes": recent_podcast_episodes, + "books_in_progress": books_in_progress, + "tvshows_in_progress": tvshows_in_progress, "layout": layout, }, ) diff --git a/common/static/lib/css/calendar_yearview_blocks.css b/common/static/lib/css/calendar_yearview_blocks.css index d5785d0e..81045f0a 100644 --- a/common/static/lib/css/calendar_yearview_blocks.css +++ b/common/static/lib/css/calendar_yearview_blocks.css @@ -26,6 +26,5 @@ .wday, .month { font-variant: small-caps; - color: #222222; font-size: 12px; } diff --git a/common/static/lib/css/collection.css b/common/static/lib/css/collection.css index 887addb3..12addddb 100644 --- a/common/static/lib/css/collection.css +++ b/common/static/lib/css/collection.css @@ -180,8 +180,8 @@ div.jsoneditor-menu { } .donut { - margin-left: 20%; - width: 60%; + margin-left: 10%; + width: 80%; aspect-ratio : 1 / 1; border-radius: 50%; display: flex; @@ -198,7 +198,7 @@ div.jsoneditor-menu { justify-content: center; } -progress { +/* progress { border-radius: 7px; width: 80%; height: 10px; @@ -209,7 +209,7 @@ progress { } ::-webkit-progress-value { background-color: #00a1cc; -} +} */ .markdownx-editor { font-family: monospace; @@ -266,6 +266,6 @@ summary::-webkit-details-marker { transform: rotate(360deg); } -.fa-lock, .fa-spin { +.action-bar .fa-lock, .fa-spin { color: lightgray; } diff --git a/common/static/lib/js/calendar_yearview_blocks.js b/common/static/lib/js/calendar_yearview_blocks.js index 6c7abf06..40826d1a 100644 --- a/common/static/lib/js/calendar_yearview_blocks.js +++ b/common/static/lib/js/calendar_yearview_blocks.js @@ -167,7 +167,7 @@ // Fixed size with width= 721 and height = 110 var wire_html = - '' + + '' + '' + loop_html + '' + '"Your browser does not support inline SVG."' + diff --git a/common/static/sass/_common.sass b/common/static/sass/_common.sass new file mode 100644 index 00000000..8cc005a5 --- /dev/null +++ b/common/static/sass/_common.sass @@ -0,0 +1,15 @@ + +@mixin clear + content: ' ' + clear: both + display: table + +// Breakpoints +// Small devices (landscape phones, 576px and up) +$small-devices: 575.98px +// Medium devices (tablets, 768px and up) +$medium-devices: 767.98px +// Large devices (desktops, 992px and up) +$large-devices: 991.98px +// Extra large devices (large desktops, 1200px and up) +$x-large-devices: 1199.98px diff --git a/common/static/scss/_card.scss b/common/static/scss/_card.scss new file mode 100644 index 00000000..8929d285 --- /dev/null +++ b/common/static/scss/_card.scss @@ -0,0 +1,48 @@ +div.item { + display: grid; + grid-template-columns: + calc(4rem * var(--pico-line-height)) auto; + column-gap: var(--pico-block-spacing-horizontal); + align-items: top; + justify-content: left; + word-break: break-all; + + &.player { + display: block; + } + + hgroup { + margin: 0; + } + + h5 small, + hgroup span { + font-weight: 400; + font-size: 80%; + color: var(--pico-muted-color); + + &.category { + opacity: 0.5; + } + + } + + .metadata { + display: -webkit-box; + -webkit-line-clamp: 1; + -webkit-box-orient: vertical; + overflow: hidden; + font-size: 80%; + } + + .brief { + text-overflow: ellipsis; + word-wrap: break-word; + overflow: hidden; + -webkit-line-clamp: 4; + display: -webkit-box; + -webkit-box-orient: vertical; + font-size: 80%; + } + +} diff --git a/common/static/scss/_collection.scss b/common/static/scss/_collection.scss new file mode 100644 index 00000000..cce0f745 --- /dev/null +++ b/common/static/scss/_collection.scss @@ -0,0 +1,25 @@ +#collection-page { + @media (max-width: 768px) { + main>article>header { + display: flex; + flex-flow: column nowrap; + + #collection-cover { + img { + height: 20vw; + } + + height: unset !important; + order: -1; + } + + #collection-feature { + order: 99; + + .donut { + display: none; + } + } + } + } +} diff --git a/common/static/scss/_common.scss b/common/static/scss/_common.scss new file mode 100644 index 00000000..9f44418b --- /dev/null +++ b/common/static/scss/_common.scss @@ -0,0 +1,95 @@ +body { + --pico-block-spacing-vertical: var(--pico-block-spacing-horizontal); +} + +article.item-card { + padding: calc(var(--pico-block-spacing-horizontal)); + margin-bottom: calc(var(--pico-block-spacing-vertical) / 2); + + header, + footer { + padding: calc(var(--pico-block-spacing-horizontal) / 2); + } + + section { + margin-bottom: calc(var(--pico-spacing) / 1); + } +} + +article.item-card.external h5 { + font-style: italic; +} + +details { + margin: 0; +} + +section section { + margin-bottom: var(--pico-spacing) !important; +} + +.empty { + font-style: italic; + font-size: 80%; + color: var(--pico-muted-color); +} + +.oneline { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; +} + +.handler { + color: var(--pico-muted-color); + word-break: break-all; + opacity: 0.5; +} + +.loading { + color: var(--pico-muted-color); + opacity: 0.5; +} + +.action { + width: max-content; + float: right; + + &.inline { + float: none; + } + + >span { + padding-left: calc(var(--pico-spacing)/2); + } + + a { + cursor: pointer; + text-decoration: none; + } + + a.hidden { + display: none; + } + + a.disabled, + .timestamp { + cursor: unset; + color: var(--pico-muted-color); + opacity: 0.5; + } + + a:not(:hover) { + color: var(--pico-muted-color); + opacity: 0.5; + } + + a.activated:not(:hover) { + color: var(--pico-primary); + opacity: 1; + } +} + +form img { + max-height: 20vh; +} diff --git a/common/static/scss/_dialog.scss b/common/static/scss/_dialog.scss new file mode 100644 index 00000000..cfba0596 --- /dev/null +++ b/common/static/scss/_dialog.scss @@ -0,0 +1,103 @@ +dialog { + header { + margin-bottom: 16px; + padding: 8px !important; + } + + &>article { + /* Animate when opening */ + animation-name: zoomIn; + animation-duration: 150ms; + animation-timing-function: ease; + } + + &.closing { + /* Animate when closing */ + animation-name: fadeOut; + animation-duration: 150ms; + animation-timing-function: ease; + } + + &.closing>article { + /* Aniate when closing */ + animation-name: zoomOut; + animation-duration: 150ms; + animation-timing-function: ease; + } + + @keyframes fadeIn { + 0% { + opacity: 0; + } + + 100% { + opacity: 1; + } + } + + @keyframes fadeOut { + 0% { + opacity: 1; + } + + 100% { + opacity: 0; + } + } + + @keyframes zoomIn { + 0% { + transform: scale(0.9); + } + + 100% { + transform: scale(1); + } + } + + @keyframes zoomOut { + 0% { + transform: scale(1); + } + + 100% { + transform: scale(0.9); + } + } + + .grid>div { + min-width: max-content; + } + + .grid>div:nth-child(2) fieldset { + float: right; + } + + + @media (max-width: 768px) { + .grid>div:nth-child(2) fieldset { + float: unset; + } + } + + fieldset { + margin: 0; + } + + article { + padding-bottom: 1em; + } + + input[type="date"] { + margin: 0px; + } + + .tag-input { + margin-bottom: var(--pico-spacing); + } + + .rating-editor { + font-size: 120%; + padding-bottom: 1em; + } +} diff --git a/common/static/scss/_feed.scss b/common/static/scss/_feed.scss new file mode 100644 index 00000000..cc7ed93e --- /dev/null +++ b/common/static/scss/_feed.scss @@ -0,0 +1,63 @@ +.feed-page { + .feed { + .avatar { + height: calc(1rem * var(--pico-line-height) + var(--pico-nav-link-spacing-vertical) * 4 - 8px); + width: calc(1rem * var(--pico-line-height) + var(--pico-nav-link-spacing-vertical) * 4 - 8px); + + img { + height: 100%; + width: 100%; + } + } + + section { + margin-bottom: 2vh; + display: grid; + grid-template-columns: auto 1fr; + grid-template-rows: auto; + grid-column-gap: 1em; + grid-row-gap: 0px; + // align-items: center; + // justify-content: left; + + .nickname a { + word-break: break-all; + } + + .time { + width: max-content; + float: right; + color: var(--pico-muted-color); + opacity: 0.5; + margin-left: 1em; + } + + .rating-star { + margin: 0 0.5em; + } + + .rating-star, + .rating-star>* { + color: var(--pico-primary); + } + + + article { + padding: calc(var(--pico-block-spacing-horizontal) / 2); + + footer { + padding: calc(var(--pico-block-spacing-horizontal) / 2); + margin-left: calc(var(--pico-block-spacing-horizontal) * -0.5); + margin-right: calc(var(--pico-block-spacing-horizontal) * -0.5); + margin-top: 0; + } + + margin-right: 4px; + } + + .spacing { + margin-bottom: var(--pico-block-spacing-horizontal); + } + } + } +} diff --git a/common/static/scss/_gallery.scss b/common/static/scss/_gallery.scss new file mode 100644 index 00000000..d3e8f0fb --- /dev/null +++ b/common/static/scss/_gallery.scss @@ -0,0 +1,120 @@ +.calendar_view { + overflow-x: scroll; + + svg { + height: 108px; + } +} + +.shelf { + max-width: 1400px; + padding: 0; + + * { + box-sizing: border-box; + padding: 0; + margin: 0; + } + + .cards { + display: grid; + grid-auto-columns: 100%; + grid-column-gap: var(--pico-block-spacing-horizontal); + grid-auto-flow: column; + padding: var(--pico-nav-link-spacing-vertical) 0px; + list-style: none; + overflow-x: scroll; + scroll-snap-type: x mandatory; + + &::-webkit-scrollbar { + height: var(--pico-nav-link-spacing-vertical); + } + + &::-webkit-scrollbar-thumb, + &::-webkit-scrollbar-track { + border-radius: 92px; + } + + &::-webkit-scrollbar-thumb { + background: var(--pico-range-active-border-color); + } + + &::-webkit-scrollbar-track { + background: var(--pico-range-border-color); + } + + + } + + .card { + display: flex; + flex-direction: column; + padding: 0px; + // border-radius: 5px; + scroll-snap-align: start; + transition: all 0.2s; + text-align: center; + vertical-align: middle; + + a { + margin-top: auto; + } + + img { + background: var(--pico-background-color); + // border-radius: 5px; + width: 100%; + min-height: 3rem; + vertical-align: middle; + margin: auto; + font-size: 80%; + border: 2px solid transparent; + } + + a:hover img { + border: 2px solid var(--pico-primary-hover); + } + + a>div { + font-size: 80%; + text-overflow: ellipsis; + word-wrap: break-word; + overflow: hidden; + -webkit-line-clamp: 2; + display: -webkit-box; + -webkit-box-orient: vertical; + min-height: 2.5rem; + } + } + + @media (min-width: 200px) { + .cards { + grid-auto-columns: calc(25% - var(--pico-block-spacing-horizontal)); + } + } + + @media (min-width: 500px) { + .cards { + grid-auto-columns: calc(20% - var(--pico-block-spacing-horizontal)); + } + } + + @media (min-width: 769px) { + .cards { + grid-auto-columns: calc(calc(100% / 6) - var(--pico-block-spacing-horizontal)); + } + } + + @media (min-width: 1281px) { + .cards { + grid-auto-columns: calc(calc(100% / 8) - var(--pico-block-spacing-horizontal)); + } + } +} + +@media (min-width: 769px) { + .sidebar .shelf .cards { + grid-auto-columns: calc(34% - var(--pico-spacing)) !important; + gap: var(--pico-spacing) !important; + } +} diff --git a/common/static/scss/_header.scss b/common/static/scss/_header.scss new file mode 100644 index 00000000..144c2611 --- /dev/null +++ b/common/static/scss/_header.scss @@ -0,0 +1,220 @@ +body>header { + padding: 0px !important; +} + +.nav-logo { + margin-left: 8px !important; +} + +.nav-dropdown { + margin-right: 8px !important; +} + +.nav-dropdown ul li a { + padding: var(--pico-form-element-spacing-vertical) var(--pico-form-element-spacing-horizontal) !important +} + +nav form { + margin-bottom: 0px !important; +} + +nav summary { + border: 0px !important; +} + +nav ul { + min-width: -webkit-max-content; + min-width: -moz-max-content; +} + +nav ul li a.current { + font-weight: bold; + color: var(--pico-primary); +} + +.nav-search, +.nav-search li { + width: 100%; +} + +.nav-search select { + max-width: max-content; +} + +.nav-search input[type="submit"] { + background-color: var(--pico-secondary-background); + border-color: var(--pico-secondary-background); + padding-left: calc(var(--pico-nav-link-spacing-horizontal)*2); + padding-right: calc(var(--pico-nav-link-spacing-horizontal)*2); +} + +.nav-logo img { + max-height: 56px; +} + +.unhide { + display: unset !important; +} + +.nav-dropdown summary { + padding-top: 0px !important; + padding-bottom: 0px !important; +} + +.nav-dropdown summary::after { + height: calc(1rem * var(--pico-line-height, 1.5) + 8px) !important; +} + +.avatar { + line-height: 0; + /* remove line-height */ + display: inline-block; + /* circle wraps image */ + border: 4px solid lightgray; + border-radius: 50%; + /* relative value */ + transition: linear 0.25s; +} + +.avatar img { + border-radius: 50%; + height: calc(1rem * var(--pico-line-height) + var(--pico-nav-link-spacing-vertical) * 2 - 8px); + width: calc(1rem * var(--pico-line-height) + var(--pico-nav-link-spacing-vertical) * 2 - 8px); +} + +.avatar:hover { + transition: ease-out 0.2s; + border: 4px solid var(--pico-primary); + -webkit-transition: ease-out 0.2s; +} + +@media (max-width: 1200px) { + :root { + --pico-font-size: 80%; + } +} + +@media (min-width: 769px) { + .small-only { + display: none; + } + +} + +@media (max-width: 768px) { + :root { + --pico-font-size: 100%; + } + + .large-only { + display: none; + } + + body>header { + position: sticky; + top: 0px; + background: var(--pico-background-color); + z-index: 999; + box-shadow: var(--pico-box-shadow); + } + + nav { + display: flex; + flex-flow: row wrap; + } + + .nav-logo img { + max-height: 28px; + } + + .nav-search { + order: 999; + display: none; + } + + .nav-search li { + padding: 0px; + padding-bottom: 4px; + } + + .nav-search input[type="submit"] { + padding-left: calc(var(--pico-nav-link-spacing-horizontal)*1); + padding-right: calc(var(--pico-nav-link-spacing-horizontal)*1); + } + + .nav-dropdown::after { + flex-basis: 100%; + width: 0; + } + + .nav-dropdown { + margin-right: 0 !important; + } +} + +/* Dark color scheme (Auto) */ +/* Automatically enabled if user has Dark mode enabled */ +@media only screen and (prefers-color-scheme: dark) { + .nav-logo img { + filter: brightness(100%) grayscale(100%) invert(40%); + } +} + +/* Dark color scheme (Forced) */ +/* Enabled if forced with data-theme="dark" */ +.nav-logo img [data-theme="dark"] { + filter: brightness(100%) grayscale(100%) invert(40%); +} + +.tldr { + overflow: hidden; + text-overflow: " ... [点击展开]"; + display: -webkit-box; + -webkit-line-clamp: 10; + -webkit-box-orient: vertical; +} + +.tldr-2 { + overflow: hidden; + text-overflow: " ... [点击展开]"; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; +} + +.hidden { + display: none; +} + +.invisible { + visibility: hidden; +} + +a:not(:hover) { + text-decoration: none; +} + +body>footer { + padding-top: 0.4em !important; + text-align: center; + margin-bottom: 4px !important; + width: 100%; + + .footer__border { + width: 100%; + padding-top: 4px; + } + + .footer__link { + margin: 0 12px; + white-space: nowrap; + min-width: 15vw; + } + + @media (min-width: 769px) { + clear: both; + position: absolute !important; + left: 50%; + transform: translateX(-50%); + } +} diff --git a/common/static/scss/_item.scss b/common/static/scss/_item.scss new file mode 100644 index 00000000..81f84870 --- /dev/null +++ b/common/static/scss/_item.scss @@ -0,0 +1,165 @@ +#item-page { + a:not(:hover) { + text-decoration: none; + } + + .muted { + color: var(--pico-muted-color); + } + + .muted a:not(:hover) { + color: var(--pico-muted-color); + } + + .muted .fa-lock { + filter: brightness(50%) !important; + } + + a>button { + padding: 2px 8px; + font-size: 75%; + } + + main>div { + margin: 2vw; + } + + main>div>section { + margin-bottom: 2vw; + } + + #item-title { + margin-bottom: 0; + } + + #item-title-more { + margin-top: 0; + } + + #item-title h1 { + margin: 0; + } + + h1 small { + font-size: 80%; + margin-left: 0.5vw; + font-weight: 400; + } + + + + #item-cover { + align-items: stretch; + } + + #item-cover img { + transition: transform 0.2s ease-in-out; + filter: drop-shadow(0 0.2rem 0.8rem rgba(0, 0, 0, 0.2)); + max-width: 80%; + max-height: 50vh; + } + + .item-edit, + .item-edit a { + color: var(--pico-muted-color); + } + + + + .item-action { + display: flex; + flex-flow: row wrap; + justify-content: center; + align-items: center; + margin-top: 5%; + } + + .item-action button { + margin-left: 4px; + margin-right: 4px; + } + + .mark { + text-align: center; + } + + #item-cover { + text-align: center; + } + + main { + padding-top: 1vh !important; + } + + @media (min-width: 769px) { + #item-title h1 { + display: inline; + } + + .middle { + margin: auto; + width: 50%; + } + + .left { + float: left; + clear: left; + width: 22%; + margin-left: 0; + margin-right: 3%; + } + + .right { + float: right; + clear: right; + width: 22%; + margin-left: 3%; + margin-right: 0%; + } + } + + @media (max-width: 768px) { + main { + display: flex; + flex-flow: column nowrap; + } + + #item-title { + order: -5; + display: flex; + flex-flow: column nowrap; + } + + #item-title .site-list { + order: -1; + } + + #item-title-more { + order: -4; + } + + #item-cover { + order: -3; + } + + #item-primary-action { + order: -2; + } + + #item-primary-mark { + order: -2; + } + + #item-metadata { + order: -1; + } + + #item-sidebar { + order: 99; + } + + #item-cover img { + max-width: 60vw; + } + } +} diff --git a/common/static/scss/_layout.scss b/common/static/scss/_layout.scss new file mode 100644 index 00000000..cc812871 --- /dev/null +++ b/common/static/scss/_layout.scss @@ -0,0 +1,144 @@ +.classic-page, +.feed-page { + main { + width: 100vw; + display: grid; + grid-template-columns: 64% 32%; + grid-template-areas: "main aside"; + gap: 4%; + padding: calc(3*var(--pico-spacing)); + + .grid__main { + grid-area: main; + } + + .grid__aside { + grid-area: aside; + } + } + + @media (max-width: 768px) { + main { + display: flex; + flex-flow: column nowrap; + padding: 0; + gap: 0; + + .grid__main { + order: -1; + margin: var(--pico-spacing); + } + + .grid__aside { + order: -2; + } + + ul { + padding: 0; + } + + aside.bottom { + order: 2 !important; + } + + + } + } +} + +.content-page { + article { + margin: 0; + } + + main { + display: grid; + grid-template-columns: auto 25%; + grid-template-areas: "main aside"; + gap: calc(3*var(--pico-spacing)); + padding: calc(3*var(--pico-spacing)); + + &>div { + grid-area: main; + } + + &>aside { + grid-area: aside; + + article>div { + text-align: center; + } + } + } + + .owner-info { + height: max-content; + display: flex; + flex-direction: row; + gap: var(--pico-spacing); + + .owner { + .avatar { + width: calc(2rem * var(--pico-line-height)); + height: calc(2rem * var(--pico-line-height)); + + img { + width: 100%; + height: 100%; + } + } + } + + .info { + width: 70%; + } + + .more { + width: 20%; + } + } + + @media (max-width: 768px) { + main { + display: flex; + max-width: 100%; + flex-flow: column nowrap; + padding: 0; + gap: 0; + + >div { + order: 1; + // margin: var(--pico-spacing); + } + + >aside { + order: 2; + + article.item { + display: grid; + grid-template-columns: 25% auto; + grid-template-areas: "main aside"; + margin: calc(var(--pico-spacing)/2); + padding: 0; + border: var(--pico-border-width) solid var(--pico-card-border-color); + + >* { + margin: 0; + border: 0; + } + + >div { + margin-left: var(--pico-spacing); + background-color: var(--pico-card-sectioning-background-color); + align-items: center; + display: flex; + } + } + } + + ul { + padding: 0; + } + } + } +} diff --git a/common/static/scss/_legacy.sass b/common/static/scss/_legacy.sass new file mode 100644 index 00000000..45093622 --- /dev/null +++ b/common/static/scss/_legacy.sass @@ -0,0 +1,1028 @@ +$color-initial: #fff !default +$color-primary: #00a1cc !default +$color-secondary: #606c76 !default +$color-tertiary: #bbb !default +$color-quaternary: #d5d5d5 !default +$color-quinary: #e5e5e5 !default + +$color-light: #ccc +$color-bright: rgb(247, 247, 247) + + +@mixin clear + content: ' ' + clear: both + display: table + +// Breakpoints +// Small devices (landscape phones, 576px and up) +$small-devices: 575.98px +// Medium devices (tablets, 768px and up) +$medium-devices: 767.98px +// Large devices (desktops, 992px and up) +$large-devices: 767.98px +// Extra large devices (large desktops, 1200px and up) +$x-large-devices: 1199.98px + + +$main-section-padding: 32px 48px 32px 36px +$main-section-padding-mobile: 32px 28px 28px 28px + +$section-title-margin: 20px +$sub-sections-between-margin: 28px +$sub-section-title-margin: 8px + +.main-section-wrapper + + & input, & select + width: 100% + +// for search result etc +.entity-list + li + list-style: none + + & &__title + // font-size: large + margin-bottom: $section-title-margin + + & &__entities + + & &__entity + display: flex + margin-bottom: 36px + &::after + @include clear + + & &__entity-img + // float: left + object-fit: contain + // height: 150px + $width: 130px + min-width: $width + max-width: $width + + & &__entity-text + margin-left: 20px + overflow: hidden + width: 100% + // float: left + & .tag-collection + margin-left: -3px + + & &__entity-link + font-size: 1.2em + + & &__entity-title + display: block + + & &__entity-category + color: $color-tertiary + margin-left: 5px + position: relative; + top: -1px; + + & &__entity-info + // max-width: 73% + white-space: nowrap + overflow: hidden + display: inline-block + text-overflow: ellipsis + position: relative + top: 0.52em + &--full-length + // display: block + max-width: 100% + // margin-bottom: 12px + + & &__entity-brief + margin-top: 8px + display: -webkit-box + -webkit-box-orient: vertical + -webkit-line-clamp: 4 + overflow: hidden + margin-bottom: 0 + + $rating-info-gap-width: 5px + & &__rating + display: inline-block + margin: 0 + + & &__rating--empty + // width: $empty-rating-width + margin-right: $rating-info-gap-width + + & &__rating-score + margin-right: $rating-info-gap-width + position: relative + top: 1px + + & &__rating-star + display: inline + position: relative + top: 0.3em + left: -0.3em + +// detail page +.entity-detail + + & &__img + height: 210px + float: left + object-fit: contain + max-width: 150px + object-position: top + + & &__img-origin + cursor: zoom-in + + & &__info + float: left + margin-left: 20px + overflow: hidden + text-overflow: ellipsis + width: 70% + + & &__title + font-weight: bold + + & &__title--secondary + color: $color-tertiary + + & &__fields + display: inline-block + vertical-align: top + width: 46% + margin-left: 2% + // margin-bottom: 5px + & div, & span + margin: 1px 0 + & + .tag-collection + margin-top: 5px + margin-left: 6px + + & &__rating + position: relative + top: -5px + + & &__rating-star + position: relative + left: -4px + top: 3px + + & &__rating-score + font-weight: bold + + &::after + @include clear + +$mark-review-padding: 3px 0 +$mark-review-padding-wider: 6px 0 + +// includes title and description of an entity +// sub section +.entity-desc + margin-bottom: $sub-sections-between-margin + & &__title + display: inline-block + margin-bottom: $sub-section-title-margin + & &__content + overflow: hidden + &--folded + max-height: 202px + + & &__unfold-button + display: flex + color: $color-primary + background-color: transparent + justify-content: center + text-align: center + &--hidden + display: none + + & &__empty + +// includes marks of an entity +// sub section +.entity-marks + margin-bottom: $sub-sections-between-margin + & &__title + margin-bottom: $sub-section-title-margin + display: inline-block + & > a + margin-right: 5px + &--stand-alone + margin-bottom: $section-title-margin + + & &__more-link + margin-left: 5px + & &__mark-list + + & &__mark + margin: 0 + padding: $mark-review-padding + border-bottom: 1px dashed $color-quinary + &:last-child + border: none + &--wider + padding: $mark-review-padding-wider + + & &__mark-content + margin-bottom: 0 + + & &__mark-time + color: $color-light + margin-left: 2px + + & &__owner-link + + & &__rating-star + position: relative + top: 4px + + & &__empty + +// includes reviews of an entity +// sub section +.entity-reviews + // when used alone + &:first-child + margin-bottom: $sub-sections-between-margin + & &__title + display: inline-block + margin-bottom: $sub-section-title-margin + & > a + margin-right: 5px + &--stand-alone + margin-bottom: $section-title-margin + & &__more-link + margin-left: 5px + & &__review-list + + & &__review + margin: 0 + padding: $mark-review-padding + border-bottom: 1px dashed $color-quinary + &:last-child + border: none + &--wider + padding: $mark-review-padding-wider + + & &__review-time + color: $color-light + margin-left: 2px + & &__review-title + + & &__owner-link + + & &__empty + +.dividing-line + height: 0 + width: 100% + margin: 40px 0 24px 0 + border-top: solid 1px $color-light + &.dividing-line--dashed + margin: 0 + margin-top: 10px + margin-bottom: 2px + border-top: 1px dashed $color-quinary; + +// on home page +.entity-sort + position: relative + margin-bottom: 30px + & &__label + font-size: large + display: inline-block + margin-bottom: $section-title-margin + + & &__more-link + margin-left: 8px + + & &__count + // font-size: large + // letter-spacing: -1px + color: $color-tertiary + &::before + content: '(' + &::after + content: ')' + + & &__entity-list + display: flex + justify-content: flex-start + // padding-left: 24px + flex-wrap: wrap + + & &__entity + padding: 0 10px + flex-basis: 20% + text-align: center + display: inline-block + color: $color-secondary + &:hover + color: $color-primary + & > a + color: inherit + + & &__entity-img + height: 110px + + & &__entity-name + text-overflow: ellipsis + overflow: hidden + display: -webkit-box + -webkit-box-orient: vertical + -webkit-line-clamp: 2 + + & &__empty + + // for drag and sort purpose + &--placeholder + border: dashed $color-tertiary 4px + &--hover + padding: 10px + border: dashed $color-primary 2px !important + border-radius: 3px + &--sortable + padding: 10px + margin: 10px 0 + border: dashed $color-tertiary 2px + cursor: all-scroll + &--hidden + opacity: 0.4 + +.entity-sort-control + display: flex + justify-content: flex-end + + &__button + margin-top: 5px + margin-left: 12px + padding: 0 2px + cursor: pointer + color: $color-tertiary + &:hover + color: $color-primary + & > .icon-save svg, & > .icon-edit svg + fill: $color-primary + + &--float-right + position: absolute + top: 4px + right: 10px + + &__text + + +// follower/following list page +.related-user-list + & &__title + margin-bottom: $section-title-margin + + & &__user + display: flex + justify-content: flex-start + margin-bottom: 20px + + & &__user-info + margin-left: 15px + overflow: auto + + & &__user-name + + & &__user-bio + + & &__user-avatar + max-height: 72px + min-width: 72px + +// review detail page +.review-head + & &__title + display: inline-block + font-weight: bold + + & &__body + margin-bottom: 10px + &::after + @include clear + + & &__info + float: left + + & &__owner-link + color: $color-light + &:hover + color: $color-primary + + & &__time + color: $color-light + + & &__rating-star + position: relative + top: 3px + left: -1px + + & &__actions + float: right + + & &__action-link + &:not(:first-child) + margin-left: 5px + +// tag list +.tag-collection + margin-left: -9px + & &__tag + position: relative; + display: block; + float: left; + color: white; + background: $color-light; + padding: 5px; + // margin: 4px; + border-radius: .3rem; + line-height: 1.2em; + font-size: 80% + margin: 3px + & a + color: white + &:hover + color: $color-primary + +// Small devices (landscape phones, 576px and up) +@media (max-width: $small-devices) + .entity-list + & &__entity + // flex-direction: column + // margin-bottom: 30px + & &__entity-text + // margin-left: 0 + & &__entity-img-wrapper + // margin-bottom: 8px + & &__entity-img + max-width: 25vw + min-width: 25vw + & &__entity-info + // max-width: unset + white-space: unset + & &__rating--empty + &__entity-info + // max-width: 70% + & &__entity-brief + -webkit-line-clamp: 5 + + .action-bar + display: flex + flex-direction: column + + .entity-detail + flex-direction: column + & &__title + margin-bottom: 5px + & &__info + margin-left: 0 + float: none + display: flex + flex-direction: column + width: 100% + & &__img + margin-bottom: 24px + float: none + height: unset + max-width: 170px; + & &__fields + width: unset + margin-left: unset + & + .tag-collection + margin-left: -3px + + .entity-marks + & &__mark-list + + & &__mark + + & &__rating-star + + & &__rating-star + &__mark-time + // display: block + + .dividing-line + margin-top: 24px + + .entity-sort + & &__entity + flex-basis: 50% + // margin-left: 10px + // margin-right: 10px + & &__entity-img + height: 130px + + .review-head + & &__info + float: unset + & &__actions + float: unset + + .track-carousel + &__content + padding-bottom: 10px + &__track + min-width: 31% + max-width: 31% + margin-right: 4.5% + + +// Medium devices (tablets, 768px and up) +@media (max-width: $medium-devices) + .calendar_view + overflow-x: scroll + +// Large devices (desktops, 992px and up) +@media (max-width: $large-devices) + .main-section-wrapper + padding: $main-section-padding-mobile + + .entity-detail + display: flex + & &__info + // display: flex +// Extra large devices (large desktops, 1200px and up) +@media (max-width: $x-large-devices) + pass + +$aside-section-padding: 28px 25px 12px 25px +$aside-section-padding-mobile: 24px 25px 10px 25px + +.aside-section-wrapper + display: flex + flex: 1 + flex-direction: column + // align-items: center + width: 100% + padding: $aside-section-padding + background-color: $color-bright + margin-bottom: 30px + overflow: auto + + &--transparent + background-color: unset + &--collapse + padding: unset + +.add-entity-entries + + & &__entry + margin-bottom: 10px + + & &__label + font-size: 1.2em + margin-bottom: 8px + + & &__button + line-height: unset; + height: unset; + padding: 4px 15px; + margin: 5px; + +.action-panel + margin-bottom: 20px + + & &__label + // font-size: 1.2em + font-weight: bold + margin-bottom: 12px + + & &__button-group + display: flex + justify-content: space-between + &--center + justify-content: center + + & &__button + line-height: unset + height: unset + padding: 4px 15px + margin: 0 5px + // width: 100% + +.mark-panel + margin-bottom: 20px + + & &__status + font-weight: bold + + & &__rating-star + position: relative + top: 2px + + & &__actions + float: right + & form + display: inline + + & &__time + color: $color-light + margin-bottom: 10px + + & &__content + + & &__clear + @include clear + +.review-panel + & &__label + font-weight: bold + & &__actions + float: right + & &__time + color: $color-light + margin-bottom: 10px + + & &__review-title + display: block + margin-bottom: 15px + font-weight: bold + + & &__clear + @include clear + +.user-profile + & &__header + display: flex + align-items: flex-start + margin-bottom: 15px + + & &__avatar + width: 72px + + & &__username + font-size: large + margin-left: 10px + margin-bottom: 0 + + & &__bio + + & &__report-link + color: $color-light + +.user-relation + & &__label + display: inline-block + font-size: large + margin-bottom: 10px + + & &__more-link + margin-left: 5px + + & &__related-user-list + display: flex + justify-content: flex-start + &:last-of-type + margin-bottom: 0 + + & &__related-user + flex-basis: 25% + padding: 0px 3px + text-align: center + display: inline-block + overflow: hidden + & > a + &:hover + color: $color-secondary + + & &__related-user-avatar + background-image: url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7") + width: 48px + height: 48px + @media (min-width: $small-devices) and (max-width: $large-devices) + height: unset + width: 60% + max-width: 96px + + + & &__related-user-name + color: inherit + overflow: hidden + text-overflow: ellipsis + // display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; + +$panel-padding : 0 + +.report-panel + & &__label + display: inline-block + margin-bottom: 10px + + & &__body + padding-left: $panel-padding + + & &__report-list + + & &__report + margin: 2px 0 + + & &__user-link + margin: 0 2px + + & &__all-link + margin-left: 5px + +.import-panel + overflow-x: hidden + + & &__label + display: inline-block + margin-bottom: 10px + + & &__body + padding-left: $panel-padding + border: 2px dashed #00a1cc + padding: 6px 9px + form + margin: 0 + + @media (max-width: $large-devices) + border: unset + padding-left: 0 + + & &__help + background-color: $color-quinary + border-radius: 100000px + display: inline-block + width: 16px + height: 16px + text-align: center + font-size: 12px + cursor: help + + & &__checkbox + display: inline-block + margin-right: 10px + label + display: inline + input[type="checkbox"] + margin: 0 + position: relative + top: 2px + &--last + margin-right: 0 + + & &__file-input + margin-top: 10px + + & &__button + line-height: unset + height: unset + padding: 4px 15px + + & &__progress + padding-top: 10px + // padding-top: 4px + &:not(:first-child) + border-top: $color-tertiary 1px dashed + label + display: inline + progress + background-color: $color-quaternary + border-radius: 0 + height: 10px + width: 54% + + progress::-webkit-progress-bar + background-color: $color-quaternary + + progress::-webkit-progress-value + background-color: $color-primary + + progress::-moz-progress-bar + background-color: $color-quaternary + + & &__last-task + &:not(:first-child) + padding-top: 4px + border-top: $color-tertiary 1px dashed + .index:not(:last-of-type) + margin-right: 8px + + & &__fail-urls + margin-top: 10px + li + word-break: break-all + ul + // padding: 4px + max-height: 100px + overflow-y: auto + + +.relation-dropdown + & &__button + display: none + +.entity-card + display: flex + margin-bottom: 10px + flex-direction: column + &--horizontal + flex-direction: row + + & &__img + height: 150px + + & &__rating-star + position: relative + top: 4px + left: -3px + + & &__rating-score + position: relative + top: 1px + margin-left: 2px + + & &__title + margin-bottom: 10px + margin-top: 5px + + & &__info-wrapper + &--horizontal + margin-left: 20px + + & &__img-wrapper + flex-basis: 100px + + +// Small devices (landscape phones, 576px and up) +@media (max-width: $small-devices) + .add-entity-entries + display: block !important + & &__button + width: 100% + margin: 5px 0 5px 0; + + .aside-section-wrapper + &:first-child + margin-right: 0 !important + margin-bottom: 0 !important + &--singular:first-child + margin-bottom: 20px !important + // &--no-padding + // padding: $aside-section-padding-mobile !important + // margin-top: 0 !important + .action-panel + flex-direction: column !important + + .entity-card + &--horizontal + flex-direction: column !important + // flex-direction: column !important + & &__info-wrapper + margin-left: 10px !important + &--horizontal + margin-left: 0 !important +// Medium devices (tablets, 768px and up) +@media (max-width: $medium-devices) + pass +// Large devices (desktops, 992px and up) +@media (max-width: $large-devices) + .add-entity-entries + display: flex + justify-content: space-around + + .aside-section-wrapper + padding: $aside-section-padding-mobile + + margin-top: 20px + // &:not(:first-child) + &:not(:last-child) + margin-right: 20px + &--collapse + padding: $aside-section-padding-mobile !important + margin-top: 0 + margin-bottom: 0 + &:first-child + margin-right: 0 + &--no-margin + margin: 0 + + .action-panel + flex-direction: row + & &__button-group + justify-content: space-evenly + + .relation-dropdown + margin-bottom: 20px + & &__button + padding-bottom: 10px + background-color: $color-bright + width: 100% + display: flex + justify-content: center + align-items: center + cursor: pointer + transition: transform 0.3s + &:focus + background-color: red + + & &__button > .icon-arrow + transition: transform 0.3s + + & &__button:hover > .icon-arrow > svg + fill: $color-primary + + & &__button > .icon-arrow--expand + transform: rotate(-180deg) + + & &__body + background-color: $color-bright + max-height: 0 + transition: max-height 1s ease-out + overflow: hidden + + & &__body--expand + max-height: 2000px + transition: max-height 1s ease-in + + .entity-card + flex-direction: row + & &__info-wrapper + margin-left: 30px + +.entity-list ul + 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 +// Medium devices (tablets, 768px and up) +@media (max-width: $medium-devices) + pass +// Large devices (desktops, 992px and up) +@media (max-width: $large-devices) + pass +// Extra large devices (large desktops, 1200px and up) +@media (max-width: $x-large-devices) + pass + +img.emoji + height: 14px + box-sizing: border-box + object-fit: contain + position: relative + top: 3px + + &--large + height: 20px + position: relative + top: 2px diff --git a/common/static/scss/_legacy2.scss b/common/static/scss/_legacy2.scss new file mode 100644 index 00000000..7eedb997 --- /dev/null +++ b/common/static/scss/_legacy2.scss @@ -0,0 +1,191 @@ +/*** django-jsoneditor ***/ +div.jsoneditor { + border-color: #ccc !important; +} + +div.jsoneditor-menu { + background-color: #606c76 !important; + border-color: #606c76 !important; +} + +/***** MODAL DIALOG ****/ +#modal { + /* Underlay covers entire screen. */ + position: fixed; + top: 0px; + bottom: 0px; + left: 0px; + right: 0px; + background-color: rgba(0, 0, 0, 0.5); + z-index: 1000; + + /* Flexbox centers the .modal-content vertically and horizontally */ + display: flex; + flex-direction: column; + align-items: center; + + /* Animate when opening */ + animation-name: fadeIn; + animation-duration: 150ms; + animation-timing-function: ease; +} + +#modal>.modal-underlay { + /* underlay takes up the entire viewport. This is only + required if you want to click to dismiss the popup */ + position: absolute; + z-index: -1; +} + +#modal>.modal-content { + /* Position visible dialog near the top of the window */ + margin-top: 10vh; + + /* Sizing for visible dialog */ + width: 80%; + max-width: 600px; + + /* Display properties for visible dialog*/ + background-color: #f7f7f7; + padding: 20px 20px 10px 20px; + color: #606c76; + + /* Animate when opening */ + animation-name: zoomIn; + animation-duration: 150ms; + animation-timing-function: ease; +} + +#modal.closing { + /* Animate when closing */ + animation-name: fadeOut; + animation-duration: 150ms; + animation-timing-function: ease; +} + +#modal.closing>.modal-content { + /* Aniate when closing */ + animation-name: zoomOut; + animation-duration: 150ms; + animation-timing-function: ease; +} + +@keyframes fadeIn { + 0% { + opacity: 0; + } + + 100% { + opacity: 1; + } +} + +@keyframes fadeOut { + 0% { + opacity: 1; + } + + 100% { + opacity: 0; + } +} + +@keyframes zoomIn { + 0% { + transform: scale(0.9); + } + + 100% { + transform: scale(1); + } +} + +@keyframes zoomOut { + 0% { + transform: scale(1); + } + + 100% { + transform: scale(0.9); + } +} + +#modal .add-to-list-modal__head { + margin-bottom: 20px; +} + +#modal .add-to-list-modal__head::after { + content: ' '; + clear: both; + display: table; +} + +#modal .add-to-list-modal__title { + font-weight: bold; + font-size: 1.2em; + float: left; +} + +#modal .add-to-list-modal__close-button { + float: right; + cursor: pointer; +} + +#modal .add-to-list-modal__confirm-button { + float: right; +} + +#modal li, +#modal ul, +#modal label { + display: inline; +} + +.donut { + margin-left: 10%; + width: 80%; + aspect-ratio: 1 / 1; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; +} + +.donut .hole { + width: 80%; + aspect-ratio: 1 / 1; + border-radius: 50%; + background: #f7f7f7; + display: flex; + align-items: center; + justify-content: center; +} + +input:invalid#position { + border: red dashed 1px; +} + +.gg-play-button-o { + box-sizing: border-box; + position: relative; + display: inline-block; + transform: scale(var(--ggs, 1)); + width: 22px; + height: 22px; + border: 2px solid; + border-radius: 20px +} + +.gg-play-button-o::before { + content: ""; + display: inline-block; + box-sizing: border-box; + position: absolute; + width: 0; + height: 10px; + border-top: 5px solid transparent; + border-bottom: 5px solid transparent; + border-left: 6px solid; + top: 4px; + left: 7px +} diff --git a/common/static/scss/_login.scss b/common/static/scss/_login.scss new file mode 100644 index 00000000..fbc82c0f --- /dev/null +++ b/common/static/scss/_login.scss @@ -0,0 +1,72 @@ +.login-page { + input:invalid { + border: var(--pico-primary) dashed 2px; + } + + mark { + padding: 0; + } + + .autoComplete_wrapper>ul { + padding: 0; + } + + .autoComplete_wrapper>ul li { + list-style: none; + padding: 0 var(--pico-spacing); + } + + .autoComplete_wrapper>ul>li:hover { + cursor: pointer; + background-color: var(--pico-text-selection-color); + } + + .autoComplete_wrapper>ul>li[aria-selected="true"] { + background-color: var(--pico-text-selection-color); + } + + article { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + min-width: max-content; + width: 50%; + height: max-content; + + p { + max-width: 50vw; + } + } + + header img { + padding: 10%; + } + + html, + body { + height: 100%; + display: flex; + align-items: center; + justify-content: center; + } + + @media (max-width: 768px) { + article { + width: 96%; + + p { + max-width: 96vw; + } + } + } + + body>footer { + color: lightgrey; + text-align: center; + bottom: 0px; + position: fixed; + width: 100%; + padding: 0; + } +} diff --git a/common/static/scss/_mark.scss b/common/static/scss/_mark.scss new file mode 100644 index 00000000..9f12e3c2 --- /dev/null +++ b/common/static/scss/_mark.scss @@ -0,0 +1,68 @@ +#mark_date { + width: unset !important; +} + +.tag-input { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-pack: start; + -ms-flex-pack: start; + justify-content: flex-start; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + border-bottom: 0.1rem solid #ccc; + padding-bottom: 2px; +} + +.tag-input__tag { + position: relative; + display: block; + float: left; + color: white; + background: #d5d5d5; + padding: 5px 20px 5px 6px; + margin: 4px; + border-radius: .4rem; + line-height: 1em; + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; + height: min-content; +} + +.tag-input__tag.tag-input__tag--highlight { + color: white; + background: #00a1cc; +} + +.tag-input__close { + position: absolute; + top: 0; + right: 0; + width: 14px; + height: 100%; + cursor: pointer; + border-radius: 0 2px 2px 0; + -webkit-transition: background 0.2s; + transition: background 0.2s; +} + +.tag-input__close:after { + position: absolute; + content: "×"; + top: 5px; + left: 2px; +} + +.tag-input__close:hover { + color: #606c76; +} + +.tag-input input { + border: 0; + margin: 4px; + padding: 3px 7px 3px 0; + width: auto; + outline: none; + height: unset; +} diff --git a/common/static/scss/_markdown.scss b/common/static/scss/_markdown.scss new file mode 100644 index 00000000..455b43d5 --- /dev/null +++ b/common/static/scss/_markdown.scss @@ -0,0 +1,31 @@ +.spoiler { + background: grey; + filter: blur(2px); + cursor: pointer; + + &.revealed { + background: unset; + filter: unset; + color: inherit; + } +} + +.markdown-content { + + h1 { + font-size: 1.5rem + } + + h2 { + font-size: 1.25rem + } + + h3 { + font-size: 1.1rem; + } +} + + +.markdownx-editor { + font-family: monospace; +} diff --git a/common/static/scss/_rating.scss b/common/static/scss/_rating.scss new file mode 100644 index 00000000..e8f3cbc5 --- /dev/null +++ b/common/static/scss/_rating.scss @@ -0,0 +1,133 @@ +.rating { + margin-top: 2vh; + margin-bottom: 2vh; + position: relative; +} + +.rating * { + margin: 0; + padding: 0; +} + +.rating h3 small { + font-weight: 200; +} + +.rating .display { + display: flex; + flex: row; + vertical-align: bottom; +} + +.rating .display div { + align-items: center; + justify-content: center; + display: flex; +} + +.rating .display div:nth-child(1) { + width: max-content; + padding-right: 12px; +} + +.rating .display div:nth-child(2) { + flex-grow: 100; +} + +.rating .chart { + flex-grow: 100; + display: table; + table-layout: fixed; + height: max-content; +} + +.rating .chart li { + position: relative; + display: table-cell; + vertical-align: bottom; + height: 6vh; +} + +.rating .chart span { + margin: 2%; + display: block; + background: var(--pico-muted-color); + border-radius: 0.5vh 0.5vh 0 0; +} + +.rating .undisplay { + position: absolute; + display: none; + align-items: center; + justify-content: center; + inset: 0; +} + +.rating.unavailable .undisplay { + display: flex; +} + +.rating.unavailable .display { + filter: blur(0.5vh); +} + +.rating.unavailable .chart li:nth-child(1) span { + height: 10% !important; +} + +.rating.unavailable .chart li:nth-child(2) span { + height: 20% !important; +} + +.rating.unavailable .chart li:nth-child(3) span { + height: 30% !important; +} + +.rating.unavailable .chart li:nth-child(4) span { + height: 40% !important; +} + +.rating.unavailable .chart li:nth-child(5) span { + height: 50% !important; +} + +// Rating Star with Font Awesome +.rating-star { + position: relative; + vertical-align: middle; + font-family: FontAwesome; + display: inline-block; +} + +.rating-star:before { + content: "\f006 \f006 \f006 \f006 \f006"; + text-underline-offset: 0.125em; + line-height: 1; + vertical-align: middle; +} + +.rating-star>div { + position: absolute; + left: 0; + top: 0; + white-space: nowrap; + overflow: hidden; +} + +.rating-star>div:before { + content: "\f005 \f005 \f005 \f005 \f005"; + text-underline-offset: 0.125em; + line-height: 1; + vertical-align: middle; +} + +.rating-star .yellow { + color: #F68127; + text-shadow: 0 0 1px #FFE205, 0 0 2px #FFE205, 0 0 6px #EDD205; + // color: #FFE205; +} + +.rating-editor { + cursor: pointer !important; + border: none !important; +} diff --git a/common/static/scss/_sidebar.scss b/common/static/scss/_sidebar.scss new file mode 100644 index 00000000..beb1cc05 --- /dev/null +++ b/common/static/scss/_sidebar.scss @@ -0,0 +1,84 @@ +.sidebar { + article { + padding: calc(var(--pico-block-spacing-horizontal)); + } + + details { + margin-bottom: 0; + } + + hgroup { + margin: 0; + margin-right: 1rem; + } + + section.announcement { + p { + font-size: 80%; + } + + // details[open]>summary { + // margin: 0; + // } + + details { + margin-bottom: var(--pico-spacing); + } + } + + section.profile { + summary>div { + display: grid; + grid-template-columns: auto 1fr; + grid-template-rows: auto; + grid-column-gap: 0.5em; + grid-row-gap: 0px; + // align-items: center; + // justify-content: left; + } + + summary::after { + position: relative; + bottom: calc(2rem * var(--pico-line-height)); + } + + a:hover { + text-decoration: none; + } + + .avatar { + height: calc(1rem * var(--pico-line-height) + var(--pico-nav-link-spacing-vertical) * 4); + width: calc(1rem * var(--pico-line-height) + var(--pico-nav-link-spacing-vertical) * 4); + + img { + height: 100%; + width: 100%; + } + } + + .nickname { + word-break: break-all; + } + + + .relation { + width: max-content; + float: right; + color: var(--pico-muted-color); + opacity: 0.5; + margin-left: 1em; + } + + + } + + + @media (max-width: 768px) { + + section, + article, + details { + margin-bottom: 0; + } + } +} diff --git a/common/static/scss/_sitelabel.scss b/common/static/scss/_sitelabel.scss new file mode 100644 index 00000000..b7599f3a --- /dev/null +++ b/common/static/scss/_sitelabel.scss @@ -0,0 +1,115 @@ +.site-list { + a { + display: inline; + background: transparent; + padding: calc(var(--pico-spacing)/8); + border-radius: calc(var(--pico-spacing)/4); + border-style: solid; + border-width: 1px; + font-size: 80%; + margin: 3px; + // padding: 1px 3px; + // padding-top: 2px; + font-weight: lighter; + word-break: keep-all; + opacity: 1; + font-weight: 400; + text-decoration: none; + white-space: nowrap; + } + + .douban { + border: none; + color: white; + background-color: #319840; + } + + .spotify { + background-color: #1ed760; + color: black; + border: none; + font-weight: bold; + } + + .imdb { + background-color: #F5C518; + color: #121212; + border: none; + font-weight: bold; + } + + .igdb { + background-color: #323A44; + color: #DFE1E2; + border: none; + font-weight: bold; + } + + .steam { + background: linear-gradient(30deg, #1387b8, #111d2e); + color: white; + border: none; + // font-weight: 600 + // padding-top: 2px + } + + .bangumi { + background: #F09199; + color: #FCFCFC; + font-weight: 600; + } + + .goodreads { + background: #F4F1EA; + color: #372213; + font-weight: lighter; + } + + .bookstw { + background: #7FBA19; + color: white; + font-weight: lighter; + } + + .tmdb { + background: linear-gradient(90deg, #91CCA3, #1FB4E2); + color: white; + border: none; + font-weight: lighter; + padding-top: 2px; + } + + .googlebooks { + color: white; + background-color: #4285F4; + border-color: #4285F4; + } + + .rss { + color: white; + background-color: #E1A02F; + border-color: #E1A02F; + } + + .bandcamp { + color: white; + background-color: #28A0C1; + } + + // .bandcamp { + // color: white + // background-color: #28A0C1 + // // transform: skewX(-30deg) + // display: inline-block + // } + // .bandcamp span + // // transform: skewX(30deg) + // display: inline-block + // margin: 0 4px + + .discogs { + color: white; + background-color: #262626; + border-color: #262626; + } +} diff --git a/common/static/scss/_tag.scss b/common/static/scss/_tag.scss new file mode 100644 index 00000000..0c2c13e1 --- /dev/null +++ b/common/static/scss/_tag.scss @@ -0,0 +1,20 @@ +.tag-list { + // margin-bottom: var(--pico-spacing); + + span { + // margin: 3px; + + a { + color: white; + background: var(--pico-muted-color); + padding: calc(var(--pico-spacing)/4); + border-radius: calc(var(--pico-spacing)/4); + line-height: 1.2em; + font-size: 80%; + margin: 3px !important; + text-decoration: none; + white-space: nowrap; + display: inline-block; + } + } +} diff --git a/common/static/scss/neodb.scss b/common/static/scss/neodb.scss new file mode 100644 index 00000000..3af02899 --- /dev/null +++ b/common/static/scss/neodb.scss @@ -0,0 +1,18 @@ +@import '_header.scss'; +@import '_dialog.scss'; +@import '_rating.scss'; +@import '_mark.scss'; +@import '_item.scss'; +@import '_layout.scss'; +@import '_sitelabel.scss'; +@import '_tag.scss'; +@import '_markdown.scss'; +@import '_legacy.sass'; +@import '_legacy2.scss'; +@import '_collection.scss'; +@import '_feed.scss'; +@import '_card.scss'; +@import '_gallery.scss'; +@import '_sidebar.scss'; +@import '_common.scss'; +@import '_login.scss'; diff --git a/common/templates/400.html b/common/templates/400.html index 6c5047e6..3996b689 100644 --- a/common/templates/400.html +++ b/common/templates/400.html @@ -8,33 +8,23 @@ {% load thumb %} - - - - - {{ site_name }} - {% trans '无效请求' %} - {% include "common_libs.html" with jquery=0 %} - - - - -
-
- {% include "partial/_navbar.html" %} -
- - - -
- 无效的请求 -
-
- {{ exception }} - 您可能提交了无效的数据,或者相关内容已被作者删除。如果您确信这是我们的错误,欢迎通过页面底部的链接联系。 -
-
-
+ + + + {{ site_name }} - {% trans '无效请求' %} + {% include "common_libs.html" with jquery=0 v2=1 %} + + + {% include "_header.html" %} +
+
+
+

🤦🏻 无效的请求

+
+ {{ exception }} + 您可能提交了无效的数据,或者相关内容已被作者删除。如果您确信这是我们的错误,请通过页面底部的链接联系我们。 +
+
{% include "partial/_footer.html" %} -
- + diff --git a/common/templates/403.html b/common/templates/403.html index 7a18a32b..b3633a8c 100644 --- a/common/templates/403.html +++ b/common/templates/403.html @@ -8,32 +8,23 @@ {% load thumb %} - - - - - {{ site_name }} - {% trans '权限不符' %} - {% include "common_libs.html" with jquery=0 %} - - - - -
-
- {% include "partial/_navbar.html" %} -
- - - -
- 权限不符 -
-
- 您可能访问了错误的网址,或者相关内容已被作者隐藏。如果您确信这是我们的错误,欢迎通过页面底部的链接联系。 -
-
-
+ + + + {{ site_name }} - {% trans '权限不符' %} + {% include "common_libs.html" with jquery=0 v2=1 %} + + + {% include "_header.html" %} +
+
+
+

🤦🏻 权限不符

+
+ {{ exception }} + 您可能访问了错误的网址,或者相关内容已被作者删除。如果您确信这是我们的错误,请通过页面底部的链接联系我们。 +
+
{% include "partial/_footer.html" %} -
- + diff --git a/common/templates/404.html b/common/templates/404.html index 46d3ea92..e1bb5901 100644 --- a/common/templates/404.html +++ b/common/templates/404.html @@ -8,32 +8,22 @@ {% load thumb %} - - - - - {{ site_name }} - {% trans '未找到' %} - {% include "common_libs.html" with jquery=0 %} - - - - -
-
- {% include "partial/_navbar.html" %} -
- - - -
- 请求条目未找到 -
-
- 您可能输入了一个无效的网址,或者相关内容已被作者删除。如果您确信这是我们的错误,欢迎通过页面底部的链接联系。 -
-
-
+ + + + {{ site_name }} - {% trans '未找到' %} + {% include "common_libs.html" with jquery=0 v2=1 %} + + + {% include "_header.html" %} +
+
+
+

🤦🏻 条目未找到

+
+ 您可能输入了一个无效的网址,或者相关内容已被作者删除。如果您确信这是我们的错误,请通过页面底部的链接联系我们。 +
+
{% include "partial/_footer.html" %} -
- + diff --git a/common/templates/500.html b/common/templates/500.html index 7213d7de..41d63a12 100644 --- a/common/templates/500.html +++ b/common/templates/500.html @@ -8,32 +8,22 @@ {% load thumb %} - - - - - {{ site_name }} - {% trans '系统错误' %} - {% include "common_libs.html" with jquery=0 %} - - - - -
-
- {% include "partial/_navbar.html" %} -
- - - -
- 系统内部错误 -
-
- 发生了一个内部错误,如果这个错误多次出现,后台会记录并会由人工处理。如果您有紧急情况或疑问,欢迎通过页面底部的链接联系。 -
-
-
+ + + + {{ site_name }} - {% trans '系统错误' %} + {% include "common_libs.html" with jquery=0 v2=1 %} + + + {% include "_header.html" %} +
+
+
+

🤦🏻 系统内部错误

+
+ 发生了一个内部错误,如果这个错误多次出现,后台会记录并会由人工处理。如果您有紧急情况或任何疑问,请通过页面底部的链接联系我们。 +
+
{% include "partial/_footer.html" %} -
- + diff --git a/common/templates/503.html b/common/templates/503.html index 069b11f4..302115a6 100644 --- a/common/templates/503.html +++ b/common/templates/503.html @@ -1,22 +1,25 @@ - - - - -☃ NeoDB 升级中... - - -
-
-
NeoDB 升级中...
-
-
-
-
-
-
-
-
- - - - \ No newline at end of file + + + + diff --git a/common/templates/_header.html b/common/templates/_header.html new file mode 100644 index 00000000..282a5eca --- /dev/null +++ b/common/templates/_header.html @@ -0,0 +1,103 @@ +{% load admin_url %} +{% load static %} +{% load i18n %} +
+ +
+{% if messages %} +
+
    + {% for message in messages %} +
  • {{ message }}
  • + {% endfor %} +
+
+{% endif %} diff --git a/common/templates/_sidebar.html b/common/templates/_sidebar.html new file mode 100644 index 00000000..e7c62461 --- /dev/null +++ b/common/templates/_sidebar.html @@ -0,0 +1,215 @@ +{% load static %} +{% load i18n %} +{% load admin_url %} +{% load mastodon %} +{% load oauth_token %} +{% load truncate %} +{% load thumb %} +{% load collection %} +{% load bleach_tags %} + + diff --git a/common/templates/api_doc.html b/common/templates/api_doc.html index 301a1656..885fc004 100644 --- a/common/templates/api_doc.html +++ b/common/templates/api_doc.html @@ -1,24 +1,28 @@ - - - -{{ api.title }} API Documentation -{% include "common_libs.html" with jquery=0 %} - - - -
- {% include "partial/_navbar.html" %} - -
-
- - - + -
- + + diff --git a/common/templates/common/error.html b/common/templates/common/error.html index 7e29db79..fec60e03 100644 --- a/common/templates/common/error.html +++ b/common/templates/common/error.html @@ -1,32 +1,29 @@ -{% load i18n %} {% load static %} +{% load i18n %} +{% load l10n %} +{% load admin_url %} +{% load mastodon %} +{% load oauth_token %} +{% load truncate %} +{% load thumb %} - + - - - - - {% trans '错误' %} - - -
- - - - -
- {{ msg }} -
-
- {% if secondary_msg %} - {{ secondary_msg }} - {% endif %} -
- -
- - - \ No newline at end of file + {{ site_name }} - {% trans '错误' %} + {% include "common_libs.html" with jquery=0 v2=1 %} + + + {% include "_header.html" %} +
+
+
+

🙅🏻 {{ msg }}

+
+ {{ secondary_msg|default:"" }} +
+
+ {% include "partial/_footer.html" %} + + diff --git a/common/templates/partial/_announcement.html b/common/templates/partial/_announcement.html index c8aecf90..82b407ba 100644 --- a/common/templates/partial/_announcement.html +++ b/common/templates/partial/_announcement.html @@ -6,41 +6,39 @@ {% load truncate %} {% load thumb %}
- -
\ No newline at end of file + diff --git a/common/templates/widgets/image.html b/common/templates/widgets/image.html index 64cf282a..bfd2c280 100644 --- a/common/templates/widgets/image.html +++ b/common/templates/widgets/image.html @@ -1,5 +1,11 @@ - - + + \ No newline at end of file + diff --git a/common/templates/widgets/tag.html b/common/templates/widgets/tag.html index 31e3eba1..1b4a4913 100644 --- a/common/templates/widgets/tag.html +++ b/common/templates/widgets/tag.html @@ -1,17 +1,14 @@
- +
- - - - - \ No newline at end of file diff --git a/common/templatetags/duration.py b/common/templatetags/duration.py index e2d61d88..a8834b31 100644 --- a/common/templatetags/duration.py +++ b/common/templatetags/duration.py @@ -1,6 +1,7 @@ from django import template from django.template.defaultfilters import stringfilter from django.utils.text import Truncator +from django.utils.safestring import mark_safe register = template.Library() @@ -14,3 +15,19 @@ def duration_format(value, unit): s = duration % 60 return f"{h}:{m:02}:{s:02}" if h else f"{m}:{s:02}" # return (f"{h}小时 " if h else "") + (f"{m}分钟" if m else "") + + +@register.filter(is_safe=True) +@stringfilter +def rating_star(value): + try: + v = float(value or 0) + except ValueError: + v = 0 + pct = round(10 * v) + if pct > 100: + pct = 100 + elif pct < 0: + pct = 0 + html = f'
' + return mark_safe(html) diff --git a/common/templatetags/highlight.py b/common/templatetags/highlight.py index cae95206..83cab300 100644 --- a/common/templatetags/highlight.py +++ b/common/templatetags/highlight.py @@ -2,7 +2,7 @@ from django import template from django.utils.safestring import mark_safe from django.template.defaultfilters import stringfilter from opencc import OpenCC - +import re cc = OpenCC("t2s") register = template.Library() @@ -23,7 +23,7 @@ def highlight(text, search): m = None for w in words: if otext[i : i + len(w)] == w: - m = f'{text[i:i+len(w)]}' + m = f"{text[i:i+len(w)]}" i += len(w) break if not m: @@ -31,3 +31,9 @@ def highlight(text, search): i += 1 rtext += m return mark_safe(rtext) + + +@register.filter +@stringfilter +def strip_season(text): + return re.sub(r"\s*第\s*\d+\s*季$", "", text) diff --git a/common/urls.py b/common/urls.py index c18e195b..aa327496 100644 --- a/common/urls.py +++ b/common/urls.py @@ -4,6 +4,7 @@ from .views import * app_name = "common" urlpatterns = [ path("", home), - path("api-doc/", api_doc), + path("api-doc/", api_doc, name="api_doc"), path("home/", home, name="home"), + path("me/", me, name="me"), ] diff --git a/common/views.py b/common/views.py index 35e38edc..ca0765f4 100644 --- a/common/views.py +++ b/common/views.py @@ -6,14 +6,23 @@ from .api import api @login_required +def me(request): + return redirect( + reverse("journal:user_profile", args=[request.user.mastodon_username]) + ) + + def home(request): - home = request.user.get_preference().classic_homepage - if home == 1: - return redirect( - reverse("journal:user_profile", args=[request.user.mastodon_username]) - ) - elif home == 2: - return redirect(reverse("social:feed")) + if request.user.is_authenticated: + home = request.user.get_preference().classic_homepage + if home == 1: + return redirect( + reverse("journal:user_profile", args=[request.user.mastodon_username]) + ) + elif home == 2: + return redirect(reverse("social:feed")) + else: + return redirect(reverse("catalog:discover")) else: return redirect(reverse("catalog:discover")) diff --git a/doc/install.md b/doc/install.md index 362d2bd8..3f995a78 100644 --- a/doc/install.md +++ b/doc/install.md @@ -47,6 +47,7 @@ Build static assets ``` python3 manage.py sass common/static/sass/boofilsic.sass common/static/css/boofilsic.min.css -t compressed python3 manage.py sass common/static/sass/boofilsic.sass common/static/css/boofilsic.css +python3 manage.py compilescss python3 manage.py collectstatic ``` diff --git a/journal/models.py b/journal/models.py index 9a94f516..d317412e 100644 --- a/journal/models.py +++ b/journal/models.py @@ -156,12 +156,6 @@ class Content(Piece): metadata = models.JSONField(default=dict) item = models.ForeignKey(Item, on_delete=models.PROTECT) - @cached_property - def mark(self): - m = Mark(self.owner, self.item) - m.review = self - return m - def __str__(self): return f"{self.uuid}@{self.item}" @@ -221,6 +215,16 @@ class Comment(Content): def html(self): return render_text(self.text) + @cached_property + def rating_grade(self): + return Rating.get_item_rating_by_user(self.item, self.owner) + + @cached_property + def mark(self): + m = Mark(self.owner, self.item) + m.comment = self + return m + @property def item_url(self): if self.focus_item: @@ -259,6 +263,19 @@ class Review(Content): def html_content(self): return render_md(self.body) + @property + def plain_content(self): + html = render_md(self.body) + return _RE_HTML_TAG.sub( + " ", _RE_SPOILER_TAG.sub("***", html.replace("\n", " ")) + ) + + @cached_property + def mark(self): + m = Mark(self.owner, self.item) + m.review = self + return m + @cached_property def rating_grade(self): return Rating.get_item_rating_by_user(self.item, self.owner) @@ -291,6 +308,9 @@ class Review(Content): return review +MIN_RATING_COUNT = 5 + + class Rating(Content): class Meta: unique_together = [["owner", "item"]] @@ -304,7 +324,7 @@ class Rating(Content): stat = Rating.objects.filter(item=item, grade__isnull=False).aggregate( average=Avg("grade"), count=Count("item") ) - return round(stat["average"], 1) if stat["count"] >= 5 else None + return round(stat["average"], 1) if stat["count"] >= MIN_RATING_COUNT else None @staticmethod def get_rating_count_for_item(item): @@ -337,9 +357,34 @@ class Rating(Content): rating = Rating.objects.filter(owner=user, item=item).first() return rating.grade if rating else None + @staticmethod + def get_rating_distribution_for_item(item): + stat = ( + Rating.objects.filter(item=item, grade__isnull=False) + .values("grade") + .annotate(count=Count("grade")) + .order_by("grade") + ) + g = [0] * 11 + t = 0 + for s in stat: + g[s["grade"]] = s["count"] + t += s["count"] + if t < MIN_RATING_COUNT: + return [0] * 5 + r = [ + 100 * (g[1] + g[2]) // t, + 100 * (g[3] + g[4]) // t, + 100 * (g[5] + g[6]) // t, + 100 * (g[7] + g[8]) // t, + 100 * (g[9] + g[10]) // t, + ] + return r + Item.rating = property(Rating.get_rating_for_item) Item.rating_count = property(Rating.get_rating_count_for_item) +Item.rating_dist = property(Rating.get_rating_distribution_for_item) class Reply(Piece): @@ -440,6 +485,17 @@ class List(Piece): ) member.delete() + def update_member_order(self, ordered_member_ids): + members = self.ordered_members + for m in self.members.all(): + try: + i = ordered_member_ids.index(m.id) + if m.position != i + 1: + m.position = i + 1 + m.save() + except ValueError: + pass + def move_up_item(self, item): members = self.ordered_members member = self.get_member_for_item(item) @@ -674,8 +730,11 @@ class ShelfManager: def get_shelf(self, shelf_type): return self.shelf_list[shelf_type] - def get_members(self, shelf_type, item_category): - return self.shelf_list[shelf_type].get_members_in_category(item_category) + def get_members(self, shelf_type, item_category=None): + if item_category: + return self.shelf_list[shelf_type].get_members_in_category(item_category) + else: + return self.shelf_list[shelf_type].members.all() # def get_items_on_shelf(self, item_category, shelf_type): # shelf = ( @@ -748,6 +807,7 @@ class CollectionMember(ListMember): _RE_HTML_TAG = re.compile(r"<[^>]*>") +_RE_SPOILER_TAG = re.compile(r'<(div|span)\sclass="spoiler">.*') class Collection(List): @@ -781,8 +841,9 @@ class Collection(List): html = render_md(self.brief) return _RE_HTML_TAG.sub(" ", html) - def is_featured_by_user(self, user): - return self.featured_by_users.all().filter(id=user.id).exists() + def featured_by_user_since(self, user): + f = FeaturedCollection.objects.filter(target=self, owner=user).first() + return f.created_time if f else None def get_stats_for_user(self, user): items = list(self.members.all().values_list("item_id", flat=True)) @@ -881,13 +942,15 @@ class TagManager: return sorted(list(map(lambda t: t["title"], tags))) @staticmethod - def all_tags_for_user(user): + def all_tags_for_user(user, public_only=False): tags = ( user.tag_set.all() .values("title") .annotate(frequency=Count("members__id")) .order_by("-frequency") ) + if public_only: + tags = tags.filter(visibility=0) return list(map(lambda t: t["title"], tags)) @staticmethod @@ -935,6 +998,10 @@ class TagManager: def all_tags(self): return TagManager.all_tags_for_user(self.owner) + @property + def public_tags(self): + return TagManager.all_tags_for_user(self.owner, public_only=True) + def add_item_tags(self, item, tags, visibility=0): for tag in tags: TagManager.add_tag_by_user(item, tag, self.owner, visibility) @@ -1021,6 +1088,10 @@ class Mark: def rating(self): return Rating.get_item_rating_by_user(self.item, self.owner) + @cached_property + def rating_grade(self): + return Rating.get_item_rating_by_user(self.item, self.owner) + @cached_property def comment(self): return Comment.objects.filter( @@ -1059,7 +1130,8 @@ class Mark: ) ) share_as_new_post = shelf_type != self.shelf_type - if shelf_type != self.shelf_type or visibility != self.visibility: + original_visibility = self.visibility + if shelf_type != self.shelf_type or visibility != original_visibility: self.shelfmember = self.owner.shelf_manager.move_item( self.item, shelf_type, visibility=visibility, metadata=metadata ) @@ -1082,11 +1154,11 @@ class Mark: metadata=self.metadata, timestamp=created_time, ) - if comment_text != self.text or visibility != self.visibility: + if comment_text != self.text or visibility != original_visibility: self.comment = Comment.comment_item_by_user( self.item, self.owner, comment_text, visibility ) - if rating_grade != self.rating or visibility != self.visibility: + if rating_grade != self.rating or visibility != original_visibility: Rating.rate_item_by_user(self.item, self.owner, rating_grade, visibility) self.rating = rating_grade if share: @@ -1120,7 +1192,7 @@ class Mark: ) -def reset_visibility_for_user(user: User, visibility: int): +def reset_journal_visibility_for_user(user: User, visibility: int): ShelfMember.objects.filter(owner=user).update(visibility=visibility) Comment.objects.filter(owner=user).update(visibility=visibility) Rating.objects.filter(owner=user).update(visibility=visibility) diff --git a/journal/renderers.py b/journal/renderers.py index d6aaa01e..04c8f2dc 100644 --- a/journal/renderers.py +++ b/journal/renderers.py @@ -37,7 +37,7 @@ def _spolier(s): r = l[1].split("!<", 1) return ( escape(l[0]) - + '' + + '' + escape(r[0]) + "" + (_spolier(r[1]) if len(r) == 2 else "") diff --git a/journal/templates/_feature_stats.html b/journal/templates/_feature_stats.html new file mode 100644 index 00000000..84730b5b --- /dev/null +++ b/journal/templates/_feature_stats.html @@ -0,0 +1,17 @@ + +{% if featured %} + + + +{% else %} + + + +{% endif %} diff --git a/journal/templates/add_to_collection.html b/journal/templates/add_to_collection.html index 0ad30452..89248e57 100644 --- a/journal/templates/add_to_collection.html +++ b/journal/templates/add_to_collection.html @@ -8,22 +8,27 @@ {% load truncate %} {% load highlight %} {% load thumb %} - - - - + diff --git a/journal/templates/collection_items.html b/journal/templates/collection_items.html index b2440c92..cc3781f5 100644 --- a/journal/templates/collection_items.html +++ b/journal/templates/collection_items.html @@ -1,27 +1,57 @@ {% load thumb %} {% load i18n %} {% load l10n %} -
    - {% for member in collection.ordered_members %} - {% with "list_item_"|add:member.item.class_name|add:".html" as template %} - {% include template with item=member.item mark=None collection_member=member %} - {% endwith %} - {% empty %} - 暂无条目 - {% endfor %} - {% if collection_edit %} -
  • -
    - {% csrf_token %} - - - -
    -
  • - {% endif %} -
-{% if msg %} - -{% endif %} \ No newline at end of file +
+ {% for member in collection.ordered_members %} + {% with "list_item_"|add:member.item.class_name|add:".html" as template %} + {% include template with item=member.item mark=None collection_member=member %} + {% endwith %} + {% empty %} + 暂无条目 + {% endfor %} +
+{% if collection_edit %} +
+ {% csrf_token %} + + + +
+ +
+ {% csrf_token %} + + +
+{% endif %} +{% if msg %}{% endif %} diff --git a/journal/templates/collection_share.html b/journal/templates/collection_share.html index f1f1a683..35b03234 100644 --- a/journal/templates/collection_share.html +++ b/journal/templates/collection_share.html @@ -8,43 +8,66 @@ {% load truncate %} {% load highlight %} {% load thumb %} - -