refactor of authentication and html-templates into separate modules
[websub-hub] / src / template / template-helper.js
index 0ff90772ec7ebd2479344979b08e7d4c075fdb01..bb4dad1e3f574f2332683c9e4bc990c9bc98b3a2 100644 (file)
@@ -1,56 +1,6 @@
 'use strict';
 
-/**
- * A bunch of shorthand to put together common parts of an HTML page. 
- */
-
-/**
- * Some fields may have values outside normal dates, handle them here.
- * @param {Date} date
- * @param {String} otherwise
- */
-const dateOrNot = (date, otherwise) => {
-  if (!date) {
-    return otherwise;
-  }
-  if (typeof date === 'number') {
-    date = new Date(date);
-  }
-  const dateMs = date.getTime();
-  if (!Number.isFinite(dateMs)
-  ||  dateMs == 0) {
-    return otherwise;
-  }
-  return date.toString();
-};
-
-
-/**
- * Render a duration.
- * @param {Number} seconds
- * @returns {String}
- */
-const secondsToPeriod = (seconds) => {
-  let value = seconds;
-  const result = [];
-
-  const nextResult = (factor, label) => {
-    const r = factor ? value % factor : value;
-    if (r) {
-      result.push(`${r} ${label}${r != 1 ? 's' : ''}`);
-    }
-    value = factor ? Math.floor(value / factor) : value;
-  }
-
-  nextResult(60, 'second');
-  nextResult(60, 'minute');
-  nextResult(24, 'hour');
-  nextResult(30, 'day');
-  nextResult(undefined, 'month');
-
-  result.reverse();
-  return result.join(' ');
-};
+const { TemplateHelper } = require('@squeep/html-template-helper');
 
 
 /**
@@ -69,17 +19,17 @@ function renderTopicRow(topic, subscribers, detailsLink = true) {
   return `<tr>
   <th scope="row">${detailsLink ? '<a href="topic/' + topic.id + '">' : ''}${topic.url}${detailsLink ? '</a>' : ''}</th>
   <td>${subscribers.length}</td>
-  <td>${dateOrNot(topic.created, 'Unknown')}</td>
-  <td>${secondsToPeriod(topic.leaseSecondsPreferred)}</td>
-  <td>${secondsToPeriod(topic.leaseSecondsMin)}</td>
-  <td>${secondsToPeriod(topic.leaseSecondsMax)}</td>
+  <td>${TemplateHelper.dateOrNot(topic.created, 'Unknown')}</td>
+  <td>${TemplateHelper.secondsToPeriod(topic.leaseSecondsPreferred)}</td>
+  <td>${TemplateHelper.secondsToPeriod(topic.leaseSecondsMin)}</td>
+  <td>${TemplateHelper.secondsToPeriod(topic.leaseSecondsMax)}</td>
   <td>${topic.publisherValidationUrl ? topic.publisherValidationUrl : 'None'}</td>
   <td>${topic.isActive}</td>
   <td>${topic.isDeleted}</td>
-  <td>${dateOrNot(topic.lastPublish, 'Never')}</td>
-  <td>${dateOrNot(topic.contentFetchNextAttempt, 'Next Publish')}</td>
+  <td>${TemplateHelper.dateOrNot(topic.lastPublish, 'Never')}</td>
+  <td>${TemplateHelper.dateOrNot(topic.contentFetchNextAttempt, 'Next Publish')}</td>
   <td>${topic.contentFetchAttemptsSinceSuccess}</td>
-  <td>${dateOrNot(topic.contentUpdated, 'Never')}</td>
+  <td>${TemplateHelper.dateOrNot(topic.contentUpdated, 'Never')}</td>
   <td>${topic.contentType}</td>
   <td>${topic.id}</td>
 </tr>`;
@@ -124,16 +74,16 @@ function renderSubscriptionRow(subscription) {
   }
   return `<tr>
   <td scope="row">${subscription.callback}</td>
-  <td>${dateOrNot(subscription.created, 'Unknown')}</td>
-  <td>${dateOrNot(subscription.verified, 'Never')}</td>
-  <td>${dateOrNot(subscription.expires, 'Never')}</td>
+  <td>${TemplateHelper.dateOrNot(subscription.created, 'Unknown')}</td>
+  <td>${TemplateHelper.dateOrNot(subscription.verified, 'Never')}</td>
+  <td>${TemplateHelper.dateOrNot(subscription.expires, 'Never')}</td>
   <td>${!!subscription.secret}</td>
   <td>${subscription.signatureAlgorithm}</td>
   <td>${subscription.httpRemoteAddr}</td>
   <td>${subscription.httpFrom}</td>
-  <td>${dateOrNot(subscription.contentDelivered, 'Never')}</td>
+  <td>${TemplateHelper.dateOrNot(subscription.contentDelivered, 'Never')}</td>
   <td>${subscription.deliveryAttemptsSinceSuccess}</td>
-  <td>${dateOrNot(subscription.deliveryNextAttempt, 'Next Publish')}</td>
+  <td>${TemplateHelper.dateOrNot(subscription.deliveryNextAttempt, 'Next Publish')}</td>
   <td>${subscription.id}</td>
 </tr>`;
 }
@@ -162,135 +112,9 @@ function renderSubscriptionRowHeader() {
 }
 
 
-/**
- * Render the preamble for an HTML page, up through body.
- * @param {Number} pagePathLevel number of paths below root this page is
- * @param {String} pageTitle
- * @param {String[]} headElements
- * @returns 
- */
-function htmlHead(pagePathLevel, pageTitle, headElements = []) {
-  const rootPathPfx = '../'.repeat(pagePathLevel);
-  return `<!DOCTYPE html>
-<html lang="en">
-  <head>
-    <meta charset="utf-8">` +
-  headElements.map((e) => `${'  '.repeat(2)}${e}`).join('\n') + `
-    <title>${pageTitle}</title>
-    <link rel="stylesheet" href="${rootPathPfx}static/theme.css">
-  </head>
-  <body>`;
-}
-
-
-/**
- * Closes remainder of HTML page body.
- * @returns {String}
- */
-function htmlTail() {
-  return `  </body>
-</html>`;
-}
-
-
-/**
- * Render a navigation link for the header section.
- * @param {Object} nav
- * @param {String} nav.href
- * @param {String} nav.class
- * @param {String} nav.text
- * @returns {String}
- */
-function renderNavLink(nav) {
-  return `<li>
-  <a href="${nav.href}"${nav.class ? (' class="' + nav.class + '"') : ''}>${nav.text}</a>
-</li>`;
-}
-
-
-/**
- * Render the navigation header, and open the main section.
- * @param {String} pageTitle
- * @param {Object[]} navLinks
- * @returns {String}
- */
-function htmlHeader(pageTitle, navLinks = []) {
-  return `    <header>
-      <h1>${pageTitle}</h1>
-      <nav>` +
-    (navLinks.length ? `
-        <ol>
-          ${navLinks.map((l) => renderNavLink(l)).join('\n')}
-        </ol>`
-      : '') + `
-      </nav>
-    </header>
-    <main>`;
-}
-
-
-/**
- * Close the main section and finish off with boilerplate.
- * @param {String[]} footerEntries
- * @returns {String}
- */
-function htmlFooter(footerEntries = []) {
-  return `    </main>
-    <footer>` +
-    (footerEntries.length ? `
-      <ol>` + footerEntries.map((f) => `        <li>${f}</li>`).join('\n') + `
-      </ol>`
-      : '') + `
-    </footer>`;
-}
-
-
-/**
- * Render all parts of an HTML page. Adds user logout nav link automatically.
- * @param {Object} ctx
- * @param {Number} pagePathLevel
- * @param {String} pageTitle
- * @param {String[]} headElements
- * @param {Object[]} navLinks
- * @param {String[]} main
- * @param {String[]} footerEntries
- * @returns {String}
- */
-function htmlTemplate(ctx, pagePathLevel, pageTitle, headElements = [], navLinks = [], main = [], footerEntries = []) {
-  const user = (ctx && ctx.session && ctx.session.authenticatedProfile) || (ctx && ctx.session && ctx.session.authenticatedIdentifier);
-  if (user) {
-    let logoutPath;
-    if (pagePathLevel > 0) {
-      logoutPath = `${'../'.repeat(pagePathLevel - 1)}`;
-    } else {
-      logoutPath = 'admin/';
-    }
-    navLinks.push({
-      text: `Logout (${user})`,
-      href: `${logoutPath}logout`,
-    });
-  }
-  return [
-    htmlHead(pagePathLevel, pageTitle, headElements),
-    htmlHeader(pageTitle, navLinks),
-    ...main,
-    htmlFooter(footerEntries),
-    htmlTail(),
-  ].join('\n');
-}
-
-
-module.exports = {
-  dateOrNot,
-  secondsToPeriod,
-  htmlHeader,
-  htmlFooter,
-  htmlHead,
-  htmlTail,
-  renderNavLink,
+module.exports = Object.assign(Object.create(TemplateHelper), {
   renderTopicRowHeader,
   renderTopicRow,
   renderSubscriptionRowHeader,
   renderSubscriptionRow,
-  htmlTemplate,
-};
\ No newline at end of file
+});
\ No newline at end of file