mathieudutour / gatsby-digital-garden

🌷 🌻 🌺 Create a digital garden with Gatsby
https://mathieudutour.github.io/gatsby-digital-garden
MIT License
671 stars 102 forks source link

Support relative filepaths/links in `gatsby-transformer-markdown-references` #196

Open flyinggrizzly opened 2 years ago

flyinggrizzly commented 2 years ago

In my setup (not fully working yet), my notes live in the content/ directory, and within that I have some sub-directories. Obsidian, Logseq, etc support linking to those notes with [[sub_dir/note_name]] syntax.

The issue is that gatsby-transformer-markdown-references seems to assume either a flat file hierarchy, or a root dir of the project root (e.g. siblings to index.js or any entrypoint), computing inbound references fails when the link is coming from a file whose contextual ID is sub_dir/note_name.

To replicate:

  1. Set up gatsby-transformer-markdown-references, and have Remark or Mdx use content/ as the FS source dir
  2. Create a note my-topic.md in content/
  3. Create another note at content/blog/2022-10-10-my-blog-post.md
  4. (Optional, I think): Set up Gatsby to publish notes from the content/blog/ directory at /blog/YYYY/MM/note-name
  5. Check outboundReferences on content/blog/2022-10-10-my-blog-post.md--you should see my-topic.md correctly linked
  6. Check inboundReferences on my-topic.md--there are no references to links coming from blog/2022-10-10-my-blog-post.md or its URL pattern either

Looking at compute-inbounds.ts, it looks to me like adding something like this might handle links based on paths relative to a content/ or notes/ directory:

diff --git a/packages/gatsby-transformer-markdown-references/src/compute-inbounds.ts b/packages/gatsby-transformer-markdown-references/src/compute-inbounds.ts
index 2acde5c..08521b3 100644
--- a/packages/gatsby-transformer-markdown-references/src/compute-inbounds.ts
+++ b/packages/gatsby-transformer-markdown-references/src/compute-inbounds.ts
@@ -1,4 +1,4 @@
-import { basename, extname } from "path";
+import { basename, extname, format as formatPath } from "path";
 import { Node } from "gatsby";
 import { nonNullable } from "./non-nullable";
 import {
@@ -33,6 +33,24 @@ function hasChildInArrayExcept(

 let currentGeneration: Promise<boolean> | undefined;

+// For contexts where you transform nodes so that their URLs or titles do not match
+// their filesystem reference.
+//
+// For example, blog/2022-10-01-post.md is published at /2022/10/post, but you want to link to it using its filesystem
+// ref of [[blog/2022-10-01-post]]
+const isRelativeNameMatch = (title, path) => {
+  if (typeof path !== "string")
+    return false
+
+  const relativePath = relative(__dirname, path)
+  const extension = extname(relativePath)
+
+  const reference = relativePath.replace(extension, '')
+
+  return reference === title
+}
+
+
 export async function generateData(cache: any, getNode: Function) {
   if (currentGeneration) {
     return currentGeneration;
@@ -55,7 +73,8 @@ export async function generateData(cache: any, getNode: Function) {
             ) === title) ||
           (typeof x.node.absolutePath === "string" &&
             basename(x.node.absolutePath, extname(x.node.absolutePath)) ===
-              title)
+              title) ||
+          (isRelativeNameMatch(title, x.node.fileAbsolutePath))
       );
     }

I've run this locally (I originally wrote this in the JS file installed in node_modules/ in my project), and it's working in my case. Dump of my GraphQL query result:

{
  "data": {
    "allMdx": {
      "edges": [
        {
          "node": {
            "fields": {
              "title": "Intro"
            },
            "fileAbsolutePath": "/Users/me/src/plamo-site/content/intro.md",
            "outboundReferences": [],
            "inboundReferences": [
              {
                "fields": {
                  "title": "Test",
                  "path": "/2022/10/test"
                },
                "fileAbsolutePath": "/Users/me/src/plamo-site/content/blog/2022-10-01-test.md"
              }
            ]
          }
        },
        {
          "node": {
            "fields": {
              "title": "Test"
            },
            "fileAbsolutePath": "/Users/me/src/plamo-site/content/blog/2022-10-01-test.md",
            "outboundReferences": [
              {
                "fields": {
                  "title": "Intro"
                }
              }
            ],
            "inboundReferences": []
          }
        }
      ]
    }
  },
  "extensions": {}
}

If this is sufficient to open up into a PR, I'm happy to do so, but I haven't written any tests and I'm not sure of any other considerations you might have, so proposing as an issue first.

Also, this so far only handles inbound refs, but similar work would I think be needed to handle and create the outbound refs.

Context

I'm migrating an old Jekyll blog and merging it with my Obsidian notes. The file => URL naming convention and transformation from Jekyll in this case is ./_posts/YYYY-MM-DD-blog-post-title.md => https://DOMAIN/blog/YYYY/MM/blog-post-title.

Most notes are flat in the content/ directory in the Gatsby project root, so linking from them isn't an issue. Similarly

I could go and rename all the old blog post files to content/blog/YYYY/MM/blog-post-title.md, but it's much harder to manage and review old posts if they're scattered across tons of date-based folders. The date string is meaningful when it's all lined up in a single _posts/ or content/blog folder, but a nightmare when there are directories obfuscating the content that's there.