Screenshot of OC Website

Improving My OC Website

Despite it being the original motivation for picking up a Static Site Generator, my OC Website has been severely neglected. This was mainly because my lack of understanding of how Jekyll worked made me hesitant to make anything complicated. Quite some time has passed since then I’ve gotten a better idea of how I wanted to go about the website. With overcomplicated file structures and a bit of Obsidian shenanigans, join me as I develop the workflow for the ultimate oc repository!!

Heads Up

As per usual, this is not a tutorial. This blog is particularly experimental with me messing around with stuff that’s still kinda new to me. There’s a lot I would like to improve on but this is my current workflow and I felt like sharing it.

The Goal

Normally you’d write all your character information in a single linear file. Unfortunately I am not normal person. So for each item I’d like there to be an index.md file with each section separated in it’s own file. So if I wanted a page for Psyche, the folder would look like this:

└───psyche
        index.md
        _appearance.md
        _gallery.md
        _history.md
        _personality.md
        _relationships.md

I’d also like to be able to sort the items into separate categories. Like whether or not something is a character or a location. Additionally, it be neat to have subdirectories in each category. For things like segregating characters based in the order of their importance.

└───char
    ├───1st
    │   ├───psyche
    │   ├───evan
    │   └───jay
    ├───2nd
    │   ├───mary
    │   ├───lily
    │   └───august
    └───3rd
        ├───athena
        ├───charmaine
        └───benedict

To make all of this easier to manage we will be utilizing Jekyll’s Collections again. So with all of this in mind, a file’s path will probably look a bit like this.

# src/collection/category/subdirectory/item-name/filename
public/psych/char/1st/psyche/index.md

You may think that all of this is a lot of trouble for what can easily be put into one file - and you’d absolutely right! But writing everything in separate files lets me know what content is in what and makes moving the order of sections around a lot easier. There also the added bonus of adding visual difference in each section by putting them in separate elements. Besides, can’t go around calling it the Ultimate OC Repository if I don’t put in an extra bit of work.

Configuration

Before anything else we need to mess around with the _config.yml. Since each universe is put into its’ own collection, we should put all the collections inside of it’s own folder to keep it tidy. We achieve this using the collections_dir with all future collection folders placed into it.

collections_dir: _files

Then we list out all the collections we’d like and set the output to true. This way, everything inside the collection will render as it’s own page.

collections:
  psych:
    output: true
  pmd:
    output: true
  misc:
    output: true
  # so on, so forth

We can target everything inside a collection to set default values, such as what layout the file should be rendered in.

defaults:
  - scope:
      path: _files/*/
    values:
      permalink: /:collection/:folder_below/:folder_first
      layout: view

And that should be about everything in the config file!

Custom Placeholders

You may have notice that in the default values from the previous section included something called the Permalink. It basically just sets the files path. If we leave it empty it will just defaults to files original path, which is really long. It be better to keep the urls short and constant. We don’t want links to be broken because of arbitrary folders getting renamed to another. Using the permalink typically looks like this:

permalink: /:collection/:filename

Everything starting with a colon (:) is a placeholder that refers to the file’s own property. There’s a handful to choose from but because my files are setup weirdly, most of them are not that helpful. Since all the files are named index.md they will all have the same permalink. In order for this to work, we need to get the name of the folder above the file. This way the file /psyche/index.md will become psyche.html instead. You know last blog post when I said I’d figure out how to make custom ruby plugins? Yeah, was being super cereal about.

In the Drop module there’s UrlDrop. You can the find code here but basically you just define a function that you can use as the placeholder for the permalink. So we create a function called folder_above which can be access in the permalink with the same name. The function just splits the file’s path then grabs the second to the last item on the array - which is the filename we want.

module Jekyll
  module Drops
    class UrlDrop < Drop      
      
      def folder_above
        # obj.path returns » D:/path/to/_collection/folder-name/index.md
        @obj.path.split("/")[-2] # returns 'folder-name'
      end

    end
  end
end

Additionally I want to be able to grab the first folder the file is in, relative to the root of the collection folder. So we grab the collection the file is in and search for its’ index in the files path. We increment that index to grab the folder!

def folder_first
  split = @obj.path.split("/")
  collection = "_" + @obj.collection.label
  index = split.find_index(collection)
  split[index + 1]
end

Just as long as you know what properties to get and how to manipulate the string, you can make as many custom placeholders to your heart’s content. This isn’t all that impressive but it technically marks my first ever ruby script so that probably constitutes for something. One day I’ll fully wrap my head around object oriented programming then I can rule the world…!

Adding in the Sections

In Jekyll, anything starting with an underscore (_) will be ignore and not process during the building process. So each index.md file should have a list of the additional files we’d like to include in the page. They’re capitalize because we’ll be using it as the title of the section.

files:
  - Personality
  - Relationships
  - Appearance
  - Gallery
  - History

Using this list we grab the content using include_relative which does exactly what it does. For example, it would look for the _personality file inside of the folder it is currently in. Also we need to wrap the content inside a capture variable to get the markdown to actually render.

<!-- Start of the page -->
<article>
    <h1>{{ page.title }}</h1>
    {{ content }}
</article>

<!-- Looping through everything in files -->
{%- for item in page.files -%}
<article id="{{ item | downcase }}" class="view-item">
    <!-- Title of Section -->
    <h2>{{ item }}</h2>

    <!-- Section Content -->
    {%- capture result -%}
        {%- include_relative {{ item | downcase | prepend: "_" | append: '.md' }} -%}
    {%- endcapture -%}
    {{ result | markdownify }} <!-- Converts the markdown -->
</article>
{%- endfor -%}

Obsidian Vault

Alrighty now that we’ve got the website configured, we gotta set up an easier way to go through and edit the files. The way it’s set up right now means we’d have to go through several nested folders which isn’t all that fun. This is where we’ll be using Obsidian and the fairly recently added in Bases Feature. Obsidian is a personal knowledge database which is just the nerd equivalent saying it’s a kickass fucking note taking app.

We filter for items that only had the filename index. The rest of the columns will be custom properties made with Obsidian’s functions. For example we could make a column that links to the index file like so.

link(file.path, note["title"])

For the thumbnails we just use grab the folder the file is in and link to the right path.

image( "img/thumb/" + file.folder.split("/")[-1] + ".jpg" )

We could also have a column that lists outs all the other files listed in the index

note["files"].map(
  [link(file.path.replace("index.md","") + "_" + value.lower() + ".md", value)]
)

We just toggle on the properties and arrange the table, then boom! Instead of having to sift through multiple folders, we could just take a gander of this nifty list of all the index and their related files. Screenshot of Obsidian Base


2025/12/15 » Wohoo an update because I am indecisive! I like wikilinks using obsidian to work but as it current stands, whenever I want to link another article, I have to settle for writing the whole absolute path. Soooo we’re gonna use a custom filter instead. We need to rename the index.md files to the actual names. Which means the folder_above function we made previously is useless now. Moving on, we have to add ‘wikilinks’ to the layouts content tag.

{{ content | wikilinks }}

Then we jump into ruby, make a function in our module (?); In case it isn’t painfully obvious, I have no idea what I’m doing. But hey if it works, it works! Then in that function we look for anything that matches the expression below. It basically gets everything enclosed in double square brackets.

/\[\[(.*?)\]\]/

We grab the current collection based on the url. This is because I keep each of the collections in their own Obsidian Vault so there really isn’t much of a reason to look through everything else.

collection_label = @context.registers[:page]["url"].split("/")[-3]
collection = @context.registers[:site].collections[collection_label]

Then loop through the affirmation collection and search for the file!

collection.docs.each do |doc|
    if doc.url.split("/")[-1] == target
        target = doc.url
        found = doc.data["title"]
        break      
    end
end

Once we find the file we can use that information to return a link.

"<a href='#{target}'>#{found}</a>"

The entire script looks like this.

module Jekyll
  module RegexFilter
    def wikilinks(input)
        # Gets everything inside an open and closed double square brackets
        input.gsub(/\[\[(.*?)\]\]/) do |match|
        found = Regexp.last_match[1]
        target = found.downcase.gsub(" ", "-") # This slugifies

        # grabs the current collection
        collection_label = @context.registers[:page]["url"].split("/")[-3]
        collection = @context.registers[:site].collections[collection_label]
        
        # loops through collection to find file
        if collection && collection.docs
            collection.docs.each do |doc|
                if doc.url.split("/")[-1] == target
                    target = doc.url
                    found = doc.data["title"]
                    break      
                end
            end
        end

        "<a href='#{target}'>#{found}</a>"
      end
    end
  end
end

Liquid::Template.register_filter(Jekyll::RegexFilter)

There’s definitely room for improvement. Like how I could take in account for alt text and white spaces. Either way, I am too lazy to improve it right not. As per usual, I am making this future me’s problem


Fetching Document Data

2026/02/12 » Sometimes I need to be able to fetch the properties of another document. For example if I’d list all the characters relationships, I would like to be able to automatically add the url and title. Originally I did this purely with liquid, which in retrospect wasn’t that fun. I wanted it be as simple as typing down:

{%- assign doc = "psyche" | fetchDoc -%}

So yeah. Making another custom filter!! The script just looks like this.

def fetchDoc(input)
  label = getCollLabel()
  return getCollItem(input, label)
end

This is what getCollLabel() does. It basically gets the first folder in the url.

def getCollLabel ()
  source = @context.registers[:site].source
  return @context.registers[:page]["url"].split(source)[0].split("/")[1]
end

Then using that label we can scan through the collection’s docs and look for a match.

def getCollItem (input, label) 
  collection = @context.registers[:site].collections[label]
  # it's done like this just in case input is an empty sting!
  if (collection && input)
    input = input.downcase
    collection.docs.each do |doc|
        if (doc.basename.split('.')[0].downcase == input)
            return doc
        end
    end
  end
  return input
end

That’s basically it. If you haven’t figured out already. Everything above is just an improved version of the wikilinks implementation above. So we could just replace the old script with this new one.

def wikilinks(input)
    input.gsub(/\[\[(.*?)\]\]/) do |match|
    goal = Regexp.last_match[1]

    label = getCollLabel()
    source = getCollItem(goal, label)

    if (source == goal)
      "<a href=\"\">#{goal}</a>"
    else 
      "<a href=\"#{source['url']}\" title=\"#{source['title']}\">#{source['title']}</a>"
    end
end

Feeling more confident in my ruby coding mwhaha >:D


Conclusion

Welp, that’s about wraps everything up! Quite happy with the way it is at the moment. It’s now just a matter of actually writing all the information down haha. If you have any suggestions to improve this workflow or feel like rambling about your own, pretty pretty please comment it below because I’d love learn!! Till then, listen to the snail in your ear and get your self a little treat ♥

Stupied was Here