« get me outta code hell

content: cut html.template boilerplate - hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
path: root/src/content/dependencies/generatePageLayout.js
diff options
context:
space:
mode:
author(quasar) nebula <qznebula@protonmail.com>2023-06-12 16:35:38 -0300
committer(quasar) nebula <qznebula@protonmail.com>2023-06-12 16:35:38 -0300
commit535acb34613b5cf7e22654619f4337b94b70644d (patch)
tree5a713eb4f12eae7e1fe1aa60941709708585db32 /src/content/dependencies/generatePageLayout.js
parent630af0a345f3be6c3e4aa3300ce138e48ed5ae91 (diff)
content: cut html.template boilerplate
Diffstat (limited to 'src/content/dependencies/generatePageLayout.js')
-rw-r--r--src/content/dependencies/generatePageLayout.js930
1 files changed, 462 insertions, 468 deletions
diff --git a/src/content/dependencies/generatePageLayout.js b/src/content/dependencies/generatePageLayout.js
index 84acca0..610b4a1 100644
--- a/src/content/dependencies/generatePageLayout.js
+++ b/src/content/dependencies/generatePageLayout.js
@@ -1,5 +1,48 @@
 import {empty, openAggregate} from '../../util/sugar.js';
 
+function sidebarSlots(side) {
+  return {
+    // Content is a flat HTML array. It'll generate one sidebar section
+    // if specified.
+    [side + 'Content']: {type: 'html'},
+
+    // Multiple is an array of {content: (HTML)} objects. Each of these
+    // will generate one sidebar section.
+    [side + 'Multiple']: {
+      validate: v =>
+        v.arrayOf(
+          v.validateProperties({
+            content: v.isHTML,
+          })),
+    },
+
+    // Sticky mode controls which sidebar section(s), if any, follow the
+    // scroll position, "sticking" to the top of the browser viewport.
+    //
+    // 'last' - last or only sidebar box is sticky
+    // 'column' - entire column, incl. multiple boxes from top, is sticky
+    // 'none' - sidebar not sticky at all, stays at top of page
+    //
+    // Note: This doesn't affect the content of any sidebar section, only
+    // the whole section's containing box (or the sidebar column as a whole).
+    [side + 'StickyMode']: {
+      validate: v => v.is('last', 'column', 'static'),
+    },
+
+    // Collapsing sidebars disappear when the viewport is sufficiently
+    // thin. (This is the default.) Override as false to make the sidebar
+    // stay visible in thinner viewports, where the page layout will be
+    // reflowed so the sidebar is as wide as the screen and appears below
+    // nav, above the main content.
+    [side + 'Collapse']: {type: 'boolean', default: true},
+
+    // Wide sidebars generally take up more horizontal space in the normal
+    // page layout, and should be used if the content of the sidebar has
+    // a greater than typical focus compared to main content.
+    [side + 'Wide']: {type: 'boolean', defualt: false},
+  };
+}
+
 export default {
   contentDependencies: [
     'generateColorStyleRules',
@@ -49,495 +92,446 @@ export default {
     return relations;
   },
 
-  generate(data, relations, {
+  slots: {
+    title: {type: 'html'},
+    showWikiNameInTitle: {type: 'boolean', default: true},
+
+    cover: {type: 'html'},
+
+    socialEmbed: {type: 'html'},
+
+    colorStyleRules: {
+      validate: v => v.arrayOf(v.isString),
+      default: [],
+    },
+
+    additionalStyleRules: {
+      validate: v => v.arrayOf(v.isString),
+      default: [],
+    },
+
+    mainClasses: {
+      validate: v => v.arrayOf(v.isString),
+      default: [],
+    },
+
+    // Main
+
+    mainContent: {type: 'html'},
+
+    headingMode: {
+      validate: v => v.is('sticky', 'static'),
+      default: 'static',
+    },
+
+    // Sidebars
+
+    ...sidebarSlots('leftSidebar'),
+    ...sidebarSlots('rightSidebar'),
+
+    // Nav & Footer
+
+    navContent: {type: 'html'},
+    navBottomRowContent: {type: 'html'},
+
+    navLinkStyle: {
+      validate: v => v.is('hierarchical', 'index'),
+      default: 'index',
+    },
+
+    navLinks: {
+      validate: v =>
+        v.arrayOf(object => {
+          v.isObject(object);
+
+          const aggregate = openAggregate({message: `Errors validating navigation link`});
+
+          aggregate.call(v.validateProperties({
+            auto: () => true,
+            html: () => true,
+
+            path: () => true,
+            title: () => true,
+            accent: () => true,
+          }), object);
+
+          if (object.auto || object.html) {
+            if (object.auto && object.html) {
+              aggregate.push(new TypeError(`Don't specify both auto and html`));
+            } else if (object.auto) {
+              aggregate.call(v.is('home', 'current'), object.auto);
+            } else {
+              aggregate.call(v.isHTML, object.html);
+            }
+
+            if (object.path || object.title) {
+              aggregate.push(new TypeError(`Don't specify path or title along with auto or html`));
+            }
+          } else {
+            aggregate.call(v.validateProperties({
+              path: v.arrayOf(v.isString),
+              title: v.isString,
+            }), {
+              path: object.path,
+              title: object.title,
+            });
+          }
+
+          aggregate.close();
+
+          return true;
+        })
+    },
+
+    footerContent: {type: 'html'},
+  },
+
+  generate(data, relations, slots, {
     cachebust,
     html,
     language,
     to,
   }) {
-    const sidebarSlots = side => ({
-      // Content is a flat HTML array. It'll generate one sidebar section
-      // if specified.
-      [side + 'Content']: {type: 'html'},
-
-      // Multiple is an array of {content: (HTML)} objects. Each of these
-      // will generate one sidebar section.
-      [side + 'Multiple']: {
-        validate: v =>
-          v.arrayOf(
-            v.validateProperties({
-              content: v.isHTML,
-            })),
-      },
-
-      // Sticky mode controls which sidebar section(s), if any, follow the
-      // scroll position, "sticking" to the top of the browser viewport.
-      //
-      // 'last' - last or only sidebar box is sticky
-      // 'column' - entire column, incl. multiple boxes from top, is sticky
-      // 'none' - sidebar not sticky at all, stays at top of page
-      //
-      // Note: This doesn't affect the content of any sidebar section, only
-      // the whole section's containing box (or the sidebar column as a whole).
-      [side + 'StickyMode']: {
-        validate: v => v.is('last', 'column', 'static'),
-      },
-
-      // Collapsing sidebars disappear when the viewport is sufficiently
-      // thin. (This is the default.) Override as false to make the sidebar
-      // stay visible in thinner viewports, where the page layout will be
-      // reflowed so the sidebar is as wide as the screen and appears below
-      // nav, above the main content.
-      [side + 'Collapse']: {type: 'boolean', default: true},
-
-      // Wide sidebars generally take up more horizontal space in the normal
-      // page layout, and should be used if the content of the sidebar has
-      // a greater than typical focus compared to main content.
-      [side + 'Wide']: {type: 'boolean', defualt: false},
-    });
-
-    return html.template({
-      annotation: 'generatePageLayout',
-
-      slots: {
-        title: {type: 'html'},
-        showWikiNameInTitle: {type: 'boolean', default: true},
-
-        cover: {type: 'html'},
-
-        socialEmbed: {type: 'html'},
-
-        colorStyleRules: {
-          validate: v => v.arrayOf(v.isString),
-          default: [],
-        },
-
-        additionalStyleRules: {
-          validate: v => v.arrayOf(v.isString),
-          default: [],
-        },
-
-        mainClasses: {
-          validate: v => v.arrayOf(v.isString),
-          default: [],
-        },
-
-        // Main
-
-        mainContent: {type: 'html'},
-
-        headingMode: {
-          validate: v => v.is('sticky', 'static'),
-          default: 'static',
-        },
-
-        // Sidebars
-
-        ...sidebarSlots('leftSidebar'),
-        ...sidebarSlots('rightSidebar'),
-
-        // Nav & Footer
-
-        navContent: {type: 'html'},
-        navBottomRowContent: {type: 'html'},
-
-        navLinkStyle: {
-          validate: v => v.is('hierarchical', 'index'),
-          default: 'index',
-        },
-
-        navLinks: {
-          validate: v =>
-            v.arrayOf(object => {
-              v.isObject(object);
-
-              const aggregate = openAggregate({message: `Errors validating navigation link`});
-
-              aggregate.call(v.validateProperties({
-                auto: () => true,
-                html: () => true,
-
-                path: () => true,
-                title: () => true,
-                accent: () => true,
-              }), object);
-
-              if (object.auto || object.html) {
-                if (object.auto && object.html) {
-                  aggregate.push(new TypeError(`Don't specify both auto and html`));
-                } else if (object.auto) {
-                  aggregate.call(v.is('home', 'current'), object.auto);
-                } else {
-                  aggregate.call(v.isHTML, object.html);
-                }
-
-                if (object.path || object.title) {
-                  aggregate.push(new TypeError(`Don't specify path or title along with auto or html`));
-                }
-              } else {
-                aggregate.call(v.validateProperties({
-                  path: v.arrayOf(v.isString),
-                  title: v.isString,
-                }), {
-                  path: object.path,
-                  title: object.title,
-                });
-              }
+    let titleHTML = null;
+
+    if (!html.isBlank(slots.title)) {
+      switch (slots.headingMode) {
+        case 'sticky':
+          titleHTML =
+            relations.stickyHeadingContainer.slots({
+              title: slots.title,
+              cover: slots.cover,
+            });
+          break;
+        case 'static':
+          titleHTML = html.tag('h1', slots.title);
+          break;
+      }
+    }
+
+    let footerContent = slots.footerContent;
+
+    if (html.isBlank(footerContent)) {
+      footerContent = relations.defaultFooterContent
+        .slot('mode', 'multiline');
+    }
+
+    const mainHTML =
+      html.tag('main', {
+        id: 'content',
+        class: slots.mainClasses,
+      }, [
+        titleHTML,
+
+        slots.cover,
+
+        html.tag('div',
+          {
+            [html.onlyIfContent]: true,
+            class: 'main-content-container',
+          },
+          slots.mainContent),
+      ]);
 
-              aggregate.close();
+    const footerHTML =
+      html.tag('footer',
+        {[html.onlyIfContent]: true, id: 'footer'},
+        [
+          html.tag('div',
+            {
+              [html.onlyIfContent]: true,
+              class: 'footer-content',
+            },
+            footerContent),
 
-              return true;
-            })
-        },
+          relations.footerLocalizationLinks,
+        ]);
 
-        footerContent: {type: 'html'},
+    const navHTML = html.tag('nav',
+      {
+        [html.onlyIfContent]: true,
+        id: 'header',
+        class: [
+          !empty(slots.navLinks) && 'nav-has-main-links',
+          !html.isBlank(slots.navContent) && 'nav-has-content',
+          !html.isBlank(slots.navBottomRowContent) && 'nav-has-bottom-row',
+        ],
       },
-
-      content(slots) {
-        let titleHTML = null;
-
-        if (!html.isBlank(slots.title)) {
-          switch (slots.headingMode) {
-            case 'sticky':
-              titleHTML =
-                relations.stickyHeadingContainer.slots({
-                  title: slots.title,
-                  cover: slots.cover,
-                });
-              break;
-            case 'static':
-              titleHTML = html.tag('h1', slots.title);
-              break;
-          }
-        }
-
-        let footerContent = slots.footerContent;
-
-        if (html.isBlank(footerContent)) {
-          footerContent = relations.defaultFooterContent
-            .slot('mode', 'multiline');
-        }
-
-        const mainHTML =
-          html.tag('main', {
-            id: 'content',
-            class: slots.mainClasses,
-          }, [
-            titleHTML,
-
-            slots.cover,
-
-            html.tag('div',
-              {
-                [html.onlyIfContent]: true,
-                class: 'main-content-container',
-              },
-              slots.mainContent),
-          ]);
-
-        const footerHTML =
-          html.tag('footer',
-            {[html.onlyIfContent]: true, id: 'footer'},
-            [
-              html.tag('div',
-                {
-                  [html.onlyIfContent]: true,
-                  class: 'footer-content',
-                },
-                footerContent),
-
-              relations.footerLocalizationLinks,
-            ]);
-
-        const navHTML = html.tag('nav',
+      [
+        html.tag('div',
           {
             [html.onlyIfContent]: true,
-            id: 'header',
             class: [
-              !empty(slots.navLinks) && 'nav-has-main-links',
-              !html.isBlank(slots.navContent) && 'nav-has-content',
-              !html.isBlank(slots.navBottomRowContent) && 'nav-has-bottom-row',
+              'nav-main-links',
+              'nav-links-' + slots.navLinkStyle,
             ],
           },
-          [
-            html.tag('div',
-              {
-                [html.onlyIfContent]: true,
-                class: [
-                  'nav-main-links',
-                  'nav-links-' + slots.navLinkStyle,
-                ],
-              },
-              slots.navLinks?.map((cur, i) => {
-                let content;
-
-                if (cur.html) {
-                  content = cur.html;
-                } else {
-                  let title;
-                  let href;
-
-                  switch (cur.auto) {
-                    case 'home':
-                      title = data.wikiName;
-                      href = to('localized.home');
-                      break;
-                    case 'current':
-                      title = slots.title;
-                      href = '';
-                      break;
-                    case null:
-                    case undefined:
-                      title = cur.title;
-                      href = to(...cur.path);
-                      break;
-                  }
-
-                  content = html.tag('a',
-                    {href},
-                    title);
-                }
-
-                let className;
-
-                if (cur.auto === 'current') {
-                  className = 'current';
-                } else if (
-                  slots.navLinkStyle === 'hierarchical' &&
-                  i === slots.navLinks.length - 1
-                ) {
-                  className = 'current';
-                }
-
-                return html.tag('span',
-                  {class: className},
-                  [
-                    html.tag('span',
-                      {class: 'nav-link-content'},
-                      content),
-                    html.tag('span',
-                      {[html.onlyIfContent]: true, class: 'nav-link-accent'},
-                      cur.accent),
-                  ]);
-              })),
-
-            html.tag('div',
-              {[html.onlyIfContent]: true, class: 'nav-bottom-row'},
-              slots.navBottomRowContent),
-
-            html.tag('div',
-              {[html.onlyIfContent]: true, class: 'nav-content'},
-              slots.navContent),
-          ])
-
-        const generateSidebarHTML = (side, id) => {
-          const content = slots[side + 'Content'];
-          const multiple = slots[side + 'Multiple'];
-          const stickyMode = slots[side + 'StickyMode'];
-          const wide = slots[side + 'Wide'];
-          const collapse = slots[side + 'Collapse'];
-
-          let sidebarClasses = [];
-          let sidebarContent = html.blank();
-
-          if (!html.isBlank(content)) {
-            sidebarClasses = ['sidebar'];
-            sidebarContent = content;
-          } else if (multiple) {
-            sidebarClasses = ['sidebar-multiple'];
-            sidebarContent =
-              multiple
-                .filter(Boolean)
-                .map(({content}) =>
-                  html.tag('div',
-                    {
-                      [html.onlyIfContent]: true,
-                      class: 'sidebar',
-                    },
-                    content));
-          }
+          slots.navLinks?.map((cur, i) => {
+            let content;
+
+            if (cur.html) {
+              content = cur.html;
+            } else {
+              let title;
+              let href;
+
+              switch (cur.auto) {
+                case 'home':
+                  title = data.wikiName;
+                  href = to('localized.home');
+                  break;
+                case 'current':
+                  title = slots.title;
+                  href = '';
+                  break;
+                case null:
+                case undefined:
+                  title = cur.title;
+                  href = to(...cur.path);
+                  break;
+              }
 
-          return html.tag('div',
-            {
-              [html.onlyIfContent]: true,
-              id,
-              class: [
-                'sidebar-column',
-                wide && 'wide',
-                !collapse && 'no-hide',
-                stickyMode !== 'static' && `sticky-${stickyMode}`,
-                ...sidebarClasses,
-              ],
-            },
-            sidebarContent);
-        }
-
-        const sidebarLeftHTML = generateSidebarHTML('leftSidebar', 'sidebar-left');
-        const sidebarRightHTML = generateSidebarHTML('rightSidebar', 'sidebar-right');
-        const collapseSidebars = slots.leftSidebarCollapse && slots.rightSidebarCollapse;
-
-        const imageOverlayHTML = html.tag('div', {id: 'image-overlay-container'},
-          html.tag('div', {id: 'image-overlay-content-container'}, [
-            html.tag('a', {id: 'image-overlay-image-container'}, [
-              html.tag('img', {id: 'image-overlay-image'}),
-              html.tag('img', {id: 'image-overlay-image-thumb'}),
-            ]),
-            html.tag('div', {id: 'image-overlay-action-container'}, [
-              html.tag('div', {id: 'image-overlay-action-content-without-size'},
-                language.$('releaseInfo.viewOriginalFile', {
-                  link: html.tag('a', {class: 'image-overlay-view-original'},
-                    language.$('releaseInfo.viewOriginalFile.link')),
-                })),
-
-              html.tag('div', {id: 'image-overlay-action-content-with-size'}, [
-                language.$('releaseInfo.viewOriginalFile.withSize', {
-                  link: html.tag('a', {class: 'image-overlay-view-original'},
-                    language.$('releaseInfo.viewOriginalFile.link')),
-                  size: html.tag('span',
-                    {[html.joinChildren]: ''},
-                    [
-                      html.tag('span', {id: 'image-overlay-file-size-kilobytes'},
-                        language.$('count.fileSize.kilobytes', {
-                          kilobytes: html.tag('span', {class: 'image-overlay-file-size-count'}),
-                        })),
-                      html.tag('span', {id: 'image-overlay-file-size-megabytes'},
-                        language.$('count.fileSize.megabytes', {
-                          megabytes: html.tag('span', {class: 'image-overlay-file-size-count'}),
-                        })),
-                    ]),
-                }),
-
-                html.tag('span', {id: 'image-overlay-file-size-warning'},
-                  language.$('releaseInfo.viewOriginalFile.sizeWarning')),
-              ]),
-            ]),
-          ]));
+              content = html.tag('a',
+                {href},
+                title);
+            }
+
+            let className;
+
+            if (cur.auto === 'current') {
+              className = 'current';
+            } else if (
+              slots.navLinkStyle === 'hierarchical' &&
+              i === slots.navLinks.length - 1
+            ) {
+              className = 'current';
+            }
+
+            return html.tag('span',
+              {class: className},
+              [
+                html.tag('span',
+                  {class: 'nav-link-content'},
+                  content),
+                html.tag('span',
+                  {[html.onlyIfContent]: true, class: 'nav-link-accent'},
+                  cur.accent),
+              ]);
+          })),
+
+        html.tag('div',
+          {[html.onlyIfContent]: true, class: 'nav-bottom-row'},
+          slots.navBottomRowContent),
+
+        html.tag('div',
+          {[html.onlyIfContent]: true, class: 'nav-content'},
+          slots.navContent),
+      ])
+
+    const generateSidebarHTML = (side, id) => {
+      const content = slots[side + 'Content'];
+      const multiple = slots[side + 'Multiple'];
+      const stickyMode = slots[side + 'StickyMode'];
+      const wide = slots[side + 'Wide'];
+      const collapse = slots[side + 'Collapse'];
+
+      let sidebarClasses = [];
+      let sidebarContent = html.blank();
+
+      if (!html.isBlank(content)) {
+        sidebarClasses = ['sidebar'];
+        sidebarContent = content;
+      } else if (multiple) {
+        sidebarClasses = ['sidebar-multiple'];
+        sidebarContent =
+          multiple
+            .filter(Boolean)
+            .map(({content}) =>
+              html.tag('div',
+                {
+                  [html.onlyIfContent]: true,
+                  class: 'sidebar',
+                },
+                content));
+      }
+
+      return html.tag('div',
+        {
+          [html.onlyIfContent]: true,
+          id,
+          class: [
+            'sidebar-column',
+            wide && 'wide',
+            !collapse && 'no-hide',
+            stickyMode !== 'static' && `sticky-${stickyMode}`,
+            ...sidebarClasses,
+          ],
+        },
+        sidebarContent);
+    }
+
+    const sidebarLeftHTML = generateSidebarHTML('leftSidebar', 'sidebar-left');
+    const sidebarRightHTML = generateSidebarHTML('rightSidebar', 'sidebar-right');
+    const collapseSidebars = slots.leftSidebarCollapse && slots.rightSidebarCollapse;
+
+    const imageOverlayHTML = html.tag('div', {id: 'image-overlay-container'},
+      html.tag('div', {id: 'image-overlay-content-container'}, [
+        html.tag('a', {id: 'image-overlay-image-container'}, [
+          html.tag('img', {id: 'image-overlay-image'}),
+          html.tag('img', {id: 'image-overlay-image-thumb'}),
+        ]),
+        html.tag('div', {id: 'image-overlay-action-container'}, [
+          html.tag('div', {id: 'image-overlay-action-content-without-size'},
+            language.$('releaseInfo.viewOriginalFile', {
+              link: html.tag('a', {class: 'image-overlay-view-original'},
+                language.$('releaseInfo.viewOriginalFile.link')),
+            })),
 
-        const layoutHTML = [
-          navHTML,
-          // banner.position === 'top' && bannerHTML,
-          // secondaryNavHTML,
-          html.tag('div',
-            {
-              class: [
-                'layout-columns',
-                !collapseSidebars && 'vertical-when-thin',
-                (sidebarLeftHTML || sidebarRightHTML) && 'has-one-sidebar',
-                (sidebarLeftHTML && sidebarRightHTML) && 'has-two-sidebars',
-                !(sidebarLeftHTML || sidebarRightHTML) && 'has-zero-sidebars',
-                sidebarLeftHTML && 'has-sidebar-left',
-                sidebarRightHTML && 'has-sidebar-right',
-              ],
-            },
-            [
-              sidebarLeftHTML,
-              mainHTML,
-              sidebarRightHTML,
+          html.tag('div', {id: 'image-overlay-action-content-with-size'}, [
+            language.$('releaseInfo.viewOriginalFile.withSize', {
+              link: html.tag('a', {class: 'image-overlay-view-original'},
+                language.$('releaseInfo.viewOriginalFile.link')),
+              size: html.tag('span',
+                {[html.joinChildren]: ''},
+                [
+                  html.tag('span', {id: 'image-overlay-file-size-kilobytes'},
+                    language.$('count.fileSize.kilobytes', {
+                      kilobytes: html.tag('span', {class: 'image-overlay-file-size-count'}),
+                    })),
+                  html.tag('span', {id: 'image-overlay-file-size-megabytes'},
+                    language.$('count.fileSize.megabytes', {
+                      megabytes: html.tag('span', {class: 'image-overlay-file-size-count'}),
+                    })),
+                ]),
+            }),
+
+            html.tag('span', {id: 'image-overlay-file-size-warning'},
+              language.$('releaseInfo.viewOriginalFile.sizeWarning')),
+          ]),
+        ]),
+      ]));
+
+    const layoutHTML = [
+      navHTML,
+      // banner.position === 'top' && bannerHTML,
+      // secondaryNavHTML,
+      html.tag('div',
+        {
+          class: [
+            'layout-columns',
+            !collapseSidebars && 'vertical-when-thin',
+            (sidebarLeftHTML || sidebarRightHTML) && 'has-one-sidebar',
+            (sidebarLeftHTML && sidebarRightHTML) && 'has-two-sidebars',
+            !(sidebarLeftHTML || sidebarRightHTML) && 'has-zero-sidebars',
+            sidebarLeftHTML && 'has-sidebar-left',
+            sidebarRightHTML && 'has-sidebar-right',
+          ],
+        },
+        [
+          sidebarLeftHTML,
+          mainHTML,
+          sidebarRightHTML,
+        ]),
+      // banner.position === 'bottom' && bannerHTML,
+      footerHTML,
+    ].filter(Boolean).join('\n');
+
+    return html.tags([
+      `<!DOCTYPE html>`,
+      html.tag('html',
+        {
+          lang: language.intlCode,
+          'data-language-code': language.code,
+
+          /*
+          'data-url-key': 'localized.' + pagePath[0],
+          ...Object.fromEntries(
+            pagePath.slice(1).map((v, i) => [['data-url-value' + i], v])),
+          */
+
+          'data-rebase-localized': to('localized.root'),
+          'data-rebase-shared': to('shared.root'),
+          'data-rebase-media': to('media.root'),
+          'data-rebase-data': to('data.root'),
+        },
+        [
+          // developersComment,
+
+          html.tag('head', [
+            html.tag('title',
+              (slots.showWikiNameInTitle
+                ? language.formatString('misc.pageTitle.withWikiName', {
+                    title: slots.title,
+                    wikiName: data.wikiName,
+                  })
+                : language.formatString('misc.pageTitle', {
+                    title: slots.title,
+                  }))),
+
+            html.tag('meta', {charset: 'utf-8'}),
+            html.tag('meta', {
+              name: 'viewport',
+              content: 'width=device-width, initial-scale=1',
+            }),
+
+            /*
+            ...(
+              Object.entries(meta)
+                .filter(([key, value]) => value)
+                .map(([key, value]) => html.tag('meta', {[key]: value}))),
+
+            canonical &&
+              html.tag('link', {
+                rel: 'canonical',
+                href: canonical,
+              }),
+
+            ...(
+              localizedCanonical
+                .map(({lang, href}) => html.tag('link', {
+                  rel: 'alternate',
+                  hreflang: lang,
+                  href,
+                }))),
+
+            */
+
+            // slots.socialEmbed,
+
+            html.tag('link', {
+              rel: 'stylesheet',
+              href: to('shared.staticFile', `site4.css?${cachebust}`),
+            }),
+
+            html.tag('style', [
+              (empty(slots.colorStyleRules)
+                ? relations.defaultColorStyleRules
+                : slots.colorStyleRules),
+              slots.additionalStyleRules,
             ]),
-          // banner.position === 'bottom' && bannerHTML,
-          footerHTML,
-        ].filter(Boolean).join('\n');
 
-        const documentHTML = html.tags([
-          `<!DOCTYPE html>`,
-          html.tag('html',
-            {
-              lang: language.intlCode,
-              'data-language-code': language.code,
-
-              /*
-              'data-url-key': 'localized.' + pagePath[0],
-              ...Object.fromEntries(
-                pagePath.slice(1).map((v, i) => [['data-url-value' + i], v])),
-              */
-
-              'data-rebase-localized': to('localized.root'),
-              'data-rebase-shared': to('shared.root'),
-              'data-rebase-media': to('media.root'),
-              'data-rebase-data': to('data.root'),
-            },
-            [
-              // developersComment,
-
-              html.tag('head', [
-                html.tag('title',
-                  (slots.showWikiNameInTitle
-                    ? language.formatString('misc.pageTitle.withWikiName', {
-                        title: slots.title,
-                        wikiName: data.wikiName,
-                      })
-                    : language.formatString('misc.pageTitle', {
-                        title: slots.title,
-                      }))),
-
-                html.tag('meta', {charset: 'utf-8'}),
-                html.tag('meta', {
-                  name: 'viewport',
-                  content: 'width=device-width, initial-scale=1',
-                }),
-
-                /*
-                ...(
-                  Object.entries(meta)
-                    .filter(([key, value]) => value)
-                    .map(([key, value]) => html.tag('meta', {[key]: value}))),
-
-                canonical &&
-                  html.tag('link', {
-                    rel: 'canonical',
-                    href: canonical,
-                  }),
-
-                ...(
-                  localizedCanonical
-                    .map(({lang, href}) => html.tag('link', {
-                      rel: 'alternate',
-                      hreflang: lang,
-                      href,
-                    }))),
-
-                */
-
-                // slots.socialEmbed,
-
-                html.tag('link', {
-                  rel: 'stylesheet',
-                  href: to('shared.staticFile', `site4.css?${cachebust}`),
-                }),
-
-                html.tag('style', [
-                  (empty(slots.colorStyleRules)
-                    ? relations.defaultColorStyleRules
-                    : slots.colorStyleRules),
-                  slots.additionalStyleRules,
-                ]),
+            html.tag('script', {
+              src: to('shared.staticFile', `lazy-loading.js?${cachebust}`),
+            }),
+          ]),
 
-                html.tag('script', {
-                  src: to('shared.staticFile', `lazy-loading.js?${cachebust}`),
-                }),
+          html.tag('body',
+            // {style: body.style || ''},
+            [
+              html.tag('div', {id: 'page-container'}, [
+                // mainHTML && skippersHTML,
+                layoutHTML,
               ]),
 
-              html.tag('body',
-                // {style: body.style || ''},
-                [
-                  html.tag('div', {id: 'page-container'}, [
-                    // mainHTML && skippersHTML,
-                    layoutHTML,
-                  ]),
-
-                  // infoCardHTML,
-                  imageOverlayHTML,
-
-                  html.tag('script', {
-                    type: 'module',
-                    src: to('shared.staticFile', `client.js?${cachebust}`),
-                  }),
-                ]),
-            ])
-        ]);
+              // infoCardHTML,
+              imageOverlayHTML,
 
-        return documentHTML;
-      },
-    });
+              html.tag('script', {
+                type: 'module',
+                src: to('shared.staticFile', `client.js?${cachebust}`),
+              }),
+            ]),
+        ])
+    ]);
   },
 };