Verified Commit 0833fac5 authored by Jamie Maynard's avatar Jamie Maynard
Browse files

Various bug fixes and changes to notifications

The notifications script has switched its storage from cookies to
localstorage as it allows for more storage.  Changed the language on the
initally notification to remove ambiguity.

Bug in Changelog has been fixed where it was using the wrong URL on the
internal handbook.

Updated the page-meta-lastmod template to use `/commit` instead of
`/blob` which gives people the MR the commit appeared in.
parent 27599186
Loading
Loading
Loading
Loading
+28 −17
Original line number Diff line number Diff line
@@ -14,6 +14,8 @@ $( document ).ready(() => {
  function enableDevTools() {
    // Set up Dev tools heading and div
    sideLinks = document.getElementsByClassName("td-page-meta ml-2 pb-3 pt-2 mb-0")[0];
    if(sideLinks == null)
      return;
    devHeading = document.createElement("h5")
    devHeading.setAttribute("id", "devHeading");
    devHeading.classList.add("td-sidebar-link", "tree-root");
@@ -60,6 +62,7 @@ $( document ).ready(() => {
    devToolLinks.appendChild(viewOnOldHandbookAlt);
    devToolLinks.appendChild(copyRedirectLink);
    sideLinks.after(devToolLinks);
    if(devPreferences.notification)
      renderNotification({
        "id":  crypto.randomUUID(),
        "type": "warning",
@@ -87,7 +90,7 @@ $( document ).ready(() => {
  devPreferences = getCookie("devtool-preferences");

  if(devPreferences.length == 0)
    setCookie("devtool-preferences", JSON.stringify({enabled: false}), 365);
    setCookie("devtool-preferences", JSON.stringify({enabled: false, notification: true}), 365);
  else
    devPreferences = JSON.parse(devPreferences);

@@ -95,16 +98,24 @@ $( document ).ready(() => {
    enableDevTools()

  if(document.getElementById("enableDevToolsSwitch")) {
    console.log("Found switch");
    devToolsSwitch = document.getElementById("enableDevToolsSwitch")
    devToolsSwitch.disabled = false;
    devToolsSwitch.checked = devPreferences.enabled;
    devToolsSwitch.onchange = () => {
      setCookie("devtool-preferences", JSON.stringify({enabled: devToolsSwitch.checked}), 365);
      if(devToolsSwitch.checked)
    devToolsEnableSwitch = document.getElementById("enableDevToolsSwitch")
    devToolsEnableSwitch.disabled = false;
    devToolsEnableSwitch.checked = devPreferences.enabled;
    devToolsEnableSwitch.onchange = (e) => {
      setCookie("devtool-preferences", JSON.stringify({enabled: e.target.checked, notification: devPreferences.notification}), 365);
      if(devToolsEnableSwitch.checked)
        enableDevTools();
      else
        disableDevTools();
    }
  }
  if(document.getElementById("showDevNotificationSwitch")) {
    devToolsNotificationSwitch = document.getElementById("showDevNotificationSwitch")
    devToolsNotificationSwitch.disabled = false;
    devToolsNotificationSwitch.checked = devPreferences.notification;
    devToolsNotificationSwitch.onchange = (e) => {
      setCookie("devtool-preferences", JSON.stringify({enabled: devPreferences.enabled, notification: e.target.checked}), 365);
    }
  }

});
+9 −20
Original line number Diff line number Diff line
@@ -6,7 +6,9 @@ $( document ).ready(() => {
            id = key.split('toast-')[1];
            document.getElementById(id+"-check").checked = false
            setCookie(key, false, 0);
          }});
          }
        });
        localStorage.removeItem("toasts")
    }

    function disableNotifications() {
@@ -47,23 +49,14 @@ $( document ).ready(() => {
        nid = e.currentTarget.nid
        if(e.currentTarget.textContent === "Unread") {
            processedNotifications.pop(nid);
            setCookie("toast-"+nid, false, 0);
            delToast(nid);
            document.getElementById(nid+"-check").checked = false
            e.currentTarget.textContent = "Read";
            e.currentTarget.classList.remove('btn-danger');
            e.currentTarget.classList.add("btn-success");
        } else {
          n = JSON.parse(document.getElementById(nid).value);
            switch(n.type) {
              case "warning":
                  setCookie("toast-"+nid, true, notificationPreferences.warningHide);
                  break;
              case "danger":
                  setCookie("toast-"+nid, true, notificationPreferences.dangerHide);
                  break;
              default:
                  setCookie("toast-"+nid, true, notificationPreferences.defaultHide);
          }
          saveToast(n.id, n.type);
          e.currentTarget.textContent = "Unread";
          document.getElementById(nid+"-check").checked = true
          e.currentTarget.classList.remove('btn-success');
@@ -139,14 +132,10 @@ $( document ).ready(() => {
        setCookie("notification-preferences", JSON.stringify(notificationPreferences), 365);
    };

    // Identify notifications we've already read
    document.cookie.split('; ').forEach(c => {
      key = c.split('=')[0];
      if (key.includes('toast-')) {
        id = key.split('toast-')[1];
        if(document.getElementById(id+"-check") != null)
          document.getElementById(id+"-check").checked = true
      }});
    getToasts().forEach(t => {
      if(document.getElementById(t.id+"-check") != null)
      document.getElementById(t.id+"-check").checked = true
    });

    // Set up server side notifications buttons
    for (let e of document.getElementsByClassName("notificationData")) {
+126 −39
Original line number Diff line number Diff line
@@ -107,13 +107,8 @@ async function processNotifications() {
      return false;
    }

    // Don't show warnings we've seen before
    seenWarning = getCookie("toast-"+ n.id);
    if(seenWarning)
        return false;
    const now = new Date();

    // Don't show warnings which have expired
    const now = new Date();
    const expiry = new Date(Date.parse(n.expires));
    if (now > expiry)
        return false;
@@ -154,6 +149,38 @@ function processUpcomingNotifications() {
  })
}

function getToasts() {
  const toasts = localStorage.getItem("toasts")
  if(toasts === null || toasts.length === 0)
    return [];
  return JSON.parse(toasts)
}

function delToast(toastId) {
  const toasts = getToasts().filter(t => t.id !== toastId);
  localStorage.setItem("toasts", JSON.stringify(toasts));
}

function saveToast(toastId, toastType) {
  const toasts = getToasts();
  // Don't add duplicates
  if( toasts.find((e) => e.id === toastId))
    return
  let expiry = new Date();
  switch(toastType) {
      case "warning":
          expiry.setDate(expiry.getDate() + notificationPreferences.warningHide)
          break;
      case "danger":
          expiry.setDate(expiry.getDate() + notificationPreferences.dangerHide)
          break;
      default:
          expiry.setDate(expiry.getDate() + notificationPreferences.defaultHide)
  }
  toasts.push({id: toastId, type: toastType, expires: expiry});
  localStorage.setItem("toasts", JSON.stringify(toasts));
}

function renderNotification(notification, withCookie=true) {
  const {title, icon, messageHTML, autohide, type, expires, posted, id} = notification;
  const toastEl = document.createElement("div");
@@ -200,16 +227,7 @@ function renderNotification(notification, withCookie=true) {
              e.classList.remove('btn-success');
              e.classList.add("btn-danger");
            }
          switch(type) {
              case "warning":
                  setCookie("toast-"+id, true, notificationPreferences.warningHide);
                  break;
              case "danger":
                  setCookie("toast-"+id, true, notificationPreferences.dangerHide);
                  break;
              default:
                  setCookie("toast-"+id, true, notificationPreferences.defaultHide);
          }
          saveToast(id, type);
      });
  }
  document.getElementById('toasts').appendChild(toastEl);
@@ -219,7 +237,58 @@ function renderNotification(notification, withCookie=true) {
  toast.show();
}

function startNotifications() {
async function saveLegacyToasts(legacyToasts) {
  let response = null;
  let jsonData = null;
  try {
    response = await fetch('/notifications.json', {cache: "no-store"});
    jsonData = await response.json();
    if(jsonData == null || jsonData.length == 0) {
      throw("Notifications Data is empty");
    }

    jsonData.notifications.forEach(n => {
      if(legacyToasts.includes(n.id)) {
        saveToast(n.id, n.type)
      }
    });
  } catch(err) {
    console.log("Something went wrong processing legacy notifications");
  }
}

async function cleanupLegacyToastCookies() {
  let decodedCookie = decodeURIComponent(document.cookie);
  let ca = decodedCookie.split(';');
  let cookieToasts = []
  for(let i = 0; i <ca.length; i++) {
    let c = ca[i];
    while (c.charAt(0) == ' ') {
      c = c.substring(1);
    }
    if (c.indexOf("toast-") == 0) {
      cookieToasts.push(c.split("=")[0].split("toast-")[1]);
    }
  }
  cookieToasts.forEach(c => {
    setCookie("toast-"+c, false, 0);
  });
  await saveLegacyToasts(cookieToasts);
}

async function startNotifications() {
  await cleanupLegacyToastCookies();
  storedToasts = getToasts();
  storedToasts = storedToasts.filter(t => {
    const now = new Date();
    const expiry = new Date(Date.parse(t.expires));
    if (now > expiry)
        return false
    processedNotifications.push(t.id);
    return true;
  });
  localStorage.setItem("toasts", JSON.stringify(storedToasts));

  // Do initial processing of notifications
  processNotifications();
  checkForPageChanges()
@@ -243,6 +312,8 @@ function stopNotifications() {
  allToasts.forEach((t) => {
    t._element.remove();
  });
  upcomingNotifications = [];
  processedNotifications = [];
}

function startPageUpdates() {
@@ -279,36 +350,49 @@ $( document ).ready(() => {
          "defaultHide": 365
      };
      setCookie("notification-preferences", JSON.stringify(notificationPreferences), 365);
      n = {
          "id": crypto.randomUUID(),
          "type": "success",
          "icon": "fa-brands fa-gitlab",
          "title": "Now with Added Notifications",
          "messageHTML": `<p>The Handbook now has notifications.  This allows us to share important updates
      const id = crypto.randomUUID();
      const toastEl = document.createElement("div");
      toastEl.id = `toast-${id}`;
      toastEl.className = "toast";
      toastEl.style = "width: 40em !important";
      toastEl.setAttribute('data-bs-autohide', false);
      toastEl.role = "alert";
      toastEl.setAttribute("aria-live", "assertive");
      toastEl.setAttribute("aria-atomic", true);
      typeClasses = "bg-success text-white";
      toastEl.innerHTML = `
            <div class="toast-header bg-success text-white h4">
              <strong class="me-auto"><i style="margin-right: 0.75em" class="fa-brands fa-gitlab"></i> Now with Added Notifications</strong>
            </div>
            <div class="toast-body">
              <p>The Handbook now has notifications.  This allows us to share important updates
              about the site and pages with you as well as providing features such as checking for page updates.
              You can control your notifications from the <a href="/handbook/about/tools/handbook-preferences">
          Handbook Preferences</a> pages or if you don't want notifications you can disable them by clicking
          the button below.</p>
              Handbook Preferences</a> pages.</p>
              <hr>
              <p><strong>Are you happy to receive notifications?</strong></p>

              <p>
            <button type="button" id="imGoodBtn" class="btn btn-success text-white">I'm Good</button>
            <button type="button" id="killNotificationsBtn" class="btn btn-light">I don't want notifications</button>
          </p>`,
          "site": window.location.origin,
          "expires": new Date(),
          "autohide": false
      }
      renderNotification(n, false);
      document.getElementById("killNotificationsBtn").addEventListener("click", () => {
                <button type="button" id="yesBtn" class="btn btn-success text-white">Yes</button>
                <button type="button" id="noBtn" class="btn btn-light">No</button>
              </p>
            </div>
        `;
      document.getElementById('toasts').appendChild(toastEl);
      const toast = new bootstrap.Toast(toastEl);
      toast.id = id;
      document.getElementById("noBtn").addEventListener("click", () => {
        notificationPreferences.showNotifications = false;
        setCookie("notification-preferences", JSON.stringify(notificationPreferences), 365);
        stopNotifications()
      });
      document.getElementById("imGoodBtn").nid = n.id;
      document.getElementById("imGoodBtn").addEventListener("click", (e) => {
      document.getElementById("yesBtn").nid = id;
      document.getElementById("yesBtn").addEventListener("click", (e) => {
          t = allToasts.find(t => t.id === e.currentTarget.nid);
          t.hide();
      });
      allToasts.push(toast);
      toast.show();
  } else {
    notificationPreferences = JSON.parse(notificationPreferences);
  }
@@ -322,6 +406,9 @@ $( document ).ready(() => {
  // Set gloabal scope items
  globalThis.getCookie = getCookie;
  globalThis.setCookie = setCookie;
  globalThis.getToasts = getToasts;
  globalThis.saveToast = saveToast;
  globalThis.delToast = delToast;
  globalThis.notificationPreferences = notificationPreferences;
  globalThis.renderNotification = renderNotification;
  globalThis.stopNotifications = stopNotifications;
+9 −5
Original line number Diff line number Diff line
baseURL: https://handbook.gitlab.com
baseURL: https://docsy-gitlab.gitlab.com
languageCode: en-us
title: The GitLab Handbook
title: The GitLab Hugo Docsy Theme

params:
  branch: main
  project: https://gitlab.com/gitlab-com/content-sites/handbook
  project: gitlab-com/content-sites/docsy-gitlab
  repoid: 40085411
  plantuml:
    enable: true
@@ -58,9 +58,13 @@ menu:
    #   name: Project
    #   url: https://gitlab.com/internal-handbook/internal-handbook.gitlab.io
    #   pre: '<i class="fa fa-code"></i>'
    - weight: 10
      name: Changelog
      url: /handbook/about/changelog/
      pre: '<i class="fa-solid fa-code-merge"></i>'
    - weight: 30
      name: Current Public Handbook
      url: https://about.gitlab.com/handbook/
      name: The Handbook
      url: https://handbook.gitlab.com/handbook/
      pre: '<i class="fa fa-book"></i>'

mediaTypes:
+5 −1
Original line number Diff line number Diff line
@@ -78,9 +78,13 @@ They are used to help test out features or provide tools used to develop and imp
at your own risk.
{{< /panel >}}
</div>
<div class="form-check form-switch mb-3 h4">
<div class="form-check form-switch mb-3 me-3 h4 float-start">
  <input class="form-check-input" type="checkbox" role="switch" id="enableDevToolsSwitch" disabled>
  <label class="form-check-label" for="enableDevToolsSwitch">Enable Developer Tools</label>
</div>
<div class="form-check form-switch mb-3 h4 float-start">
  <input class="form-check-input" type="checkbox" role="switch" id="showDevNotificationSwitch" disabled>
  <label class="form-check-label" for="showDevNotification">Show Developer Tools Notification Everytime</label>
</div>
{{< /card >}}
</div>
Loading