浏览代码

Drop jQuery, IE support (#512)

* Remove jQuery as base dependency from crx-front.js
* Remove jQuery as dependency from crx-maps.js
* Remove modernizr
* Remove date/time picker fallbacks. Modern browsers as of the past year (2021) all provide widgets for date, time, datetime-local inputs.
* Remove csrf token from ajax posts. Generally we should avoid front-end code/views which require CSRF tokens (except for actual forms) as they bust the cache.
* Update fullcalendar to 5.11.2
* Refactor map, event, and streamform code into separate scripts that live on their respective pages.
* Format javascript code with prettier

This change drops IE support from the front-end. Most functionality will continue to work in a degraded state on the front-end in IE, but support is no longer guaranteed. Wagtail 3 and Bootstrap 5 have also dropped IE support.
Vince Salvino 2 年之前
父节点
当前提交
834a66f480

+ 4 - 0
.prettierrc.toml

@@ -0,0 +1,4 @@
+trailingComma = "es5"
+tabWidth = 2
+semi = true
+singleQuote = false

+ 45 - 0
coderedcms/static/coderedcms/js/crx-events.js

@@ -0,0 +1,45 @@
+/*
+Wagtail CRX (https://www.coderedcorp.com/cms/)
+Copyright 2018-2022 CodeRed LLC
+License: https://github.com/coderedcorp/coderedcms/blob/dev/LICENSE
+@license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
+*/
+
+document.addEventListener("DOMContentLoaded", function () {
+  var calendars = document.querySelectorAll("[data-block='calendar']");
+  calendars.forEach(function (el) {
+    var pageId = el.dataset.pageId; // data-page-id
+    var defaultDate = el.dataset.defaultDate; // data-default-date
+    var defaultView = el.dataset.defaultView; // data-default-view
+    var eventDisplay = el.dataset.eventDisplay; // data-event-display
+    var eventSourceUrl = el.dataset.eventSourceUrl // data-event-source-url
+    var timezone = el.dataset.timezone; // data-timezone
+    var calendar = new FullCalendar.Calendar(el, {
+      headerToolbar: {
+        left: "prev,next today",
+        center: "title",
+        right: "dayGridMonth,timeGridWeek,timeGridDay,listMonth",
+      },
+      themeSystem: "bootstrap",
+      bootstrapFontAwesome: false,
+      buttonText: {
+        prev: "← prev",
+        next: "next →",
+      },
+      initialDate: defaultDate,
+      initialView: defaultView,
+      fixedWeekCount: false,
+      timeZone: timezone,
+      eventDisplay: eventDisplay,
+      eventSources: {
+        url: eventSourceUrl,
+        method: "GET",
+        extraParams: {
+          pid: pageId,
+        },
+      },
+    });
+    calendar.render();
+  });
+});
+/* @license-end */

+ 119 - 255
coderedcms/static/coderedcms/js/crx-front.js

@@ -5,269 +5,133 @@ License: https://github.com/coderedcorp/coderedcms/blob/dev/LICENSE
 @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
 */
 
-libs = {
-    masonry: {
-        url: "https://cdnjs.cloudflare.com/ajax/libs/masonry/4.2.2/masonry.pkgd.min.js",
-        integrity: "sha512-JRlcvSZAXT8+5SQQAvklXGJuxXTouyq8oIMaYERZQasB8SBDHZaUbeASsJWpk0UUrf89DP3/aefPPrlMR1h1yQ==",
-    },
-    modernizr: {
-        url: "https://cdnjs.cloudflare.com/ajax/libs/modernizr/2.8.3/modernizr.min.js",
-        integrity: "sha256-0rguYS0qgS6L4qVzANq4kjxPLtvnp5nn2nB5G1lWRv4=",
-    },
-    moment: {
-        url: "https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js",
-        integrity: "sha256-4iQZ6BVL4qNKlQ27TExEhBN1HFPvAvAMbFavKKosSWQ="
-    },
-    pickerbase: {
-        url: "https://cdnjs.cloudflare.com/ajax/libs/pickadate.js/3.6.3/compressed/picker.js",
-        integrity: "sha256-hjN7Qqm7pjV+lms0uyeJBro1vyCH2azVGqyuWeZ6CFM=",
-        head: '<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/pickadate.js/3.6.3/compressed/themes/default.css" integrity="sha256-wtVxHQXXtr975G710f51YDv94+6f6cuK49PcANcKccY=" crossorigin="anonymous" />'
-    },
-    pickadate: {
-        url: "https://cdnjs.cloudflare.com/ajax/libs/pickadate.js/3.6.3/compressed/picker.date.js",
-        integrity: "sha256-Z4OXXhjTbpFlc4Z6HqgVtVaz7Nt/3ptUKBOhxIze1eE=",
-        head: '<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/pickadate.js/3.6.3/compressed/themes/default.date.css" integrity="sha256-U24A2dULD5s+Dl/tKvi5zAe+CAMKBFUaHUtLN8lRnKE=" crossorigin="anonymous" />'
-    },
-    pickatime: {
-        url: "https://cdnjs.cloudflare.com/ajax/libs/pickadate.js/3.6.3/compressed/picker.time.js",
-        integrity: "sha256-mvFcf2wocDC8U1GJdTVSmMHBn/dBLNeJjYRvBhM6gc8=",
-        head: '<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/pickadate.js/3.6.3/compressed/themes/default.time.css" integrity="sha256-dtpQarv++ugnrcY7o6Gr3m7fIJFJDSx8v76jjTqEeKE=" crossorigin="anonymous" />'
-    },
-    fullcalendar: {
-        url: "https://cdn.jsdelivr.net/npm/fullcalendar@5.9.0/main.min.js",
-        integrity: "sha256-8nl2O4lMNahIAmUnxZprMxJIBiPv+SzhMuYwEuinVM0=",
-        head: '<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/fullcalendar@5.9.0/main.min.css" integrity="sha256-FjyLCG3re1j4KofUTQQXmaWJw13Jdb7LQvXlkFxTDJI=" crossorigin="anonymous">'
-    },
-    coderedmaps: {
-        url: "/static/coderedcms/js/codered-maps.js?v=" + cr_version,
-        integrity: "",
-    },
-    coderedstreamforms: {
-        url: "/static/coderedcms/js/codered-streamforms.js?v=" + cr_version,
-        integrity: "",
-    }
-}
-
+/**
+ * Main script which is used to detect CRX features requiring JavaScript.
+ *
+ * Loads the necessary libraries for that feature, then initializes any
+ * feature-specific code. This should only be used for features that might be
+ * site-wide (e.g. StreamField blocks that could occur anywhere). For
+ * functionality that is page-specific, include the JavaScript normally via a
+ * script tag on that page instead.
+ *
+ * This file must run with "pure" JavaScript - assume jQuery or any other
+ * scripts are not yet loaded.
+ */
+const libs = {
+  masonry: {
+    url: "https://cdn.jsdelivr.net/npm/masonry-layout@4.2.2/dist/masonry.pkgd.min.js",
+    integrity: "sha256-Nn1q/fx0H7SNLZMQ5Hw5JLaTRZp0yILA/FRexe19VdI=",
+  },
+};
+
+/**
+ * Dynamically loads a script and/or CSS from the `lib` object above.
+ *
+ * Put functionality related to the script you are loading into the `success`
+ * callback of the `load_script` function. Otherwise, it might not work as
+ * intended.
+ */
 function load_script(lib, success) {
-    // lib is an entry in `libs` above.
-    // It is best to put functionality related to the script you are loading into the success callback of the load_script function.
-    // Otherwise, it might not work as intended.
-    if(lib.head) {
-        $('head').append(lib.head);
-    }
-    if(lib.url){
-        $.ajax({
-            url: lib.url,
-            dataType: "script",
-            integrity: lib.integrity,
-            crossorigin: "anonymous",
-            success: success
-        });
-    }
-}
-
-
-$(document).ready(function()
-{
-
-    /*** AJAX Setup CSRF Setup ***/
-    function getCookie(name) {
-        var cookieValue = null;
-        if (document.cookie && document.cookie !== '') {
-            var cookies = document.cookie.split(';');
-            for (var i = 0; i < cookies.length; i++) {
-                var cookie = jQuery.trim(cookies[i]);
-                // Does this cookie string begin with the name we want?
-                if (cookie.substring(0, name.length + 1) === (name + '=')) {
-                    cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
-                    break;
-                }
-            }
+  var head = document.getElementsByTagName("head")[0];
+  if (lib.head) {
+    // Create a temporary element and insert the `lib.head` string to form a
+    // child element.
+    var tmpEl = document.createElement("div");
+    tmpEl.innerHTML = lib.head;
+    // Append the child element to the `<head>`
+    head.append(tmpEl.firstElementChild);
+  }
+  if (lib.url) {
+    // Fetch and execute the script in the global context.
+    // Then call the `success` callback.
+    fetch(lib.url, {
+      integrity: lib.integrity,
+      referrerPolicy: "origin",
+    })
+      .then(function (response) {
+        return response.text();
+      })
+      .then(function (txt) {
+        // Eval in the global scope.
+        eval?.(txt);
+      })
+      .then(function () {
+        if (success) {
+          success();
         }
-        return cookieValue;
-    }
-    var csrftoken = getCookie('csrftoken');
-
-    function csrfSafeMethod(method) {
-        // these HTTP methods do not require CSRF protection
-        return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
-    }
-    $.ajaxSetup({
-        beforeSend: function(xhr, settings) {
-            if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
-                xhr.setRequestHeader("X-CSRFToken", csrftoken);
-            }
-        }
-    });
-
-
-    /*** Forms ***/
-    if ( $('form').length > 0) {
-        load_script(libs.modernizr, function() {
-            if ( (!Modernizr.inputtypes.date || !Modernizr.inputtypes.time) && $("input[type='date'], input[type='time']").length > 0) {
-                load_script(libs.pickerbase, function() {
-                    $(document).trigger("base-picker-loaded");
-                });
-            }
-            if(!Modernizr.inputtypes.date && $("input[type='date']").length > 0) {
-                $(document).on("base-picker-loaded", function() {
-                    load_script(libs.pickadate, function() {
-                        // Show date picker
-                        $("input[type='date']").pickadate({
-                            format: 'mm/dd/yyyy',
-                            selectMonths: true,
-                            selectYears: true
-                        });
-                    });
-                });
-            }
-            if(!Modernizr.inputtypes.time && $("[type='time']").length > 0) {
-                $(document).on("base-picker-loaded", function() {
-                    load_script(libs.pickatime, function() {
-                        // Show time picker
-                        $("input[type='time']").pickatime({
-                            format: 'h:i A',
-                            interval: 15
-                        });
-                    });
-                });
-            }
-            if (!Modernizr.inputtypes['datetime-local'] && $("input[type='datetime-local']").length > 0) {
-                load_script(libs.moment, function() {
-                    // Show formatting help text
-                    $('.datetime-help').show();
-                    // Format input on blur
-                    $("[type='datetime-local']").blur(function() {
-                        var clean = $.trim($(this).val());
-                        if (clean != '') {
-                            clean = moment(clean).format("L LT");
-                            $(this).val(clean);
-                        }
-                    });
-                });
-            }
-        });
-    }
-
-    /*** Calendar **/
-    if ( $("[data-block='calendar']").length > 0){
-        load_script(libs.fullcalendar, function(){
-            var calendars = document.querySelectorAll("[data-block='calendar']");
-            calendars.forEach(function(el){
-                var pageId = el.dataset.pageId; // data-page-id
-                var defaultDate = el.dataset.defaultDate; // data-default-date
-                var defaultView = el.dataset.defaultView; // data-default-view
-                var eventDisplay = el.dataset.eventDisplay; // data-event-display
-                var timezone = el.dataset.timezone; // data-timezone
-                var calendar = new FullCalendar.Calendar(el, {
-                    headerToolbar: {
-                        left: 'prev,next today',
-                        center: 'title',
-                        right: 'dayGridMonth,timeGridWeek,timeGridDay,listMonth'
-                    },
-                    themeSystem: 'bootstrap',
-                    bootstrapFontAwesome: false,
-                    buttonText: {
-                        'prev': '< prev',
-                        'next': 'next >'
-                    },
-                    initialDate: defaultDate,
-                    initialView: defaultView,
-                    fixedWeekCount: false,
-                    timeZone: timezone,
-                    eventDisplay: eventDisplay,
-                    eventSources: {
-                        url: '/ajax/calendar/events/',
-                        method: 'GET',
-                        extraParams: {
-                            'pid': pageId
-                        }
-                    }
-                });
-                calendar.render();
-            });
-        });
-    }
-
-    if ($('#cr-map').length > 0) {
-        load_script(libs.coderedmaps, function() {
-            $.ajax({
-                url: 'https://maps.googleapis.com/maps/api/js',
-                type: "get",
-                dataType: "script",
-                data: {
-                    'key': $("#cr-map").data( "key" ),
-                    'callback': $("#cr-map").data( "callback" ),
-                    'libraries': $("#cr-map").data( "libraries" ),
-                }
-            });
-        });
-    }
-
-    if ($('.stream-form-input').length > 0){
-        load_script(libs.coderedstreamforms);
-    }
+      });
+  }
+}
 
-    /*** Lightbox ***/
-    $('.lightbox-preview').on('click', function(event) {
-        var orig_src = $(this).find('img').data('original-src');
-        var orig_alt = $(this).find('img').attr('alt');
-        var orig_ttl = $(this).find('img').attr('title');
-        var $lightbox = $($(this).data('target'));
-        $lightbox.find('img').attr('src', orig_src);
-        $lightbox.find('img').attr('alt', orig_alt);
-        $lightbox.find('img').attr('title', orig_ttl);
+document.addEventListener("DOMContentLoaded", function () {
+  /** Lightbox **/
+  document.querySelectorAll(".lightbox-preview").forEach(function (el) {
+    el.addEventListener("click", function (event) {
+      var el = event.currentTarget;
+      var orig_src = el.querySelector("img").dataset.originalSrc;
+      var orig_alt = el.querySelector("img").alt;
+      var orig_ttl = el.querySelector("img").title;
+      var lightbox = document.querySelector(el.dataset.bsTarget);
+      lightbox.querySelector("img").setAttribute("src", orig_src);
+      lightbox.querySelector("img").setAttribute("alt", orig_alt);
+      lightbox.querySelector("img").setAttribute("title", orig_ttl);
     });
-
-
-    /*** Content walls ***/
-    $(".modal[data-cr-wall-showonce='true']").on('hide.bs.modal', function() {
-        localStorage["cr_wall_" + $(this).data("cr-wall-id")] = "dismissed";
+  });
+
+  /** Content walls **/
+  document
+    .querySelectorAll(".modal[data-cr-wall-showonce='true']")
+    .forEach(function (el) {
+      el.addEventListener("hide.bs.modal", function () {
+        localStorage["cr_wall_" + el.dataset.crWallId] = "dismissed";
+      });
     });
-    $(".modal[data-cr-wall-id]").each(function() {
-        if(localStorage["cr_wall_" + $(this).data("cr-wall-id")] === undefined) {
-            $(this).modal('show');
+  document.querySelectorAll(".modal[data-cr-wall-id]").forEach(function (el) {
+    if (localStorage["cr_wall_" + el.dataset.crWallId] === undefined) {
+      const modal = new bootstrap.Modal(el);
+      modal.show();
+    }
+  });
+
+  /** Tracking **/
+  if (typeof cr_track_clicks !== "undefined" && cr_track_clicks) {
+    document.querySelectorAll("a").forEach(function (el) {
+      el.addEventListener("click", function (event) {
+        var el = event.currentTarget;
+        gtag_data = {
+          event_category: "Link",
+          event_label: el.textContent.trim().substring(0, 30),
+        };
+        if (el.dataset.gaEventCategory) {
+          gtag_data["event_category"] = el.dataset.gaEventCategory;
         }
+        if (el.dataset.gaEventLabel) {
+          gtag_data["event_label"] = el.dataset.gaEventLabel;
+        }
+        gtag("event", "click", gtag_data);
+      });
     });
+  }
+
+  /** Link handling **/
+  if (typeof cr_external_new_tab !== "undefined" && cr_external_new_tab) {
+    document.querySelectorAll("a").forEach(function (el) {
+      var href = el.href.trim();
+      if (
+        !href.startsWith(cr_site_url) &&
+        !href.startsWith("/") &&
+        !href.startsWith("#") &&
+        !href.startsWith("?")
+      ) {
+        el.setAttribute("target", "_blank");
+      }
+    });
+  }
 
-
-    /*** Tracking ***/
-    if(typeof cr_track_clicks !== 'undefined' && cr_track_clicks) {
-        $('a').on('click', function(){
-            gtag_data = {
-                "event_category": "Link",
-                "event_label": $(this).text().trim().substring(0, 30)
-            };
-            if ($(this).data('ga-event-category')) {
-                gtag_data['event_category'] = $(this).data('ga-event-category');
-            }
-            if ($(this).data('ga-event-label')) {
-                gtag_data['event_label'] = $(this).data('ga-event-label');
-            }
-            gtag('event', 'click', gtag_data);
-        });
-    }
-
-    /*** Link handling ***/
-    if(typeof cr_external_new_tab !== 'undefined' && cr_external_new_tab) {
-        $('a').each(function() {
-            var href = $(this).prop('href').trim();
-            if(
-                !href.startsWith(cr_site_url) &&
-                !href.startsWith('/') &&
-                !href.startsWith('#') &&
-                !href.startsWith('?')
-            ) {
-                $(this).prop('target', '_blank');
-            }
-        });
-    }
-
-    /*** Masonry ***/
-    if ($('[data-masonry]').length > 0){
-        load_script(libs.masonry);
-    }
-
+  /** Masonry **/
+  if (document.querySelectorAll("[data-masonry]").length > 0) {
+    load_script(libs.masonry);
+  }
 });
 
 /* @license-end */

+ 132 - 120
coderedcms/static/coderedcms/js/crx-maps.js

@@ -5,143 +5,155 @@ License: https://github.com/coderedcorp/coderedcms/blob/dev/LICENSE
 @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
 */
 
-// Initialize the map on the gooogle maps api js callback.
+/**
+ * Initialize the map on the Gooogle Maps API js callback.
+ */
 function initMap() {
-    // Set defaults
-    const map = new google.maps.Map(document.querySelector('#cr-map'), {
-        zoom: parseInt($("#cr-map").data("zoom")),
-        center: {
-            lat: parseFloat($("#cr-map").data("latitude")),
-            lng: parseFloat($("#cr-map").data("longitude")),
-        },
-        mapTypeControl: $("#cr-map").data("map-type-control"),
-        streetViewControl: $("#cr-map").data("street-view-control"),
-    });
-    // Create an infowindow object.
-    var infowindow = new google.maps.InfoWindow({});
-
-    if (navigator.geolocation) {
-        var currentLocationControlDiv = document.createElement('div');
-        var currentLocation = new CurrentLocationControl(currentLocationControlDiv, map);
+  const crMap = document.querySelector("#cr-map");
+  // Set defaults
+  const map = new google.maps.Map(crMap, {
+    zoom: parseInt(crMap.dataset.zoom),
+    center: {
+      lat: parseFloat(crMap.dataset.latitude),
+      lng: parseFloat(crMap.dataset.longitude),
+    },
+    mapTypeControl: crMap.dataset.mapTypeControl,
+    streetViewControl: crMap.dataset.streetViewControl,
+  });
+  // Create an infowindow object.
+  var infowindow = new google.maps.InfoWindow({});
 
-        currentLocationControlDiv.index = 1;
-        map.controls[google.maps.ControlPosition.TOP_LEFT].push(currentLocationControlDiv);
-    }
+  if (navigator.geolocation) {
+    var currentLocationControlDiv = document.createElement("div");
+    var currentLocation = new CurrentLocationControl(
+      currentLocationControlDiv,
+      map
+    );
 
+    currentLocationControlDiv.index = 1;
+    map.controls[google.maps.ControlPosition.TOP_LEFT].push(
+      currentLocationControlDiv
+    );
+  }
 
-    // Listener to update the map markers when the map is idling.
-    google.maps.event.addListener(map, 'idle', () => {
-        const sw = map.getBounds().getSouthWest();
-        const ne = map.getBounds().getNorthEast();
-        let locationDataFeatures = [];
-        map.data.loadGeoJson(
-            $("#cr-map").data("geojson-url") + `&viewport=${sw.lat()},${sw.lng()}|${ne.lat()},${ne.lng()}`,
-            null,
-            features => {
-                locationDataFeatures.forEach(dataFeature => {
-                    map.data.remove(dataFeature);
-                });
-                locationDataFeatures = features;
-                if ($("#cr-map").data("show-list") == "True") {
-                    updateList(locationDataFeatures);
-                }
-            }
-        );
-    });
-
-    // Listener to update the info window when a marker is clicked.
-    map.data.addListener('click', ev => {
-        const f = ev.feature;
-        infowindow.setContent(f.getProperty('pin_description'));
-        infowindow.setPosition(f.getGeometry().get());
-        infowindow.setOptions({
-            pixelOffset: new google.maps.Size(0, -30)
+  // Listener to update the map markers when the map is idling.
+  google.maps.event.addListener(map, "idle", () => {
+    const sw = map.getBounds().getSouthWest();
+    const ne = map.getBounds().getNorthEast();
+    let locationDataFeatures = [];
+    map.data.loadGeoJson(
+      crMap.dataset.geojsonUrl +
+        `&viewport=${sw.lat()},${sw.lng()}|${ne.lat()},${ne.lng()}`,
+      null,
+      (features) => {
+        locationDataFeatures.forEach((dataFeature) => {
+          map.data.remove(dataFeature);
         });
-        infowindow.open(map);
-    });
+        locationDataFeatures = features;
+        if (crMap.dataset.showList == "True") {
+          updateList(locationDataFeatures);
+        }
+      }
+    );
+  });
 
+  // Listener to update the info window when a marker is clicked.
+  map.data.addListener("click", (ev) => {
+    const f = ev.feature;
+    infowindow.setContent(f.getProperty("pin_description"));
+    infowindow.setPosition(f.getGeometry().get());
+    infowindow.setOptions({
+      pixelOffset: new google.maps.Size(0, -30),
+    });
+    infowindow.open(map);
+  });
 
-    // Logic to create a search box and move the map on successful search.
+  // Logic to create a search box and move the map on successful search.
 
-    if ($("#cr-map").data("show-search") == "True") {
-        var input = document.getElementById('pac-input');
-        var searchBox = new google.maps.places.SearchBox(input);
-        map.controls[google.maps.ControlPosition.TOP_LEFT].push(input);
-        map.addListener('bounds_changed', function () {
-            searchBox.setBounds(map.getBounds());
-        });
-        searchBox.addListener('places_changed', function () {
-            var places = searchBox.getPlaces();
-            if (places.length == 0) {
-                return;
-            }
-            // For each place, get the icon, name and location.
-            var bounds = new google.maps.LatLngBounds();
-            places.forEach(function (place) {
-                if (!place.geometry) {
-                    return;
-                }
-                if (place.geometry.viewport) {
-                    // Only geocodes have viewport.
-                    bounds.union(place.geometry.viewport);
-                } else {
-                    bounds.extend(place.geometry.location);
-                }
-            });
-            map.fitBounds(bounds);
-        });
-    }
-
-    // Updates the list to the side of the map with markers that are in the viewport.
-    function updateList(features) {
-        new_html = "";
-        if (features.length == 0) {
-            $("#LocationList").hide();
-            $("#LocationListEmpty").show();
+  if (crMap.dataset.showSearch == "True") {
+    var input = document.getElementById("pac-input");
+    var searchBox = new google.maps.places.SearchBox(input);
+    map.controls[google.maps.ControlPosition.TOP_LEFT].push(input);
+    map.addListener("bounds_changed", function () {
+      searchBox.setBounds(map.getBounds());
+    });
+    searchBox.addListener("places_changed", function () {
+      var places = searchBox.getPlaces();
+      if (places.length == 0) {
+        return;
+      }
+      // For each place, get the icon, name and location.
+      var bounds = new google.maps.LatLngBounds();
+      places.forEach(function (place) {
+        if (!place.geometry) {
+          return;
+        }
+        if (place.geometry.viewport) {
+          // Only geocodes have viewport.
+          bounds.union(place.geometry.viewport);
         } else {
-            $("#LocationList").show();
-            $("#LocationListEmpty").hide();
-            for (i = 0; i < features.length; i++) {
-                feature = features[i];
-                new_html += feature.getProperty('list_description');
-            }
+          bounds.extend(place.geometry.location);
         }
-        $("#LocationList").html(new_html);
+      });
+      map.fitBounds(bounds);
+    });
+  }
+
+  // Updates the list to the side of the map with markers that are in the viewport.
+  function updateList(features) {
+    locList = document.querySelector("#LocationList");
+    locListEmpty = document.querySelector("#LocationListEmpty");
+    new_html = "";
+    if (features.length == 0) {
+      locList.style.display = "none";
+      locListEmpty.style.display = "";
+    } else {
+      locList.style.display = "";
+      locListEmpty.style.display = "none";
+      for (i = 0; i < features.length; i++) {
+        feature = features[i];
+        new_html += feature.getProperty("list_description");
+      }
     }
+    locList.innerHTML = new_html;
+  }
 }
 
 function CurrentLocationControl(controlDiv, map) {
-    var controlUI = document.createElement('div');
-    controlUI.style.backgroundColor = '#fff';
-    controlUI.style.border = '2px solid #fff';
-    controlUI.style.borderRadius = '3px';
-    controlUI.style.boxShadow = '0 2px 2px rgba(0,0,0,.3)';
-    controlUI.style.cursor = 'pointer';
-    controlUI.style.marginTop = '10px'
-    controlUI.style.marginBottom = '22px';
-    controlUI.style.marginLeft = '10px';
-    controlUI.style.textAlign = 'center';
-    controlUI.title = 'Near Me';
-    controlDiv.appendChild(controlUI);
+  var controlUI = document.createElement("div");
+  controlUI.style.backgroundColor = "#fff";
+  controlUI.style.border = "2px solid #fff";
+  controlUI.style.borderRadius = "3px";
+  controlUI.style.boxShadow = "0 2px 2px rgba(0,0,0,.3)";
+  controlUI.style.cursor = "pointer";
+  controlUI.style.marginTop = "10px";
+  controlUI.style.marginBottom = "22px";
+  controlUI.style.marginLeft = "10px";
+  controlUI.style.textAlign = "center";
+  controlUI.title = "Near Me";
+  controlDiv.appendChild(controlUI);
 
-    // Set CSS for the control interior.
-    var controlText = document.createElement('div');
-    controlText.style.color = 'rgb(25,25,25)';
-    controlText.style.fontFamily = 'Roboto,Arial,sans-serif';
-    controlText.style.fontSize = '16px';
-    controlText.style.lineHeight = '36px';
-    controlText.style.paddingLeft = '5px';
-    controlText.style.paddingRight = '5px';
-    controlText.innerHTML = 'Near Me';
-    controlUI.appendChild(controlText);
+  // Set CSS for the control interior.
+  var controlText = document.createElement("div");
+  controlText.style.color = "rgb(25,25,25)";
+  controlText.style.fontFamily = "Roboto,Arial,sans-serif";
+  controlText.style.fontSize = "16px";
+  controlText.style.lineHeight = "36px";
+  controlText.style.paddingLeft = "5px";
+  controlText.style.paddingRight = "5px";
+  controlText.innerHTML = "Near Me";
+  controlUI.appendChild(controlText);
 
-    // Setup the click event listeners: simply set the map to Chicago.
-    controlUI.addEventListener('click', function () {
-        navigator.geolocation.getCurrentPosition(function (position) {
-            currentPosition = new google.maps.LatLng(position.coords.latitude, position.coords.longitude);
-            map.setCenter(currentPosition);
-        });
+  // Setup the click event listeners: simply set the map to Chicago.
+  controlUI.addEventListener("click", function () {
+    navigator.geolocation.getCurrentPosition(function (position) {
+      currentPosition = new google.maps.LatLng(
+        position.coords.latitude,
+        position.coords.longitude
+      );
+      map.setCenter(currentPosition);
     });
+  });
 }
 
 /* @license-end */

+ 48 - 40
coderedcms/static/coderedcms/js/crx-streamforms.js

@@ -1,47 +1,55 @@
-condition_triggered = function($source_field, $target_field) {
-    // custom logic for checkboxes since `.val()` always returns a fixed value, :checked property
-    // must be evaluated instead.
-    if ($source_field.prop("type") == "checkbox") {
-        $source_field.each(function() {
-            var $source_field = $(this);
-            $trigger_checkbox = $source_field.closest("[value='" + $target_field.data("condition-trigger-value") + "']");
-            if ($trigger_checkbox.length > 0) {
-                if($trigger_checkbox.prop("checked")) {
-                    $target_field.show();
-                }
-                else {
-                    $target_field.hide();
-                }
-            }
-        });
-    }
-    else {
-        if ($source_field.val().trim() == $target_field.data("condition-trigger-value").trim()) {
-            $target_field.show();
-        }
-        else {
-            $target_field.hide();
+condition_triggered = function ($source_field, $target_field) {
+  // Custom logic for checkboxes since `.val()` always returns a fixed value,
+  // :checked property must be evaluated instead.
+  if ($source_field.prop("type") == "checkbox") {
+    $source_field.each(function () {
+      var $source_field = $(this);
+      $trigger_checkbox = $source_field.closest(
+        "[value='" + $target_field.data("condition-trigger-value") + "']"
+      );
+      if ($trigger_checkbox.length > 0) {
+        if ($trigger_checkbox.prop("checked")) {
+          $target_field.show();
+        } else {
+          $target_field.hide();
         }
+      }
+    });
+  } else {
+    if (
+      $source_field.val().trim() ==
+      $target_field.data("condition-trigger-value").trim()
+    ) {
+      $target_field.show();
+    } else {
+      $target_field.hide();
     }
-}
+  }
+};
 
 $("[data-condition-trigger-id]").each(function () {
+  // Get source/target fields from data attributes.
+  var $target_field = $(this);
+  var source_query = "#" + $(this).data("condition-trigger-id");
+  var $source_field = $(
+    source_query +
+      " input, " +
+      source_query +
+      " textarea, " +
+      source_query +
+      " select"
+  );
+  var source_field_name = $source_field.prop("name");
 
-    // Get source/target fields from data attributes.
-    var $target_field = $(this);
-    var source_query = "#" + $(this).data("condition-trigger-id");
-    var $source_field = $(source_query + " input, " + source_query + " textarea, " + source_query + " select");
-    var source_field_name = $source_field.prop("name");
+  // Trigger initial state of input.
+  condition_triggered($source_field, $target_field);
 
-    // Trigger initial state of input.
-    condition_triggered($source_field, $target_field);
-
-    // Watch change event for similarly named inputs within this form.
-    // It is necessary to watch based on name
-    // because selecting another radio button does not trigger a `change` for other radio buttons,
-    // it only triggers a change for the whole radio group (identified by "name").
-    var $form = $(this).closest("form");
-    $form.find("[name='" + source_field_name + "']").change(function () {
-        condition_triggered($(this), $target_field);
-    });
+  // Watch change event for similarly named inputs within this form. It is
+  // necessary to watch based on name because selecting another radio button
+  // does not trigger a `change` for other radio buttons, it only triggers a
+  // change for the whole radio group (identified by "name").
+  var $form = $(this).closest("form");
+  $form.find("[name='" + source_field_name + "']").change(function () {
+    condition_triggered($(this), $target_field);
+  });
 });

+ 2 - 1
coderedcms/templates/coderedcms/includes/ical/calendar.html

@@ -1,9 +1,10 @@
 {% load coderedcms_tags %}
 <div
   data-block="calendar"
-  data-default-date="{% now 'Y-m-d' %}""
+  data-default-date="{% now 'Y-m-d' %}"
   data-default-view="{{ page.fullcalendar_view }}"
   data-event-display="{{ page.fullcalendar_event_display }}"
+  data-event-source-url="{% url 'event_get_calendar_events' %}"
   data-page-id="{{ page.id }}"
   data-timezone="{{ 'TIME_ZONE'|django_settings }}">
   <noscript>JavaScript is required to view the Calendar.</noscript>

+ 0 - 4
coderedcms/templates/coderedcms/pages/base.html

@@ -158,10 +158,6 @@
 
   {% block footer %}{% endblock %}
 
-  {% block required_scripts %}
-  <script src="{% static 'coderedcms/vendor/jquery/jquery-3.5.1.min.js' %}?v={% coderedcms_version %}"></script>
-  {% endblock %}
-
   {% block frontend_scripts %}
   <script src="{% static 'coderedcms/vendor/bootstrap/dist/js/bootstrap.bundle.min.js' %}?v={% coderedcms_version %}"></script>
   {% endblock %}

+ 12 - 1
coderedcms/templates/coderedcms/pages/event_index_page.html

@@ -1,5 +1,5 @@
 {% extends "coderedcms/pages/web_page.html" %}
-{% load wagtailcore_tags wagtailimages_tags coderedcms_tags %}
+{% load coderedcms_tags static wagtailcore_tags wagtailimages_tags %}
 {% block content_post_body %}
 {% if self.default_calendar_view %}
 <div class="container">
@@ -43,3 +43,14 @@
   {% include "coderedcms/includes/pagination.html" with items=index_paginated %}
 </div>
 {% endblock %}
+
+{% block coderedcms_assets %}
+{{ block.super }}
+<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/fullcalendar@5.11.2/main.min.css" integrity="sha256-5veQuRbWaECuYxwap/IOE/DAwNxgm4ikX7nrgsqYp88=" crossorigin="anonymous">
+{% endblock %}
+
+{% block coderedcms_scripts %}
+{{ block.super }}
+<script src="https://cdn.jsdelivr.net/npm/fullcalendar@5.11.2/main.min.js" integrity="sha256-sR+oJaZ3c0FHR6+kKaX1zeXReUGbzuNI8QTKpGHE0sg=" crossorigin="anonymous"></script>
+<script src="{% static 'coderedcms/js/crx-events.js' %}?v={% coderedcms_version %}"></script>
+{% endblock %}

+ 6 - 4
coderedcms/templates/coderedcms/pages/location_index_page.html

@@ -1,5 +1,5 @@
 {% extends "coderedcms/pages/web_page.html" %}
-{% load wagtailcore_tags static %}
+{% load coderedcms_tags wagtailcore_tags static %}
 {% block content_body %}
 {{ block.super }}
 <div class="container">
@@ -13,9 +13,6 @@
         data-latitude="{{ page.center_latitude }}"
         data-longitude="{{ page.center_longitude }}"
         data-geojson-url="{{ request.path }}?data-format=geojson"
-        data-key="{{ google_api_key }}"
-        data-callback="initMap"
-        data-libraries="places"
         data-show-list="True"
         data-show-search="True"
         data-map-type-control=false>
@@ -39,3 +36,8 @@
   </div>
 </div>
 {% endblock %}
+{% block coderedcms_scripts %}
+{{ block.super }}
+<script src="{% static 'coderedcms/js/crx-maps.js' %}?v={% coderedcms_version %}"></script>
+<script defer src="https://maps.googleapis.com/maps/api/js?key={{ google_api_key }}&callback=initMap&libraries=places"></script>
+{% endblock %}

+ 6 - 1
coderedcms/templates/coderedcms/pages/stream_form_page.html

@@ -1,5 +1,5 @@
 {% extends "coderedcms/pages/web_page.html" %}
-{% load wagtailcore_tags coderedcms_tags %}
+{% load coderedcms_tags static wagtailcore_tags %}
 {% block content_body %}
 {{ block.super }}
 {% if page.form_live %}
@@ -58,3 +58,8 @@
 </div>
 {% endif %}
 {% endblock %}
+{% block coderedcms_scripts %}
+{{ block.super }}
+<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
+<script src="{% static 'coderedcms/js/crx-streamforms.js' %}?v={% coderedcms_version %}"></script>
+{% endblock %}