فهرست منبع

:hammer: Migrate the website from Jekyll to Zensical

Replace the Jekyll and just-the-docs build with Zensical, the static site
generator from the Material for MkDocs team. MkDocs itself has been
unmaintained since August 2024, and Material for MkDocs is now in
maintenance mode.

README.md stays the source of truth. docs/README.md is a symlink to it,
so the GitHub README and the website cannot drift apart. The CNAME and
the Django logos are symlinked the same way.

Heading anchors are preserved. 59 of the 61 anchors are identical
between kramdown and Python-Markdown. The two that differ are the page
title and the duplicate Templates heading, and both are redirected by
docs/javascripts/anchor-compat.js. A check of the built page reports no
broken in-page links.

The Django palette moves from _sass/color_schemes/django.scss to
docs/stylesheets/django.css. The head and footer additions move from
_includes/head_custom.html and the _config.yml footer_content into
overrides/main.html.

The site gains search, which was disabled under Jekyll, along with dark
mode and a sticky table of contents.
Jeff Triplett 4 روز پیش
والد
کامیت
863f9803af

+ 35 - 0
.github/workflows/docs.yml

@@ -0,0 +1,35 @@
+name: Documentation
+
+on:
+  push:
+    branches:
+      - main
+
+permissions:
+  contents: read
+  pages: write
+  id-token: write
+
+concurrency:
+  group: pages
+  cancel-in-progress: false
+
+jobs:
+  deploy:
+    environment:
+      name: github-pages
+      url: ${{ steps.deployment.outputs.page_url }}
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/configure-pages@v6
+      - uses: actions/checkout@v7
+      - uses: actions/setup-python@v6
+        with:
+          python-version: 3.x
+      - run: pip install zensical
+      - run: zensical build --clean
+      - uses: actions/upload-pages-artifact@v5
+        with:
+          path: site
+      - uses: actions/deploy-pages@v5
+        id: deployment

+ 0 - 24
404.html

@@ -1,24 +0,0 @@
----
-layout: default
----
-
-<style type="text/css" media="screen">
-  .container {
-    margin: 10px auto;
-    max-width: 600px;
-    text-align: center;
-  }
-  h1 {
-    margin: 30px 0;
-    font-size: 4em;
-    line-height: 1;
-    letter-spacing: -1px;
-  }
-</style>
-
-<div class="container">
-  <h1>404</h1>
-
-  <p><strong>Page not found :(</strong></p>
-  <p>The requested page could not be found.</p>
-</div>

+ 0 - 5
Gemfile

@@ -1,5 +0,0 @@
-source "https://rubygems.org"
-
-gem "webrick"
-
-gem "github-pages", group: :jekyll_plugins

+ 0 - 144
_config.yml

@@ -1,144 +0,0 @@
-assets:
-  cachebust: hard
-
-title: Awesome Django
-email: your-email@example.com
-description: >-
-  A curated list of awesome things related to Django. Maintained by
-  William Vincent and Jeff Triplett
-baseurl: ""
-url: "https://awesomedjango.org"
-
-# Build settings
-markdown: kramdown
-remote_theme: pmarsceill/just-the-docs
-plugins:
-  - jekyll-feed
-  - jekyll-optional-front-matter
-  - jekyll-seo-tag
-include:
-  - "_projects"
-  - README.md
-exclude:
-  - "*.md"
-  - "*.py"
-  - docker*
-  - Gemfile*
-  - justfile
-  - LICENSE
-  - Makefile
-  - modd.conf
-  - node_modules
-  - package*.json
-  - requirements.*
-  - scripts
-  - src
-  - vendor
-defaults:
-  - scope:
-      path: "" # an empty string here means all files in the project
-    values:
-      layout: "default"
-  - scope:
-      path: "README.md"
-    values:
-      permalink: "/"
-      layout: "default"
-
-collections:  # process the _data folder
-  components:
-    output: false
-  projects:
-    output: false
-    permalink: /:collection/:name/
-
-aux_links:
-  "Awesome Django on GitHub":
-    - "//github.com/wsvincent/awesome-django"
-
-# Makes Aux links open in a new tab. Default is false
-aux_links_new_tab: false
-
-color_scheme: django
-# color_scheme: light
-
-heading_anchors: true
-
-footer_content: >-
-  <iframe src="https://django-news.com/embed?color1=ffffff&color2=092e20&color_bg_button=092e20&color_border=ccc&color_button=ffffff&color_links=979797&color_terms=808080&title=Subscribe+to+Django+News" width="450" height="310" frameborder="0" style="max-width: 100%"></iframe>
-
-  <div align="center">
-  <webring-css site="https://awesomedjango.org"></webring-css>
-  <script src="https://djangowebring.com/static/webring.js"></script>
-  </div>
-
-  <p>Please consider supporting Django by making a donation to the
-  <a rel="sponsored" href="https://www.djangoproject.com/fundraising/">Django Software Foundation</a>,
-  sponsoring via <a rel="sponsored" href="https://github.com/sponsors/django">GitHub Sponsors</a>,
-  or buying <a rel="sponsored" href="https://django.threadless.com/">official merchandise</a>.</p>
-
-  <p>Awesome Django is not associated with the Django Software Foundation.<br>
-  Django is a registered trademark of the Django Software Foundation.</p>
-
-# Footer last edited timestamp
-last_edit_timestamp: true # show or hide edit time - page must have `last_modified_date` defined in the frontmatter
-last_edit_time_format: "%b %e %Y at %I:%M %p" # uses ruby's time format: https://ruby-doc.org/stdlib-2.7.0/libdoc/time/rdoc/Time.html
-
-# Footer "Edit this page on GitHub" link text
-gh_edit_link: true # show or hide edit this page link
-gh_edit_link_text: "Edit this page on GitHub."
-gh_edit_repository: "https://github.com/wsvincent/awesome-django" # the github URL for your repo
-gh_edit_branch: "main" # the branch that your docs is served from
-# gh_edit_source: docs # the source that your files originate from
-gh_edit_view_mode: "tree" # "tree" or "edit" if you want the user to jump into the editor immediately
-
-
-search_enabled: false
-
-search:
-  # Split pages into sections that can be searched individually
-  # Supports 1 - 6, default: 2
-  heading_level: 2
-  # Maximum amount of previews per search result
-  # Default: 3
-  previews: 3
-  # Maximum amount of words to display before a matched word in the preview
-  # Default: 5
-  preview_words_before: 5
-  # Maximum amount of words to display after a matched word in the preview
-  # Default: 10
-  preview_words_after: 10
-  # Set the search token separator
-  # Default: /[\s\-/]+/
-  # Example: enable support for hyphenated search words
-  tokenizer_separator: /[\s/]+/
-  # Display the relative url in search results
-  # Supports true (default) or false
-  rel_url: true
-  # Enable or disable the search button that appears in the bottom right corner of every page
-  # Supports true or false (default)
-  button: false
-
-
-nav_external_links:
-  - title: Third-Party Packages
-    url: "#third-party-packages"
-    hide_icon: true
-  - title: Python Packages
-    url: "#python-packages"
-    hide_icon: true
-  - title: Resources
-    url: "#resources"
-    hide_icon: true
-  - title: Hosting
-    url: "#hosting"
-    hide_icon: true
-  - title: Projects
-    url: "#projects"
-    hide_icon: true
-  - title: Django REST Framework
-    url: "#django-rest-framework"
-    hide_icon: true
-  - title: Wagtail
-    url: "#wagtail"
-    hide_icon: true

+ 0 - 3
_includes/head_custom.html

@@ -1,3 +0,0 @@
-<link rel="icon" href="https://fav.farm/🦄" />
-<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-fork-ribbon-css/0.2.3/gh-fork-ribbon.min.css" />
-<script defer src="https://uma.cog.gs/script.js" data-website-id="744740f8-2ff1-4a59-9e0f-7740fc8ca3c4"></script>

+ 0 - 32
_sass/color_schemes/django.scss

@@ -1,32 +0,0 @@
-// Django Brand Colors
-$django-green-dark: #0C4B33;
-$django-green-mid: #20AA76;
-$django-green-light: #44B78B;
-$django-green-pale: #E8F5F0;
-$django-cream: #F9F7F3;
-
-// Theme Configuration
-$color-scheme: light;
-
-// Body & Text
-$body-background-color: $white;
-$body-heading-color: $django-green-dark;
-$body-text-color: #333333;
-
-// Links & Buttons
-$link-color: $django-green-mid;
-$btn-primary-color: $django-green-dark;
-$base-button-color: $django-green-pale;
-
-// Sidebar & Navigation
-$sidebar-color: $django-green-pale;
-$nav-child-link-color: $django-green-dark;
-
-// Code Blocks
-$code-background-color: $django-cream;
-
-// Search & Tables
-$search-background-color: $white;
-$search-result-preview-color: #666666;
-$table-background-color: $white;
-$feedback-color: darken($django-green-pale, 5%);

+ 0 - 24
compose.yml

@@ -1,24 +0,0 @@
-services:
-
-  devd:
-    image: ghcr.io/jefftriplett/devd:latest
-    command: ["--livewatch"]
-    init: true
-    ports:
-      - 8000:8000
-    volumes:
-      - .:/srv/jekyll:cached
-
-  jekyll:
-    image: jekyll/jekyll:latest
-    command: ["jekyll", "serve", "--host", "0.0.0.0", "--port", "4000", "--livereload", "--drafts", "--force_polling"]
-    environment:
-      - BUNDLE_PATH=/usr/local/bundle
-    expose:
-      - 4000
-    init: true
-    ports:
-      - 4000:4000
-    volumes:
-      - .:/srv/jekyll:cached
-      - ./.vendor/bundle:/usr/local/bundle:cached

+ 1 - 0
docs/CNAME

@@ -0,0 +1 @@
+../CNAME

+ 1 - 0
docs/README.md

@@ -0,0 +1 @@
+../README.md

+ 1 - 0
docs/assets/django-logo-negative.svg

@@ -0,0 +1 @@
+../../assets/django-logo-negative.svg

+ 1 - 0
docs/assets/django-logo-positive.svg

@@ -0,0 +1 @@
+../../assets/django-logo-positive.svg

+ 27 - 0
docs/javascripts/anchor-compat.js

@@ -0,0 +1,27 @@
+/*
+ * Anchor compatibility with the old Jekyll (kramdown) site.
+ *
+ * Python-Markdown and kramdown agree on 59 of the 61 heading anchors. These
+ * two differ, so old bookmarks and external deep links are redirected here.
+ */
+var LEGACY_ANCHORS = {
+  "awesome-django-": "awesome-django",
+  "templates-1": "templates_1",
+};
+
+function resolveLegacyAnchor() {
+  var hash = decodeURIComponent(window.location.hash.replace(/^#/, ""));
+  if (!hash) return;
+
+  var target = LEGACY_ANCHORS[hash];
+  if (!target) return;
+
+  var el = document.getElementById(target);
+  if (!el) return;
+
+  el.scrollIntoView();
+  history.replaceState(null, "", "#" + target);
+}
+
+window.addEventListener("DOMContentLoaded", resolveLegacyAnchor);
+window.addEventListener("hashchange", resolveLegacyAnchor);

+ 93 - 0
docs/stylesheets/django.css

@@ -0,0 +1,93 @@
+/*
+ * Django brand palette, ported from the old Jekyll theme at
+ * _sass/color_schemes/django.scss
+ */
+
+:root {
+  --django-green-dark: #0c4b33;
+  --django-green-mid: #20aa76;
+  --django-green-light: #44b78b;
+  --django-green-pale: #e8f5f0;
+  --django-cream: #f9f7f3;
+}
+
+/* Light scheme */
+[data-md-color-scheme="default"] {
+  --md-primary-fg-color: var(--django-green-dark);
+  --md-primary-fg-color--light: var(--django-green-mid);
+  --md-primary-fg-color--dark: #072e1f;
+  --md-primary-bg-color: #ffffff;
+  --md-primary-bg-color--light: rgba(255, 255, 255, 0.7);
+
+  --md-accent-fg-color: var(--django-green-mid);
+  --md-accent-fg-color--transparent: rgba(32, 170, 118, 0.1);
+
+  --md-typeset-a-color: var(--django-green-mid);
+  --md-code-bg-color: var(--django-cream);
+}
+
+/* Dark scheme */
+[data-md-color-scheme="slate"] {
+  --md-primary-fg-color: var(--django-green-dark);
+  --md-primary-fg-color--light: var(--django-green-light);
+  --md-primary-fg-color--dark: #051f15;
+  --md-primary-bg-color: #ffffff;
+
+  --md-accent-fg-color: var(--django-green-light);
+  --md-accent-fg-color--transparent: rgba(68, 183, 139, 0.1);
+
+  --md-typeset-a-color: var(--django-green-light);
+}
+
+/* Headings use the dark Django green, as they did in the Jekyll theme. */
+[data-md-color-scheme="default"] .md-typeset h1,
+[data-md-color-scheme="default"] .md-typeset h2,
+[data-md-color-scheme="default"] .md-typeset h3,
+[data-md-color-scheme="default"] .md-typeset h4 {
+  color: var(--django-green-dark);
+}
+
+/* The Django logo is a wide SVG. Keep it from overflowing on small screens. */
+.md-typeset picture img {
+  max-width: 320px;
+  height: auto;
+}
+
+/* The list is long and mostly one-line entries. Tighten the leading so more
+   of each category fits on screen. */
+.md-typeset ul li {
+  margin-bottom: 0.2rem;
+}
+
+/* Footer additions */
+.awesome-django-footer {
+  padding: 1rem 0.8rem 0;
+  font-size: 0.7rem;
+  line-height: 1.5;
+  text-align: center;
+}
+
+.awesome-django-footer iframe {
+  max-width: 100%;
+  border: 0;
+}
+
+.awesome-django-footer p {
+  margin: 0.6rem 0;
+}
+
+/*
+ * The webring is a custom element with a shadow root. Its :host rule sets
+ * display: block, so it grows to the full width of the footer. Outer document
+ * rules beat :host rules, so constrain it here and keep it centered.
+ */
+.awesome-django-webring {
+  margin: 1rem auto;
+  max-width: 34rem;
+}
+
+.awesome-django-webring webring-css {
+  display: block;
+  margin: 0 auto;
+  max-width: 100%;
+}

+ 8 - 17
justfile

@@ -11,18 +11,18 @@
     python -m pip install --upgrade pip uv
     uv pip install --upgrade --requirement pyproject.toml
 
-# Generate table of contents and build the Jekyll site
+# Generate table of contents and build the Zensical site
 @build:
     just doctoc
-    bundle exec jekyll build
+    uv run zensical build --clean
 
 # Generate table of contents for README.md
 @doctoc:
     bunx doctoc README.md
 
-# Stop the Docker containers
-@down:
-    docker compose down
+# Serve the site with live reload on port 8000
+@serve:
+    uv run zensical serve --dev-addr localhost:8000
 
 # Run linting on all files
 @lint *ARGS:
@@ -30,15 +30,6 @@
     # -bunx awesome-lint README.md
     uv tool run --with pre-commit-uv pre-commit run {{ ARGS }} --all-files
 
-# Start local development server
-@serve:
-    # modd --file=modd.conf
-    just up ""
-
-# Start containers (detached by default)
-@start *ARGS="--detach":
-    just up {{ ARGS }}
-
-# Start Docker containers with optional arguments
-@up *ARGS:
-    docker compose up {{ ARGS }}
+# Remove the generated site
+@clean:
+    rm -rf site

+ 0 - 20
modd.conf

@@ -1,20 +0,0 @@
-{
-	# prep: "killall ruby"
-	daemon +sigterm: "bundle exec jekyll serve --drafts --watch --port 4000"
-}
-
-# DEVD proxy with livereload listening on port 8000
-
-**/*.css
-**/*.html
-**/*.js
-!src/**
-{
-    daemon: "devd --modd -l http://localhost:4000"
-}
-
-# src/*.css
-# src/*.js
-# {
-#     prep +onchange: "just static"
-# }

+ 41 - 0
overrides/main.html

@@ -0,0 +1,41 @@
+{% extends "base.html" %}
+
+{#
+  Carried over from the Jekyll theme:
+  - _includes/head_custom.html (favicon, fork ribbon, umami analytics)
+  - _config.yml footer_content (Django News embed, webring, DSF notice)
+#}
+
+{% block extrahead %}
+  <link rel="icon" href="https://fav.farm/&#129412;" />
+  <script defer src="https://uma.cog.gs/script.js" data-website-id="744740f8-2ff1-4a59-9e0f-7740fc8ca3c4"></script>
+{% endblock %}
+
+{% block footer %}
+  {{ super() }}
+  <div class="awesome-django-footer">
+    <iframe
+      src="https://django-news.com/embed?color1=ffffff&color2=092e20&color_bg_button=092e20&color_border=ccc&color_button=ffffff&color_links=979797&color_terms=808080&title=Subscribe+to+Django+News"
+      width="450"
+      height="310"
+      loading="lazy"
+      title="Subscribe to Django News"></iframe>
+
+    <div class="awesome-django-webring">
+      <webring-css site="https://awesomedjango.org"></webring-css>
+      <script src="https://djangowebring.com/static/webring.js"></script>
+    </div>
+
+    <p>
+      Please consider supporting Django by making a donation to the
+      <a rel="sponsored" href="https://www.djangoproject.com/fundraising/">Django Software Foundation</a>,
+      sponsoring via <a rel="sponsored" href="https://github.com/sponsors/django">GitHub Sponsors</a>,
+      or buying <a rel="sponsored" href="https://django.threadless.com/">official merchandise</a>.
+    </p>
+
+    <p>
+      Awesome Django is not associated with the Django Software Foundation.<br />
+      Django is a registered trademark of the Django Software Foundation.
+    </p>
+  </div>
+{% endblock %}

+ 1 - 2
pyproject.toml

@@ -5,9 +5,8 @@ description = "A curated list of awesome things related to Django. Maintained by
 readme = "README.md"
 requires-python = ">=3.12"
 dependencies = [
-  "mkdocs",
-  "mkdocs-material",
   "requests",
   "rich",
   "typer",
+  "zensical",
 ]

+ 67 - 0
zensical.toml

@@ -0,0 +1,67 @@
+[project]
+site_name = "Awesome Django"
+site_url = "https://awesomedjango.org/"
+site_description = "A curated list of awesome things related to Django. Maintained by William Vincent and Jeff Triplett"
+
+repo_url = "https://github.com/wsvincent/awesome-django"
+repo_name = "wsvincent/awesome-django"
+edit_uri = "edit/main/README.md"
+
+# README.md is the source of truth. docs/README.md is a symlink to it, so the
+# GitHub README and the website never drift apart.
+nav = [
+  { "Awesome Django" = "README.md" },
+]
+
+extra_css = ["stylesheets/django.css"]
+extra_javascript = ["javascripts/anchor-compat.js"]
+
+[project.theme]
+custom_dir = "overrides"
+language = "en"
+features = [
+  "announce.dismiss",
+  "content.action.edit",
+  "content.code.copy",
+  "navigation.instant",
+  "navigation.instant.prefetch",
+  "navigation.top",
+  "navigation.tracking",
+  "search.highlight",
+  "search.suggest",
+  "toc.follow",
+  # The site is one long page. Put the table of contents in the left sidebar
+  # so the category list is always visible, like the old just-the-docs nav.
+  "toc.integrate",
+]
+
+[[project.theme.palette]]
+media = "(prefers-color-scheme: light)"
+scheme = "default"
+toggle.icon = "lucide/sun"
+toggle.name = "Switch to dark mode"
+
+[[project.theme.palette]]
+media = "(prefers-color-scheme: dark)"
+scheme = "slate"
+toggle.icon = "lucide/moon"
+toggle.name = "Switch to light mode"
+
+[[project.extra.social]]
+icon = "fontawesome/brands/github"
+link = "https://github.com/wsvincent/awesome-django"
+
+[project.markdown_extensions]
+attr_list = {}
+md_in_html = {}
+admonition = {}
+def_list = {}
+# permalink restores the click-to-link heading anchors the Jekyll theme had
+# via heading_anchors: true.
+toc = { permalink = true }
+pymdownx.details = {}
+pymdownx.highlight = { anchor_linenums = true, pygments_lang_class = true }
+pymdownx.inlinehilite = {}
+pymdownx.magiclink = {}
+pymdownx.superfences = {}
+pymdownx.tasklist = { custom_checkbox = true }