# Site making

## Concept

I don't use HTML. It's so 90s.
I don't use CMS. It's corpo.
I don't use frameworks. You do it yourself.
I write text, and the page is intantiated. It's OOP.

※ Read with the most annoying teenager voice imaginable:
I'm a writer!

## The HTML

Well you need some. Barebone, the layout, title, loading.

```
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Cyberpunk - Still the future</title>
    <link rel="stylesheet" href="style.css" />
  </head>
  <body>
    <div id="app">
      <div id="banner">
        <img src="banner.jpg" alt="" onerror="this.style.display = 'none'" />
      </div>

      <div id="title"></div>

      <div id="layout">
        <nav id="nav"></nav>
        <div id="content-wrap">
          <div id="content"></div>
        </div>
      </div>
    </div>

    <script src="site.js"></script>
  </body>
</html>
```

That's it! It just loads the Javascript and CSS.

## The markdown file

I just create a md file. It is super easy :

```
# Title

Text

## Subtitle

List :
- item 1
- item 2

# Link

[Caption name](url or path)
```

I don't have to think HTML or formating.

## The Javascript

Well it does only one thing : convert a md file to HTML using my CSS and display it.
I don't even do the whole markdown specification, only what I need.

```
// ---- inline MD parser ----
function parseMD(md) {
  const lines = md.replace(/\n+$/, "").split("\n");
  let html = "";
  let inList = false, listTag = "";
  let inFence = false, fenceLines = [];

  const closeList = () => {
    if (inList) { html += `</${listTag}>\n`; inList = false; listTag = ""; }
  };

  const inline = (s) =>
    s
      .replace(/&/g, "&amp;")
      .replace(/</g, "&lt;")
      .replace(/>/g, "&gt;")
      .replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>")
      .replace(/\*(.+?)\*/g, "<em>$1</em>")
      .replace(/`(.+?)`/g, "<code>$1</code>")
      .replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '<img src="$2" alt="$1">')
      .replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>');

  const escapeHTML = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");

  for (const raw of lines) {
    const line = raw;

    if (line.trim().startsWith("\`\`\`")) {
      if (!inFence) {
        closeList();
        inFence = true;
        fenceLines = [];
      } else {
        html += `<pre><code>${escapeHTML(fenceLines.join("\n"))}</code></pre>\n`;
        inFence = false;
        fenceLines = [];
      }
      continue;
    }

    if (inFence) { fenceLines.push(raw); continue; }

    const h = line.match(/^(#{1,6}) (.+)/);
    if (h) { closeList(); html += `<h${h[1].length}>${inline(h[2])}</h${h[1].length}>\n`; continue; }

    if (/^---+$/.test(line.trim())) { closeList(); html += "<hr>\n"; continue; }

    if (/^> /.test(line)) { closeList(); html += `<blockquote>${inline(line.slice(2))}</blockquote>\n`; continue; }

    const ul = line.match(/^[-*] (.+)/);
    if (ul) {
      if (listTag !== "ul") { closeList(); html += "<ul>\n"; inList = true; listTag = "ul"; }
      html += `<li>${inline(ul[1])}</li>\n`; continue;
    }

    const ol = line.match(/^\d+\. (.+)/);
    if (ol) {
      if (listTag !== "ol") { closeList(); html += "<ol>\n"; inList = true; listTag = "ol"; }
      html += `<li>${inline(ol[1])}</li>\n`; continue;
    }

    closeList();
    if (line.trim() === "") { html += "<br>\n"; continue; }
    html += `<p>${inline(line)}</p>\n`;
  }

  if (inFence) html += `<pre><code>${escapeHTML(fenceLines.join("\n"))}</code></pre>\n`;
  closeList();
  return html;
}

// ---- parse list.md into nav ----
function renderNav(md) {
  const nav = document.getElementById("nav");
  nav.innerHTML = "";

  for (const raw of md.split("\n")) {
    const m = raw.match(/^(#{1,3}) (.+)/);
    if (!m) continue;

    const level = m[1].length;
    const content = m[2].trim();
    const link = content.match(/^\[([^\]]+)\]\(([^)]+)\)$/);

    const el = document.createElement("div");
    el.className = `nav-item nav-h${level}`;

    if (link) {
      el.classList.add("nav-link");
      el.textContent = link[1];
      el.dataset.src = link[2];
      el.addEventListener("click", () => loadPage(link[2], el));
    } else {
      el.textContent = content;
    }

    nav.appendChild(el);
  }
}

// ---- load a content page ----
let currentNavEl = null;

async function loadPage(src, navEl) {
  if (currentNavEl) currentNavEl.classList.remove("active");
  if (navEl) { navEl.classList.add("active"); currentNavEl = navEl; }

  // keep URL in sync so pages are linkable and crawlable
  const url = src === "home.md" ? "/" : "?p=" + src;
  history.pushState({src}, "", url);

  const content = document.getElementById("content");
  try {
    const res = await fetch(src + "?v=" + Date.now());
    if (!res.ok) throw new Error(res.status);
    content.innerHTML = parseMD(await res.text());
  } catch (e) {
    content.innerHTML = `<p style="color:var(--text-muted)">— ${src} —</p>`;
  }
  document.getElementById("content-wrap").scrollTop = 0;
}

// ---- init ----
(async () => {
  try {
    const res = await fetch("title.md");
    if (res.ok) document.getElementById("title").innerHTML = parseMD(await res.text());
  } catch (e) {}

  try {
    const res = await fetch("list.md");
    if (res.ok) renderNav(await res.text());
  } catch (e) {}

  const params = new URLSearchParams(window.location.search);
  loadPage(params.get("p") || "home.md", null);
})();
```

## The influence

I think I kind subconsciously remembered Swagger.
So OpenAPI is a markdown standard. And [swagger.io](https://petstore.swagger.io/#/pet/uploadFile) or other take it and make it a nice interface.
What they do is fairly complex, the also manage JSON. But the idea is there, and it is an old idea, SOC, Separation of Concern.
Here content and presentation are entirerly separated. The page you are looking at does not exist as HTML, it is a HTML view generated in JS.
Now, Javascript framekworks generating pages are nothing new, but they are more feature-rich, easier use with a Backend. They are not really simple.

## Conclusion

Well, nothing genius but I can just write text, upload it and voila!
What is cool is that there is that I can't break anything, I don't edit more than I need to.
