diff --git a/.gitignore b/.gitignore
index 59955fc3..c80a33c5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -198,7 +198,6 @@ cython_debug/
# $GIT_DIR/info/exclude or the core.excludesFile configuration variable as
# described in https://git-scm.com/docs/gitignore
*.pyc
-src
*DS_Store
*~
*.db
diff --git a/base/components/components.py b/base/components/components.py
new file mode 100644
index 00000000..7579bde0
--- /dev/null
+++ b/base/components/components.py
@@ -0,0 +1,51 @@
+from typing import Literal, Optional
+
+from django_components import Component, register
+from pydantic import BaseModel
+
+from base.pagination import PAGE_VAR, Pagination
+from base.templatetags.base_templatetags import querystring
+
+
+class PaginationItem(BaseModel):
+ kind: Literal["current", "ellipsis", "number"]
+ text: Optional[str | int] = None
+ attrs: Optional[dict] = None
+
+
+@register("pagination")
+class PaginationComponent(Component):
+ template_file = "pagination.html"
+
+ class Kwargs(BaseModel):
+ pagination_obj: Pagination
+ model_config = {"arbitrary_types_allowed": True}
+
+ def pagination_number(self, pagination: Pagination, num: int) -> PaginationItem:
+ """
+ Generates a list of `PaginatedItem`, each representing an individual page with
+ its associated properties in a pagination navigation list.
+ """
+ if num == pagination.paginator.ELLIPSIS:
+ return PaginationItem(kind="ellipsis", text=pagination.paginator.ELLIPSIS)
+ elif num == pagination.page_num:
+ return PaginationItem(kind="current", text=num)
+ else:
+ link = querystring(None, {**pagination.params, PAGE_VAR: num})
+ return PaginationItem(
+ kind="number",
+ text=num,
+ attrs={"href": link},
+ )
+
+ def get_template_data(self, args, kwargs, slots, context):
+ pagination = kwargs.pagination_obj
+ page_elements = [self.pagination_number(pagination, page_num) for page_num in pagination.page_range]
+ previous_page_link = f"?{PAGE_VAR}={pagination.page_num - 1}" if pagination.page.has_previous() else ""
+ next_page_link = f"?{PAGE_VAR}={pagination.page_num + 1}" if pagination.page.has_next() else ""
+ return {
+ "pagination": pagination,
+ "previous_page_link": previous_page_link,
+ "next_page_link": next_page_link,
+ "page_elements": page_elements,
+ }
diff --git a/base/components/pagination.html b/base/components/pagination.html
new file mode 100644
index 00000000..e2899ec9
--- /dev/null
+++ b/base/components/pagination.html
@@ -0,0 +1,30 @@
+{% if pagination.multi_page %}
+
+{% endif %}
diff --git a/base/templatetags/components.py b/base/templatetags/components.py
deleted file mode 100644
index a271b7d0..00000000
--- a/base/templatetags/components.py
+++ /dev/null
@@ -1,40 +0,0 @@
-from django import template
-from django.utils.html import format_html
-from django.utils.safestring import mark_safe
-
-from base.pagination import PAGE_VAR
-
-from .base_templatetags import querystring
-
-register = template.Library()
-
-
-@register.simple_tag
-def pagination_number(pagination, i):
- """
- Generate an individual page index link in a paginated list.
- """
- if i == pagination.paginator.ELLIPSIS:
- return format_html("{} ", pagination.paginator.ELLIPSIS)
- elif i == pagination.page_num:
- return format_html('{} ', i)
- else:
- link = querystring(None, pagination.params, {PAGE_VAR: i})
- return format_html(
- '{} ',
- link,
- i,
- mark_safe(' class="end"' if i == pagination.paginator.num_pages else ""),
- i,
- )
-
-
-@register.inclusion_tag("base/components/pagination.html", name="pagination")
-def pagination_tag(pagination):
- previous_page_link = f"?{PAGE_VAR}={pagination.page_num - 1}"
- next_page_link = f"?{PAGE_VAR}={pagination.page_num + 1}"
- return {
- "pagination": pagination,
- "previous_page_link": previous_page_link,
- "next_page_link": next_page_link,
- }
diff --git a/djangosnippets/settings/base.py b/djangosnippets/settings/base.py
index 76d884bd..ee45b4df 100644
--- a/djangosnippets/settings/base.py
+++ b/djangosnippets/settings/base.py
@@ -104,15 +104,18 @@ def user_url(user):
"django.contrib.messages.context_processors.messages",
"django.template.context_processors.request",
],
- "loaders": [(
- "django.template.loaders.cached.Loader", [
- "django.template.loaders.filesystem.Loader",
- "django.template.loaders.app_directories.Loader",
- "django_components.template_loader.Loader",
- ]
- )],
- 'builtins': [
- 'django_components.templatetags.component_tags',
+ "loaders": [
+ (
+ "django.template.loaders.cached.Loader",
+ [
+ "django.template.loaders.filesystem.Loader",
+ "django.template.loaders.app_directories.Loader",
+ "django_components.template_loader.Loader",
+ ],
+ )
+ ],
+ "builtins": [
+ "django_components.templatetags.component_tags",
],
},
}
diff --git a/djangosnippets/settings/production.py b/djangosnippets/settings/production.py
index 1f011110..16462a13 100644
--- a/djangosnippets/settings/production.py
+++ b/djangosnippets/settings/production.py
@@ -7,7 +7,7 @@
import sentry_sdk
from sentry_sdk.integrations.django import DjangoIntegration
-from .base import * # noqa: F403
+from .base import * # noqa
def env_to_bool(input):
@@ -23,18 +23,6 @@ def env_to_bool(input):
DEBUG = env_to_bool(os.environ.get("DEBUG", False))
-# Use the cached template loader.
-del TEMPLATES[0]["APP_DIRS"]
-TEMPLATES[0]["OPTIONS"]["loaders"] = (
- (
- "django.template.loaders.cached.Loader",
- (
- "django.template.loaders.filesystem.Loader",
- "django.template.loaders.app_directories.Loader",
- ),
- ),
-)
-
AWS_ACCESS_KEY_ID = os.environ.get("AWS_ACCESS_KEY_ID", "")
AWS_SECRET_ACCESS_KEY = os.environ.get("AWS_SECRET_ACCESS_KEY", "")
AWS_STORAGE_BUCKET_NAME = os.environ.get("AWS_STORAGE_BUCKET_NAME", "")
diff --git a/djangosnippets/settings/testing.py b/djangosnippets/settings/testing.py
index 10e26f65..b2081951 100644
--- a/djangosnippets/settings/testing.py
+++ b/djangosnippets/settings/testing.py
@@ -22,6 +22,7 @@
"django.contrib.sessions",
"django.contrib.sites",
"django.contrib.staticfiles",
+ "django_components",
"allauth",
"allauth.account",
"allauth.socialaccount",
@@ -53,13 +54,20 @@
os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, "cab", "tests", "templates"),
SNIPPETS_TEMPLATES_DIR,
],
- "APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
"django.template.context_processors.request",
],
+ "loaders": [
+ "django.template.loaders.filesystem.Loader",
+ "django.template.loaders.app_directories.Loader",
+ "django_components.template_loader.Loader",
+ ],
+ "builtins": [
+ "django_components.templatetags.component_tags",
+ ],
},
}
]
diff --git a/djangosnippets/static/scss/main.scss b/djangosnippets/static/scss/main.scss
index daf52980..aed047fc 100644
--- a/djangosnippets/static/scss/main.scss
+++ b/djangosnippets/static/scss/main.scss
@@ -359,61 +359,6 @@ body.simple {
}
}
-nav.pagination {
- display: flex;
- justify-content: center;
- text-align: center;
- ul {
- margin-left: 1rem;
- margin-right: 1rem;
- }
- li {
- display: inline-block;
- a, em, span {
- padding: 5px 10px;
- line-height: 20px;
- border: 1px solid transparent;
- border-radius: 6px;
- transition: border-color .2s cubic-bezier(0.3, 0, 0.5, 1);
- cursor: pointer;
- }
- a:hover {
- border-color: $secondary-color;
- text-decoration: none;
- }
- em {
- font-style: normal;
- cursor: default;
- }
- .current-page {
- font-weight: bold;
- color: white;
- background-color: $secondary-color;
- }
- .disabled {
- color: gray;
- cursor: default;
- border-color: transparent;
- }
- }
- .previous-page::before, .next-page::after {
- display: inline-block;
- width: 1rem;
- height: 1rem;
- vertical-align: text-bottom;
- content: "";
- background-color: currentColor;
- }
- .previous-page::before {
- clip-path: polygon(9.8px 12.8px, 8.7px 12.8px, 4.5px 8.5px, 4.5px 7.5px, 8.7px 3.2px, 9.8px 4.3px, 6.1px 8px, 9.8px 11.7px, 9.8px 12.8px);
- margin-right: 4px;
- }
- .next-page::after {
- clip-path: polygon(6.2px 3.2px, 7.3px 3.2px, 11.5px 7.5px, 11.5px 8.5px, 7.3px 12.8px, 6.2px 11.7px, 9.9px 8px, 6.2px 4.3px, 6.2px 3.2px);
- margin-left: 4px;
- }
-}
-
footer {
padding: 30px 0 30px 0;
clear: both;
diff --git a/djangosnippets/templates/base/components/pagination.html b/djangosnippets/templates/base/components/pagination.html
deleted file mode 100644
index 4b44fc99..00000000
--- a/djangosnippets/templates/base/components/pagination.html
+++ /dev/null
@@ -1,21 +0,0 @@
-{% load components %}
-
-{% if pagination.multi_page %}
-
-{% endif %}
diff --git a/djangosnippets/templates/cab/language_list.html b/djangosnippets/templates/cab/language_list.html
index 949b5e2b..27f9b550 100644
--- a/djangosnippets/templates/cab/language_list.html
+++ b/djangosnippets/templates/cab/language_list.html
@@ -1,5 +1,5 @@
{% extends "base.html" %}
-{% load core_tags components %}
+{% load core_tags %}
{% block head_title %}All languages{% endblock %}
@@ -12,6 +12,6 @@
{% endfor %}
- {% pagination pagination %}
+ {% component "pagination" pagination_obj=pagination / %}
{% endblock %}
diff --git a/djangosnippets/templates/cab/partials/language_list.html b/djangosnippets/templates/cab/partials/language_list.html
index 3a4c5a3c..3cf478f3 100644
--- a/djangosnippets/templates/cab/partials/language_list.html
+++ b/djangosnippets/templates/cab/partials/language_list.html
@@ -1,4 +1,4 @@
-{% load static components %}
+{% load static %}
@@ -10,5 +10,5 @@
All languages
{% endfor %}
- {% pagination pagination %}
+ {% component "pagination" pagination_obj=pagination / %}
diff --git a/djangosnippets/templates/cab/partials/most_bookmarked.html b/djangosnippets/templates/cab/partials/most_bookmarked.html
index 81445100..6054046b 100644
--- a/djangosnippets/templates/cab/partials/most_bookmarked.html
+++ b/djangosnippets/templates/cab/partials/most_bookmarked.html
@@ -1,5 +1,5 @@
-{% load core_tags components %}
+{% load core_tags %}
{% load static %}
Most bookmarked snippets{% if months %} last {{ months }} months{% endif %}
@@ -30,7 +30,7 @@ Most bookmarked snippets{% if months %} last {{ months }} months{% endif %}<
{% endfor %}
- {% pagination pagination %}
+ {% component "pagination" pagination_obj=pagination / %}
{{ hits }} snippet{{ hits|pluralize }} posted so far.
{% else %}
No snippets posted yet.
diff --git a/djangosnippets/templates/cab/partials/tag_list.html b/djangosnippets/templates/cab/partials/tag_list.html
index 010e94fb..663ad053 100644
--- a/djangosnippets/templates/cab/partials/tag_list.html
+++ b/djangosnippets/templates/cab/partials/tag_list.html
@@ -1,5 +1,5 @@
-{% load core_tags components %}
+{% load core_tags %}
All tags
{% if object_list %}
@@ -9,7 +9,7 @@
All tags
{% endfor %}
- {% pagination pagination %}
+ {% component "pagination" pagination_obj=pagination / %}
{% else %}
No tags have been used yet.
{% endif %}
diff --git a/djangosnippets/templates/cab/partials/top_rated.html b/djangosnippets/templates/cab/partials/top_rated.html
index 0a48a82b..644f4329 100644
--- a/djangosnippets/templates/cab/partials/top_rated.html
+++ b/djangosnippets/templates/cab/partials/top_rated.html
@@ -1,6 +1,6 @@
{% load static %}
-{% load core_tags components %}
+{% load core_tags %}
Top-rated snippets{% if months %} last {{ months }} months{% endif %}
@@ -28,7 +28,7 @@
Top-rated snippets{% if months %} last {{ months }} months{% endif %}
{% endfor %}
- {% pagination pagination %}
+ {% component "pagination" pagination_obj=pagination / %}
{{ hits }} snippet{{ hits|pluralize }} posted so far.
{% else %}
No snippets posted yet.
diff --git a/djangosnippets/templates/cab/snippet_list.html b/djangosnippets/templates/cab/snippet_list.html
index 5fceeb60..e28fd62f 100644
--- a/djangosnippets/templates/cab/snippet_list.html
+++ b/djangosnippets/templates/cab/snippet_list.html
@@ -1,5 +1,5 @@
{% extends "base.html" %}
-{% load core_tags components %}
+{% load core_tags %}
{% load static %}
{% block bodyclass %}snippet-list{% endblock %}
{% block head_title %}All snippets{% if months %} last {{ months }} months{% endif %}{% endblock %}
@@ -30,7 +30,7 @@
{% endfor %}
- {% pagination pagination %}
+ {% component "pagination" pagination_obj=pagination / %}
{{ hits }} snippet{{ hits|pluralize }} posted so far.
{% else %}
No snippets posted yet.
diff --git a/djangosnippets/templates/cab/tag_list.html b/djangosnippets/templates/cab/tag_list.html
index 77114235..635ae1e1 100644
--- a/djangosnippets/templates/cab/tag_list.html
+++ b/djangosnippets/templates/cab/tag_list.html
@@ -1,5 +1,5 @@
{% extends "base.html" %}
-{% load core_tags components %}
+{% load core_tags %}
{% block head_title %}All tags{% endblock %}
{% block bodyclass %}tags-list{% endblock %}
@@ -14,7 +14,7 @@
{% endfor %}
- {% pagination pagination %}
+ {% component "pagination" pagination_obj=pagination / %}
{% else %}
No tags have been used yet.
{% endif %}
diff --git a/djangosnippets/templates/cab/user_bookmarks.html b/djangosnippets/templates/cab/user_bookmarks.html
index 69be39c0..68063a2c 100644
--- a/djangosnippets/templates/cab/user_bookmarks.html
+++ b/djangosnippets/templates/cab/user_bookmarks.html
@@ -1,5 +1,5 @@
{% extends "base_user.html" %}
-{% load core_tags components %}
+{% load core_tags %}
{% block bodyclass %}bookmarks{% endblock %}
{% block head_title %}Your bookmarks{% endblock %}
@@ -29,7 +29,7 @@
- {% pagination pagination %}
+ {% component "pagination" pagination_obj=pagination / %}
{% else %}
You haven't bookmarked any snippets yet.
{% endif %}
diff --git a/djangosnippets/templates/cab/user_detail.html b/djangosnippets/templates/cab/user_detail.html
index 42915ebb..0e2043a3 100644
--- a/djangosnippets/templates/cab/user_detail.html
+++ b/djangosnippets/templates/cab/user_detail.html
@@ -1,5 +1,5 @@
{% extends "base_user.html" %}
-{% load cache core_tags components %}
+{% load cache core_tags %}
{% block bodyclass %}user{% endblock %}
{% load static %}
@@ -35,7 +35,7 @@
{% cache 600 author_detail_sidebar author.username %}
{% if request.user.username == author.username %}You've{% else %}{{ author.username }} has{% endif %} posted {{ author.snippet_set.count }} snippet{{ author.snippet_set.count|pluralize }}.
{% endcache %}
- {% pagination pagination %}
+ {% component "pagination" pagination_obj=pagination / %}
{% else %}
No snippets posted yet.
{% endif %}
diff --git a/theme/static_src/src/styles.css b/theme/static_src/src/styles.css
new file mode 100644
index 00000000..7f4b903c
--- /dev/null
+++ b/theme/static_src/src/styles.css
@@ -0,0 +1,28 @@
+@import "tailwindcss" important;
+
+@theme {
+ --color-base-orange-400: #ffc656;
+ --color-base-white-400: #fafafa;
+ --color-base-gray-400: #7a7a7a;
+}
+
+@layer components {
+ .arrow-left::before {
+ @apply content-[''] w-4 h-4 bg-current mr-2;
+ clip-path: polygon(9.8px 12.8px, 8.7px 12.8px, 4.5px 8.5px, 4.5px 7.5px, 8.7px 3.2px, 9.8px 4.3px, 6.1px 8px, 9.8px 11.7px, 9.8px 12.8px);
+ }
+
+ .arrow-right::after {
+ @apply content-[''] w-4 h-4 bg-current ml-2;
+ clip-path: polygon(6.2px 3.2px, 7.3px 3.2px, 11.5px 7.5px, 11.5px 8.5px, 7.3px 12.8px, 6.2px 11.7px, 9.9px 8px, 6.2px 4.3px, 6.2px 3.2px);
+ }
+}
+
+/**
+ * A catch-all path to Django template files, JavaScript, and Python files
+ * that contain Tailwind CSS classes and will be scanned by Tailwind to generate the final CSS file.
+ *
+ * If your final CSS file is not being updated after code changes, you may want to broaden or narrow
+ * the scope of this path.
+ */
+@source "../../../**/*.{html,py,js}";