]> Lady’s Gitweb - Beorn/commitdiff
Add <link>s for feed/page discovery
authorLady <redacted>
Mon, 8 Aug 2022 01:23:42 +0000 (18:23 -0700)
committerLady <redacted>
Sat, 29 Apr 2023 03:43:42 +0000 (20:43 -0700)
And other various improvements to page metadata generation.

README.markdown
build.js

index 6cce62f773505b9381c3ea3442189fb75714aa07..2207d3a2e480140000aeddef0f27f67ced4659d4 100644 (file)
@@ -58,6 +58,9 @@ probably look something like this :—
 </awol:Feed>
 ```
 
 </awol:Feed>
 ```
 
+The value of `rdf:about` on the root element needs to be the absolute
+U·R·L for your blog.
+
 Your `entry.rdf` will look similar, but it needs a `sioc:content`
 property :—
 
 Your `entry.rdf` will look similar, but it needs a `sioc:content`
 property :—
 
@@ -78,6 +81,9 @@ I love it!
 </awol:Entry>
 ```
 
 </awol:Entry>
 ```
 
+In this case, the `rdf:about` can be automatically filled in based on
+the path to the entry from the blog root.
+
 As you can see from the above example, 🧸📔 Bjørn supports a
 (nonstandard) `Markdown` value for `rdf:parseType` in addition to
 `Literal`. Some words of caution regarding this :—
 As you can see from the above example, 🧸📔 Bjørn supports a
 (nonstandard) `Markdown` value for `rdf:parseType` in addition to
 `Literal`. Some words of caution regarding this :—
@@ -96,6 +102,26 @@ Alongside the metadata files, there are two template files,
 content will replace the first `<bjørn-content>` element. Metadata will
 be inserted into the `<head>` for you automatically.
 
 content will replace the first `<bjørn-content>` element. Metadata will
 be inserted into the `<head>` for you automatically.
 
+## Serving Content
+
+You will need a file server which supports index files with a `.xhtml`
+file extension. A sample `.rsync-filter` might be as follows :—
+
+```rsync-filter
+- /.rsync-filter
+-s .git
+-s .gitignore
+-s /README*
+-s /build.js
+-s index#*.xhtml
+-s #*.rdf
+```
+
+## Customization
+
+Feel free to add styles, additional content, and whatever else you like
+to the template files (as well as the rest of the website).
+
 To modify what content is generated, simply edit `build.js` :) .
 
 [CommonMark]: <https://commonmark.org>
 To modify what content is generated, simply edit `build.js` :) .
 
 [CommonMark]: <https://commonmark.org>
index 743de647467f963c661e95eb092d75f18a6fd3bf..36953290fd9f0b28a69ea25753ea18e0ffa7e98e 100755 (executable)
--- a/build.js
+++ b/build.js
@@ -201,55 +201,17 @@ const applyMetadata = (node, metadata) => {
     if (hasExpandedName(documentElement, XHTML, "html")) {
       // This is an XHTML template.
       const LMN = Lemon.bind({ document });
     if (hasExpandedName(documentElement, XHTML, "html")) {
       // This is an XHTML template.
       const LMN = Lemon.bind({ document });
-      const head = Array.from(documentElement.childNodes).find(($) =>
-        hasExpandedName($, XHTML, "head")
-      ) ?? documentElement.insertBefore(
-        LMN.head``,
-        documentElement.childNodes.item(0),
-      );
-      const titleElement = Array.from(head.childNodes).find(($) =>
-        hasExpandedName($, XHTML, "title")
-      ) ?? head.appendChild(LMN.title``);
       const {
         id,
         title,
         author,
       const {
         id,
         title,
         author,
-        summary,
         contributor,
         published,
         content,
         rights,
         updated,
       } = metadata;
         contributor,
         published,
         content,
         rights,
         updated,
       } = metadata;
-      titleElement.textContent = Object(title) instanceof String
-        ? title
-        : Array.from(title ?? []).map(($) =>
-          $.textContent
-        ).join("");
-      for (const person of author) {
-        // Iterate over authors and add appropriate meta tags.
-        head.appendChild(
-          LMN.meta({ name: "author" })
-            .content(`${person.name ?? person.uri}`)``,
-        );
-      }
-      if (summary) {
-        // The entry has a summary.
-        head.appendChild(
-          LMN.meta({ name: "description" })
-            .content(
-              `${
-                Object(summary) instanceof String
-                  ? summary
-                  : Array.from(summary).map(($) => $.textContent).join(
-                    "",
-                  )
-              }`,
-            )``,
-        );
-      } else {
-        /* do nothing */
-      }
+      fillOutHead(document, metadata, "entry");
       const contentPlaceholder = document.getElementsByTagNameNS(
         XHTML,
         "bjørn-content",
       const contentPlaceholder = document.getElementsByTagNameNS(
         XHTML,
         "bjørn-content",
@@ -455,6 +417,12 @@ const applyMetadata = (node, metadata) => {
     // Assume it is an Atom element of some sort and add the
     // the appropriate metadata as child elements.
     const { ownerDocument: document } = node;
     // Assume it is an Atom element of some sort and add the
     // the appropriate metadata as child elements.
     const { ownerDocument: document } = node;
+    const alternateLink = node.appendChild(
+      document.createElement("link"),
+    );
+    alternateLink.setAttribute("rel", "alternate");
+    alternateLink.setAttribute("type", "application/xhtml+xml");
+    alternateLink.setAttribute("href", metadata.id);
     for (const [property, values] of Object.entries(metadata)) {
       for (const value of Array.isArray(values) ? values : [values]) {
         const propertyNode = document.createElement(property);
     for (const [property, values] of Object.entries(metadata)) {
       for (const value of Array.isArray(values) ? values : [values]) {
         const propertyNode = document.createElement(property);
@@ -582,6 +550,75 @@ const {
   };
 })();
 
   };
 })();
 
+/**
+ * Fills out the `head` of the provided H·T·M·L document with the
+ * appropriate metadata.
+ */
+const fillOutHead = (document, metadata, type) => {
+  const { documentElement } = document;
+  const LMN = Lemon.bind({ document });
+  const head =
+    Array.from(documentElement.childNodes).find(($) =>
+      hasExpandedName($, XHTML, "head")
+    ) ?? documentElement.insertBefore(
+      LMN.head``,
+      documentElement.childNodes.item(0),
+    );
+  const titleElement =
+    Array.from(head.childNodes).find(($) =>
+      hasExpandedName($, XHTML, "title")
+    ) ?? head.appendChild(LMN.title``);
+  const {
+    title,
+    author,
+    summary,
+  } = metadata;
+  titleElement.textContent = Object(title) instanceof String
+    ? title
+    : Array.from(title ?? []).map(($) => $.textContent).join("");
+  for (const person of author) {
+    // Iterate over authors and add appropriate meta tags.
+    head.appendChild(
+      LMN.meta({
+        name: "author",
+        content: person.name ?? person.uri,
+      })``,
+    );
+  }
+  head.appendChild(
+    LMN.meta({ name: "generator", content: "🧸📔 Bjørn" })``,
+  );
+  if (type == "entry") {
+    // The provided document is an entry document.
+    if (summary) {
+      // The entry has a summary.
+      head.appendChild(
+        LMN.meta({
+          name: "description",
+          content: Object(summary) instanceof String
+            ? summary
+            : Array.from(summary).map(($) => $.textContent).join(""),
+        })``,
+      );
+    } else {
+      /* do nothing */
+    }
+    head.appendChild(
+      LMN.link
+        .rel("alternate")
+        .type("application/atom+xml")
+        .href("../../feed.atom")``,
+    );
+  } else {
+    head.appendChild(
+      LMN.link
+        .rel("alternate")
+        .type("application/atom+xml")
+        .href("./feed.atom")``,
+    );
+  }
+};
+
 /**
  * Returns the language of the provided node, or null if it has no
  * language.
 /**
  * Returns the language of the provided node, or null if it has no
  * language.
@@ -850,13 +887,7 @@ const validateMetadata = (metadata, type) => {
 await (async () => { // this is the run script
   const writes = [];
 
 await (async () => { // this is the run script
   const writes = [];
 
-  // Set up the Atom feed.
-  const document = parser.parseFromString(
-    `<?xml version="1.0" encoding="utf-8"?>
-<feed xmlns="http://www.w3.org/2005/Atom"></feed>`,
-    "application/xml",
-  );
-  const { documentElement: feed } = document;
+  // Set up the feed metadata and Atom feed document.
   const feedMetadata = metadataFromDocument(
     parser.parseFromString(
       await Deno.readTextFile(`${basePath}/#feed.rdf`),
   const feedMetadata = metadataFromDocument(
     parser.parseFromString(
       await Deno.readTextFile(`${basePath}/#feed.rdf`),
@@ -864,6 +895,15 @@ await (async () => { // this is the run script
     ),
   );
   const feedURI = new URL(feedMetadata.id);
     ),
   );
   const feedURI = new URL(feedMetadata.id);
+  const document = parser.parseFromString(
+    `<?xml version="1.0" encoding="utf-8"?>
+<feed xmlns="http://www.w3.org/2005/Atom"><generator>🧸📔 Bjørn</generator><link rel="self" type="application/atom+xml" href="${new URL(
+      "./feed.atom",
+      feedURI,
+    )}"/></feed>`,
+    "application/xml",
+  );
+  const { documentElement: feed } = document;
   applyMetadata(feed, feedMetadata);
 
   // Set up the index page.
   applyMetadata(feed, feedMetadata);
 
   // Set up the index page.
@@ -883,9 +923,11 @@ await (async () => { // this is the run script
       continue;
     } else {
       // This is a dated directory.
       continue;
     } else {
       // This is a dated directory.
-      for await (
-        const { name: entryName, isDirectory } of Deno.readDir(
-          `${basePath}/${date}/`,
+      for (
+        const { name: entryName, isDirectory } of Array.from(
+          Deno.readDirSync(`${basePath}/${date}/`),
+        ).sort(({ name: a }, { name: b }) =>
+          a < b ? -1 : a > b ? 1 : 0
         )
       ) {
         // Iterate over each directory and process the ones which look
         )
       ) {
         // Iterate over each directory and process the ones which look
@@ -962,34 +1004,13 @@ await (async () => { // this is the run script
   if (hasExpandedName(feedRoot, XHTML, "html")) {
     // This is an XHTML template.
     const LMN = Lemon.bind({ document: feedTemplate });
   if (hasExpandedName(feedRoot, XHTML, "html")) {
     // This is an XHTML template.
     const LMN = Lemon.bind({ document: feedTemplate });
-    const head = Array.from(feedRoot.childNodes).find(($) =>
-      hasExpandedName($, XHTML, "head")
-    ) ?? feedRoot.insertBefore(
-      LMN.head``,
-      feedRoot.childNodes.item(0),
-    );
-    const titleElement = Array.from(head.childNodes).find(($) =>
-      hasExpandedName($, XHTML, "title")
-    ) ?? head.appendChild(LMN.title``);
     const {
       id,
       title,
     const {
       id,
       title,
-      author,
       rights,
       updated,
     } = feedMetadata;
       rights,
       updated,
     } = feedMetadata;
-    titleElement.textContent = Object(title) instanceof String
-      ? title
-      : Array.from(title ?? []).map(($) =>
-        $.textContent
-      ).join("");
-    for (const person of author) {
-      // Iterate over authors and add appropriate meta tags.
-      head.appendChild(
-        LMN.meta({ name: "author" })
-          .content(`${person.name ?? person.uri}`)``,
-      );
-    }
+    fillOutHead(feedTemplate, feedMetadata, "feed");
     const contentPlaceholder = feedTemplate.getElementsByTagNameNS(
       XHTML,
       "bjørn-content",
     const contentPlaceholder = feedTemplate.getElementsByTagNameNS(
       XHTML,
       "bjørn-content",
This page took 0.056365 seconds and 4 git commands to generate.