Przeglądaj źródła

Implement custom navbar and footer in "pro" project (#636)

* Moved `button_title` field from ButtonMixin to BaseLinkBlock.
* Add utility `get_title` to LinkStructValue to generate button/link
title.
* Add custom navbar and footer models and templates to the "pro"
project.

The new pro navbar is 100% local to the project, therefore can be
completely customized, or simply deleted if not wanted. But this should
provide a pretty nice starting point to accelerate development based on
current design trends.
Vince Salvino 10 miesięcy temu
rodzic
commit
4ba33d7ab3

+ 1 - 0
coderedcms/blocks/__init__.py

@@ -60,6 +60,7 @@ from .base_blocks import (  # noqa
     CoderedAdvSettings,
     CoderedAdvTrackingSettings,
     CollectionChooserBlock,
+    LinkStructValue,
 )
 
 # Collections of blocks commonly used together.

+ 25 - 6
coderedcms/blocks/base_blocks.py

@@ -115,11 +115,6 @@ class ButtonMixin(blocks.StructBlock):
     Standard style and size options for buttons.
     """
 
-    button_title = blocks.CharBlock(
-        max_length=255,
-        required=True,
-        label=_("Button Title"),
-    )
     button_style = blocks.ChoiceBlock(
         choices=crx_settings.CRX_FRONTEND_BTN_STYLE_CHOICES,
         default=crx_settings.CRX_FRONTEND_BTN_STYLE_DEFAULT,
@@ -282,9 +277,28 @@ class BaseLayoutBlock(BaseBlock):
 
 class LinkStructValue(blocks.StructValue):
     """
-    Generates a URL for blocks with multiple link choices.
+    Generates a URL and Title for blocks with multiple link choices.
+    Designed to be used with ``BaseLinkBlock``.
     """
 
+    @property
+    def get_title(self):
+        title = self.get("title")
+        button_title = self.get("button_title")
+        page = self.get("page_link")
+        doc = self.get("doc_link")
+        ext = self.get("other_link")
+        if title:
+            return title
+        if button_title:
+            return button_title
+        if page:
+            return page.title
+        elif doc:
+            return doc.title
+        else:
+            return ext
+
     @property
     def url(self):
         page = self.get("page_link")
@@ -318,6 +332,11 @@ class BaseLinkBlock(BaseBlock):
         max_length=255,
         label=_("Other link"),
     )
+    button_title = blocks.CharBlock(
+        required=False,
+        max_length=255,
+        label=_("Title"),
+    )
 
     advsettings_class = CoderedAdvTrackingSettings
 

Plik diff jest za duży
+ 6 - 7038
coderedcms/project_template/basic/website/migrations/0001_initial.py


+ 6 - 0
coderedcms/project_template/pro/project_name/settings/base.py

@@ -188,3 +188,9 @@ TAGGIT_CASE_INSENSITIVE = True
 # Sets default for primary key IDs
 # See https://docs.djangoproject.com/en/{{ docs_version }}/ref/models/fields/#bigautofield
 DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
+
+
+# Disable built-in CRX Navbar and Footer since this project has a
+# custom implementation.
+CRX_DISABLE_NAVBAR = True
+CRX_DISABLE_FOOTER = True

Plik diff jest za duży
+ 7 - 4
coderedcms/project_template/pro/website/migrations/0001_initial.py


+ 109 - 0
coderedcms/project_template/pro/website/models.py

@@ -2,6 +2,11 @@
 Create or customize your page models here.
 """
 
+from coderedcms.blocks import HTML_STREAMBLOCKS
+from coderedcms.blocks import LAYOUT_STREAMBLOCKS
+from coderedcms.blocks import BaseBlock
+from coderedcms.blocks import BaseLinkBlock
+from coderedcms.blocks import LinkStructValue
 from coderedcms.forms import CoderedFormField
 from coderedcms.models import CoderedArticleIndexPage
 from coderedcms.models import CoderedArticlePage
@@ -13,7 +18,12 @@ from coderedcms.models import CoderedFormPage
 from coderedcms.models import CoderedLocationIndexPage
 from coderedcms.models import CoderedLocationPage
 from coderedcms.models import CoderedWebPage
+from django.db import models
 from modelcluster.fields import ParentalKey
+from wagtail import blocks
+from wagtail.admin.panels import FieldPanel
+from wagtail.fields import StreamField
+from wagtail.snippets.models import register_snippet
 
 
 class ArticlePage(CoderedArticlePage):
@@ -148,3 +158,102 @@ class WebPage(CoderedWebPage):
         verbose_name = "Web Page"
 
     template = "coderedcms/pages/web_page.html"
+
+
+# -- Navbar & Footer ----------------------------------------------------------
+
+
+class NavbarLinkBlock(BaseLinkBlock):
+    """
+    Simple link in the navbar.
+    """
+
+    class Meta:
+        icon = "link"
+        label = "Link"
+        template = "website/blocks/navbar_link.html"
+        value_class = LinkStructValue
+
+
+class NavbarDropdownBlock(BaseBlock):
+    """
+    Custom dropdown menu with heading, links, and rich content.
+    """
+
+    class Meta:
+        icon = "arrow-down"
+        label = "Dropdown"
+        template = "website/blocks/navbar_dropdown.html"
+
+    title = blocks.CharBlock(
+        max_length=255,
+        required=True,
+        label="Title",
+    )
+    links = blocks.StreamBlock(
+        [("link", NavbarLinkBlock())],
+        required=True,
+        label="Links",
+    )
+    description = blocks.StreamBlock(
+        HTML_STREAMBLOCKS,
+        required=False,
+        label="Description",
+    )
+
+
+@register_snippet
+class Navbar(models.Model):
+    """
+    Custom navigation bar / menu.
+    """
+
+    class Meta:
+        verbose_name = "Navigation Bar"
+
+    name = models.CharField(
+        max_length=255,
+    )
+    content = StreamField(
+        [
+            ("link", NavbarLinkBlock()),
+            ("dropdown", NavbarDropdownBlock()),
+        ],
+        use_json_field=True,
+    )
+
+    panels = [
+        FieldPanel("name"),
+        FieldPanel("content"),
+    ]
+
+    def __str__(self) -> str:
+        return self.name
+
+
+@register_snippet
+class Footer(models.Model):
+    """
+    Custom footer for bottom of pages on the site.
+    """
+
+    class Meta:
+        verbose_name = "Footer"
+
+    name = models.CharField(
+        max_length=255,
+    )
+    content = StreamField(
+        LAYOUT_STREAMBLOCKS,
+        verbose_name="Content",
+        blank=True,
+        use_json_field=True,
+    )
+
+    panels = [
+        FieldPanel("name"),
+        FieldPanel("content"),
+    ]
+
+    def __str__(self) -> str:
+        return self.name

+ 24 - 0
coderedcms/project_template/pro/website/static/website/src/custom.scss

@@ -34,3 +34,27 @@
   padding-top: 0;
   padding-bottom: 0;
 }
+
+
+// Full-width dropdowns in our custom Navbar.
+.custom-dropdown-full {
+  position: initial;
+
+  .dropdown-menu {
+    background-color: darken($dark, 5%);
+    border-radius: 0;
+    border: none;
+    margin: 0;
+
+    @include media-breakpoint-up(lg) {
+      font-size: 1.25em;
+      padding: 40px 0;
+      width: 100%;
+    }
+  }
+
+  .container {
+    margin: 0 auto;
+    padding: 0 15px;
+  }
+}

+ 14 - 0
coderedcms/project_template/pro/website/templates/coderedcms/snippets/footer.html

@@ -0,0 +1,14 @@
+{% load wagtailcore_tags website_tags %}
+<footer class="bg-light">
+
+  {# Loop through all Footer snippets and dynamically render the `content` StreamField. #}
+
+  {% get_website_footers as footers %}
+  {% for footer in footers %}
+  <div>
+    {% for item in footer.content %}
+    {% include_block item with settings=settings %}
+    {% endfor %}
+  </div>
+  {% endfor %}
+</footer>

+ 43 - 0
coderedcms/project_template/pro/website/templates/coderedcms/snippets/navbar.html

@@ -0,0 +1,43 @@
+{% load wagtailcore_tags wagtailsettings_tags wagtailimages_tags coderedcms_tags i18n website_tags %}
+{% wagtail_site as site %}
+<nav class="navbar navbar-expand-lg bg-dark" data-bs-theme="dark">
+  <div class="container">
+
+    {# Allow the Logo to be user-customizable via the CRX Settings in the Wagtail Admin. #}
+
+    <a class="navbar-brand" href="/">
+      {% django_setting "CRX_DISABLE_LAYOUT" as disable_layout %}
+      {% if not disable_layout and settings.coderedcms.LayoutSettings.logo %}
+      {% image settings.coderedcms.LayoutSettings.logo original format-webp preserve-svg as logo %}
+      <img src="{{logo.url}}" alt="{{site.site_name}}">
+      {% else %}
+      {{site.site_name}}
+      {% endif %}
+    </a>
+
+    <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbar"
+      aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation">
+      <span class="navbar-toggler-icon"></span>
+    </button>
+
+    <div class="collapse navbar-collapse" id="navbar">
+
+      {# Loop through all Navbar snippets and dynamically render the `content` StreamField. #}
+
+      {% get_website_navbars as navbars %}
+      {% for navbar in navbars %}
+      <div class="navbar-nav me-auto">
+        {% for item in navbar.content %}
+        {% include_block item with li_class="nav-item" a_class="nav-link" %}
+        {% endfor %}
+      </div>
+      {% endfor %}
+
+      <div class="navbar-nav d-flex">
+        <div class="nav-item">
+          <a href="{% url 'crx_search' %}" class="nav-link">Search</a>
+        </div>
+      </div>
+    </div>
+  </div>
+</nav>

+ 19 - 0
coderedcms/project_template/pro/website/templates/website/blocks/navbar_dropdown.html

@@ -0,0 +1,19 @@
+{% load wagtailcore_tags %}
+<div class="nav-item dropdown custom-dropdown-full">
+  <button class="nav-link dropdown-toggle" role="button" data-bs-toggle="dropdown" aria-expanded="false">
+    {{ self.title }}
+  </button>
+  <div class="dropdown-menu">
+    <div class="container">
+      <span class="d-none d-lg-block mb-5 display-3">{{ self.title }}</span>
+      <div class="row">
+        <div class="col-lg-6 mb-3">
+          {% for item in self.links %}{% include_block item with a_class="dropdown-item" %}{% endfor %}
+        </div>
+        <div class="col-lg-6">
+          {% for item in self.description %}{% include_block item %}{% endfor %}
+        </div>
+      </div>
+    </div>
+  </div>
+</div>

+ 16 - 0
coderedcms/project_template/pro/website/templates/website/blocks/navbar_link.html

@@ -0,0 +1,16 @@
+{% load coderedcms_tags %}
+{% django_setting "CRX_DISABLE_ANALYTICS" as disable_analytics %}
+<div class="{{ li_class }}">
+  {% is_active_page page self.page_link as is_active_url %}
+  <a
+    class="{{ a_class }} {% if is_active_url %}active{% endif %} {{ value.settings.custom_css_class }}"
+    {% if self.settings.custom_css_id %}id="{{ self.settings.custom_css_class }}"{% endif %}
+    href="{{self.url}}"
+    {% if not disable_analytics and settings.coderedcms.AnalyticsSettings.ga_track_button_clicks %}
+    data-ga-event-category='{{self.settings.ga_tracking_event_category|default:"Navbar"}}'
+    data-ga-event-label='{{self.settings.ga_tracking_event_label|default:self.get_title}}'
+    {% endif %}
+    title="{{ self.get_title|safe }}">
+    {{ self.get_title|safe }}
+  </a>
+</div>

+ 23 - 0
coderedcms/project_template/pro/website/templatetags/website_tags.py

@@ -0,0 +1,23 @@
+from django import template
+
+from website.models import Footer
+from website.models import Navbar
+
+
+register = template.Library()
+
+
+@register.simple_tag
+def get_website_navbars():
+    # NOTE: For a multi-site, you may need to create SiteSettings to
+    # choose a Navbar, then query those here. Or, add a Foreign Key to
+    # the Site on the Navbar, and query those.
+    return Navbar.objects.all()
+
+
+@register.simple_tag
+def get_website_footers():
+    # NOTE: For a multi-site, you may need to create SiteSettings to
+    # choose a Footer, then query those here. Or, add a Foreign Key to
+    # the Site on the Footer, and query those.
+    return Footer.objects.all()

+ 2 - 6
coderedcms/templates/coderedcms/blocks/button_block.html

@@ -3,14 +3,10 @@
 <a href="{{self.url}}"
   {% if not disable_analytics and settings.coderedcms.AnalyticsSettings.ga_track_button_clicks %}
   data-ga-event-category='{{self.settings.ga_tracking_event_category|default:"Button"}}'
-  data-ga-event-label='{{self.settings.ga_tracking_event_label|default:self.button_title}}'
+  data-ga-event-label='{{self.settings.ga_tracking_event_label|default:self.get_title}}'
   {% endif %}
   title="{{ self.button_title|safe }}"
   class="btn {{ self.button_style }} {{ self.button_size }} {{ self.settings.custom_css_class }}"
   {% if self.settings.custom_id %}id="{{ self.settings.custom_id }}"{% endif %}>
-  {% if self.button_title %}
-  {{ self.button_title|safe }}
-  {% else %}
-  {{ self.url }}
-  {% endif %}
+  {{ self.get_title|safe }}
 </a>

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików