Welcome to Mima's Room

Many developers face a question of how one should present their work to the world. One of first ways that comes to minds is GitHub, which became a de-facto standard library filled with blueprints of ideas. However, thoughts expressed only in code inevitably limit themselves not only to those who are able to read it, but also willing to do so. This can lead to a vicious cycle where ideas struggle to find an audience, motivation decreases, and thoughts fade into obscurity.
To avoid loosing heart it seems like thoughts should be presented in more engaging form of practical application, rather then dry theory. This approach demands a place to show one's project and maybe share some relevant insight. To fill that missing piece comes the website. While there is an option to go full cypherpunk and run .onion server in some South Asia country paid with Monero, which to be frank I absolutely respect and think shouldn't be dismissed by many, for my use case it looked like a little overkill. Of course there are ready solutions offering hosting for both app's backend and frontend, while also providing personal blog. But using those services comes with at least two downsides, which I consider critical:
- The tendency of increasing censorship can't be overstated for anyone who experienced early 2000s Internet. And even Zuck dismissing his fact-chekers at the moment this post is written should be viewed as a temporary fluctuation in main stream. So when presented with a choice between convenience of social network custody and ownership of my own work, I lean towards tin foil hat.
- Ready solutions often provide less customization and fail to encourage a deeper understanding of underlying processes compared to creating things with your own hands, which is inherently more satisfying. Although I did cut some corners on that part, which I mention in the end of this post.
So, enter the personal website.
I had a few little, but strong opinions on how it should be made.
Python was my pick for programming language. It is the first language that I learned, and that is definitely part of the reason why I love it and feel right at home when writing, debugging and try new libraries. For framework I thought about Django or Flask. While the latter is considered to be easier and maybe more appropriate for couple of simple HTML pages that I needed, I chose Django because it felt like right path. I already had some experience with it and wanted to learn more, it was unlikely that I would stuck on some problem due to it's big community, and Django clearly has more room for scaling in the future.
In design process for my Projects list I wanted to create little tags to quickly show used technologies. I did so by implementing separate model for tags and linking it with main Projects model. Tags embedding done with default models.ManyToManyField
interface:
> python
from django.db import models
class Tag(models.Model):
LANGUAGE = "Language"
LIBRARY = "Library"
TECHNOLOGY = 'Technology'
TAG_TYPES = [
(LANGUAGE, "Programming Language"),
(LIBRARY, "Library"),
(TECHNOLOGY, 'Technology'),
]
name = models.CharField(max_length=50, unique=True) # Tag name
type = models.CharField(max_length=10, choices=TAG_TYPES) # Type of tag
def __str__(self):
return self.name
class Project(models.Model):
# ...
stack_list = models.ManyToManyField(Tag, related_name="projects") # Tags linked to projects
Being a fan of Markdown markup language I wanted to find a way to use it for text formatting, ideally to just transfer text from Obsidian note. It has all features that I could need – code highlighting, tables, lists, etc. After search for ways to implement it in the end I decided to use MarkdownX library, as it provided the way convert Markdown to html and vice versa basically with one wrapper:
> python
import markdown
from django.utils.safestring import mark_safe
from markdown.extensions.codehilite import CodeHiliteExtension
def markdownify(text):
"""
Converts Markdown text to HTML with code highlighting, adds ids to headings,
and wraps consecutive images in a div with the class "image-gallery".
"""
# Convert the Markdown to HTML
html = markdown.markdown(
text,
extensions=['fenced_code', 'codehilite', 'tables', 'sane_lists', CodeHiliteExtension(pygments_formatter=CustomHtmlFormatter)],
extension_configs={'codehilite': {'linenums': False, 'guess_lang': True, 'lang_prefix': ''}},
)
# Add IDs to headings
html = add_ids_to_headings(html)
# Wrap consecutive images in a div with class "image-gallery"
html = wrap_consecutive_images(html)
With except of couple helper functions to format table of contents and image gallery this wrapper is the only thing needed to make the magic work. Also the ability to install extensions comes in handy, especially CodeHilite with Pygments. I wanted code blocks to show language they are written in, but it seems there are no recommendations on proper way to do that. CodeHilite embeds language in separate html class name, and while it has dedicated lang_prefix
config, for some reason I could not change it. This resulted in somewhat messy, but working solution:
> python
from markdown.extensions.codehilite import CodeHiliteExtension
class CustomHtmlFormatter(HtmlFormatter):
def __init__(self, lang_str='', **options):
super().__init__(**options)
# lang_str has the value {lang_prefix}{lang} specified by the CodeHilite's options
# default {lang_prefix} is 'language-'
self.lang_str = lang_str
def _wrap_code(self, source):
language = f"> {self.lang_str.removeprefix('language-')}"
yield 0, f'<code class="{self.lang_str}"><span class="c1"><i>{language}</i><br><br>''</span>'
yield from source
yield 0, '</code>'
For some reason I convinced myself, that posts should have table of contents. I placed corresponding section after first paragraph, and it looked nicely if Markdown text indeed had any # headings
inside. But if it did not, empty section showed up nonetheless. I thought about modifying function that parses headings, but in the end decided that work around condition inside Jinja does the job:
> html
<!-- Table of Contents -->
{% with post.content|generate_toc as toc %}
{% if toc|length > 9 %} <!-- Empty Table of Contents consist of 9 symbols: <ul></ul> -->
<div class="toc-section">{{ post.content | generate_toc }}</div>
{% endif %}
{% endwith %}
There was no real need for this websites to have frightening amounts of JavaScript. Reasons for it's extensive use on the web are obvious and there is no point to yell at clouds like Abe Simpson. But if something is excepted, it doesn't automatically have to be this way, so I kept design and internal logic as lightweight as possible. I believe right now the only time client receives any JavaScript is image expansion script, which is reasonable compromise between simplicity and usability. However, my dislike of JavaScript doesn't prevent me from using it in scenarios where it is actually needed. Post submitting form in Admin panel was one of those cases. After applying markdownify
wrapper for post preview I made image upload section, which demanded some interactivity. My guess is that there are a ton of ready solutions for this task, but I was interested in trying to do my own. Final code supports multiple image upload, assigning captions and button inside text editor to insert Markdown link to file:
> javascript
// Add a confirm button to insert the selected image
var confirmButton = document.createElement("button");
confirmButton.textContent = "Insert";
confirmButton.classList.add("confirm-image-button");
// Insert the selected image into the editor when confirmed
confirmButton.addEventListener("click", function () {
var selectedOption =
dropdown.options[dropdown.selectedIndex];
var imageUrl = selectedOption.value;
var caption = selectedOption.textContent;
// Get the current content and append the image Markdown syntax
var contentField = document.querySelector(
"textarea[name='content']",
);
var imageMarkdown = ``;
contentField.value += imageMarkdown;
contentField.dispatchEvent(new Event("input")); // Trigger input event to update the field
});
After thoroughly examining my project on localhost and celebrating victory over CSS demons I turned on Responsive Device Mode to get an idea of how my masterpiece would look on mobile device. The famous Groove Street poet quote came to mind right away – ah shit, here we go again. I didn't want to increase Universe entropy with another Corporate Memphis 'hamburger' menu and got by manual @media (orientation: landscape)
and @media (orientation: portrait)
CSS parameters setup.
I won't talk much about hosting, because I decided not to loose my mind with bare VPS and manual DNS configuration. Instead I faced second downside of my rant above and used ready solution with default cPanel. Even this approach taught me new things, for example I learned new stuff about SSL certificates and was stuck for a whole day with WSGI configuration. My hopes of painless e-mail setup also were shuttered once I discovered that Gmail refuses to receive my messages without completing SPF and DKIM quest.
On surprising side I discovered a couple of bugs not authored by me. Looks like cPanel tracks integrity of client-side rendered pages – 'Dark Reader' browser extension when turned on somehow messes up cPanel in such a way, that it constantly demands login. Also it refused to change my git repository branch in internal version control tool, so the only way to fix this I could find was switching default branch on GitHub and adding it again in cPanel interface. Weird, but whatever.
Anyway, I am satisfied with result. Welcome to Mima's Room and thanks for coming to my TED talk.