diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 323b1b0..0000000 Binary files a/.DS_Store and /dev/null differ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea1472e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +output/ diff --git a/about.org b/about.org deleted file mode 100644 index 02f9d93..0000000 --- a/about.org +++ /dev/null @@ -1,7 +0,0 @@ -#+TITLE: About Page -#+DATE: <2025-08-04 Mon> -#+OPTIONS: toc:nil num:nil title:nil - -* About - -TODO diff --git a/assets/.DS_Store b/assets/.DS_Store deleted file mode 100644 index 6716543..0000000 Binary files a/assets/.DS_Store and /dev/null differ diff --git a/assets/images/competencies/Aware-of-the-Benefits-of-Normalisation.png b/assets/images/competencies/Aware-of-the-Benefits-of-Normalisation.png new file mode 100644 index 0000000..3a9304a Binary files /dev/null and b/assets/images/competencies/Aware-of-the-Benefits-of-Normalisation.png differ diff --git a/assets/images/competencies/competencies-09-11-25.png b/assets/images/competencies/competencies-09-11-25.png new file mode 100644 index 0000000..5a50a0a Binary files /dev/null and b/assets/images/competencies/competencies-09-11-25.png differ diff --git a/assets/images/competencies/dev-plan-09-11-25.png b/assets/images/competencies/dev-plan-09-11-25.png new file mode 100644 index 0000000..93ce0b5 Binary files /dev/null and b/assets/images/competencies/dev-plan-09-11-25.png differ diff --git a/assets/images/diagrams/airflow.png b/assets/images/diagrams/airflow.png new file mode 100644 index 0000000..2d57902 Binary files /dev/null and b/assets/images/diagrams/airflow.png differ diff --git a/assets/images/hzone/patpat.png b/assets/images/hzone/patpat.png new file mode 100644 index 0000000..4468e0d Binary files /dev/null and b/assets/images/hzone/patpat.png differ diff --git a/assets/images/owasp/Screenshot 2025-10-16 134403.png b/assets/images/owasp/Screenshot 2025-10-16 134403.png new file mode 100644 index 0000000..416ec68 Binary files /dev/null and b/assets/images/owasp/Screenshot 2025-10-16 134403.png differ diff --git a/assets/images/owasp/Screenshot 2025-10-16 134438.png b/assets/images/owasp/Screenshot 2025-10-16 134438.png new file mode 100644 index 0000000..98a1bb2 Binary files /dev/null and b/assets/images/owasp/Screenshot 2025-10-16 134438.png differ diff --git a/assets/images/owasp/Screenshot 2025-10-16 134457.png b/assets/images/owasp/Screenshot 2025-10-16 134457.png new file mode 100644 index 0000000..ffc8e03 Binary files /dev/null and b/assets/images/owasp/Screenshot 2025-10-16 134457.png differ diff --git a/assets/images/owasp/Screenshot 2025-10-16 134512.png b/assets/images/owasp/Screenshot 2025-10-16 134512.png new file mode 100644 index 0000000..ccc4d6f Binary files /dev/null and b/assets/images/owasp/Screenshot 2025-10-16 134512.png differ diff --git a/assets/images/owasp/swappy-20251106-214613.png b/assets/images/owasp/swappy-20251106-214613.png new file mode 100644 index 0000000..8393049 Binary files /dev/null and b/assets/images/owasp/swappy-20251106-214613.png differ diff --git a/assets/images/owasp/swappy-20251106-214648.png b/assets/images/owasp/swappy-20251106-214648.png new file mode 100644 index 0000000..fe2deca Binary files /dev/null and b/assets/images/owasp/swappy-20251106-214648.png differ diff --git a/assets/images/owasp/swappy-20251106-214700.png b/assets/images/owasp/swappy-20251106-214700.png new file mode 100644 index 0000000..895b266 Binary files /dev/null and b/assets/images/owasp/swappy-20251106-214700.png differ diff --git a/assets/images/owasp/swappy-20251106-214709.png b/assets/images/owasp/swappy-20251106-214709.png new file mode 100644 index 0000000..6e02177 Binary files /dev/null and b/assets/images/owasp/swappy-20251106-214709.png differ diff --git a/assets/images/owasp/swappy-20251106-214717.png b/assets/images/owasp/swappy-20251106-214717.png new file mode 100644 index 0000000..b7b7697 Binary files /dev/null and b/assets/images/owasp/swappy-20251106-214717.png differ diff --git a/assets/images/retrospective_boat_model.png b/assets/images/retrospective_boat_model.png new file mode 100644 index 0000000..8ddb37b Binary files /dev/null and b/assets/images/retrospective_boat_model.png differ diff --git a/assets/images/reviews/02-12-25-retros.jpg b/assets/images/reviews/02-12-25-retros.jpg new file mode 100644 index 0000000..d9d2882 Binary files /dev/null and b/assets/images/reviews/02-12-25-retros.jpg differ diff --git a/assets/images/reviews/07-12-25-daily-retros.jpg b/assets/images/reviews/07-12-25-daily-retros.jpg new file mode 100644 index 0000000..eab8f37 Binary files /dev/null and b/assets/images/reviews/07-12-25-daily-retros.jpg differ diff --git a/assets/images/reviews/15-11-2025-guac.png b/assets/images/reviews/15-11-2025-guac.png new file mode 100644 index 0000000..f849bb5 Binary files /dev/null and b/assets/images/reviews/15-11-2025-guac.png differ diff --git a/assets/images/reviews/16-11-25-daily-retros.jpg b/assets/images/reviews/16-11-25-daily-retros.jpg new file mode 100644 index 0000000..41abafc Binary files /dev/null and b/assets/images/reviews/16-11-25-daily-retros.jpg differ diff --git a/assets/images/reviews/23-11-2025-netdata.png b/assets/images/reviews/23-11-2025-netdata.png new file mode 100644 index 0000000..245c4dd Binary files /dev/null and b/assets/images/reviews/23-11-2025-netdata.png differ diff --git a/assets/images/reviews/23-11-2025-reader.png b/assets/images/reviews/23-11-2025-reader.png new file mode 100644 index 0000000..047c0ed Binary files /dev/null and b/assets/images/reviews/23-11-2025-reader.png differ diff --git a/assets/images/reviews/23-11-2025-vnc.png b/assets/images/reviews/23-11-2025-vnc.png new file mode 100644 index 0000000..07068dd Binary files /dev/null and b/assets/images/reviews/23-11-2025-vnc.png differ diff --git a/assets/images/reviews/23-11-25-daily-retros.jpg b/assets/images/reviews/23-11-25-daily-retros.jpg new file mode 100644 index 0000000..4c47a96 Binary files /dev/null and b/assets/images/reviews/23-11-25-daily-retros.jpg differ diff --git a/assets/images/reviews/30-11-2025-acutal.png b/assets/images/reviews/30-11-2025-acutal.png new file mode 100644 index 0000000..390219f Binary files /dev/null and b/assets/images/reviews/30-11-2025-acutal.png differ diff --git a/assets/images/reviews/30-11-25-daily-retros.jpg b/assets/images/reviews/30-11-25-daily-retros.jpg new file mode 100644 index 0000000..ca7c6ec Binary files /dev/null and b/assets/images/reviews/30-11-25-daily-retros.jpg differ diff --git a/assets/images/reviews/probation-objectives/probation-objectives-1.png b/assets/images/reviews/probation-objectives/probation-objectives-1.png new file mode 100644 index 0000000..21ed801 Binary files /dev/null and b/assets/images/reviews/probation-objectives/probation-objectives-1.png differ diff --git a/assets/images/reviews/probation-objectives/probation-objectives-2.png b/assets/images/reviews/probation-objectives/probation-objectives-2.png new file mode 100644 index 0000000..21ed801 Binary files /dev/null and b/assets/images/reviews/probation-objectives/probation-objectives-2.png differ diff --git a/assets/images/reviews/probation-objectives/probation-objectives-3.png b/assets/images/reviews/probation-objectives/probation-objectives-3.png new file mode 100644 index 0000000..4a98a64 Binary files /dev/null and b/assets/images/reviews/probation-objectives/probation-objectives-3.png differ diff --git a/assets/images/reviews/probation-objectives/probation-objectives-4.png b/assets/images/reviews/probation-objectives/probation-objectives-4.png new file mode 100644 index 0000000..f4ce82e Binary files /dev/null and b/assets/images/reviews/probation-objectives/probation-objectives-4.png differ diff --git a/assets/images/reviews/probation-objectives/probation-objectives-5.png b/assets/images/reviews/probation-objectives/probation-objectives-5.png new file mode 100644 index 0000000..3bafa29 Binary files /dev/null and b/assets/images/reviews/probation-objectives/probation-objectives-5.png differ diff --git a/assets/images/stress-in-workplace.png b/assets/images/stress-in-workplace.png new file mode 100644 index 0000000..b076bff Binary files /dev/null and b/assets/images/stress-in-workplace.png differ diff --git a/assets/images/syntax/assets.png b/assets/images/syntax/assets.png new file mode 100644 index 0000000..a09ec0d Binary files /dev/null and b/assets/images/syntax/assets.png differ diff --git a/assets/images/syntax/filebrowser.png b/assets/images/syntax/filebrowser.png new file mode 100644 index 0000000..79b237b Binary files /dev/null and b/assets/images/syntax/filebrowser.png differ diff --git a/assets/images/syntax/not_clutter.png b/assets/images/syntax/not_clutter.png new file mode 100644 index 0000000..183be97 Binary files /dev/null and b/assets/images/syntax/not_clutter.png differ diff --git a/assets/images/user_stories.jpg b/assets/images/user_stories.jpg new file mode 100644 index 0000000..f5b1b9c Binary files /dev/null and b/assets/images/user_stories.jpg differ diff --git a/assets/images/wireframes/Screenshot 2025-10-31 112139.png b/assets/images/wireframes/Screenshot 2025-10-31 112139.png new file mode 100644 index 0000000..0c134bb Binary files /dev/null and b/assets/images/wireframes/Screenshot 2025-10-31 112139.png differ diff --git a/assets/images/wireframes/Screenshot 2025-10-31 112206.png b/assets/images/wireframes/Screenshot 2025-10-31 112206.png new file mode 100644 index 0000000..c7a8cb0 Binary files /dev/null and b/assets/images/wireframes/Screenshot 2025-10-31 112206.png differ diff --git a/assets/images/wireframes/Screenshot 2025-10-31 112427.png b/assets/images/wireframes/Screenshot 2025-10-31 112427.png new file mode 100644 index 0000000..493ca4f Binary files /dev/null and b/assets/images/wireframes/Screenshot 2025-10-31 112427.png differ diff --git a/assets/images/wireframes/Screenshot 2025-10-31 115621.png b/assets/images/wireframes/Screenshot 2025-10-31 115621.png new file mode 100644 index 0000000..832b54a Binary files /dev/null and b/assets/images/wireframes/Screenshot 2025-10-31 115621.png differ diff --git a/assets/images/wireframes/Screenshot 2025-10-31 115949.png b/assets/images/wireframes/Screenshot 2025-10-31 115949.png new file mode 100644 index 0000000..4484439 Binary files /dev/null and b/assets/images/wireframes/Screenshot 2025-10-31 115949.png differ diff --git a/assets/images/wireframes/Screenshot 2025-10-31 120038.png b/assets/images/wireframes/Screenshot 2025-10-31 120038.png new file mode 100644 index 0000000..2aee318 Binary files /dev/null and b/assets/images/wireframes/Screenshot 2025-10-31 120038.png differ diff --git a/assets/images/wireframes/Screenshot 2025-10-31 120203.png b/assets/images/wireframes/Screenshot 2025-10-31 120203.png new file mode 100644 index 0000000..4a93953 Binary files /dev/null and b/assets/images/wireframes/Screenshot 2025-10-31 120203.png differ diff --git a/assets/rnote/07-12-2025-retrospectives.rnote b/assets/rnote/07-12-2025-retrospectives.rnote new file mode 100644 index 0000000..0f14ac4 Binary files /dev/null and b/assets/rnote/07-12-2025-retrospectives.rnote differ diff --git a/assets/rnote/09-11-2025-nginx-architecture.rnote b/assets/rnote/09-11-2025-nginx-architecture.rnote new file mode 100644 index 0000000..fb79e05 Binary files /dev/null and b/assets/rnote/09-11-2025-nginx-architecture.rnote differ diff --git a/assets/rnote/09-11-2025-retrospectives.rnote b/assets/rnote/09-11-2025-retrospectives.rnote new file mode 100644 index 0000000..b951c72 Binary files /dev/null and b/assets/rnote/09-11-2025-retrospectives.rnote differ diff --git a/assets/rnote/10-08-2025-weekly-review.rnote b/assets/rnote/10-08-2025-weekly-review.rnote new file mode 100644 index 0000000..b83e87f Binary files /dev/null and b/assets/rnote/10-08-2025-weekly-review.rnote differ diff --git a/assets/rnote/23-11-2025-retrospectives.rnote b/assets/rnote/23-11-2025-retrospectives.rnote new file mode 100644 index 0000000..3dfe723 Binary files /dev/null and b/assets/rnote/23-11-2025-retrospectives.rnote differ diff --git a/assets/rnote/30-11-2025-retrospectives.rnote b/assets/rnote/30-11-2025-retrospectives.rnote new file mode 100644 index 0000000..22f0fa4 Binary files /dev/null and b/assets/rnote/30-11-2025-retrospectives.rnote differ diff --git a/assets/rnote/retros-template.rnote b/assets/rnote/retros-template.rnote new file mode 100644 index 0000000..71bae3d Binary files /dev/null and b/assets/rnote/retros-template.rnote differ diff --git a/output/assets/scripts/bigger-picture.min.js b/assets/scripts/bigger-picture.min.js similarity index 100% rename from output/assets/scripts/bigger-picture.min.js rename to assets/scripts/bigger-picture.min.js diff --git a/assets/scripts/comments.js b/assets/scripts/comments.js new file mode 100644 index 0000000..3d49226 --- /dev/null +++ b/assets/scripts/comments.js @@ -0,0 +1,197 @@ +let pageSlug = null; + +/* ----------------------------- + * DOM builders + * ----------------------------- */ + + +function showReplyForm(container, parentId) { + if (container.querySelector(".reply-form")) return; + + const form = document.createElement("form"); + form.className = "reply-form"; + + form.innerHTML = ` + + + + `; + + form.addEventListener("submit", async e => { + e.preventDefault(); + + const author = form.author.value.trim(); + const content = form.content.value.trim(); + if (!content) return; + + const ok = await postComment(author, content, parentId); + if (ok) loadComments(); + else alert("Failed to post reply"); + }); + + container.appendChild(form); +} + +function createComment(comment) { + const wrapper = document.createElement("div"); + wrapper.className = "comment"; + wrapper.dataset.id = comment.id; + + const author = document.createElement("strong"); + author.textContent = comment.author || "Anonymous"; + + const body = document.createElement("p"); + body.textContent = comment.content; + + const time = document.createElement("time"); + time.className = "comment-date"; + time.dateTime = comment.created_at; + time.textContent = new Date(comment.created_at).toLocaleString(); + + const replyBtn = document.createElement("button"); + replyBtn.className = "comment-reply"; + replyBtn.textContent = "Reply"; + replyBtn.onclick = () => + showReplyForm(wrapper, Number(comment.id)); + + wrapper.append(author, body, time, replyBtn); + + if (comment.children?.length) { + const replies = document.createElement("div"); + replies.className = "comment-children"; + + comment.children.forEach(child => { + replies.appendChild(createComment(child)); + }); + + wrapper.appendChild(replies); + } + console.log("PARENT_ID RECEIVED:", comment.parent_id) + + return wrapper; +} + + + +function buildCommentTree(comments) { + const byId = {}; + const roots = []; + + comments.forEach(c => { + c.children = []; + byId[c.id] = c; + }); + + comments.forEach(c => { + if (c.parent_id) { + const parent = byId[c.parent_id]; + if (parent) parent.children.push(c); + } else { + roots.push(c); + } + }); + + return roots; +} + + +/* ----------------------------- + * Rendering + * ----------------------------- */ + +function renderComments(comments) { + const list = document.getElementById("comments-list"); + list.innerHTML = ""; + + if (!comments.length) { + const empty = document.createElement("p"); + empty.className = "comments-empty"; + empty.textContent = "No comments yet."; + list.appendChild(empty); + return; + } + + const tree = buildCommentTree(comments); + tree.forEach(c => list.appendChild(createComment(c))); +} + + +/* ----------------------------- + * API + * ----------------------------- */ + +async function fetchComments() { + const res = await fetch(`/api/comments/${pageSlug}`); + if (!res.ok) { + console.error("Failed to fetch comments"); + return []; + } + return await res.json(); +} + +async function postComment(author, content, parentId = null) { + const res = await fetch("/api/comments", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + page_slug: pageSlug, + author: author || null, + content, + parent_id: parentId + }) + }); + + return res.ok; +} + + + +/* ----------------------------- + * Form handling + * ----------------------------- */ + +function wireCommentForm() { + const form = document.getElementById("comment-form"); + + form.addEventListener("submit", async e => { + e.preventDefault(); + + const authorInput = form.querySelector("input[name='author']"); + const textarea = form.querySelector("textarea[name='content']"); + + const author = authorInput.value.trim(); + const content = textarea.value.trim(); + + if (!content) return; + + const ok = await postComment(author, content); + if (ok) { + textarea.value = ""; + loadComments(); // same pattern as your Kanban board + } else { + alert("Failed to post comment"); + } + }); +} + + +/* ----------------------------- + * Entry point + * ----------------------------- */ + +async function loadComments() { + const comments = await fetchComments(); + renderComments(comments); +} + +function initComments() { + const section = document.getElementById("comments"); + if (!section) return; + + pageSlug = section.dataset.slug; + + wireCommentForm(); + loadComments(); +} + +initComments(); diff --git a/assets/scripts/comments.js~ b/assets/scripts/comments.js~ new file mode 100644 index 0000000..7b6cdd0 --- /dev/null +++ b/assets/scripts/comments.js~ @@ -0,0 +1,127 @@ +let pageSlug = null; + +/* ----------------------------- + * DOM builders + * ----------------------------- */ + +function createComment(comment) { + const wrapper = document.createElement("div"); + wrapper.className = "comment"; + wrapper.dataset.id = comment.id; + + const author = document.createElement("strong"); + author.textContent = comment.author || "Anonymous"; + + const body = document.createElement("p"); + body.textContent = comment.content; + + const time = document.createElement("time"); + time.className = "comment-date"; + time.dateTime = comment.created_at; + time.textContent = new Date(comment.created_at).toLocaleString(); + + wrapper.appendChild(author); + wrapper.appendChild(body); + wrapper.appendChild(time); + + return wrapper; +} + +/* ----------------------------- + * Rendering + * ----------------------------- */ + +function renderComments(comments) { + const list = document.getElementById("comments-list"); + list.innerHTML = ""; + + if (!comments.length) { + const empty = document.createElement("p"); + empty.className = "comments-empty"; + empty.textContent = "No comments yet."; + list.appendChild(empty); + return; + } + + comments.forEach(c => { + list.appendChild(createComment(c)); + }); +} + +/* ----------------------------- + * API + * ----------------------------- */ + +async function fetchComments() { + const res = await fetch(`/api/comments/${pageSlug}`); + if (!res.ok) { + console.error("Failed to fetch comments"); + return []; + } + return await res.json(); +} + +async function postComment(author, content) { + const res = await fetch("/api/comments", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + page_slug: pageSlug, + author: author || null, + content: content + }) + }); + + return res.ok; +} + + +/* ----------------------------- + * Form handling + * ----------------------------- */ + +function wireCommentForm() { + const form = document.getElementById("comment-form"); + + form.addEventListener("submit", async e => { + e.preventDefault(); + + const authorInput = form.querySelector("input[name='author']"); + const textarea = form.querySelector("textarea[name='content']"); + + const author = authorInput.value.trim(); + const content = textarea.value.trim(); + + if (!content) return; + + const ok = await postComment(author, content); + if (ok) { + textarea.value = ""; + loadComments(); // same pattern as your Kanban board + } else { + alert("Failed to post comment"); + } + }); +} + + +/* ----------------------------- + * Entry point + * ----------------------------- */ + +async function loadComments() { + const comments = await fetchComments(); + renderComments(comments); +} + +function initComments() { + const section = document.getElementById("comments"); + if (!section) return; + + pageSlug = section.dataset.slug; + + wireCommentForm(); + loadComments(); +} + +initComments(); diff --git a/assets/scripts/competency-status-board.js b/assets/scripts/competency-status-board.js new file mode 100644 index 0000000..0d54656 --- /dev/null +++ b/assets/scripts/competency-status-board.js @@ -0,0 +1,169 @@ +const STATES = [ + { key: "completed", label: "Completed" }, + { key: "manager_review", label: "Manager Review" }, + { key: "in_progress", label: "In Progress" }, + { key: "not_started", label: "Not Started" }, + { key: "comments", label: "Comments" } +]; + +function isMobile() { + return window.matchMedia("(max-width: 600px)").matches; +} + +function populateMobileControls(items) { + if (!isMobile()) return; + + const panel = document.getElementById("mobile-move-panel"); + const itemSelect = document.getElementById("move-item"); + const fromSelect = document.getElementById("move-from"); + const toSelect = document.getElementById("move-to"); + + itemSelect.innerHTML = ""; + fromSelect.innerHTML = ""; + toSelect.innerHTML = ""; + + items.forEach(item => { + const opt = document.createElement("option"); + opt.value = item.id; + opt.textContent = item.title; + opt.dataset.state = item.state; + itemSelect.appendChild(opt); + }); + + STATES.forEach(s => { + fromSelect.appendChild(new Option(s.label, s.key)); + toSelect.appendChild(new Option(s.label, s.key)); + }); + + // Auto-fill "from" when item selected + itemSelect.onchange = () => { + const selected = itemSelect.selectedOptions[0]; + if (selected?.dataset.state) { + fromSelect.value = selected.dataset.state; + } + }; +} + +document.getElementById("move-confirm").addEventListener("click", async () => { + const itemId = document.getElementById("move-item").value; + const from = document.getElementById("move-from").value; + const to = document.getElementById("move-to").value; + + if (!itemId || !to) { + alert("Select an item and target column"); + return; + } + + if (from === to) { + alert("Item is already in that column"); + return; + } + + const res = await fetch(`/api/competencies/items/${itemId}/state`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ state: to }) + }); + + if (res.ok) { + loadBoard(); + } else { + alert("Failed to move competency"); + } +}); + + + +let draggedItemId = null; + +function countByState(items) { + return items.reduce((acc, item) => { + acc[item.state] = (acc[item.state] || 0) + 1; + return acc; + }, {}); +} + +function createCard(item) { + const card = document.createElement("div"); + card.className = "kanban-card"; + card.draggable = true; + card.dataset.id = item.id; + card.textContent = item.title; + + card.addEventListener("dragstart", () => { + draggedItemId = item.id; + card.classList.add("dragging"); + }); + + card.addEventListener("dragend", () => { + draggedItemId = null; + card.classList.remove("dragging"); + }); + + return card; +} + +function createColumn(state, items, counts) { + const col = document.createElement("div"); + col.className = "kanban-column"; + col.dataset.state = state.key; + + const header = document.createElement("h3"); + header.innerHTML = ` + ${state.label} + ${counts[state.key] || 0} +`; + + const list = document.createElement("div"); + list.className = "kanban-list"; + + list.addEventListener("dragover", e => e.preventDefault()); + + list.addEventListener("drop", async () => { + if (!draggedItemId) return; + + const res = await fetch(`/api/competencies/items/${draggedItemId}/state`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ state: state.key }) + }); + + if (res.ok) { + loadBoard(); // simple & safe re-render + } else { + alert("Failed to update state"); + } + }); + + items + .filter(i => i.state === state.key) + .forEach(i => list.appendChild(createCard(i))); + + col.appendChild(header); + col.appendChild(list); + + return col; +} + +function renderBoard(items) { + const board = document.getElementById("kanban-board"); + if (!board) return; + board.innerHTML = ""; + + const counts = countByState(items); + + STATES.forEach(state => { + board.appendChild(createColumn(state, items, counts)); + }); +} + + +async function loadBoard() { + const res = await fetch("/api/competencies/items"); + const items = await res.json(); + renderBoard(items); + populateMobileControls(items); +} + + +loadBoard(); diff --git a/assets/scripts/competency-status-board.js~ b/assets/scripts/competency-status-board.js~ new file mode 100644 index 0000000..4335924 --- /dev/null +++ b/assets/scripts/competency-status-board.js~ @@ -0,0 +1,168 @@ +const STATES = [ + { key: "completed", label: "Completed" }, + { key: "manager_review", label: "Manager Review" }, + { key: "in_progress", label: "In Progress" }, + { key: "not_started", label: "Not Started" }, + { key: "comments", label: "Comments" } +]; + +function isMobile() { + return window.matchMedia("(max-width: 600px)").matches; +} + +function populateMobileControls(items) { + if (!isMobile()) return; + + const panel = document.getElementById("mobile-move-panel"); + const itemSelect = document.getElementById("move-item"); + const fromSelect = document.getElementById("move-from"); + const toSelect = document.getElementById("move-to"); + + itemSelect.innerHTML = ""; + fromSelect.innerHTML = ""; + toSelect.innerHTML = ""; + + items.forEach(item => { + const opt = document.createElement("option"); + opt.value = item.id; + opt.textContent = item.title; + opt.dataset.state = item.state; + itemSelect.appendChild(opt); + }); + + STATES.forEach(s => { + fromSelect.appendChild(new Option(s.label, s.key)); + toSelect.appendChild(new Option(s.label, s.key)); + }); + + // Auto-fill "from" when item selected + itemSelect.onchange = () => { + const selected = itemSelect.selectedOptions[0]; + if (selected?.dataset.state) { + fromSelect.value = selected.dataset.state; + } + }; +} + +document.getElementById("move-confirm").addEventListener("click", async () => { + const itemId = document.getElementById("move-item").value; + const from = document.getElementById("move-from").value; + const to = document.getElementById("move-to").value; + + if (!itemId || !to) { + alert("Select an item and target column"); + return; + } + + if (from === to) { + alert("Item is already in that column"); + return; + } + + const res = await fetch(`/api/competencies/items/${itemId}/state`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ state: to }) + }); + + if (res.ok) { + loadBoard(); + } else { + alert("Failed to move competency"); + } +}); + + + +let draggedItemId = null; + +function countByState(items) { + return items.reduce((acc, item) => { + acc[item.state] = (acc[item.state] || 0) + 1; + return acc; + }, {}); +} + +function createCard(item) { + const card = document.createElement("div"); + card.className = "kanban-card"; + card.draggable = true; + card.dataset.id = item.id; + card.textContent = item.title; + + card.addEventListener("dragstart", () => { + draggedItemId = item.id; + card.classList.add("dragging"); + }); + + card.addEventListener("dragend", () => { + draggedItemId = null; + card.classList.remove("dragging"); + }); + + return card; +} + +function createColumn(state, items, counts) { + const col = document.createElement("div"); + col.className = "kanban-column"; + col.dataset.state = state.key; + + const header = document.createElement("h3"); + header.innerHTML = ` + ${state.label} + ${counts[state.key] || 0} +`; + + const list = document.createElement("div"); + list.className = "kanban-list"; + + list.addEventListener("dragover", e => e.preventDefault()); + + list.addEventListener("drop", async () => { + if (!draggedItemId) return; + + const res = await fetch(`/api/competencies/items/${draggedItemId}/state`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ state: state.key }) + }); + + if (res.ok) { + loadBoard(); // simple & safe re-render + } else { + alert("Failed to update state"); + } + }); + + items + .filter(i => i.state === state.key) + .forEach(i => list.appendChild(createCard(i))); + + col.appendChild(header); + col.appendChild(list); + + return col; +} + +function renderBoard(items) { + const board = document.getElementById("kanban-board"); + board.innerHTML = ""; + + const counts = countByState(items); + + STATES.forEach(state => { + board.appendChild(createColumn(state, items, counts)); + }); +} + + +async function loadBoard() { + const res = await fetch("/api/competencies/items"); + const items = await res.json(); + renderBoard(items); + populateMobileControls(items); +} + + +loadBoard(); diff --git a/assets/scripts/gallery-init.js b/assets/scripts/gallery-init.js new file mode 100644 index 0000000..548a989 --- /dev/null +++ b/assets/scripts/gallery-init.js @@ -0,0 +1,255 @@ +document.addEventListener('DOMContentLoaded', () => { + if (typeof window.BiggerPicture !== 'function') { + console.error('[gallery-init] BiggerPicture not found. Check script path.'); + return; + } + + // 1) Wrap Org-exported images so they’re clickable + const imgs = document.querySelectorAll('.figure img, img.org-svg'); + imgs.forEach((img) => { + if (img.closest('a')) return; // already wrapped + const a = document.createElement('a'); + const href = img.currentSrc || img.src; + a.href = href; + a.dataset.img = href; // lets BP pre-size/raster slides + a.dataset.alt = img.alt || ''; + const setDims = () => { + a.dataset.width = img.naturalWidth || img.width || 1920; + a.dataset.height = img.naturalHeight || img.height || 1080; + }; + if (img.complete) setDims(); else img.addEventListener('load', setDims); + img.style.cursor = 'zoom-in'; + img.parentElement.insertBefore(a, img); + a.appendChild(img); + }); + + // 2) One global BP instance + const bp = BiggerPicture({ target: document.body }); + + // SVG pan/zoom handle + let activePanZoom = null; + const destroyPanZoom = () => { try { activePanZoom?.destroy(); } catch(_){} activePanZoom = null; }; + + // Simple rotate state (for non-SVG images) + let activeContainer = null; + let currentRotation = 0; + let rotateControls = null; + + + // 3) Build galleries per content container + const containers = document.querySelectorAll('main, article, .content, body'); + containers.forEach((container) => { + const links = Array.from(container.querySelectorAll('.figure a, a:has(img.org-svg)')); + if (!links.length) return; + + // Start the lightbox on click + links.forEach((link, index) => { + link.addEventListener('click', (e) => { + e.preventDefault(); + document.querySelectorAll(".theme-toggle").forEach(el => { + el.classList.add("hidden"); + }); + + bp.open({ + // IMPORTANT: pass the anchor ELEMENTS, not custom objects + items: links, + el: link, + caption: (el) => el.querySelector('img')?.alt || el.title || '', + maxZoom: 40, // for raster images (PNG/JPG); SVG handled separately + + // Fade-out polish + cleanup + onClose(containerEl) { + destroyPanZoom(); + teardownRotation(); + if (containerEl) containerEl.classList.add('bp-fadeout'); + const themeToggle = document.querySelector(".theme-toggle"); + if (themeToggle) { + themeToggle.classList.remove("hidden"); + } + }, + + // Called once after open and on every slide change + onOpen(containerEl) { setupRotation(containerEl); enhanceSVG(containerEl); }, + onUpdate(containerEl){ setupRotation(containerEl); enhanceSVG(containerEl); } + }); + }); + }); + }); + + // 4) Simple rotate buttons for raster images + function ensureRotateControls() { + if (rotateControls) return rotateControls; + + const wrapper = document.createElement('div'); + wrapper.className = 'bp-rotate-controls'; + Object.assign(wrapper.style, { + position: 'fixed', + bottom: '1.5rem', + right: '1.5rem', + display: 'flex', + gap: '0.5rem', + zIndex: '9999', + pointerEvents: 'auto' + }); + + const mkBtn = (label, title) => { + const btn = document.createElement('button'); + btn.type = 'button'; + btn.textContent = label; + btn.title = title; + btn.setAttribute('aria-label', title); + Object.assign(btn.style, { + padding: '0.4rem 0.6rem', + borderRadius: '999px', + border: 'none', + fontSize: '1.2rem', + cursor: 'pointer', + background: 'rgba(30,30,30,0.8)', + color: '#fff' + }); + return btn; + }; + + const left = mkBtn('⟲', 'Rotate image 90° left'); + const right = mkBtn('⟳', 'Rotate image 90° right'); + + left.addEventListener('click', (e) => { + e.stopPropagation(); // don’t close the lightbox + if (!activeContainer) return; + currentRotation = (currentRotation - 90 + 360) % 360; + applyRotation(); + }); + + right.addEventListener('click', (e) => { + e.stopPropagation(); + if (!activeContainer) return; + currentRotation = (currentRotation + 90) % 360; + applyRotation(); + }); + + wrapper.append(left, right); + document.body.appendChild(wrapper); + rotateControls = wrapper; + return rotateControls; + } + + function ensureRotateWrapper() { + if (!activeContainer) return null; + + const imgRoot = activeContainer.querySelector('.bp-img'); + if (!imgRoot) return null; + + let imgEl = imgRoot.querySelector('img'); + if (!imgEl) return null; + + const src = (imgEl.currentSrc || imgEl.src || '').toLowerCase(); + // We only rotate raster images; SVGs are handled via svg-pan-zoom + if (src.endsWith('.svg')) return null; + + let wrapper = imgRoot.querySelector('.bp-rotate-wrapper'); + if (!wrapper) { + wrapper = document.createElement('div'); + wrapper.className = 'bp-rotate-wrapper'; + wrapper.style.display = 'inline-block'; + wrapper.style.transformOrigin = 'center center'; + + imgRoot.appendChild(wrapper); + wrapper.appendChild(imgEl); + } else if (!wrapper.contains(imgEl)) { + // Slide changed and BiggerPicture replaced the + wrapper.innerHTML = ''; + wrapper.appendChild(imgEl); + } + + return wrapper; + } + + function applyRotation() { + const wrapper = ensureRotateWrapper(); + if (!wrapper) return; + wrapper.style.transform = `rotate(${currentRotation}deg)`; + } + + function setupRotation(containerEl) { + activeContainer = containerEl; + currentRotation = 0; + const controls = ensureRotateControls(); + controls.style.display = 'flex'; + applyRotation(); + } + + function teardownRotation() { + activeContainer = null; + currentRotation = 0; + if (rotateControls) { + rotateControls.style.display = 'none'; + } + } + + + // 4) If current slide is an SVG, swap to inline + enable svg-pan-zoom + async function enhanceSVG(containerEl) { + try { + destroyPanZoom(); + + const imgEl = containerEl.querySelector('.bp-img img'); + if (!imgEl) return; + + const src = imgEl.currentSrc || imgEl.src || ''; + const isSVG = src.toLowerCase().endsWith('.svg'); + const htmlLayer = containerEl.querySelector('.bp-html'); + if (!isSVG || !htmlLayer) { + // ensure any previous holder is removed and bitmap is visible + const old = htmlLayer?.querySelector('.bp-svg-holder'); + if (old) old.remove(); + imgEl.style.visibility = ''; + return; + } + + // Create/clear holder + let holder = htmlLayer.querySelector('.bp-svg-holder'); + if (!holder) { + holder = document.createElement('div'); + holder.className = 'bp-svg-holder'; + holder.style.maxWidth = '95vw'; + holder.style.maxHeight = '95vh'; + htmlLayer.appendChild(holder); + } + holder.innerHTML = ''; + + // Hide the bitmap so only the inline SVG shows + imgEl.style.visibility = 'hidden'; + + // Inline the SVG + const res = await fetch(src, { cache: 'force-cache' }); + const text = await res.text(); + holder.innerHTML = text; + + const svg = holder.querySelector('svg'); + if (!svg) { imgEl.style.visibility = ''; return; } + + svg.style.maxWidth = '95vw'; + svg.style.maxHeight = '95vh'; + svg.style.display = 'block'; + + if (typeof window.svgPanZoom === 'function') { + activePanZoom = svgPanZoom(svg, { + zoomEnabled: true, + controlIconsEnabled: true, + fit: true, + center: true, + minZoom: 0.05, + maxZoom: 400, // effectively "unlimited" + zoomScaleSensitivity: 0.25, + dblClickZoomEnabled: true + }); + // Keep wheel inside lightbox + holder.addEventListener('wheel', (e) => e.stopPropagation(), { passive: true }); + } else { + console.warn('[gallery-init] svg-pan-zoom not loaded'); + } + } catch (err) { + console.error('[gallery-init] SVG enhance failed:', err); + } + } +}); diff --git a/output/assets/scripts/gallery-init.js b/assets/scripts/gallery-init.js~ similarity index 100% rename from output/assets/scripts/gallery-init.js rename to assets/scripts/gallery-init.js~ diff --git a/assets/scripts/notes.js b/assets/scripts/notes.js new file mode 100644 index 0000000..9e14baf --- /dev/null +++ b/assets/scripts/notes.js @@ -0,0 +1,102 @@ +(function () { + const wall = document.getElementById("notes-wall"); + const form = document.getElementById("notes-form"); + const authorInput = document.getElementById("note-author"); + const contentInput = document.getElementById("note-content"); + + if (!wall) return; + + function escapeHtml(str) { + return str + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); + } + + function renderNote(note) { + const el = document.createElement("div"); + el.className = "note"; + + el.innerHTML = ` +
+ ${escapeHtml(note.author_name)} + +
+
+ ${escapeHtml(note.content).replace(/\n/g, "
")} +
+ `; + + return el; + } + + async function loadNotes() { + wall.innerHTML = "

Loading notes…

"; + + try { + const res = await fetch("/api/notes"); + if (!res.ok) throw new Error("Failed to fetch notes"); + + const notes = await res.json(); + wall.innerHTML = ""; + + if (notes.length === 0) { + wall.innerHTML = "

No notes yet.

"; + return; + } + + notes.forEach(note => { + wall.appendChild(renderNote(note)); + }); + } catch (err) { + console.error(err); + wall.innerHTML = "

Could not load notes.

"; + } + } + + async function submitNote(e) { + e.preventDefault(); + + const author = authorInput.value.trim(); + const content = contentInput.value.trim(); + + if (!author || !content) return; + + form.querySelector("button").disabled = true; + + try { + const res = await fetch("/api/notes", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + author_name: author, + content: content, + }), + }); + + if (!res.ok) { + const err = await res.json(); + throw new Error(err.detail || "Failed to post note"); + } + + contentInput.value = ""; + await loadNotes(); + } catch (err) { + alert(err.message); + } finally { + form.querySelector("button").disabled = false; + } + } + + if (form) { + form.addEventListener("submit", submitNote); + } + + loadNotes(); +})(); diff --git a/blogs/2025/08/spending-the-whole-day-on-this-website.org~ b/assets/scripts/notes.js~ similarity index 100% rename from blogs/2025/08/spending-the-whole-day-on-this-website.org~ rename to assets/scripts/notes.js~ diff --git a/assets/scripts/script.js b/assets/scripts/script.js index 4436a66..78033b6 100644 --- a/assets/scripts/script.js +++ b/assets/scripts/script.js @@ -94,6 +94,47 @@ document.addEventListener("DOMContentLoaded", () => { })(); +// Simple, timezone-safe if you pass UTC (…Z) in the datetime +document.addEventListener('DOMContentLoaded', () => { + const els = document.querySelectorAll('time.countdown'); + if (!els.length) return; + + const plural = (n, w) => `${n} ${w}${n === 1 ? '' : 's'}`; + + const render = (el) => { + const raw = el.getAttribute('datetime'); + const label = el.dataset.label || ''; + const target = new Date(raw); // Prefer ISO like 2025-12-31T00:00:00Z + if (isNaN(target)) { el.textContent = '—'; return; } + + const now = new Date(); + let diff = target - now; + + if (diff <= 0) { + el.textContent = `${label ? label + ' ' : ''}today`; + el.classList.add('expired'); + return; + } + + const d = Math.floor(diff / 86400000); diff -= d * 86400000; + const h = Math.floor(diff / 3600000); diff -= h * 3600000; + const m = Math.floor(diff / 60000); diff -= m * 60000; + const s = Math.floor(diff / 1000); + + const pieces = []; + if (d) pieces.push(plural(d, 'day')); + pieces.push(`${h}h ${m}m ${s}s`); + + el.textContent = `${label ? label + ' in : ' : ''}${pieces.join(' ')}`; + }; + + const tick = () => els.forEach(render); + tick(); + setInterval(tick, 1000); // update every second +}); + + + /* Event listener for scrolling and changing the active label on the TOC */ document.addEventListener("DOMContentLoaded", () => { const toc = document.querySelector("#text-table-of-contents"); @@ -182,3 +223,5 @@ document.addEventListener("DOMContentLoaded", () => { history.pushState(null, "", `#${id}`); }); }); + + diff --git a/assets/scripts/status-board.js~ b/assets/scripts/status-board.js~ new file mode 100644 index 0000000..4cfbd79 --- /dev/null +++ b/assets/scripts/status-board.js~ @@ -0,0 +1,98 @@ +const STATES = [ + { key: "completed", label: "Completed" }, + { key: "manager_review", label: "Manager Review" }, + { key: "in_progress", label: "In Progress" }, + { key: "not_started", label: "Not Started" }, + { key: "comments", label: "Comments" } +]; + +let draggedItemId = null; + +function countByState(items) { + return items.reduce((acc, item) => { + acc[item.state] = (acc[item.state] || 0) + 1; + return acc; + }, {}); +} + +function createCard(item) { + const card = document.createElement("div"); + card.className = "kanban-card"; + card.draggable = true; + card.dataset.id = item.id; + card.textContent = item.title; + + card.addEventListener("dragstart", () => { + draggedItemId = item.id; + card.classList.add("dragging"); + }); + + card.addEventListener("dragend", () => { + draggedItemId = null; + card.classList.remove("dragging"); + }); + + return card; +} + +function createColumn(state, items, counts) { + const col = document.createElement("div"); + col.className = "kanban-column"; + col.dataset.state = state.key; + + const header = document.createElement("h3"); + header.innerHTML = ` + ${state.label} + ${counts[state.key] || 0} +`; + + const list = document.createElement("div"); + list.className = "kanban-list"; + + list.addEventListener("dragover", e => e.preventDefault()); + + list.addEventListener("drop", async () => { + if (!draggedItemId) return; + + const res = await fetch(`/api/competencies/items/${draggedItemId}/state`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ state: state.key }) + }); + + if (res.ok) { + loadBoard(); // simple & safe re-render + } else { + alert("Failed to update state"); + } + }); + + items + .filter(i => i.state === state.key) + .forEach(i => list.appendChild(createCard(i))); + + col.appendChild(header); + col.appendChild(list); + + return col; +} + +function renderBoard(items) { + const board = document.getElementById("kanban-board"); + board.innerHTML = ""; + + const counts = countByState(items); + + STATES.forEach(state => { + board.appendChild(createColumn(state, items, counts)); + }); +} + + +async function loadBoard() { + const res = await fetch("/api/competencies/items"); + const items = await res.json(); + renderBoard(items); +} + +loadBoard(); diff --git a/output/assets/scripts/svg-pan-zoom.min.js b/assets/scripts/svg-pan-zoom.min.js similarity index 100% rename from output/assets/scripts/svg-pan-zoom.min.js rename to assets/scripts/svg-pan-zoom.min.js diff --git a/assets/sketchnotes/07-12-2025-retrospectives.svg b/assets/sketchnotes/07-12-2025-retrospectives.svg new file mode 100644 index 0000000..0ac6269 --- /dev/null +++ b/assets/sketchnotes/07-12-2025-retrospectives.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/assets/sketchnotes/09-11-2025-nginx-architecture.svg b/assets/sketchnotes/09-11-2025-nginx-architecture.svg new file mode 100644 index 0000000..6edaa73 --- /dev/null +++ b/assets/sketchnotes/09-11-2025-nginx-architecture.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/assets/sketchnotes/09-11-2025-retrospectives.svg b/assets/sketchnotes/09-11-2025-retrospectives.svg new file mode 100644 index 0000000..cb7334a --- /dev/null +++ b/assets/sketchnotes/09-11-2025-retrospectives.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/output/assets/sketchnotes/10-08-2025-weekly-review.svg b/assets/sketchnotes/10-08-2025-weekly-review.svg similarity index 100% rename from output/assets/sketchnotes/10-08-2025-weekly-review.svg rename to assets/sketchnotes/10-08-2025-weekly-review.svg diff --git a/assets/sketchnotes/16-11-2025-retrospectives.svg b/assets/sketchnotes/16-11-2025-retrospectives.svg new file mode 100644 index 0000000..4959d3c --- /dev/null +++ b/assets/sketchnotes/16-11-2025-retrospectives.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/assets/sketchnotes/23-11-2025-retrospectives.svg b/assets/sketchnotes/23-11-2025-retrospectives.svg new file mode 100644 index 0000000..3783de6 --- /dev/null +++ b/assets/sketchnotes/23-11-2025-retrospectives.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/assets/sketchnotes/30-11-2025-retrospectives.svg b/assets/sketchnotes/30-11-2025-retrospectives.svg new file mode 100644 index 0000000..b391b6b --- /dev/null +++ b/assets/sketchnotes/30-11-2025-retrospectives.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/assets/sketchnotes/elt-yed.svg b/assets/sketchnotes/elt-yed.svg new file mode 100644 index 0000000..189dfd4 --- /dev/null +++ b/assets/sketchnotes/elt-yed.svg @@ -0,0 +1,265 @@ + + + + + + + + + + ELT PROCESS + + + + + + + + Transformation (ELT) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Source + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Destination + + + + + + + Extract + + + + + + + Load + + + + + + + Transform + + + + + + + + + + + diff --git a/output/assets/styles/bigger-picture.min.css b/assets/styles/bigger-picture.min.css similarity index 100% rename from output/assets/styles/bigger-picture.min.css rename to assets/styles/bigger-picture.min.css diff --git a/assets/styles/comments.css b/assets/styles/comments.css new file mode 100644 index 0000000..f3185e3 --- /dev/null +++ b/assets/styles/comments.css @@ -0,0 +1,118 @@ +.comments { + margin-top: 4rem; + padding-top: 2rem; + border-top: 1px solid var(--border-muted, #ddd); +} + +.comments h2 { + margin: 0 0 1rem 0; + font-size: 1.4rem; +} + + +.comments-list { + margin: 1.5rem 0 2rem; +} + +.comments-empty { + font-style: italic; + color: var(--text-muted, #777); +} + + +.comment { + margin-bottom: 1.5rem; + padding-bottom: 1.25rem; + border-bottom: 1px solid var(--border-muted, #eee); +} + +.comment:last-child { + border-bottom: none; +} + +.comment strong { + display: block; + font-weight: 600; + margin-bottom: 0.25rem; +} + +.comment p { + margin: 0.25rem 0 0.5rem; +} + +.comment-date { + display: block; + font-size: 0.8rem; + color: var(--text-muted, #777); +} + +.comment-children { + margin-top: 1rem; + margin-left: 1.25rem; + padding-left: 1rem; + border-left: 2px solid var(--border-muted, #ddd); +} + +.comment-reply { + margin-top: 0.4rem; + padding: 0; + font-size: 0.85rem; + + background: none; + border: none; + color: var(--text-muted, #555); + cursor: pointer; +} + +.comment-reply:hover { + text-decoration: underline; +} + +.comment-form { + max-width: 40rem; +} + +.comment-author { + display: block; + margin-bottom: 1rem; +} + +.comment-form span { + display: block; + margin-bottom: 0.25rem; + font-size: 0.85rem; + color: var(--fg); +} + +.comment-form input, +.comment-form textarea { + width: 100%; + padding: 0; + + font-family: inherit; + font-size: 1rem; + + color: var(--fg); + background: var(--bg); + border: 1px solid var(--border-muted, #ccc); + border-radius: 3px; +} + +.comment-form textarea { + min-height: 6rem; + resize: vertical; +} + +.comment-form button { + margin-top: 0.75rem; + padding: 0.45rem 1rem; + + font-family: inherit; + font-size: 0.9rem; + + color: var(--fg); + + background: transparent; + border: 1px solid var(--border-muted, #aaa); + cursor: pointer; +} diff --git a/assets/styles/kanban.css b/assets/styles/kanban.css new file mode 100644 index 0000000..da639bd --- /dev/null +++ b/assets/styles/kanban.css @@ -0,0 +1,183 @@ +#kanban-board { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); + gap: 1rem; + margin-top: 1rem; +} + +.kanban-column { + background: #f8f9fa; + border-radius: 8px; + padding: 0.6rem; + display: flex; + flex-direction: column; +} + +.kanban-column h3 { + text-align: center; + font-size: 0.9rem; + margin: 0.4rem 0 0.6rem; + font-weight: 600; +} + +.kanban-list { + flex-grow: 1; + min-height: 3rem; + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.kanban-card { + background: var(--bg); + border-radius: 6px; + padding: 0.5rem 0.6rem; + font-size: 0.85rem; + cursor: grab; + box-shadow: 0 1px 2px rgba(0,0,0,0.1); + color: var(--fg); +} + +.kanban-card.dragging { + opacity: 0.5; + cursor: grabbing; +} + + +.kanban-column[data-state="completed"] { + background: var(--kb-bg-completed); +} + +.kanban-column[data-state="manager_review"] { + background: var(--kb-bg-manager-review); +} + +.kanban-column[data-state="in_progress"] { + background: var(--kb-bg-in-progress); +} + +.kanban-column[data-state="not_started"] { + background: var(--kb-bg-not-started); +} + +.kanban-column[data-state="comments"] { + background: var(--kb-bg-comments); +} + + +#mobile-move-panel { + display: none; + max-width: 22rem; + margin: 1.5rem auto 0 auto; + padding: 1rem; + + box-sizing: border-box; + + border: 1px solid var(--border-color, #ddd); + border-radius: 0.5rem; + background: var(--bg); + color: var(--fg); + + overflow: hidden; + + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05); + + font-size: 0.9rem; +} + +#mobile-move-panel * { + box-sizing: border-box; +} + +#mobile-move-panel h4 { + margin: 0 0 0.75rem 0; + font-size: 0.95rem; + font-weight: 600; +} + +#mobile-move-panel label { + display: flex; + flex-direction: column; + gap: 0.35rem; + width: 100%; +} + +#mobile-move-panel select, +#mobile-move-panel button { + font-size: 0.9rem; + padding: 0.4rem 0.5rem; + min-width: 0; +} + +#mobile-move-panel button { + margin-top: 0.5rem; + align-self: flex-start; +} + +#mobile-move-panel select { + width: 100%; + max-width: 100%; + + padding: 0.45rem 2rem 0.45rem 0.6rem; + font-size: 0.9rem; + line-height: 1.2; + + border: 1px solid var(--border-color, #ccc); + border-radius: 0.4rem; + background-color: var(--bg, #fff); + + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; + + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + + background-image: + linear-gradient(45deg, transparent 50%, #666 50%), + linear-gradient(135deg, #666 50%, transparent 50%), + linear-gradient(to right, transparent, transparent); + + background-position: + calc(100% - 1.2rem) 50%, + calc(100% - 0.9rem) 50%, + 100% 0; + + background-size: + 6px 6px, + 6px 6px, + 2.5rem 100%; + + background-repeat: no-repeat; + margin-bottom: 1em; +} + +#mobile-move-panel select:focus { + outline: none; + border-color: #666; + box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.08); +} +#mobile-move-panel button { + width: 100%; + padding: 0.5rem 0.6rem; + font-size: 0.9rem; + + border-radius: 0.4rem; + border: 1px solid #888; + background: #f8f8f8; + + cursor: pointer; +} + +#mobile-move-panel button:active { + background: #eee; +} +#mobile-move-panel select:disabled { + background-color: #f3f3f3; + color: #555; + border-color: #ccc; + opacity: 1; + cursor: default; + background-image: none; +} diff --git a/assets/styles/media.css b/assets/styles/media.css new file mode 100644 index 0000000..ac30e08 --- /dev/null +++ b/assets/styles/media.css @@ -0,0 +1,106 @@ +@media (max-width: 600px){ + .banner-header{ flex-direction: column; align-items: center; text-align: center; } + .banner-logo{ margin: 0 0 .5rem 0; } + nav{ flex-wrap: wrap; justify-content: center; gap: .5rem; font-size: 1rem; } + + + .theme-toggle { + position: static; + order: 2; + margin-left: .5rem; + padding: .3rem .6rem; + background: transparent; + } + + .banner-header { + flex-wrap: wrap; + justify-content: center; + } + .banner-header nav { + display: flex; + align-items: center; + flex-wrap: wrap; + } + + body.no-sidenotes { + margin-left: 1em; + margin-right: 1em; + } + + #mobile-move-panel { + display: block; + } + +} +@media (prefers-color-scheme: dark){ + :root:not([data-theme="light"]){ + --bg: #0f1115; + --fg: #e6e6e6; + --link: #8ab4ff; + --heading: #9ecbff; + --code-bg: #1a1d24; + + --note-color: #c2c7cf; + --note-bg: transparent; + + --border: #2a2f3a; + --chip-bg: #1f2330; + --chip-fg: #cfd3da; + --muted: #a9b0bb; + } + .countdown-wrap { + color: var(--fg, #ddd); + } + time.countdown { + color: var(--accent, #7abfff); + background: color-mix(in srgb, var(--accent, #7abfff) 15%, transparent); + box-shadow: 0 0 6px rgba(255,255,255,0.05); + } + time.countdown.expired { + color: #777; + } +} + +@media (max-width: 1250px){ + #preamble.status{ + padding-right: var(--body-pad); + } + + #content.content{ + padding-right: var(--body-pad); + } + .sidenote, + .marginnote{ + float: none; + clear: both; + width: auto; + display: block; + margin: 0.5rem 0 0.75rem; + padding-left: 0.75rem; + border-left: 3px solid rgba(0,0,0,.08); + margin-right: 0; + } + .fullwidth{ + max-width: calc(100vw - 2 * var(--body-pad)); + } + .sidenote, .marginnote{ + border-left: 3px solid color-mix(in oklab, var(--fg) 12%, transparent); + } + .sidenote .mn-img, + .marginnote .mn-img{ + border-color: color-mix(in oklab, var(--fg) 12%, transparent); + } + + #table-of-contents{ + float: none; + position: static; + width: auto; + margin: 0 0 1rem; + padding: .5rem .75rem; + border-right: 0; + border-left: 3px solid color-mix(in oklab, var(--fg) 12%, transparent); + background: color-mix(in oklab, var(--bg) 96%, var(--fg) 4%); + border-radius: 4px; + } + +} diff --git a/assets/styles/misc.css b/assets/styles/misc.css new file mode 100644 index 0000000..e2a0118 --- /dev/null +++ b/assets/styles/misc.css @@ -0,0 +1,213 @@ + +#updated{ + font-size: .8rem; color: var(--fg); + margin: .5rem 0 1rem; font-style: italic; +} + +.sidenote, +.marginnote{ + float: right; + width: var(--margin); + /* margin-right: calc(-1 * ( (95vw - var(--content)) / 2 )); */ + margin-right: calc(-1 * ((100vi - var(--content)) / 2)); + padding-right: var(--gutter); + overflow-wrap: break-word; + word-wrap: break-word; + word-break: break-word; + margin-top: .3rem; + margin-bottom: .6rem; + font-size: .95rem; + line-height: 1.35; + color: var(--note-color); + background: var(--note-bg); + box-sizing: border-box; +} + +.sidenote, +.marginnote{ + display: block; + clear: right; +} + +.sidenote + .sidenote, +.sidenote + .marginnote, +.marginnote + .sidenote, +.marginnote + .marginnote{ + margin-top: .4rem; +} + +.footnote-sidenote::before{ + content: attr(data-fn) " "; + font-size: 0.85em; + vertical-align: super; + margin-right: 0.2rem; + opacity: 0.85; +} + +body{ counter-reset: sidenote-counter; } +.sidenote-number:after{ + counter-increment: sidenote-counter; + content: counter(sidenote-counter); + font-size: .85em; + vertical-align: super; + margin-left: .15rem; +} + +.margin-toggle{ + position: absolute; + opacity: 0; + pointer-events: none; +} + +/* #content .figure, */ +/* #content .org-src-container, */ +/* #content img.fullwidth{ */ +/* //clear: both; */ +/* } */ + +.fullwidth{ + display: block; + position: relative; + left: 50%; + transform: translateX(-50%); + height: auto; + margin: 1.25rem 0 1.75rem; + width: 100%; + max-width: min( + calc(var(--content) + var(--bleed) * 2), + var(--fullwidth-cap), + calc(100vw - 2 * var(--body-pad)) + ); +} +.figure .fullwidth{ width: inherit; } +.figure figcaption{ + text-align: center; font-size: .95rem; color: #666; margin-top: .4rem; +} + + +.post-date{ color: pink; font-size: .9em; margin-left: 8px; } + +.post-tag{ + float: right; + display: inline-block; + background-color: #f0f0f0; + color: #444; + border-radius: 5px; + padding: 2px 6px; + margin-left: 6px; + font-size: 0.8em; +} + +.filetags { + margin-top: .5rem; + display: flex; + flex-wrap: wrap; + gap: .3rem .4rem; + font-size: .9rem; + color: #666; +} +.filetags .tag { + display: inline-block; + padding: .1rem .45rem; + border-radius: .5rem; + background: #f0f0f0; + line-height: 1.6; +} + +.figure figcaption{ color: var(--muted); } +.post-tag{ + background-color: var(--chip-bg); + color: var(--chip-fg); +} + +.org-src-container{ + border: 1px solid var(--border); + box-shadow: 3px 3px 3px rgba(0,0,0,.15); +} + +:not(pre) > code{ + border: 1px solid var(--border); + background-color: var(--code-bg); + color: var(--fg); +} + +.theme-toggle { + position: fixed; + top: 0.75rem; + right: 1rem; + z-index: 1000; + border: 1px solid var(--border, #ccc); + background: transparent; + color: var(--fg); + padding: .35rem .7rem; + border-radius: 6px; + cursor: pointer; + font: inherit; + transition: background-color 0.3s, color 0.3s, border-color 0.3s; +} +.theme-toggle:hover { + background-color: rgba(0,0,0,0.05); + color: var(--heading); + border-color: var(--heading); +} + + + +.sidenote .mn-fig, +.marginnote .mn-fig{ + margin: 0; +} +.sidenote .mn-img, +.marginnote .mn-img{ + display: block; + max-width: 100%; + height: auto; + border: 1px solid var(--border, #d7d7d7); + border-radius: 6px; + background: var(--bg); +} +.sidenote .mn-fig figcaption, +.marginnote .mn-fig figcaption{ + margin-top: .35rem; + font-size: .85rem; + color: var(--muted, #666); + line-height: 1.35; +} + + +.sidenote img { + margin-top: 0.35rem; + display: block; +} + +.countdown-wrap { + display: flex; + justify-content: center; + align-items: center; + padding: 1em 0; + font-family: system-ui, sans-serif; + font-size: 1.2rem; + color: var(--fg, #222); + background: var(--bg-alt, transparent); + text-align: center; +} + +time.countdown { + font-weight: 600; + color: var(--accent, #2a7fff); + font-variant-numeric: tabular-nums; + letter-spacing: 0.02em; + padding: 0.25em 0.6em; + border-radius: 0.5em; + background: color-mix(in srgb, var(--accent, #2a7fff) 10%, transparent); + box-shadow: 0 0 8px rgba(0,0,0,0.1); + transition: color 0.3s ease, background 0.3s ease; +} + +time.countdown.expired { + color: #999; + background: none; + font-weight: 500; + text-decoration: line-through; +} + diff --git a/assets/styles/org-syntax.css b/assets/styles/org-syntax.css new file mode 100644 index 0000000..4064bdd --- /dev/null +++ b/assets/styles/org-syntax.css @@ -0,0 +1,72 @@ +.org-comment { color: #b22222; } +.org-string { color: #8b2252; } +.org-keyword { color: #a020f0; } +.org-builtin { color: #483d8b; } +.org-constant { color: #008b8b; } +.org-function-name { color: #0000ff; } +.org-variable-name { color: sienna; } +.org-type { color: #228b22; } +.org-preprocessor { color: #483d8b; } +.org-doc { color: #8b2252; } +.org-warning { color: #ff8c00; font-weight: 700; } + +:not(pre) > code{ + padding: 2px 5px; margin: 0 1px; + border: 1px solid #ddd; border-radius: 3px; + background-clip: padding-box; + color: #333; font-size: 80%; +} +pre { + background-color: var(--bg); +} + +.org-src-container{ + border: 1px solid #ccc; + box-shadow: 3px 3px 3px #eee; + font-family: Lucida Console, monospace; + font-size: 80%; + margin: 1em auto; + padding: .1em .5em; + position: relative; +} +.org-src-container > pre{ overflow: auto; } + +.org-src-container > pre:before{ + display: block; position: absolute; top: 0; right: 0; + background-color: #b3b3b3; color: #fff; + padding: 0 .5em; border-bottom-left-radius: 8px; border: 0; + font-size: 80%; +} +.org-src-container > pre.src-bash:before { content: "bash"; } +.org-src-container > pre.src-sh:before { content: "sh"; } +.org-src-container > pre.src-shell:before { content: "shell"; } +.org-src-container > pre.src-python:before { content: "Python"; } +.org-src-container > pre.src-emacs-lisp:before { content: "Emacs Lisp"; } +.org-src-container > pre.src-js:before, +.org-src-container > pre.src-javascript:before { content: "Javascript"; } +.org-src-container > pre.src-typescript:before { content: "Typescript"; } +.org-src-container > pre.src-html:before { content: "HTML"; } +.org-src-container > pre.src-css:before { content: "CSS"; } +.org-src-container > pre.src-c:before { content: "C"; } +.org-src-container > pre.src-cpp:before { content: "C++"; } +.org-src-container > pre.src-java:before { content: "Java"; } +.org-src-container > pre.src-rust:before { content: "Rust"; } +.org-src-container > pre.src-R:before { content: "R"; } + +.copy-btn{ + position: absolute; top: .4em; right: .4em; + background-color: var(--code-bg); color: var(--heading); + border: 1px solid var(--heading); border-radius: 4px; + padding: .2em .6em; font-size: .8rem; cursor: pointer; z-index: 10; + transition: background-color .3s; +} +.copy-btn:hover{ background-color: var(--heading); color: var(--code-bg); } + +pre.src::before{ content: none !important; } +pre.src{ + padding: 1.5em 1em 1em; + font-family: "Fira Mono","Courier New",monospace; + font-size: .9rem; line-height: 1.4; + overflow-x: auto; white-space: pre-wrap; +} + diff --git a/assets/styles/style.css b/assets/styles/style.css index 0639f51..a8f7763 100644 --- a/assets/styles/style.css +++ b/assets/styles/style.css @@ -1,9 +1,4 @@ -/* ========================= - Theme variables - ========================= */ :root{ - /* Layout */ - --gutter: 2rem; --margin: 420px; --body-pad: 1rem; @@ -14,26 +9,61 @@ ); --content-min: 60ch; --content-max: 880px; - /* Fullwidth media tuning */ --bleed: 48px; --fullwidth-cap: 860px; - - /* Colors */ --bg: #faf9f6; --fg: #000000; --link: #1a0dab; --heading: #004c99; --code-bg: #f0f0f0; --active-toc: #cacaca; - - /* Notes */ --note-color: #555; --note-bg: transparent; + --border: #d7d7d7; + --chip-bg: #f0f0f0; + --chip-fg: #444; + --muted: #666; + + --kb-bg-completed: #e6f4ea; + --kb-bg-manager-review: #fff4e5; + --kb-bg-in-progress: #e8f0fe; + --kb-bg-not-started: #f1f3f5; + --kb-bg-comments: #fdecea; + + --kb-column-border: rgba(0,0,0,0.06); + --kb-card-bg: #ffffff; + --kb-card-text: #1f2933; } -/* ========================= - Base - ========================= */ +:root[data-theme="dark"]{ + --bg: #0f1115; + --fg: #e6e6e6; + --link: #8ab4ff; + --heading: #9ecbff; + --code-bg: #1a1d24; + + --note-color: #c2c7cf; + --note-bg: transparent; + + --border: #2a2f3a; + --chip-bg: #1f2330; + --chip-fg: #cfd3da; + --muted: #a9b0bb; + --active-toc: #313131; + + --kb-bg-completed: #1e3a2b; + --kb-bg-manager-review: #3a2f1e; + --kb-bg-in-progress: #1f2a44; + --kb-bg-not-started: #2a2d31; + --kb-bg-comments: #3b1f24; + + --kb-column-border: rgba(255,255,255,0.08); + --kb-card-bg: #16181c; + --kb-card-text: #e5e7eb; + +} + + html, body{ background-color: var(--bg); color: var(--fg); @@ -46,16 +76,13 @@ h1, h2, h3{ color: var(--heading); } a { color: var(--link); - text-decoration: none; /* removes underline by default */ + text-decoration: none; } a:hover { - text-decoration: underline; /* only show on hover */ + text-decoration: underline; } -/* ========================= - Header / Banner - ========================= */ .banner-header{ display: flex; align-items: center; @@ -72,17 +99,7 @@ nav{ display: flex; gap: 1rem; font-weight: 600; font-size: 1.1rem; } -#updated{ - font-size: .8rem; color: var(--fg); - margin: .5rem 0 1rem; font-style: italic; -} -/* Mobile banner */ -@media (max-width: 600px){ - .banner-header{ flex-direction: column; align-items: center; text-align: center; } - .banner-logo{ margin: 0 0 .5rem 0; } - nav{ flex-wrap: wrap; justify-content: center; gap: .5rem; font-size: 1rem; } -} -/* Make the preamble align with the content column + right rail */ + #preamble.status{ max-width: var(--content); margin-left: auto; @@ -97,15 +114,8 @@ nav{ max-width: 100%; } -@media (max-width: 1250px){ - #preamble.status{ - padding-right: var(--body-pad); - } -} -/* ========================= - Footer - ========================= */ + footer{ color: var(--fg); padding: 1rem; margin-top: 2rem; @@ -113,566 +123,400 @@ footer{ font-size: .9rem; font-style: italic; } -/* ========================= - Content column + reserved right margin - ========================= */ #content.content{ - max-width: var(--content); - margin-left: auto !important; - margin-right: auto !important; - padding-left: var(--body-pad); - padding-right: var(--body-pad); - box-sizing: content-box; - position: relative; /* fine to keep */ + max-width: var(--content); + margin-left: auto !important; + margin-right: auto !important; + padding-left: var(--body-pad); + padding-right: var(--body-pad); + box-sizing: content-box; + position: relative; } -/* Figures & normal images obey text column */ #content .figure, #content img:not(.fullwidth){ max-width: 100%; height: auto; } -/* ========================= - Sidenotes (Tufte-like) - ========================= */ -/* HTML pattern: - label.margin-toggle.sidenote-number + input.margin-toggle + span.sidenote -*/ -/* Sidenotes: float-right inside #content, then push them into the right gutter+edge */ -.sidenote, -.marginnote{ - float: right; - width: var(--margin); - //margin-right: calc(-1 * ( (95vw - var(--content)) / 2 )); - margin-right: calc(-1 * ((100vi - var(--content)) / 2)); - padding-right: var(--gutter); - overflow-wrap: break-word; /* preferred */ - word-wrap: break-word; /* legacy support */ - word-break: break-word; /* safety for stubborn cases */ - margin-top: .3rem; - margin-bottom: .6rem; - font-size: .95rem; - line-height: 1.35; - color: var(--note-color); - background: var(--note-bg); - box-sizing: border-box; -} -/* Optional: show the same footnote number in the margin */ -.footnote-sidenote::before{ - content: attr(data-fn) " "; - font-size: 0.85em; - vertical-align: super; - margin-right: 0.2rem; - opacity: 0.85; -} -/* Sidenote numbering */ -body{ counter-reset: sidenote-counter; } -.sidenote-number:after{ - counter-increment: sidenote-counter; - content: counter(sidenote-counter); - font-size: .85em; - vertical-align: super; - margin-left: .15rem; -} - -/* Hide checkbox toggles (kept for mobile patterns if added later) */ -.margin-toggle{ - position: absolute; - opacity: 0; - pointer-events: none; -} - -/* Safety: only big blocks should clear floats to avoid gaps */ -#content .figure, -#content .org-src-container, -#content img.fullwidth{ - //clear: both; -} - -/* ========================= - Fullwidth media (centered, modest bleed, no collision) - ========================= */ -.fullwidth{ - display: block; - position: relative; - left: 50%; - transform: translateX(-50%); - height: auto; - margin: 1.25rem 0 1.75rem; - width: 100%; - max-width: min( - calc(var(--content) + var(--bleed) * 2), /* centered bleed */ - var(--fullwidth-cap), /* absolute cap */ - calc(100vw - 2 * var(--body-pad)) /* never cause horizontal scroll */ - ); -} -.figure .fullwidth{ width: inherit; } /* if img sits inside a figure */ -.figure figcaption{ - text-align: center; font-size: .95rem; color: #666; margin-top: .4rem; -} - -@media (max-width: 1250px){ - #content.content{ - padding-right: var(--body-pad); - } - .sidenote, - .marginnote{ - float: none; - clear: both; - width: auto; - display: block; /* ⬅ make them start on a new line */ - margin: 0.5rem 0 0.75rem; /* spacing above/below */ - padding-left: 0.75rem; - border-left: 3px solid rgba(0,0,0,.08); - margin-right: 0; - } - .fullwidth{ - max-width: calc(100vw - 2 * var(--body-pad)); - } -} - - -/* Ultra-wide comfort tweaks */ -/* @media (min-width: 1600px){ */ -/* :root{ --content: 720px; --margin: 360px; } */ -/* } */ - -/* ========================= - Code highlighting (Org-mode classes) - ========================= */ -.org-comment { color: #b22222; } /* comments */ -.org-string { color: #8b2252; } /* string literals */ -.org-keyword { color: #a020f0; } /* keywords */ -.org-builtin { color: #483d8b; } /* builtins */ -.org-constant { color: #008b8b; } /* constants / numbers */ -.org-function-name { color: #0000ff; } /* function names */ -.org-variable-name { color: sienna; } /* variables */ -.org-type { color: #228b22; } /* types/classes */ -.org-preprocessor { color: #483d8b; } /* #define, import, etc. */ -.org-doc { color: #8b2252; } /* docstrings */ -.org-warning { color: #ff8c00; font-weight: 700; } /* warnings */ - -/* Inline code */ -:not(pre) > code{ - padding: 2px 5px; margin: 0 1px; - border: 1px solid #ddd; border-radius: 3px; - background-clip: padding-box; - color: #333; font-size: 80%; -} -pre { - background-color: var(--bg); -} -/* Code blocks */ -.org-src-container{ - border: 1px solid #ccc; - box-shadow: 3px 3px 3px #eee; - font-family: Lucida Console, monospace; - font-size: 80%; - margin: 1em auto; - padding: .1em .5em; - position: relative; -} -.org-src-container > pre{ overflow: auto; } - -/* Language badges (optional) */ -.org-src-container > pre:before{ - display: block; position: absolute; top: 0; right: 0; - background-color: #b3b3b3; color: #fff; - padding: 0 .5em; border-bottom-left-radius: 8px; border: 0; - font-size: 80%; -} -.org-src-container > pre.src-bash:before { content: "bash"; } -.org-src-container > pre.src-sh:before { content: "sh"; } -.org-src-container > pre.src-shell:before { content: "shell"; } -.org-src-container > pre.src-python:before { content: "Python"; } -.org-src-container > pre.src-emacs-lisp:before { content: "Emacs Lisp"; } -.org-src-container > pre.src-js:before, -.org-src-container > pre.src-javascript:before { content: "Javascript"; } -.org-src-container > pre.src-typescript:before { content: "Typescript"; } -.org-src-container > pre.src-html:before { content: "HTML"; } -.org-src-container > pre.src-css:before { content: "CSS"; } -.org-src-container > pre.src-c:before { content: "C"; } -.org-src-container > pre.src-cpp:before { content: "C++"; } -.org-src-container > pre.src-java:before { content: "Java"; } -.org-src-container > pre.src-rust:before { content: "Rust"; } -.org-src-container > pre.src-R:before { content: "R"; } - -/* Org copy button on blocks */ -.copy-btn{ - position: absolute; top: .4em; right: .4em; - background-color: var(--code-bg); color: var(--heading); - border: 1px solid var(--heading); border-radius: 4px; - padding: .2em .6em; font-size: .8rem; cursor: pointer; z-index: 10; - transition: background-color .3s; -} -.copy-btn:hover{ background-color: var(--heading); color: var(--code-bg); } - -/* Org’s default pre label removal if duplicated elsewhere */ -pre.src::before{ content: none !important; } -pre.src{ - padding: 1.5em 1em 1em; - font-family: "Fira Mono","Courier New",monospace; - font-size: .9rem; line-height: 1.4; - overflow-x: auto; white-space: pre-wrap; -} - -/* ========================= - Lists with post meta chips - ========================= */ -.post-date{ color: pink; font-size: .9em; margin-left: 8px; } - -/* Post tag chip no longer needs a clearfix */ -.post-tag{ - float: right; - display: inline-block; - background-color: #f0f0f0; - color: #444; - border-radius: 5px; - padding: 2px 6px; - margin-left: 6px; - font-size: 0.8em; -} -/* Shared base styles for all lists */ ul, ol { - margin: 1rem 0 1.5rem 1.5rem; /* space above/below and indent */ - padding: 0; - line-height: 1.5; + margin: 1rem 0 1.5rem 1.5rem; + padding: 0; + line-height: 1.5; } -/* Unordered lists: custom bullet */ ul { - list-style: none; /* remove default bullet */ + list-style: none; } + ul li { - position: relative; - padding-left: 1.2em; /* space for custom bullet */ + position: relative; + padding-left: 1.2em; } + ul li::before { - content: "•"; /* bullet character */ - position: absolute; - left: 0; - top: 0; - color: var(--heading); /* matches your headings color */ - font-weight: bold; + content: "•"; + position: absolute; + left: 0; + top: 0; + color: var(--heading); + font-weight: bold; } -/* Ordered lists: styled numbers */ ol { - counter-reset: list-counter; - list-style: none; /* remove default numbering */ + counter-reset: list-counter; + list-style: none; } + ol li { - counter-increment: list-counter; - position: relative; - padding-left: 1.8em; /* space for custom number */ + counter-increment: list-counter; + position: relative; + padding-left: 1.8em; } + ol li::before { - content: counter(list-counter) "."; - position: absolute; - left: 0; - top: 0; - color: var(--heading); - font-weight: bold; + content: counter(list-counter) "."; + position: absolute; + left: 0; + top: 0; + color: var(--heading); + font-weight: bold; } -/* List items: keep your float containment and spacing */ li { - margin-bottom: 8px; - display: flow-root; /* contains floats like .post-tag */ + margin-bottom: 8px; + display: flow-root; } -/* Remove the old clearfix ::after */ li::after { - content: none; + content: none; } -/* Nested lists: smaller bullet/number and reduced spacing */ li ul, li ol { - margin-top: 0.5rem; - margin-bottom: 0.5rem; + margin-top: 0.5rem; + margin-bottom: 0.5rem; } + li ul li::before { - content: "–"; /* en dash for nested bullets */ - font-weight: normal; - color: var(--note-color); + content: "–"; + font-weight: normal; + color: var(--note-color); } + li ol li::before { - font-weight: normal; - color: var(--note-color); + font-weight: normal; + color: var(--note-color); } -/* Epigraph container: center and limit width */ + .epigraph { - margin: 2rem auto; - max-width: 80%; /* narrower than full text width */ - font-style: italic; + margin: 2rem auto; + max-width: 80%; + font-style: italic; } -/* Main blockquote style */ .epigraph blockquote { - margin: 0; - padding: 1rem 1.5rem; - border-left: 4px solid var(--heading); - background-color: rgba(0, 0, 0, 0.02); /* subtle background */ - color: var(--fg); - line-height: 1.6; + margin: 0; + padding: 1rem 1.5rem; + border-left: 4px solid var(--heading); + background-color: rgba(0, 0, 0, 0.02); + color: var(--fg); + line-height: 1.6; } -/* Footer inside blockquote: author/source */ .epigraph blockquote footer { - margin-top: 0.75rem; - font-style: normal; /* turn off italic for attribution */ - font-size: 0.9em; - color: var(--note-color); - text-align: right; + margin-top: 0.75rem; + font-style: normal; + font-size: 0.9em; + color: var(--note-color); + text-align: right; } -/* Cite: make source title distinct */ .epigraph blockquote cite { - font-style: italic; - font-weight: 500; - color: var(--link); + font-style: italic; + font-weight: 500; + color: var(--link); } -/* Optional: remove automatic quotes from
in some browsers */ .epigraph blockquote::before, .epigraph blockquote::after { - content: none; -} -/* ========================================= - Dark mode — manual (data-theme) wins - ========================================= */ -:root { - /* optional: shared tokens for borders/chips */ - --border: #d7d7d7; - --chip-bg: #f0f0f0; - --chip-fg: #444; - --muted: #666; + content: none; } -:root[data-theme="dark"]{ - --bg: #0f1115; - --fg: #e6e6e6; - --link: #8ab4ff; - --heading: #9ecbff; - --code-bg: #1a1d24; - --note-color: #c2c7cf; - --note-bg: transparent; - - --border: #2a2f3a; - --chip-bg: #1f2330; - --chip-fg: #cfd3da; - --muted: #a9b0bb; - --active-toc: #313131; - -} - -/* Auto scheme if no manual override is set */ -@media (prefers-color-scheme: dark){ - :root:not([data-theme="light"]){ - --bg: #0f1115; - --fg: #e6e6e6; - --link: #8ab4ff; - --heading: #9ecbff; - --code-bg: #1a1d24; - - --note-color: #c2c7cf; - --note-bg: transparent; - - --border: #2a2f3a; - --chip-bg: #1f2330; - --chip-fg: #cfd3da; - --muted: #a9b0bb; - } -} - -/* Tweak components that used fixed light colors */ -.figure figcaption{ color: var(--muted); } -.post-tag{ - background-color: var(--chip-bg); - color: var(--chip-fg); -} -.org-src-container{ - border: 1px solid var(--border); - box-shadow: 3px 3px 3px rgba(0,0,0,.15); -} -:not(pre) > code{ - border: 1px solid var(--border); - background-color: var(--code-bg); - color: var(--fg); -} .epigraph blockquote{ - border-left-color: var(--heading); - background-color: color-mix(in oklab, var(--bg) 94%, var(--fg) 6%); - color: var(--fg); + border-left-color: var(--heading); + background-color: color-mix(in oklab, var(--bg) 94%, var(--fg) 6%); + color: var(--fg); } .epigraph blockquote footer{ color: var(--muted); } -/* Mobile sidenote rule uses a light border; make it theme-aware */ -@media (max-width: 1250px){ - .sidenote, .marginnote{ - border-left: 3px solid color-mix(in oklab, var(--fg) 12%, transparent); - } + +.hidden { display: none !important; } + +.bp-x{ right:72px; } + +.bp-next,.bp-prev{ right:8px; } +.bp-prev{ left:8px; } + +.bp-wrap{ transition: opacity .18s ease; } +.bp-wrap.bp-fadeout{ opacity: 0; } + +.bp-controls{ + padding-top: env(safe-area-inset-top, 0); + padding-right: calc(env(safe-area-inset-right, 0) + 8px); } -/* Theme toggle button positioning + transparent background */ -.theme-toggle { - position: fixed; - top: 0.75rem; - right: 1rem; - z-index: 1000; - border: 1px solid var(--border, #ccc); - background: transparent; - color: var(--fg); - padding: .35rem .7rem; - border-radius: 6px; - cursor: pointer; - font: inherit; - transition: background-color 0.3s, color 0.3s, border-color 0.3s; -} -.theme-toggle:hover { - background-color: rgba(0,0,0,0.05); /* subtle hover tint */ - color: var(--heading); - border-color: var(--heading); -} -/* Mobile: place next to nav links */ -@media (max-width: 600px) { - .theme-toggle { - position: static; - order: 2; /* after nav links in flex layout */ - margin-left: .5rem; - padding: .3rem .6rem; - background: transparent; - } - .banner-header { - flex-wrap: wrap; - justify-content: center; - } - .banner-header nav { - display: flex; - align-items: center; - flex-wrap: wrap; - } -} -/* Margin/sidenote images */ -.sidenote .mn-fig, -.marginnote .mn-fig{ - margin: 0; -} -.sidenote .mn-img, -.marginnote .mn-img{ - display: block; - max-width: 100%; - height: auto; - border: 1px solid var(--border, #d7d7d7); - border-radius: 6px; - background: var(--bg); -} -.sidenote .mn-fig figcaption, -.marginnote .mn-fig figcaption{ - margin-top: .35rem; - font-size: .85rem; - color: var(--muted, #666); - line-height: 1.35; -} +body.no-sidenotes { + --body-pad: 0; + --margin: 0; + --gutter: 0; -/* Mobile: these already stack under content via your media query; nothing special needed, - but we can soften the border a touch to match the mobile sidenote rule */ -@media (max-width: 1250px){ - .sidenote .mn-img, - .marginnote .mn-img{ - border-color: color-mix(in oklab, var(--fg) 12%, transparent); - } -} -.sidenote img { - margin-top: 0.35rem; - display: block; -} -/* .sidenote.footnote-sidenote img { */ -/* margin-top: 0.35rem; */ -/* display: block; */ -/* } */ + --content-min: 80ch; + --content-max: 1200px; + --content: clamp( + var(--content-min), + 100vi, + var(--content-max) + ); +} /* ========================= - Left sticky Table of Contents (desktop) + NOTES BOARD ========================= */ -#table-of-contents{ - /* push into the left gutter, mirroring .sidenote on the right */ - float: left; - width: var(--margin); - //margin-left: calc(-1 * ( (95vw - var(--content)) / 2 )); - margin-left: calc(-1 * ((100vi - var(--content)) / 2)); - padding-left: var(--gutter); - box-sizing: border-box; - /* sticky behavior */ - position: sticky; - top: 5.5rem; /* clear your banner/header */ - max-height: calc(100vh - 6rem); - overflow: auto; - - /* look & feel */ - font-size: .95rem; - border-right: 1px solid var(--border, #d7d7d7); - padding-top: .25rem; +#notes-wall { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); + gap: 1.5rem; + margin: 2.5rem 0; + align-items: start; } -#table-of-contents > h2{ - margin: 0 0 .5rem; - font-size: 1rem; +/* ========================= + NOTE CARD + ========================= */ + +.note { + position: relative; + padding: 1rem 1rem 0.9rem; + background: #fffef8; + border-radius: 8px; + box-shadow: + 0 2px 6px rgba(0, 0, 0, 0.08), + 0 12px 24px rgba(0, 0, 0, 0.06); + transform: rotate(var(--note-tilt, 0deg)); + transition: + transform 0.15s ease, + box-shadow 0.15s ease; +} + +/* subtle randomness */ +.note:nth-child(3n) { --note-tilt: -0.6deg; } +.note:nth-child(4n) { --note-tilt: 0.4deg; } +.note:nth-child(5n) { --note-tilt: -0.3deg; } + +.note:hover { + transform: rotate(0deg) translateY(-4px); + box-shadow: + 0 6px 14px rgba(0, 0, 0, 0.12), + 0 18px 36px rgba(0, 0, 0, 0.10); +} + +/* ========================= + PIN + ========================= */ + +.note::before { + content: ""; + position: absolute; + top: 0.6rem; + left: 50%; + width: 10px; + height: 10px; + background: #c33; + border-radius: 50%; + transform: translateX(-50%); + box-shadow: + 0 1px 2px rgba(0, 0, 0, 0.3); + opacity: 0.85; +} + +/* ========================= + META + ========================= */ + +.note-meta { + display: flex; + justify-content: space-between; + align-items: baseline; + font-size: 0.7rem; + margin-bottom: 0.5rem; + color: #555; +} + +.note-author { + font-weight: 700; text-transform: uppercase; - letter-spacing: .02em; - color: var(--muted, #666); + letter-spacing: 0.04em; } -#text-table-of-contents ul{ - list-style: none; - margin: 0; - padding-left: 0; +.note-meta time { + font-variant-numeric: tabular-nums; + opacity: 0.7; } -#text-table-of-contents li{ - margin: .25rem 0; -} -#text-table-of-contents a{ - text-decoration: none; /* no underline by default */ -} -#text-table-of-contents a:hover{ - text-decoration: underline; -} - -/* Indent nested ToC levels a bit */ -#text-table-of-contents ul ul { margin-left: .75rem; opacity: .9; } - -/* Headings offset so anchors don’t hide under the sticky header */ -h2[id], h3[id], h4[id] { scroll-margin-top: 6.5rem; } /* ========================= - Mobile / narrow view + CONTENT ========================= */ -@media (max-width: 1250px){ - #table-of-contents{ - float: none; - position: static; /* no sticky on small screens */ - width: auto; - margin: 0 0 1rem; - padding: .5rem .75rem; - border-right: 0; - border-left: 3px solid color-mix(in oklab, var(--fg) 12%, transparent); - background: color-mix(in oklab, var(--bg) 96%, var(--fg) 4%); - border-radius: 4px; + +.note-content { + font-size: 0.95rem; + line-height: 1.45; + white-space: pre-wrap; +} + +/* ========================= + MOBILE TWEAKS + ========================= */ + +@media (max-width: 600px) { + #notes-wall { + grid-template-columns: 1fr; + } + + .note { + transform: none; + } + + .note::before { + left: 0.75rem; + transform: none; } } -#text-table-of-contents a.is-active{ - // font-weight: 700; - text-decoration: underline; - background-color: var(--active-toc); - //border: 1px solid var(--border, #ccc); - +/* ========================= + NOTES FORM + ========================= */ + +#notes-form-wrapper { + margin-top: 3rem; + display: flex; + justify-content: center; +} + +#notes-form { + width: 100%; + max-width: 420px; + background: #fffef8; + padding: 1.25rem 1.25rem 1.1rem; + border-radius: 10px; + box-shadow: + 0 2px 6px rgba(0, 0, 0, 0.08), + 0 14px 30px rgba(0, 0, 0, 0.06); + position: relative; +} + +/* pin */ +#notes-form::before { + content: ""; + position: absolute; + top: 0.6rem; + left: 50%; + width: 12px; + height: 12px; + background: #c33; + border-radius: 50%; + transform: translateX(-50%); + box-shadow: + 0 1px 2px rgba(0, 0, 0, 0.35); +} + +/* ========================= + LABELS + ========================= */ + +#notes-form label { + display: block; + font-size: 0.7rem; + letter-spacing: 0.08em; + text-transform: uppercase; + color: #666; + margin-bottom: 0.25rem; +} + +/* ========================= + INPUTS + ========================= */ + +#notes-form input, +#notes-form textarea { + width: 100%; + border: none; + background: transparent; + font-family: inherit; + font-size: 0.95rem; + line-height: 1.45; + padding: 0.3rem 0; + margin-bottom: 0.9rem; + border-bottom: 1px solid #ddd; +} + +#notes-form textarea { + resize: vertical; + min-height: 4.5rem; +} + +/* focus state */ +#notes-form input:focus, +#notes-form textarea:focus { + outline: none; + border-bottom-color: #999; +} + +/* placeholder */ +#notes-form ::placeholder { + color: #aaa; +} + +/* ========================= + SUBMIT BUTTON + ========================= */ + +#notes-form button { + margin-top: 0.5rem; + padding: 0.35rem 0.9rem; + font-family: inherit; + font-size: 0.8rem; + letter-spacing: 0.06em; + text-transform: uppercase; + border-radius: 999px; + border: none; + background: #222; + color: #fff; + cursor: pointer; + float: right; +} + +#notes-form button:hover { + background: #000; +} + +#notes-form button:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +/* ========================= + MOBILE + ========================= */ + +@media (max-width: 600px) { + #notes-form { + max-width: 100%; + } } diff --git a/assets/styles/toc.css b/assets/styles/toc.css new file mode 100644 index 0000000..cefd21a --- /dev/null +++ b/assets/styles/toc.css @@ -0,0 +1,53 @@ +#table-of-contents{ + float: left; + width: var(--margin); + /* margin-left: calc(-1 * ( (95vw - var(--content)) / 2 )); */ + margin-left: calc(-1 * ((100vi - var(--content)) / 2)); + padding-left: var(--gutter); + box-sizing: border-box; + + position: sticky; + top: 5.5rem; + max-height: calc(100vh - 6rem); + overflow: auto; + + font-size: .95rem; + border-right: 1px solid var(--border, #d7d7d7); + padding-top: .25rem; +} + +#table-of-contents > h2{ + margin: 0 0 .5rem; + font-size: 1rem; + text-transform: uppercase; + letter-spacing: .02em; + color: var(--muted, #666); +} + +#text-table-of-contents ul{ + list-style: none; + margin: 0; + padding-left: 0; +} +#text-table-of-contents li{ + margin: .25rem 0; +} +#text-table-of-contents a{ + text-decoration: none; +} +#text-table-of-contents a:hover{ + text-decoration: underline; +} + +#text-table-of-contents ul ul { margin-left: .75rem; opacity: .9; } + +h2[id], h3[id], h4[id] { scroll-margin-top: 6.5rem; } + +#text-table-of-contents a.is-active{ + /* font-weight: 700; */ + text-decoration: underline; + background-color: var(--active-toc); + /* border: 1px solid var(--border, #ccc); */ + +} + diff --git a/blogs/2025/08-august/benefits-of-reading.org b/blogs/2025/08-august/benefits-of-reading.org new file mode 100644 index 0000000..152398a --- /dev/null +++ b/blogs/2025/08-august/benefits-of-reading.org @@ -0,0 +1,27 @@ +#+TITLE: Benefits of Reading +#+OPTIONS: num:nil tags:t +#+DATE: <2025-08-14 Thu 23:36> +#+FILETAGS: :reading:insights: +#+COMMENTS: t +#+SLUG: benefits-of-reading + +* Benefits [fn:1] + +At times, I find myself being able to digest material more quickly and efficiently, and so I thought to myself as to what the probable cause of this was. The answer transpired to be the inculcating of reading in my daily routine. As humans, we are constantly surrounded with information, some are noisy, some are useful. We can choose to /filter/ out noisy information by limiting exposure of their avenues, however the topic I wanted to briefly touch upon is what are the (cognitive and non-cognitive) benefits of increasing the intake of useful information through the medium of reading? + +** Mental Stimulation +The brain is a muscle, in order to keep any muscle strong and healthy, it needs stimulation and attention. Thus, reading is an excellent way of ensuring the brain is /fit and healthy/. + +** Stress Reduction +A well written novel or informative book can go a long way in making you forget about the worries of the world. + +** Increase in Knowledge +Reading will always fill your head with new information. + +** Entertainment +A plethora of genres can keep anyone entertained! + +** Better writing skills +Being exposed to different writing styles allows you to be influenced to obtaining your own unique writing style. + +[fn:1] Check the following [[https://www.crawshawacademy.org.uk/seecmsfile/?id=165][link]] diff --git a/blogs/2025/08-august/benefits-of-reading.org~ b/blogs/2025/08-august/benefits-of-reading.org~ new file mode 100644 index 0000000..75b5c5f --- /dev/null +++ b/blogs/2025/08-august/benefits-of-reading.org~ @@ -0,0 +1,25 @@ +#+TITLE: Benefits of Reading +#+OPTIONS: num:nil tags:t +#+DATE: <2025-08-14 Thu 23:36> +#+FILETAGS: :reading:insights: + +* Benefits [fn:1] + +At times, I find myself being able to digest material more quickly and efficiently, and so I thought to myself as to what the probable cause of this was. The answer transpired to be the inculcating of reading in my daily routine. As humans, we are constantly surrounded with information, some are noisy, some are useful. We can choose to /filter/ out noisy information by limiting exposure of their avenues, however the topic I wanted to briefly touch upon is what are the (cognitive and non-cognitive) benefits of increasing the intake of useful information through the medium of reading? + +** Mental Stimulation +The brain is a muscle, in order to keep any muscle strong and healthy, it needs stimulation and attention. Thus, reading is an excellent way of ensuring the brain is /fit and healthy/. + +** Stress Reduction +A well written novel or informative book can go a long way in making you forget about the worries of the world. + +** Increase in Knowledge +Reading will always fill your head with new information. + +** Entertainment +A plethora of genres can keep anyone entertained! + +** Better writing skills +Being exposed to different writing styles allows you to be influenced to obtaining your own unique writing style. + +[fn:1] Check the following [[https://www.crawshawacademy.org.uk/seecmsfile/?id=165][link]] diff --git a/blogs/2025/08-august/hilberts.hotel.org b/blogs/2025/08-august/hilberts.hotel.org new file mode 100644 index 0000000..3ee066f --- /dev/null +++ b/blogs/2025/08-august/hilberts.hotel.org @@ -0,0 +1,10 @@ +#+TITLE: Hilbert's Hotel +#+OPTIONS: num:nil tags:t +#+DATE: <2025-08-11 Mon 18:39> +#+FILETAGS: :maths:insights: +#+COMMENTS: t +#+SLUG: hilberts-hotel + +I recently stumbled across a thought experiment in mathematics, where the counterintuitive nature of infinity is portrayed. There is a hotel with an infinite number of rooms, all of which are occupied with a guest. There is a paradox upon the arrival of a new guest, the hotel is still shown to be able to accomodate this guest, despite it being previously coined as /full/. + +See: [[https://plus.maths.org/content/hilberts-hotel][this link]] for more details on the full experimental thought and the proofs alongside. diff --git a/blogs/2025/08-august/hilberts.hotel.org~ b/blogs/2025/08-august/hilberts.hotel.org~ new file mode 100644 index 0000000..67ab9ee --- /dev/null +++ b/blogs/2025/08-august/hilberts.hotel.org~ @@ -0,0 +1,8 @@ +#+TITLE: Hilbert's Hotel +#+OPTIONS: num:nil tags:t +#+DATE: <2025-08-11 Mon 18:39> +#+FILETAGS: :maths:insights: + +I recently stumbled across a thought experiment in mathematics, where the counterintuitive nature of infinity is portrayed. There is a hotel with an infinite number of rooms, all of which are occupied with a guest. There is a paradox upon the arrival of a new guest, the hotel is still shown to be able to accomodate this guest, despite it being previously coined as /full/. + +See: [[https://plus.maths.org/content/hilberts-hotel][this link]] for more details on the full experimental thought and the proofs alongside. diff --git a/blogs/2025/08/spending-the-whole-day-on-this-website.org b/blogs/2025/08-august/spending-the-whole-day-on-this-website.org similarity index 96% rename from blogs/2025/08/spending-the-whole-day-on-this-website.org rename to blogs/2025/08-august/spending-the-whole-day-on-this-website.org index d1f9a90..c9968f7 100644 --- a/blogs/2025/08/spending-the-whole-day-on-this-website.org +++ b/blogs/2025/08-august/spending-the-whole-day-on-this-website.org @@ -1,7 +1,9 @@ #+TITLE: 09-08-2025: Website Changes -#+OPTIONS: num:nil title:nil +#+OPTIONS: num:nil toc:nil #+DATE: <2025-08-09 Sat> #+FILETAGS: :emacs:website: +#+COMMENTS: t +#+SLUG: spending-all-day-on-this-website * What was worked on diff --git a/blogs/2025/08-august/spending-the-whole-day-on-this-website.org~ b/blogs/2025/08-august/spending-the-whole-day-on-this-website.org~ new file mode 100644 index 0000000..ea37758 --- /dev/null +++ b/blogs/2025/08-august/spending-the-whole-day-on-this-website.org~ @@ -0,0 +1,36 @@ +#+TITLE: 09-08-2025: Website Changes +#+OPTIONS: num:nil toc:nil +#+DATE: <2025-08-09 Sat> +#+FILETAGS: :emacs:website: + +* What was worked on + +I saw a few websites that used marginal notes on the right side of the screen like [[https://ogbe.net/blog/emacs_org_static_site][this one]], so I decided to try and implement this feature in this wesbite. In my mind I thought of using an external pre-configured CSS that I can just insert and ta-da it would work. + +How wrong I was. + +See, this website is published with ~org-publish~, meaning the org files that are written are converted to HTML through a transcoder. And as I already had a ~org.css~[fn:1] stylesheet which was primarily used for syntax highlighting, it caused some layout and formatting issue when I tried to use something like ~tufte.css~[fn:2]. Anyways, long story short, the main thing I worked on today was styling this website, and ensuring certain macros worked: + +#+BEGIN_SRC emacs-lisp + + (setq org-export-global-macros + (append + '(("sidenote" + . "@@html:$2@@") + ("epigraph" . "@@html:
$1
$2
@@") + ("epigraph_single" . "@@html:
$1
@@") + ("epigraph3" . "@@html:
$1
$2, $3
@@") + ("kbd" . "@@html:$1@@@@latex:\\texttt{$1}@@") + ("margimg" + . "@@html:@@") + + org-export-global-macros))) + +#+END_SRC + +This worked quite nicely, and I made sure to make these changes accessible on smaller devices. Anyways, I quite like this style of posting. Nothing formal nor technical, just a bunch of rambling on about random things. + +Oh, and also there's a darkmode theme switch now for you night owls. + +[fn:1] See: https://gongzhitaao.org/orgcss/ +[fn:2] See: https://edwardtufte.github.io/tufte-css/ diff --git a/blogs/2025/08-august/third-time.org b/blogs/2025/08-august/third-time.org new file mode 100644 index 0000000..96ee348 --- /dev/null +++ b/blogs/2025/08-august/third-time.org @@ -0,0 +1,20 @@ +#+TITLE: Third Time +#+OPTIONS: num:nil tags:t +#+DATE: <2025-08-28 Thu 17:08> +#+FILETAGS: :insights: +#+COMMENTS: t +#+SLUG: third-time + +* A more structured way of working +{{{sidenote(1, Useful for people who don't like the constrained working limit of 25 minutes for instance.)}}} + +Whilst researching for different ways to use pomodoro [fn:1] on Emacs [fn:2], I came across this package titled: ~pomm.el~. In this package it had an interesting feature called /pomm-third-time/[fn:3]. The ideology behind is as follows: +- You work as long as you want. +- Your break is calculated using the formula: ~Time of break = 1/3 x Time of work~. +- If you take a shorter break, the remaining break time is accumulated and added to the next break (within the same session). + +[fn:1] - https://en.wikipedia.org/wiki/Pomodoro_Technique + +[fn:2] - https://www.gnu.org/software/emacs/ + +[fn:3] - https://www.lesswrong.com/posts/RWu8eZqbwgB9zaerh/third-time-a-better-way-to-work diff --git a/blogs/2025/08-august/third-time.org~ b/blogs/2025/08-august/third-time.org~ new file mode 100644 index 0000000..28c7769 --- /dev/null +++ b/blogs/2025/08-august/third-time.org~ @@ -0,0 +1,18 @@ +#+TITLE: Third Time +#+OPTIONS: num:nil tags:t +#+DATE: <2025-08-28 Thu 17:08> +#+FILETAGS: :insights: + +* A more structured way of working +{{{sidenote(1, Useful for people who don't like the constrained working limit of 25 minutes for instance.)}}} + +Whilst researching for different ways to use pomodoro [fn:1] on Emacs [fn:2], I came across this package titled: ~pomm.el~. In this package it had an interesting feature called /pomm-third-time/[fn:3]. The ideology behind is as follows: +- You work as long as you want. +- Your break is calculated using the formula: ~Time of break = 1/3 x Time of work~. +- If you take a shorter break, the remaining break time is accumulated and added to the next break (within the same session). + +[fn:1] - https://en.wikipedia.org/wiki/Pomodoro_Technique + +[fn:2] - https://www.gnu.org/software/emacs/ + +[fn:3] - https://www.lesswrong.com/posts/RWu8eZqbwgB9zaerh/third-time-a-better-way-to-work diff --git a/blogs/2025/08/wacom-with-arch.org b/blogs/2025/08-august/wacom-with-arch.org similarity index 92% rename from blogs/2025/08/wacom-with-arch.org rename to blogs/2025/08-august/wacom-with-arch.org index d6a1b28..5e2a249 100644 --- a/blogs/2025/08/wacom-with-arch.org +++ b/blogs/2025/08-august/wacom-with-arch.org @@ -1,9 +1,11 @@ #+TITLE: Wacom With Arch #+DATE: <2025-08-08 Fri> -#+OPTIONS: toc:nil num:nil title:nil +#+OPTIONS: toc:nil num:nil #+FILETAGS: :insights: +#+COMMENTS: t +#+SLUG: wacom-with-arch -* Wacom With Arch +* Setting up Wacom Tablet in Arch Hyprland I purchased a [[https://www.wacom.com/en-gb/products/one-by-wacom][One by Wacom]] tablet almost 2 years ago now and it works flawlessly in both Windows and MacOS and even in most Linux distros with some configuration. diff --git a/blogs/2025/08-august/wacom-with-arch.org~ b/blogs/2025/08-august/wacom-with-arch.org~ new file mode 100644 index 0000000..c7a98a9 --- /dev/null +++ b/blogs/2025/08-august/wacom-with-arch.org~ @@ -0,0 +1,18 @@ +#+TITLE: Wacom With Arch +#+DATE: <2025-08-08 Fri> +#+OPTIONS: toc:nil num:nil +#+FILETAGS: :insights: + +* Setting up Wacom Tablet in Arch Hyprland + +I purchased a [[https://www.wacom.com/en-gb/products/one-by-wacom][One by Wacom]] tablet almost 2 years ago now and it works flawlessly in both Windows and MacOS and even in most Linux distros with some configuration. + +However with Arch and Hyprland, there were some issues related to the ~xserver~ it was running on. The X input driver module (e.g. xf86-input-wacom) is linked into the X server which in turn is in charge of rendering and generic input events. Hyprland on the other hand uses the Wayland display server protocol (replacement for the X protocol) hence why there is difficulty in input mapping. + +I came across this issue as I was able to get the tablet to work across multiple monitors (mapping to a single one) through the ~xsetwacom~ function - which, at the time I was using Ubuntu. But with Hyprland there needs to be some manual intervention. I came across this [[https://thomaspurnell.com/003_wacom_graphics_hyprland/wacom_hyprland.html][post]] which explains nicely how to map the tablet to a single monitor. I took this approach with a slight configuration; in my ~hyprland.conf~ file, I simply added the following line: + +#+BEGIN_SRC + exec-once = hyprctl keyword "device[wacom-one-by-wacom-m-pen]:output" "DP-1" +#+END_SRC + +which mapped the device to the primary monitor DP-1. diff --git a/blogs/2025/08/what-do-i-want-to-do-with-emacs.org~ b/blogs/2025/08-august/what-do-i-want-to-do-with-emacs.org similarity index 93% rename from blogs/2025/08/what-do-i-want-to-do-with-emacs.org~ rename to blogs/2025/08-august/what-do-i-want-to-do-with-emacs.org index 4a63395..fff0c9e 100644 --- a/blogs/2025/08/what-do-i-want-to-do-with-emacs.org~ +++ b/blogs/2025/08-august/what-do-i-want-to-do-with-emacs.org @@ -2,6 +2,8 @@ #+DATE: <2025-08-08 Fri> #+OPTIONS: toc:nil num:nil #+FILETAGS: :emacs:review: +#+COMMENTS: t +#+SLUG: what-i-want-to-do-with-emacs I first started using Emacs (and Linux for that matter) since around the end of 2023, and it's been well over a year of trial and error with this text editor. At first I was using this for all sorts of things such as: @@ -17,7 +19,7 @@ One thing I realised is just how powerful Emacs really is, just recently I found After some thinking I narrowed it down and collated it in the following diagram: -[[../../../assets/2025-08-08-emacs.svg]] +[[../../../assets/sketchnotes/2025-08-08-emacs.svg]] Of course this is the current workflow, where the text in red are items I /currently/ use Emacs for (high percentage likelihood this will increase), and the items in blue text are ones I plan on adding to my workflow. The main thing I want from this is to ensure consistency in daily practices. diff --git a/blogs/2025/08/what-do-i-want-to-do-with-emacs.org b/blogs/2025/08-august/what-do-i-want-to-do-with-emacs.org~ similarity index 100% rename from blogs/2025/08/what-do-i-want-to-do-with-emacs.org rename to blogs/2025/08-august/what-do-i-want-to-do-with-emacs.org~ diff --git a/blogs/2025/08/zettelkasten.org~ b/blogs/2025/08-august/zettelkasten.org similarity index 84% rename from blogs/2025/08/zettelkasten.org~ rename to blogs/2025/08-august/zettelkasten.org index be898fa..5564e4d 100644 --- a/blogs/2025/08/zettelkasten.org~ +++ b/blogs/2025/08-august/zettelkasten.org @@ -2,6 +2,8 @@ #+DATE: <2025-08-07 Thu> #+OPTIONS: toc:nil num:nil #+FILETAGS: :education: +#+COMMENTS: t +#+SLUG: zettlekasten * The Zettelkasten Method @@ -9,9 +11,10 @@ This Zettelkasten method is growing in popularity for taking notes. Ever felt as The [[https://zettelkasten.de/introduction/][official website]] describes in detail exactly what the Zettelkasten method is, providing use cases and benefits to this method. The part I wanted to highlighted in this is the following paragraph, where the author depicts how this concept would be explained to someone unfamiliar with it: -#+BEGIN_QUOTE +{{{epigraph_single(A Zettelkasten is a personal tool for thinking and writing. It has hypertextual features to make a web of thoughts possible. The difference to other systems is that you create a web of thoughts instead of notes of arbitrary size and form\, and emphasize connection\, not a collection. +)}}} A Zettelkasten is a personal tool for thinking and writing. It has hypertextual features to make a web of thoughts possible. The difference to other systems is that you create a web of thoughts instead of notes of arbitrary size and form, and emphasize connection, not a collection. -#+END_QUOTE + This web of thoughts is exactly what makes it useful, it takes on the seemingly cluttered /mess of information/ and is able to link the nodes together to create something meaningful and useful. diff --git a/blogs/2025/08/zettelkasten.org b/blogs/2025/08-august/zettelkasten.org~ similarity index 100% rename from blogs/2025/08/zettelkasten.org rename to blogs/2025/08-august/zettelkasten.org~ diff --git a/blogs/2025/08/wacom-with-arch.org~ b/blogs/2025/08/wacom-with-arch.org~ deleted file mode 100644 index a6b33d1..0000000 --- a/blogs/2025/08/wacom-with-arch.org~ +++ /dev/null @@ -1,6 +0,0 @@ -#+TITLE: Wacom With Arch -#+DATE: <2025-08-08 Fri> -#+OPTIONS: toc:nil num:nil title:nil -#+FILETAGS: :insights: - -* Wacom With Arch diff --git a/blogs/2025/11-november/02-11-week-review.org b/blogs/2025/11-november/02-11-week-review.org new file mode 100644 index 0000000..d7d213d --- /dev/null +++ b/blogs/2025/11-november/02-11-week-review.org @@ -0,0 +1,22 @@ +#+TITLE: [02-11-2025] - Weekly Review +#+OPTIONS: num:nil +#+DATE: <2025-11-02 Sun> +#+filetags: :review: +#+COMMENTS: t +#+SLUG: 02-11-25-week-review + +[Was a mid week file, then moved it to a review file] + +* Mid Week Review +- Mostly work related updates. Working from home on monday 10th (for trial, so we get used to it and connecting to the VPN) +- Subsequently we will be able to work from home on mondays and fridays for the following weeks (beginning either 17th or 24th?) +- Still working on powerbi (not a fan) +- Upskilling needs to take more this week and the next +- Probation meeting on thursday next week [06/11] +- It's getting darker earlier and more windy + +* TODOS +** DONE +Write the retrospective notes +** DONE +More competencies diff --git a/blogs/2025/11-november/02-11-week-review.org~ b/blogs/2025/11-november/02-11-week-review.org~ new file mode 100644 index 0000000..24fc7f2 --- /dev/null +++ b/blogs/2025/11-november/02-11-week-review.org~ @@ -0,0 +1,20 @@ +#+TITLE: [02-11-2025] - Weekly Review +#+OPTIONS: num:nil +#+DATE: <2025-11-02 Sun> +#+filetags: :review: + +[Was a mid week file, then moved it to a review file] + +* Mid Week Review +- Mostly work related updates. Working from home on monday 10th (for trial, so we get used to it and connecting to the VPN) +- Subsequently we will be able to work from home on mondays and fridays for the following weeks (beginning either 17th or 24th?) +- Still working on powerbi (not a fan) +- Upskilling needs to take more this week and the next +- Probation meeting on thursday next week [06/11] +- It's getting darker earlier and more windy + +* TODOS +** DONE +Write the retrospective notes +** DONE +More competencies diff --git a/blogs/2025/11-november/09-11-week-review.org b/blogs/2025/11-november/09-11-week-review.org new file mode 100644 index 0000000..7508141 --- /dev/null +++ b/blogs/2025/11-november/09-11-week-review.org @@ -0,0 +1,44 @@ +#+TITLE: [09-11-2025] - Weekly Review +#+OPTIONS: num:nil +#+DATE:<2025-11-09 Sun 20:08> +#+filetags: :review: +#+COMMENTS: t +#+SLUG: 09-11-25-week-review + +* Weekly review + +- Week 5 work finished! +- Working from home on monday 10th. +- Maybe do weekly work retrospectives? Using the start stop continue model. +- Probation meeting went quite well Alhamdulillah. Keep doing what you're doing was the message. +- Should start to record successes, either here or on post-it notes. Helps with imposter syndrome. +- Led a meeting for the first time (07/11). Was with Lloyd and Oli from team monopoly (SecDevOps). They understood the problem statement clearly and everything was explained properly to them. +- Bowling Kata was reduced to 29 minutes. + +Some thoughts about the nginx reverse proxying architecture. This week I transferred all the docker services to a subdomain. + +[[../../../assets/sketchnotes/09-11-2025-nginx-architecture.svg]] + +Weekly retrospective using the start stop continue model: + +[[../../../assets/sketchnotes/09-11-2025-retrospectives.svg]] + +Here is the current state of the competencies (20.5%): + +[[../../../assets/images/competencies/competencies-09-11-25.png]] + +During the meeting, we were told to achieve 60% by the end of January. + +And here is the dev plan: + +[[../../../assets/images/competencies/dev-plan-09-11-25.png]] + + +* Next week: + +- Competencies to do: + - Aware of the Benefits of Normalisation + - Aware of PowerShell, Python, Bash and Pester + - Aware of Lean, Agile & extreme programming Principles + - Understands Server Operating Systems (might be tough) + - Understanding of TCP/IP diff --git a/blogs/2025/11-november/09-11-week-review.org~ b/blogs/2025/11-november/09-11-week-review.org~ new file mode 100644 index 0000000..30bad39 --- /dev/null +++ b/blogs/2025/11-november/09-11-week-review.org~ @@ -0,0 +1,42 @@ +#+TITLE: [09-11-2025] - Weekly Review +#+OPTIONS: num:nil +#+DATE:<2025-11-09 Sun 20:08> +#+filetags: :review: + +* Weekly review + +- Week 5 work finished! +- Working from home on monday 10th. +- Maybe do weekly work retrospectives? Using the start stop continue model. +- Probation meeting went quite well Alhamdulillah. Keep doing what you're doing was the message. +- Should start to record successes, either here or on post-it notes. Helps with imposter syndrome. +- Led a meeting for the first time (07/11). Was with Lloyd and Oli from team monopoly (SecDevOps). They understood the problem statement clearly and everything was explained properly to them. +- Bowling Kata was reduced to 29 minutes. + +Some thoughts about the nginx reverse proxying architecture. This week I transferred all the docker services to a subdomain. + +[[../../../assets/sketchnotes/09-11-2025-nginx-architecture.svg]] + +Weekly retrospective using the start stop continue model: + +[[../../../assets/sketchnotes/09-11-2025-retrospectives.svg]] + +Here is the current state of the competencies (20.5%): + +[[../../../assets/images/competencies/competencies-09-11-25.png]] + +During the meeting, we were told to achieve 60% by the end of January. + +And here is the dev plan: + +[[../../../assets/images/competencies/dev-plan-09-11-25.png]] + + +* Next week: + +- Competencies to do: + - Aware of the Benefits of Normalisation + - Aware of PowerShell, Python, Bash and Pester + - Aware of Lean, Agile & extreme programming Principles + - Understands Server Operating Systems (might be tough) + - Understanding of TCP/IP diff --git a/blogs/2025/11-november/16-11-week-review.org b/blogs/2025/11-november/16-11-week-review.org new file mode 100644 index 0000000..3495bee --- /dev/null +++ b/blogs/2025/11-november/16-11-week-review.org @@ -0,0 +1,90 @@ +#+TITLE: [16-11-2025] - Weekly Review +#+OPTIONS: num:nil +#+DATE:<2025-11-10 Mon 17:44> +#+filetags: :review: +#+WIP: +#+COMMENTS: t +#+SLUG: 16-11-25-week-review + + +* Previous Week List: + +- Competencies to do: + - +Aware of the Benefits of Normalisation+ + - Aware of PowerShell, Python, Bash and Pester + - +Aware of Lean, Agile & extreme programming Principles+ + - Understands Server Operating Systems (might be tough) + - Understanding of TCP/IP + - +Understands responsive design+ + - +Understands Network design+ + +* Weekly review: + +- <2025-11-10 Mon>: + - Got assigned to create a database that can help the whole company. Value added work! The database will have system names linked to the team names. In other words, which teams are working under which systems? Also it will have the repo names and which teams own which repos. +- <2025-11-11 Tue> + - Worked on getting the DB for the system-team-repo. Met with Jez and he said to get the IPT names and use them instead. [reminder to attatch the links for this]. + - Created a powershell script to load all the data into the SQL server (local) +- <2025-11-12 Wed> + - Met with the whole team monopoly (+ others). -> First time being in a group huddle + - Was interesting to see how [[https://www.yworks.com/][yworks]] is used +- <2025-11-13 Thu> + - Angular training. Was taught about the new Angular 19 changes and how to use them. Things like the the ~@~ decorator was discussed. +- <2025-11-14 Fri> + - Training on datamarts and airflow. Learnt a lot about how they can be used. I wrote a few slack messages explaining to the team what it is, and also got clarification from Filipo. Also sent a request to create a new repository for this project. + +Was able to complete 4/5 competencies this week. The reason for not completing them is that Friday was used mainly for meetings and other admin tasks. In hindsight, I /could/ have completed them, as there were times when I chose to not do value added work. + +[[../../../assets/sketchnotes/16-11-2025-retrospectives.svg]] + +Daily retrospectives: + +[[../../../assets/images/reviews/16-11-25-daily-retros.jpg]] + +One thing that really helped this week is ~guac~. I was able to use this whilst at work, very handy when wanting to refer to some notes/files that is only located at home. + +[[../../../assets/images/reviews/15-11-2025-guac.png]] + +* Other +** Airflow/Datamart +[[../../../posts/career/airflow.org][Airflow Post]] + +*** Diagram +*A work colleague has written the following and made this diagram* + +[[../../../assets/images/diagrams/airflow.png]] + +1. AZDO repo for our datamart to go in. Clone the data mart example template +2. Datamart, can use one big one that has different tables for everything that we need. +3. Local airflow that calls api and gets data. continuation tokens can be used. there is a good example +4. Upload everything to microlise servers where it is run automatically +5. Power bi, change data sources to be references to our sql. Can have all tables in power bi. Use filters + +*** Summary that I wrote in slack: + +- https://azdo.microlise.com/MicroliseCollection/Microlise/_git/DataMart.SQLServer: very useful link explaining what datamarts are, ELT (extract, load, transform) orchestration and how to set this up + +- https://azdo.microlise.com/MicroliseCollection/Microlise/_git/DataMart.DataEngineering.InsightReadiness: an example of what a datamart repo looks like. in the template repo, the two important files are tsqlt and DataMartTemplate (in this instance its called InsightReadiness ) + +- tsqlt coverage should be above 60% + +- https://microliseuk.sharepoint.com/sites/msteams_bb7636: This link is the CoP (community of practice) for the airflow. You'll find useful information regarding what apache airflow is and how to set things up. + +- https://microliseuk.sharepoint.com/sites/msteams_bb7636/SitePages/Getting-started-with-Airflow.aspx: This link tells you how to set airflow up locally. Once you're done, this will be published to an airflow scheduler platform. Keep in mind that when you want to publish this, document that it succeeds in the PR. + +- Just a summary from Filippo: + - A DataMart it's simply a database that is made ad-hoc to contain a certain subset of data (Specific tables with specific columns). + - Once that is created Airflow is used to put data in that database. So it extract data from other sources (APIS, databases) and loads it into the datamart we have created. + - Once you get Airflow locally working and a new Datamart (deployed locally) you can then write a dag to load data into the Mart. + +- http://airmastqa01.mms.local:8080/home: this link shows all the DAGs that are currently running in a testing environment, and when they are scheduled. Login using MSUP (edited) + +- http://airmastprd01.mms.local:8080/home: This link is the prod version + +- https://azdo.microlise.com/MicroliseCollection/Microlise/_git/DevDocs?path=/Airflow/HowTo/PowerBiReportRefresh.md&version=GBmain&_a=preview: Might prove to be useful when we do the powerbi refresh thing... + +* Next week: + + - Understands Server Operating Systems (might be tough) + - Understanding of TCP/IP + - Aware of PowerShell, Python, Bash and Pester diff --git a/blogs/2025/11-november/16-11-week-review.org~ b/blogs/2025/11-november/16-11-week-review.org~ new file mode 100644 index 0000000..fe0de5b --- /dev/null +++ b/blogs/2025/11-november/16-11-week-review.org~ @@ -0,0 +1,87 @@ +#+TITLE: [16-11-2025] - Weekly Review +#+OPTIONS: num:nil +#+DATE:<2025-11-10 Mon 17:44> +#+filetags: :review: +#+WIP: + +* Previous Week List: + +- Competencies to do: + - +Aware of the Benefits of Normalisation+ + - Aware of PowerShell, Python, Bash and Pester + - +Aware of Lean, Agile & extreme programming Principles+ + - Understands Server Operating Systems (might be tough) + - Understanding of TCP/IP + - +Understands responsive design+ + - +Understands Network design+ + +* Weekly review: + +- <2025-11-10 Mon>: + - Got assigned to create a database that can help the whole company. Value added work! The database will have system names linked to the team names. In other words, which teams are working under which systems? Also it will have the repo names and which teams own which repos. +- <2025-11-11 Tue> + - Worked on getting the DB for the system-team-repo. Met with Jez and he said to get the IPT names and use them instead. [reminder to attatch the links for this]. + - Created a powershell script to load all the data into the SQL server (local) +- <2025-11-12 Wed> + - Met with the whole team monopoly (+ others). -> First time being in a group huddle + - Was interesting to see how [[https://www.yworks.com/][yworks]] is used +- <2025-11-13 Thu> + - Angular training. Was taught about the new Angular 19 changes and how to use them. Things like the the ~@~ decorator was discussed. +- <2025-11-14 Fri> + - Training on datamarts and airflow. Learnt a lot about how they can be used. I wrote a few slack messages explaining to the team what it is, and also got clarification from Filipo. Also sent a request to create a new repository for this project. + +Was able to complete 4/5 competencies this week. The reason for not completing them is that Friday was used mainly for meetings and other admin tasks. In hindsight, I /could/ have completed them, as there were times when I chose to not do value added work. + +[[../../../assets/sketchnotes/16-11-2025-retrospectives.svg]] + +Daily retrospectives: + +[[../../../assets/images/reviews/16-11-25-daily-retros.jpg]] + +One thing that really helped this week is ~guac~. I was able to use this whilst at work, very handy when wanting to refer to some notes/files that is only located at home. + +[[../../../assets/images/reviews/15-11-2025-guac.png]] + +* Other +** Airflow/Datamart +[[../../../posts/career/airflow.org][Airflow Post]] + +*** Diagram +*A work colleague has written the following and made this diagram* + +[[../../../assets/images/diagrams/airflow.png]] + +1. AZDO repo for our datamart to go in. Clone the data mart example template +2. Datamart, can use one big one that has different tables for everything that we need. +3. Local airflow that calls api and gets data. continuation tokens can be used. there is a good example +4. Upload everything to microlise servers where it is run automatically +5. Power bi, change data sources to be references to our sql. Can have all tables in power bi. Use filters + +*** Summary that I wrote in slack: + +- https://azdo.microlise.com/MicroliseCollection/Microlise/_git/DataMart.SQLServer: very useful link explaining what datamarts are, ELT (extract, load, transform) orchestration and how to set this up + +- https://azdo.microlise.com/MicroliseCollection/Microlise/_git/DataMart.DataEngineering.InsightReadiness: an example of what a datamart repo looks like. in the template repo, the two important files are tsqlt and DataMartTemplate (in this instance its called InsightReadiness ) + +- tsqlt coverage should be above 60% + +- https://microliseuk.sharepoint.com/sites/msteams_bb7636: This link is the CoP (community of practice) for the airflow. You'll find useful information regarding what apache airflow is and how to set things up. + +- https://microliseuk.sharepoint.com/sites/msteams_bb7636/SitePages/Getting-started-with-Airflow.aspx: This link tells you how to set airflow up locally. Once you're done, this will be published to an airflow scheduler platform. Keep in mind that when you want to publish this, document that it succeeds in the PR. + +- Just a summary from Filippo: + - A DataMart it's simply a database that is made ad-hoc to contain a certain subset of data (Specific tables with specific columns). + - Once that is created Airflow is used to put data in that database. So it extract data from other sources (APIS, databases) and loads it into the datamart we have created. + - Once you get Airflow locally working and a new Datamart (deployed locally) you can then write a dag to load data into the Mart. + +- http://airmastqa01.mms.local:8080/home: this link shows all the DAGs that are currently running in a testing environment, and when they are scheduled. Login using MSUP (edited) + +- http://airmastprd01.mms.local:8080/home: This link is the prod version + +- https://azdo.microlise.com/MicroliseCollection/Microlise/_git/DevDocs?path=/Airflow/HowTo/PowerBiReportRefresh.md&version=GBmain&_a=preview: Might prove to be useful when we do the powerbi refresh thing... + +* Next week: + + - Understands Server Operating Systems (might be tough) + - Understanding of TCP/IP + - Aware of PowerShell, Python, Bash and Pester diff --git a/blogs/2025/11-november/23-11-week-review.org b/blogs/2025/11-november/23-11-week-review.org new file mode 100644 index 0000000..57b5c37 --- /dev/null +++ b/blogs/2025/11-november/23-11-week-review.org @@ -0,0 +1,78 @@ +#+TITLE: [23-11-2025] - Weekly Review +#+OPTIONS: num:nil +#+DATE: <2025-11-17 Mon 18:05> +#+filetags: :review: +#+WIP: +#+COMMENTS: t +#+SLUG: 23-11-25-week-review + +* Previous Week List: + +- Competencies to do: + - +Aware of PowerShell, Python, Bash and Pester+ + - Understands Server Operating Systems (might be tough) + - Understanding of TCP/IP + + There wasn't much time to do competencies this week because the new sprint started. We were told that this project should ideally be finished within the next two weeks so that we can start working on some other things (Angular related; Kevin did say that it would be related to creating a system that shows who makes the commits, how often and the relationship between committers). + +* Weekly Review +- <2025-11-17 Mon>: + - Created a new repository for the team using the #help-azdo channel. + - Set up this repo by renaming references etc. + - Played around with datamarts, got the team and system tables working, although haven't yet written the ~tSQLt~ tests for them yet. +- <2025-11-18 Tue> + - Demo-ed the STR (system team repo) database. + - Started to create the tables for the Mean time to restore and lead time for change. +- <2025-11-19 Wed> + - Finished off creating the data tables for MTTR and LTTC +- <2025-11-20 Thu> + - Started creating Airflows for the repositories table +- <2025-11-21 Fri> + - Data science training, Research Operations, Kafka, Airflow +** Daily retrospectives: + +[[../../../assets/images/reviews/23-11-25-daily-retros.jpg]] + +** Weekly retrospectives + +[[../../../assets/sketchnotes/23-11-2025-retrospectives.svg]] + +* Other + +** Books + +As I finished another book and also spent some time adding more services, I wanted to collate a list of books (of a wide genre) to read. One I came across is [[https://theinvisiblementor.com/ikigai-the-japanese-secret-to-a-long-and-happy-life-summary/][Ikigai]]. + +** reader.zainezq.com +Something interesting I found when I wanted to take notes on the book called /The Toyota Way/; this is a service that lets you read books as well as convert a ~pdf~ file to ~epub~. + +[[../../../assets/images/reviews/23-11-2025-reader.png]] + +** booksvnc.zainezq.com +In order to add books, edit metadata and start the server for the ~reader.zainezq.com~ + +[[../../../assets/images/reviews/23-11-2025-vnc.png]] + +** netdata.zainezq.com + +One thing I wanted to have information for is: + +#+BEGIN_SRC bash + + uname -a + hostnamectl + cat /etc/os-release + lscpu + free -h + lsblk -f + df -h + ip a + +#+END_SRC + +[[../../../assets/images/reviews/23-11-2025-netdata.png]] + +* Next Week + + - Understands Server Operating Systems (might be tough) + - Understanding of TCP/IP diff --git a/blogs/2025/11-november/23-11-week-review.org~ b/blogs/2025/11-november/23-11-week-review.org~ new file mode 100644 index 0000000..eea55c2 --- /dev/null +++ b/blogs/2025/11-november/23-11-week-review.org~ @@ -0,0 +1,76 @@ +#+TITLE: [23-11-2025] - Weekly Review +#+OPTIONS: num:nil +#+DATE: <2025-11-17 Mon 18:05> +#+filetags: :review: +#+WIP: + +* Previous Week List: + +- Competencies to do: + - +Aware of PowerShell, Python, Bash and Pester+ + - Understands Server Operating Systems (might be tough) + - Understanding of TCP/IP + + There wasn't much time to do competencies this week because the new sprint started. We were told that this project should ideally be finished within the next two weeks so that we can start working on some other things (Angular related; Kevin did say that it would be related to creating a system that shows who makes the commits, how often and the relationship between committers). + +* Weekly Review +- <2025-11-17 Mon>: + - Created a new repository for the team using the #help-azdo channel. + - Set up this repo by renaming references etc. + - Played around with datamarts, got the team and system tables working, although haven't yet written the ~tSQLt~ tests for them yet. +- <2025-11-18 Tue> + - Demo-ed the STR (system team repo) database. + - Started to create the tables for the Mean time to restore and lead time for change. +- <2025-11-19 Wed> + - Finished off creating the data tables for MTTR and LTTC +- <2025-11-20 Thu> + - Started creating Airflows for the repositories table +- <2025-11-21 Fri> + - Data science training, Research Operations, Kafka, Airflow +** Daily retrospectives: + +[[../../../assets/images/reviews/23-11-25-daily-retros.jpg]] + +** Weekly retrospectives + +[[../../../assets/sketchnotes/23-11-2025-retrospectives.svg]] + +* Other + +** Books + +As I finished another book and also spent some time adding more services, I wanted to collate a list of books (of a wide genre) to read. One I came across is [[https://theinvisiblementor.com/ikigai-the-japanese-secret-to-a-long-and-happy-life-summary/][Ikigai]]. + +** reader.zainezq.com +Something interesting I found when I wanted to take notes on the book called /The Toyota Way/; this is a service that lets you read books as well as convert a ~pdf~ file to ~epub~. + +[[../../../assets/images/reviews/23-11-2025-reader.png]] + +** booksvnc.zainezq.com +In order to add books, edit metadata and start the server for the ~reader.zainezq.com~ + +[[../../../assets/images/reviews/23-11-2025-vnc.png]] + +** netdata.zainezq.com + +One thing I wanted to have information for is: + +#+BEGIN_SRC bash + + uname -a + hostnamectl + cat /etc/os-release + lscpu + free -h + lsblk -f + df -h + ip a + +#+END_SRC + +[[../../../assets/images/reviews/23-11-2025-netdata.png]] + +* Next Week + + - Understands Server Operating Systems (might be tough) + - Understanding of TCP/IP diff --git a/blogs/2025/11-november/30-11-week-review.org b/blogs/2025/11-november/30-11-week-review.org new file mode 100644 index 0000000..00e7af9 --- /dev/null +++ b/blogs/2025/11-november/30-11-week-review.org @@ -0,0 +1,167 @@ +#+TITLE: [30-11-2025] - Weekly Review +#+OPTIONS: num:nil +#+DATE: <2025-11-30 Sun 17:09> +#+filetags: :review: +#+WIP: +#+COMMENTS: t +#+SLUG: 30-11-25-week-review + +* Previous Week List: + +- Competencies to do: + - +Aware of relational databases+ + - +Aware of TLS and SSL+ + - +Aware of good error handling and logging+ + - Understanding of TCP/IP + - Understands Server Operating Systems (might be tough) + + Been extremely busy with this sprint, getting the work delivered in a timely manner; there wasn't as much time to focus on competencies. + +* Weekly Review +- <2025-11-24 Mon>: + - Got the news the company is reducing staff. See this [[https://www.thebusinessdesk.com/eastmidlands/news/2104597-microlise-issues-profit-warning-after-weaker-than-expected-trading][article]] + - Got the DAG for PR's to work +- <2025-11-25 Tue> + - Worked mainly on threads and commits DAGs +- <2025-11-26 Wed> + - We had a meeting with Kevin on the scoring system for the redundancy thing + - Finished creating and testing all the DAG's +- <2025-11-27 Thu> + - Refinement meeting + - Started creating base case tests for SQL + - Made a script to automate the creation of testing files +- <2025-11-28 Fri> + - Finished base case tests + - Training with Lloyd on SRE and Techops +** Daily retrospectives: + +[[../../../assets/images/reviews/30-11-25-daily-retros.jpg]] + +** Weekly retrospectives + +[[../../../assets/sketchnotes/30-11-2025-retrospectives.svg]] + +* Other + +** Powershell script: + +Here is the powershell script that generates the files for testing: + +#+BEGIN_SRC ps1 + +# Define the base folders +$securityFolder = "C:\Users\zqayyum\Source\Repos\DataMart.Academy.DoraMetrics\tsqlt\Security" +$scriptsFolder = "C:\Users\zqayyum\Source\Repos\DataMart.Academy.DoraMetrics\tsqlt" + + +# Create base folders if they don't exist +if (!(Test-Path $securityFolder)) { New-Item -ItemType Directory -Path $securityFolder } +if (!(Test-Path $scriptsFolder)) { New-Item -ItemType Directory -Path $scriptsFolder } + +# Define main file names (schemas) +$fileNames = @( + "testMergeStagingDataCommits.sql", + "testMergeStagingDataEnvironments.sql", + "testMergeStagingDataPullRequests.sql", + "testMergeStagingDataReleases.sql", + "testMergeStagingDataRepositories.sql", + "testMergeStagingDataThreads.sql" +) + +# Define stored procedure test files +$storedProcFiles = @( + "Test changed rows are logged.sql", + "Test changed rows are not logged as inserted.sql", + "Test changed rows are updated.sql", + "Test errors are logged.sql", + "Test import date on changed rows is updated.sql", + "Test new rows are inserted.sql", + "Test new rows are not logged as updated.sql" +) + +foreach ($name in $fileNames) { + # Create .sql file under Security + $filePath = Join-Path $securityFolder $name + $schemaName = [System.IO.Path]::GetFileNameWithoutExtension($name) + + # Content for schema file + $schemaContent = @" +CREATE SCHEMA [$schemaName] + AUTHORIZATION [dbo]; + +GO +EXECUTE sp_addextendedproperty + @name = N'tSQLt.TestClass', + @value = 1, + @level0type = N'SCHEMA', + @level0name = N'$schemaName'; +GO +"@ + + Set-Content -Path $filePath -Value $schemaContent + + # Create schema folder under Scripts + $schemaFolderPath = Join-Path $scriptsFolder $schemaName + if (!(Test-Path $schemaFolderPath)) { + New-Item -ItemType Directory -Path $schemaFolderPath | Out-Null + } + + # Create Stored Procedures subfolder + $storedProcFolderPath = Join-Path $schemaFolderPath "Stored Procedures" + if (!(Test-Path $storedProcFolderPath)) { + New-Item -ItemType Directory -Path $storedProcFolderPath | Out-Null + } + + # Create stored procedure files with dynamic content + foreach ($procFile in $storedProcFiles) { + $procFilePath = Join-Path $storedProcFolderPath $procFile + $procName = [System.IO.Path]::GetFileNameWithoutExtension($procFile) + + $procContent = @" +CREATE PROCEDURE $schemaName.[$procName] +AS +BEGIN + SET NOCOUNT ON; + + -- Assemble + DECLARE @RowsUpdated INT; + + EXEC tsqlt.FakeTable @SchemaName = 'staging', @TableName = 'tbl_TemplateTable'; + EXEC tsqlt.FakeTable @SchemaName = 'data', @TableName = 'tbl_TemplateTable'; + EXEC tsqlt.FakeTable @SchemaName = 'logging', @TableName = 'tbl_ExecutionCounts'; + + INSERT INTO data.tbl_TemplateTable(UnitDescription, TrunionDisplacementInMM) + VALUES('UT7000', '9'); + + INSERT INTO staging.tbl_TemplateTable(UnitDescription, TrunionDisplacementInMM) + VALUES('UT7000', '6'); + + -- Act + EXEC staging.pr_MergeStagingData; + + -- Assert + SELECT @RowsUpdated = RowsUpdated FROM logging.tbl_ExecutionCounts; + + EXEC tsqlt.AssertEquals + @Actual = @RowsUpdated, + @Expected = 1; +END +"@ + + Set-Content -Path $procFilePath -Value $procContent + } +} + +Write-Host "All files and folders created successfully with dynamic procedure content" + +#+END_SRC + +** Finances +I decided to spin up a docker container to manage finances. For the moment there is no way to connect to a Bank without using an intermediary (privacy concerns). + +[[../../../assets/images/reviews/30-11-2025-acutal.png]] + +* Next Week + + - Understands Server Operating Systems (might be tough) + - Understanding of TCP/IP diff --git a/blogs/2025/11-november/30-11-week-review.org~ b/blogs/2025/11-november/30-11-week-review.org~ new file mode 100644 index 0000000..f2a8748 --- /dev/null +++ b/blogs/2025/11-november/30-11-week-review.org~ @@ -0,0 +1,165 @@ +#+TITLE: [30-11-2025] - Weekly Review +#+OPTIONS: num:nil +#+DATE: <2025-11-30 Sun 17:09> +#+filetags: :review: +#+WIP: + +* Previous Week List: + +- Competencies to do: + - +Aware of relational databases+ + - +Aware of TLS and SSL+ + - +Aware of good error handling and logging+ + - Understanding of TCP/IP + - Understands Server Operating Systems (might be tough) + + Been extremely busy with this sprint, getting the work delivered in a timely manner; there wasn't as much time to focus on competencies. + +* Weekly Review +- <2025-11-24 Mon>: + - Got the news the company is reducing staff. See this [[https://www.thebusinessdesk.com/eastmidlands/news/2104597-microlise-issues-profit-warning-after-weaker-than-expected-trading][article]] + - Got the DAG for PR's to work +- <2025-11-25 Tue> + - Worked mainly on threads and commits DAGs +- <2025-11-26 Wed> + - We had a meeting with Kevin on the scoring system for the redundancy thing + - Finished creating and testing all the DAG's +- <2025-11-27 Thu> + - Refinement meeting + - Started creating base case tests for SQL + - Made a script to automate the creation of testing files +- <2025-11-28 Fri> + - Finished base case tests + - Training with Lloyd on SRE and Techops +** Daily retrospectives: + +[[../../../assets/images/reviews/30-11-25-daily-retros.jpg]] + +** Weekly retrospectives + +[[../../../assets/sketchnotes/30-11-2025-retrospectives.svg]] + +* Other + +** Powershell script: + +Here is the powershell script that generates the files for testing: + +#+BEGIN_SRC ps1 + +# Define the base folders +$securityFolder = "C:\Users\zqayyum\Source\Repos\DataMart.Academy.DoraMetrics\tsqlt\Security" +$scriptsFolder = "C:\Users\zqayyum\Source\Repos\DataMart.Academy.DoraMetrics\tsqlt" + + +# Create base folders if they don't exist +if (!(Test-Path $securityFolder)) { New-Item -ItemType Directory -Path $securityFolder } +if (!(Test-Path $scriptsFolder)) { New-Item -ItemType Directory -Path $scriptsFolder } + +# Define main file names (schemas) +$fileNames = @( + "testMergeStagingDataCommits.sql", + "testMergeStagingDataEnvironments.sql", + "testMergeStagingDataPullRequests.sql", + "testMergeStagingDataReleases.sql", + "testMergeStagingDataRepositories.sql", + "testMergeStagingDataThreads.sql" +) + +# Define stored procedure test files +$storedProcFiles = @( + "Test changed rows are logged.sql", + "Test changed rows are not logged as inserted.sql", + "Test changed rows are updated.sql", + "Test errors are logged.sql", + "Test import date on changed rows is updated.sql", + "Test new rows are inserted.sql", + "Test new rows are not logged as updated.sql" +) + +foreach ($name in $fileNames) { + # Create .sql file under Security + $filePath = Join-Path $securityFolder $name + $schemaName = [System.IO.Path]::GetFileNameWithoutExtension($name) + + # Content for schema file + $schemaContent = @" +CREATE SCHEMA [$schemaName] + AUTHORIZATION [dbo]; + +GO +EXECUTE sp_addextendedproperty + @name = N'tSQLt.TestClass', + @value = 1, + @level0type = N'SCHEMA', + @level0name = N'$schemaName'; +GO +"@ + + Set-Content -Path $filePath -Value $schemaContent + + # Create schema folder under Scripts + $schemaFolderPath = Join-Path $scriptsFolder $schemaName + if (!(Test-Path $schemaFolderPath)) { + New-Item -ItemType Directory -Path $schemaFolderPath | Out-Null + } + + # Create Stored Procedures subfolder + $storedProcFolderPath = Join-Path $schemaFolderPath "Stored Procedures" + if (!(Test-Path $storedProcFolderPath)) { + New-Item -ItemType Directory -Path $storedProcFolderPath | Out-Null + } + + # Create stored procedure files with dynamic content + foreach ($procFile in $storedProcFiles) { + $procFilePath = Join-Path $storedProcFolderPath $procFile + $procName = [System.IO.Path]::GetFileNameWithoutExtension($procFile) + + $procContent = @" +CREATE PROCEDURE $schemaName.[$procName] +AS +BEGIN + SET NOCOUNT ON; + + -- Assemble + DECLARE @RowsUpdated INT; + + EXEC tsqlt.FakeTable @SchemaName = 'staging', @TableName = 'tbl_TemplateTable'; + EXEC tsqlt.FakeTable @SchemaName = 'data', @TableName = 'tbl_TemplateTable'; + EXEC tsqlt.FakeTable @SchemaName = 'logging', @TableName = 'tbl_ExecutionCounts'; + + INSERT INTO data.tbl_TemplateTable(UnitDescription, TrunionDisplacementInMM) + VALUES('UT7000', '9'); + + INSERT INTO staging.tbl_TemplateTable(UnitDescription, TrunionDisplacementInMM) + VALUES('UT7000', '6'); + + -- Act + EXEC staging.pr_MergeStagingData; + + -- Assert + SELECT @RowsUpdated = RowsUpdated FROM logging.tbl_ExecutionCounts; + + EXEC tsqlt.AssertEquals + @Actual = @RowsUpdated, + @Expected = 1; +END +"@ + + Set-Content -Path $procFilePath -Value $procContent + } +} + +Write-Host "All files and folders created successfully with dynamic procedure content" + +#+END_SRC + +** Finances +I decided to spin up a docker container to manage finances. For the moment there is no way to connect to a Bank without using an intermediary (privacy concerns). + +[[../../../assets/images/reviews/30-11-2025-acutal.png]] + +* Next Week + + - Understands Server Operating Systems (might be tough) + - Understanding of TCP/IP diff --git a/blogs/2025/12-december/07-12-week-review.org b/blogs/2025/12-december/07-12-week-review.org new file mode 100644 index 0000000..8849422 --- /dev/null +++ b/blogs/2025/12-december/07-12-week-review.org @@ -0,0 +1,66 @@ +#+TITLE: [07-12-2025] - Weekly Review +#+OPTIONS: num:nil +#+DATE: <2025-12-07 Sun 20:34> +#+filetags: :review: +#+WIP: +#+COMMENTS: t +#+SLUG: 07-12-25-week-review + +* Previous Week List: + + - Understands Server Operating Systems (might be tough) + - Understanding of TCP/IP + + +* Weekly Review: + This week has been so busy with getting the airflow working and the datamarts tested. Our team suggested to use the Friday as a 'training day', I told myself I'd work until lunch, then use the afternoon to tick off some competencies and reading, however it took me all day to finish off the DAG's. + +- <2025-12-01 Mon>: + - I continued working on the Datamart testing, getting majority of the tables tested +- <2025-12-02 Tue> + - Started to work on the airflow testing + - Had our retros, demos and refinement sessions +- <2025-12-03 Wed> + - Created and finished off the suite testing for the airflows. Started to look into pipelines +- <2025-12-04 Thu> + - Fixing some airflow tests + - Started to get the STR (System Team Repo) merged into a datamart/airflow + - Made a pipeline with the help of Oliver Gamble (main and gated) +- <2025-12-05 Fri> + - Completed the airflow DAG for repositories + +** Daily retrospectives: + +[[../../../assets/images/reviews/07-12-25-daily-retros.jpg]] + +** Weekly retrospectives: + +[[../../../assets/sketchnotes/07-12-2025-retrospectives.svg]] + +* Other + +- In the Weekly List, I need to document things outside of competencies. The weekly review should not be limited to work. +- I added a new docker service for running a self hosted VPN (~wg-easy~). The link for this is [[https:wgeasy.zainezq.com][here]]. +- I used to use a reading tracker to see which books I'm reading and have read. I want to get this back in use. An entry looks like: + + #+BEGIN_SRC elisp + ,* Book Title + :PROPERTIES: + :Author: + :Status: + :Group: + :STARTED: + :FINISHED: + :Category: + :END: + + #+END_SRC + +- The current amount of time it takes to build this website can be found using the following command: ~time emacs -Q --script build-site.el~. And currently it outputs: + + #+BEGIN_SRC zsh + real 0m8.468s + user 0m7.316s + sys 0m1.068s + #+END_SRC +The old website used to take a full minute to build, so this isn't too bad (also considering the other website was much /smaller/ in comparison to this one). diff --git a/blogs/2025/12-december/07-12-week-review.org~ b/blogs/2025/12-december/07-12-week-review.org~ new file mode 100644 index 0000000..baf27b9 --- /dev/null +++ b/blogs/2025/12-december/07-12-week-review.org~ @@ -0,0 +1,64 @@ +#+TITLE: [07-12-2025] - Weekly Review +#+OPTIONS: num:nil +#+DATE: <2025-12-07 Sun 20:34> +#+filetags: :review: +#+WIP: + +* Previous Week List: + + - Understands Server Operating Systems (might be tough) + - Understanding of TCP/IP + + +* Weekly Review: + This week has been so busy with getting the airflow working and the datamarts tested. Our team suggested to use the Friday as a 'training day', I told myself I'd work until lunch, then use the afternoon to tick off some competencies and reading, however it took me all day to finish off the DAG's. + +- <2025-12-01 Mon>: + - I continued working on the Datamart testing, getting majority of the tables tested +- <2025-12-02 Tue> + - Started to work on the airflow testing + - Had our retros, demos and refinement sessions +- <2025-12-03 Wed> + - Created and finished off the suite testing for the airflows. Started to look into pipelines +- <2025-12-04 Thu> + - Fixing some airflow tests + - Started to get the STR (System Team Repo) merged into a datamart/airflow + - Made a pipeline with the help of Oliver Gamble (main and gated) +- <2025-12-05 Fri> + - Completed the airflow DAG for repositories + +** Daily retrospectives: + +[[../../../assets/images/reviews/07-12-25-daily-retros.jpg]] + +** Weekly retrospectives: + +[[../../../assets/sketchnotes/07-12-2025-retrospectives.svg]] + +* Other + +- In the Weekly List, I need to document things outside of competencies. The weekly review should not be limited to work. +- I added a new docker service for running a self hosted VPN (~wg-easy~). The link for this is [[https:wgeasy.zainezq.com][here]]. +- I used to use a reading tracker to see which books I'm reading and have read. I want to get this back in use. An entry looks like: + + #+BEGIN_SRC elisp + ,* Book Title + :PROPERTIES: + :Author: + :Status: + :Group: + :STARTED: + :FINISHED: + :Category: + :END: + + #+END_SRC + +- The current amount of time it takes to build this website can be found using the following command: ~time emacs -Q --script build-site.el~. And currently it outputs: + + #+BEGIN_SRC zsh + real 0m8.468s + user 0m7.316s + sys 0m1.068s + #+END_SRC +The old website used to take a full minute to build, so this isn't too bad (also considering the other website was much /smaller/ in comparison to this one). diff --git a/blogs/2025/12-december/14-12-week-review.org b/blogs/2025/12-december/14-12-week-review.org new file mode 100644 index 0000000..76f5285 --- /dev/null +++ b/blogs/2025/12-december/14-12-week-review.org @@ -0,0 +1,22 @@ +#+TITLE: [14-12-2025] - Weekly Review +#+OPTIONS: num:nil +#+DATE: <2025-12-09 Tue 12:12> +#+filetags: :review: +#+WIP: +#+COMMENTS: t +#+SLUG: 14-12-25-week-review + +* Week List: +- +Can Measure Computer Performance on Disk, CPU, RAM, Network+ +- +Tests SQL queries and tSQL code+ +- +Aware of Basic Infrastructure as Code Concepts+ +- +Aware of Distributed Systems+ +- +Can Measure Computer Performance on Disk, CPU, RAM, Network+ +- +Tests SQL queries and tSQL code+ +- +Aware of Archiving Procedures+ + +* Weekly Review: + +Not much to show this week, I've been really busy trying to finalise the project, although I reckon it may take another week. I did get to do a lot more competencies at the start of the week however. + +* Other diff --git a/blogs/2025/12-december/14-12-week-review.org~ b/blogs/2025/12-december/14-12-week-review.org~ new file mode 100644 index 0000000..0e9eedb --- /dev/null +++ b/blogs/2025/12-december/14-12-week-review.org~ @@ -0,0 +1,21 @@ +#+TITLE: [14-12-2025] - Weekly Review +#+OPTIONS: num:nil +#+DATE: <2025-12-09 Tue 12:12> +#+filetags: :review: +#+WIP: + + +* Week List: +- +Can Measure Computer Performance on Disk, CPU, RAM, Network+ +- +Tests SQL queries and tSQL code+ +- +Aware of Basic Infrastructure as Code Concepts+ +- +Aware of Distributed Systems+ +- +Can Measure Computer Performance on Disk, CPU, RAM, Network+ +- +Tests SQL queries and tSQL code+ +- +Aware of Archiving Procedures+ + +* Weekly Review: + +Not much to show this week, I've been really busy trying to finalise the project, although I reckon it may take another week. I did get to do a lot more competencies at the start of the week however. + +* Other diff --git a/blogs/2025/2025-list.org b/blogs/2025/2025-list.org new file mode 100644 index 0000000..596eda1 --- /dev/null +++ b/blogs/2025/2025-list.org @@ -0,0 +1,26 @@ +#+TITLE: 2025 List +#+OPTIONS: toc:nil num:nil + +See the categories: @@html:Categories@@ + +* 2025 + +** December 2025 +- [[file:12-december/14-12-week-review.org][[14-12-2025] - Weekly Review]] @@html:09-12-2025 12:12@@ @@html: @@ +- [[file:12-december/07-12-week-review.org][[07-12-2025] - Weekly Review]] @@html:07-12-2025 20:34@@ @@html: @@ + +** November 2025 +- [[file:11-november/30-11-week-review.org][[30-11-2025] - Weekly Review]] @@html:30-11-2025 17:09@@ @@html: @@ +- [[file:11-november/23-11-week-review.org][[23-11-2025] - Weekly Review]] @@html:17-11-2025 18:05@@ @@html: @@ +- [[file:11-november/16-11-week-review.org][[16-11-2025] - Weekly Review]] @@html:10-11-2025 17:44@@ @@html: @@ +- [[file:11-november/09-11-week-review.org][[09-11-2025] - Weekly Review]] @@html:09-11-2025 20:08@@ @@html: @@ +- [[file:11-november/02-11-week-review.org][[02-11-2025] - Weekly Review]] @@html:02-11-2025 00:00@@ @@html: @@ + +** August 2025 +- [[file:08-august/third-time.org][Third Time]] @@html:28-08-2025 17:08@@ @@html: @@ +- [[file:08-august/benefits-of-reading.org][Benefits of Reading]] @@html:14-08-2025 23:36@@ @@html: @@ @@html: @@ +- [[file:08-august/hilberts.hotel.org][Hilbert's Hotel]] @@html:11-08-2025 18:39@@ @@html: @@ @@html: @@ +- [[file:08-august/spending-the-whole-day-on-this-website.org][09-08-2025: Website Changes]] @@html:09-08-2025 00:00@@ @@html: @@ @@html: @@ +- [[file:08-august/what-do-i-want-to-do-with-emacs.org][What Do I Want To Do With Emacs]] @@html:08-08-2025 00:00@@ @@html: @@ @@html: @@ +- [[file:08-august/wacom-with-arch.org][Wacom With Arch]] @@html:08-08-2025 00:00@@ @@html: @@ +- [[file:08-august/zettelkasten.org][Zettelkasten Method]] @@html:07-08-2025 00:00@@ @@html: @@ diff --git a/blogs/blogs-list.org b/blogs/blogs-list.org index c7b39b7..6f98e9b 100644 --- a/blogs/blogs-list.org +++ b/blogs/blogs-list.org @@ -4,8 +4,20 @@ See the categories: @@html:Categories@@ * Blogs: -- [[file:2025/08/spending-the-whole-day-on-this-website.org][09-08-2025: Website Changes]] @@html:2025-08-09@@ @@html: @@ @@html: @@ -- [[file:2025/08/wacom-with-arch.org][Wacom With Arch]] @@html:2025-08-08@@ @@html: @@ -- [[file:2025/08/what-do-i-want-to-do-with-emacs.org][What Do I Want To Do With Emacs]] @@html:2025-08-08@@ @@html: @@ @@html: @@ -- [[file:2025/08/zettelkasten.org][Zettelkasten Method]] @@html:2025-08-07@@ @@html: @@ -- [[file:blogs-intro.org][Blogs Introduction]] @@html:2025-08-06@@ @@html: @@ \ No newline at end of file +- [[file:2025/2025-list.org][2025 List]] @@html:18-12-2025 22:42@@ +- [[file:2025/12-december/14-12-week-review.org][[14-12-2025] - Weekly Review]] @@html:09-12-2025 12:12@@ @@html: @@ +- [[file:2025/12-december/07-12-week-review.org][[07-12-2025] - Weekly Review]] @@html:07-12-2025 20:34@@ @@html: @@ +- [[file:2025/11-november/30-11-week-review.org][[30-11-2025] - Weekly Review]] @@html:30-11-2025 17:09@@ @@html: @@ +- [[file:2025/11-november/23-11-week-review.org][[23-11-2025] - Weekly Review]] @@html:17-11-2025 18:05@@ @@html: @@ +- [[file:2025/11-november/16-11-week-review.org][[16-11-2025] - Weekly Review]] @@html:10-11-2025 17:44@@ @@html: @@ +- [[file:2025/11-november/09-11-week-review.org][[09-11-2025] - Weekly Review]] @@html:09-11-2025 20:08@@ @@html: @@ +- [[file:publish-pages.org][How to publish pages using Org Publish]] @@html:08-11-2025 10:57@@ @@html: @@ +- [[file:2025/11-november/02-11-week-review.org][[02-11-2025] - Weekly Review]] @@html:02-11-2025 00:00@@ @@html: @@ +- [[file:2025/08-august/third-time.org][Third Time]] @@html:28-08-2025 17:08@@ @@html: @@ +- [[file:2025/08-august/benefits-of-reading.org][Benefits of Reading]] @@html:14-08-2025 23:36@@ @@html: @@ @@html: @@ +- [[file:2025/08-august/hilberts.hotel.org][Hilbert's Hotel]] @@html:11-08-2025 18:39@@ @@html: @@ @@html: @@ +- [[file:2025/08-august/spending-the-whole-day-on-this-website.org][09-08-2025: Website Changes]] @@html:09-08-2025 00:00@@ @@html: @@ @@html: @@ +- [[file:2025/08-august/what-do-i-want-to-do-with-emacs.org][What Do I Want To Do With Emacs]] @@html:08-08-2025 00:00@@ @@html: @@ @@html: @@ +- [[file:2025/08-august/wacom-with-arch.org][Wacom With Arch]] @@html:08-08-2025 00:00@@ @@html: @@ +- [[file:2025/08-august/zettelkasten.org][Zettelkasten Method]] @@html:07-08-2025 00:00@@ @@html: @@ +- [[file:blogs-intro.org][Blogs Introduction]] @@html:06-08-2025 00:00@@ @@html: @@ \ No newline at end of file diff --git a/blogs/publish-pages.org b/blogs/publish-pages.org new file mode 100644 index 0000000..9f84c57 --- /dev/null +++ b/blogs/publish-pages.org @@ -0,0 +1,88 @@ +#+TITLE: How to publish pages using Org Publish +#+OPTIONS: num:nil +#+DATE: <2025-11-08 Sat 10:57> +#+filetags: :website: +#+COMMENTS: t +#+SLUG: publish-pages + +* How to publish web pages using ~org-publish~ + +This website is heavily inspired by some people who have decided to use ~org-publish~ as a way to convert ~org~ files into ~html~. I came across a few that took the plunge and decided to migrate from platforms like Wordpress and instead opted for a more transparent, text-based workflow. + +The Org website[fn:1] now looks a lot better than it used to. + +There is a [[https://en.wikipedia.org/wiki/Unix_philosophy][Unix philosophy]] that emphasises building simple and compact code that can be easily maintainable, and following this approach (do one thing well) aligns with this philosophy. + +So how does one go about publishing web pages through Emacs's ~org-publish~? The answer is quite simple. You probably will need to have some familiarity with ~elisp~[fn:2] although it's not a must. + +The first thing we will want to do is edit our ~init.el~ file, which is the *initialization* file. In this file we want to add the following: + +/This is a simple and quick way of getting this setup, although there is a better and more scalable approach to this, where we have a build script that runs each time we want to generate the project. See [[file:setup.org][here]] for more details./ + +#+begin_src emacs-lisp + + (setq org-publish-project-alist + '(("org-notes" + :base-directory "~/web" + :base-extension "org" + :publishing-directory "~/web" + :recursive t + :publishing-function org-html-publish-to-html + :with-author nil + :with-creator nil + :html-validation-link nil + :with-toc t + :section-numbers t + :html-head "" + ) + ("org-static" + :base-directory "~/web" + :base-extension "css\\|js\\|png\\|jpg\\|gif" + :publishing-directory "~/web/output" + :recursive t + :publishing-function org-publish-attachment) + ("website" :components ("org-notes" "org-static")))) + +#+end_src + +What this snippet of code is doing is it is telling ~org-publish~ what **configurations** we want to use when we end up running the publish command. Under ~~/web~ is where we want to include our files. Let us take a simple example and create an ~index.org~ file: + +#+begin_src emacs-lisp + + ,#+TITLE: Website Name + ,#+OPTIONS: toc:nil num:nil + + ,* Welcome! + + This is a simple website + +#+end_src + +There are some metadata at the very top as we can see, these help us customise each page the way we want to. Here I disabled the Table of Contents and section numbers just for this file. We can also create a simple ~styles.css~ file in the same directory. +After doing this, we can run the command: + +#+begin_src emacs-lisp + + M-x org-publish RET website + +#+end_src + +This will generate a html file inside the ~~/web/output/~ directory, and we can serve this file using any static site generation tools; I recommend python due to it's simplicity: + +#+begin_src bash + + cd ~/web/output + python3 -m http.server 8000 + +#+end_src + +And there we go! The files are now being hosted on ~http://localhost:8000/~ and we have a fully functioning workflow for converting org files into a static website. + +* Contact + +Feel free to reach out on GitHub: https://github.com/zainezq + + +[fn:1] See: https://orgmode.org/manual/Publishing.html + +[fn:2] Emacs Lisp is a Lisp dialect made for Emacs. See: https://en.wikipedia.org/wiki/Emacs_Lisp diff --git a/blogs/publish-pages.org~ b/blogs/publish-pages.org~ new file mode 100644 index 0000000..2ddaf6e --- /dev/null +++ b/blogs/publish-pages.org~ @@ -0,0 +1,86 @@ +#+TITLE: How to publish pages using Org Publish +#+OPTIONS: num:nil +#+DATE: <2025-11-08 Sat 10:57> +#+filetags: :website: + +* How to publish web pages using ~org-publish~ + +This website is heavily inspired by some people who have decided to use ~org-publish~ as a way to convert ~org~ files into ~html~. I came across a few that took the plunge and decided to migrate from platforms like Wordpress and instead opted for a more transparent, text-based workflow. + +The Org website[fn:1] now looks a lot better than it used to. + +There is a [[https://en.wikipedia.org/wiki/Unix_philosophy][Unix philosophy]] that emphasises building simple and compact code that can be easily maintainable, and following this approach (do one thing well) aligns with this philosophy. + +So how does one go about publishing web pages through Emacs's ~org-publish~? The answer is quite simple. You probably will need to have some familiarity with ~elisp~[fn:2] although it's not a must. + +The first thing we will want to do is edit our ~init.el~ file, which is the *initialization* file. In this file we want to add the following: + +/This is a simple and quick way of getting this setup, although there is a better and more scalable approach to this, where we have a build script that runs each time we want to generate the project. See [[file:setup.org][here]] for more details./ + +#+begin_src emacs-lisp + + (setq org-publish-project-alist + '(("org-notes" + :base-directory "~/web" + :base-extension "org" + :publishing-directory "~/web" + :recursive t + :publishing-function org-html-publish-to-html + :with-author nil + :with-creator nil + :html-validation-link nil + :with-toc t + :section-numbers t + :html-head "" + ) + ("org-static" + :base-directory "~/web" + :base-extension "css\\|js\\|png\\|jpg\\|gif" + :publishing-directory "~/web/output" + :recursive t + :publishing-function org-publish-attachment) + ("website" :components ("org-notes" "org-static")))) + +#+end_src + +What this snippet of code is doing is it is telling ~org-publish~ what **configurations** we want to use when we end up running the publish command. Under ~~/web~ is where we want to include our files. Let us take a simple example and create an ~index.org~ file: + +#+begin_src emacs-lisp + + ,#+TITLE: Website Name + ,#+OPTIONS: toc:nil num:nil + + ,* Welcome! + + This is a simple website + +#+end_src + +There are some metadata at the very top as we can see, these help us customise each page the way we want to. Here I disabled the Table of Contents and section numbers just for this file. We can also create a simple ~styles.css~ file in the same directory. +After doing this, we can run the command: + +#+begin_src emacs-lisp + + M-x org-publish RET website + +#+end_src + +This will generate a html file inside the ~~/web/output/~ directory, and we can serve this file using any static site generation tools; I recommend python due to it's simplicity: + +#+begin_src bash + + cd ~/web/output + python3 -m http.server 8000 + +#+end_src + +And there we go! The files are now being hosted on ~http://localhost:8000/~ and we have a fully functioning workflow for converting org files into a static website. + +* Contact + +Feel free to reach out on GitHub: https://github.com/zainezq + + +[fn:1] See: https://orgmode.org/manual/Publishing.html + +[fn:2] Emacs Lisp is a Lisp dialect made for Emacs. See: https://en.wikipedia.org/wiki/Emacs_Lisp diff --git a/books/books-list.org b/books/books-list.org new file mode 100644 index 0000000..8e2f828 --- /dev/null +++ b/books/books-list.org @@ -0,0 +1,7 @@ +#+TITLE: Books List +#+OPTIONS: toc:nil num:nil + +See the categories: @@html:Categories@@ + +* Book Notes: +- [[file:clean-code/clean-code-notes.org][Clean Code Notes]] @@html:10-08-2025 19:45@@ @@html: @@ @@html: @@ \ No newline at end of file diff --git a/books/clean-code/clean-code-notes.org b/books/clean-code/clean-code-notes.org new file mode 100644 index 0000000..6b19cf6 --- /dev/null +++ b/books/clean-code/clean-code-notes.org @@ -0,0 +1,612 @@ +#+TITLE: Clean Code Notes +#+OPTIONS: num:nil tags:t toc:t +#+DATE: <2025-08-10 Sun 19:45> +#+FILETAGS: :books:notes: + +/Author: Robert C. Martin/ + +* Chapter 1: Clean Code + +Referenced Items: +- Implementation Patterns, Kent Beck, Addison-Wesley, 2007. +- Literate Programming, Donald E. Knuth, Center for the Study of Language and Information, Leland Stanford Junior University, 1992. + +Principles mentioned: +Single Responsibility Principle (SRP), the Open Closed Principle (OCP), and the Dependency Inversion Principle (DIP) + +* Chapter 2: Meaningful Names + +** Use intention revealing names: + +Names should reveal intent, there is no revelation in naming an integer ~d~, intending it stands for days. Instead, you should use the following names: +#+begin_src java + int elapsedTimeInDays; + int daysSinceCreation; + int daysSinceModification; + int fileAgeInDays; +#+end_src + +** Avoid disinformation + +Don't postfix the word 'list' to the name 'accounts' unless it's actually a list. This is because the reader will /assume/ the data type of accountsList is indeed a list, instead choose a name like ~accountsGroup~. + +** Make Meaningful Distinctions + +While it is possible to name by being disinformative, it is also possible to name being non informative. Consider: + +#+begin_src java + + public static void copyChars(char a1[], char a2[]) { + for (int i = 0; i < a1.length; i++) { + a2[i] = a1[i]; + } + } + +#+end_src + +What on earth does ~a[1]~ and ~a[2]~ even stand for? We are better off using names like source and destination (due to the function's intent of copying the array). + +Furthermore, noise words are redundant. We should never use the word ~variable~ when naming a variable, or ~table~ when naming a table. +** Use Pronouncable Names + +This is quite straightforward. Do not use a name like ~genymdhms~ to refer to generation date, year, month, day, hour, minute, +and second. Instead use ~generationTimeStamp~. + +** Use Searchable Names + +In modern IDE's, it is still quite difficult to search for single-lettered variables. The writer states a personal preference of using single-letter names only as local variables and inside short methods. The following principle is given: + +/The length of a name should correspond to the size of its scope/ + +** Avoid Encodings + +Don't prefix variables with letters like m_ as was done in the past. Do not type encode as well, an example of this is: ~PhoneNumber phoneString;~ we can see the reader being misled into thinking the phone number is a String. + +** Avoid Mental Mappings + +Clarity is king, don't use a name for a variable that only you know what it stands for. For example: using the letter r as the lower-cased version of the url with the host and scheme +removed. That's being smart, not professional. + +** Class Names + +{{{epigraph_single(Classes and objects should have noun or noun phrase names like Customer\, WikiPage\, +Account\, and AddressParser. Avoid words like Manager\, Processor\, Data\, or Info in the name +of a class. A class name should not be a verb)}}} + +** Method Names + +Methods should have verb or verb phrase names. + +** Don't be cute/Don't use puns + +Do not use names that are only understandable to people whom you share jokes etc with. Furthermore, do not use colloquialism and slang in names. +- Example: ~HandGrenade~ instead of ~DeleteItems~ +- Example: ~whack()~ instead of ~kill()~ + +** Pick one word per concept + +If you have multiple choices for naming a concept, use one and stick with it. For instance if your options are fetch, get and retrieve, use one and stick with it throughout. + +** Solution Domain Names and Problem Domain Names + +Where possible use solution domain names, as the people that are going to be reading the code are programmers. Therefore, do not shy away from using CS terms, algorithm names, math names and so forth. + +However when it is not possible to use solution domain names (in other words, when there is no "programmer-eese" then use the name from the problem domain. The other programmers can ask the domain expert for clarification. If the code is more to do with the problem domain concepts, then the names should be drawn from them. + +** Add Meaningful Context + +Enclose names with well-named classes, functions, or namespaces. When all else fails, then prefix with something that provides more context. + +** Don't add gratuitous context + +Shorter names are better than longer ones, generally. This is so long as the context and intent is clear. Don't add redundant or irrelevant additions to the name in the for the sake of 'context'. + +* Chapter 3: Functions + +** Functions should be small + +Functions should be extremely short—ideally just a few lines, so they remain easy to understand and maintain. + +Avoid deeply nested blocks; keep indentation shallow (1–2 levels), often replacing blocks with descriptive function calls. + +A small function tells a concise, self-contained story, making it easier for readers to follow the program’s intent. + +The smaller the function, the more descriptive and accurate its name can be, improving self-documentation. + +Large functions hide complexity and mix abstraction levels, making errors and duplication more likely. + +** Do One Thing & One Level of Abstraction + +A function should do exactly one conceptual task, and all its statements should exist at the same abstraction level. + +Mixing details (like string concatenation) with high-level actions (like rendering a page) causes confusion. + +The Stepdown Rule: organise functions so they read like a top down narrative, each calling the next abstraction level. + +If you can extract a subfunction with a name that isn’t a restatement, the original function is doing too much. + +Functions that “do one thing” cannot be logically split into sections such as “initialize,” “process,” “finalize.” + +** Switch Statements + +Switch statements naturally violate “do one thing” by handling multiple cases; they also grow in size over time. + +They break the Single Responsibility Principle (multiple reasons to change) and Open-Closed Principle (must change for new cases). + +Preferred approach: hide switch statements inside a factory and dispatch behavior polymorphically through an interface. + +Allow only one visible switch in your system, used solely for object creation, then encapsulate it. + +This removes duplication and keeps high-level code unaware of concrete type distinctions. + +Example: + +#+begin_src java + public abstract class Employee { + public abstract boolean isPayday(); + public abstract Money calculatePay(); + public abstract void deliverPay(Money pay); + } + ----------------- + public interface EmployeeFactory { + public Employee makeEmployee(EmployeeRecord r) throws InvalidEmployeeType; + } + ----------------- + public class EmployeeFactoryImpl implements EmployeeFactory { + public Employee makeEmployee(EmployeeRecord r) throws InvalidEmployeeType { + switch (r.type) { + case COMMISSIONED: + return new CommissionedEmployee(r) ; + case HOURLY: + return new HourlyEmployee(r); + case SALARIED: + return new SalariedEmploye(r); + default: + throw new InvalidEmployeeType(r.type); + } + } + } + +#+end_src + +** Use Descriptive Names + +A function’s name should clearly state its purpose. Long, descriptive names beat short, cryptic ones. + +Consistent naming patterns (shared verbs/nouns) help code read like a coherent story and aid predictability. + +Descriptive names reduce the need for comments and improve comprehension without external documentation. + +Renaming functions can reveal design improvements, so try multiple options until the best emerges. + +IDE refactoring tools make renaming safe, encouraging experimentation. + +** Function Arguments + +{{{epigraph_single(The ideal number of arguments for a function is zero (niladic). Next comes one (monadic)\, followed closely by two (dyadic). Three arguments (triadic) should be avoided where possible. More than three (polyadic) requires very special justification—and then shouldn’t be used anyway.)}}} + +Fewer arguments = better; aim for 0–2, avoid more than 3 unless absolutely necessary. + +Flag arguments (booleans) are a red flag—they imply the function does multiple things. + +Group related parameters into objects (e.g., ~Point~ for ~x~ and ~y~) to reduce argument count and improve clarity. + +Output arguments are confusing—prefer returning values or mutating the owning object’s state. + +Match function/argument names in verb–noun or keyword style (e.g., ~writeField(name)~, ~assertExpectedEqualsActual~). + +** Have No Side Effects + +A function should do only what its name promises. Hidden state changes are misleading and dangerous. + +Side effects create temporal coupling, meaning the function must be called in a certain sequence to be safe. + +If unavoidable, make side effects explicit in the name (e.g., ~checkPasswordAndInitializeSession~). + +Clear separation of command and query functions avoids ambiguity in meaning and intent. + +Functions that modify state and return information often cause confusion and should be split. + +** Error Handling + +Error handling is a single responsibility—separate it from normal logic to keep both paths clear. + +Prefer exceptions over error codes to avoid cluttering the happy path and to reduce dependency magnets. + +Extract try/catch bodies into their own functions for cleaner structure. + +See below: + +#+begin_src java + + public void delete(Page page) { + try { + deletePageAndAllReferences(page); + } + catch (Exception e) { + logError(e); + } + } + + private void deletePageAndAllReferences(Page page) throws Exception { + deletePage(page); + registry.deleteReference(page.name); + configKeys.deleteKey(page.name.makeKey()); + } + private void logError(Exception e) { + logger.log(e.getMessage()); + } + +#+end_src + +Keep functions small enough that occasional multiple return or break statements are acceptable. + +Avoid duplication in error handling, and follow the DRY principle to ensure changes occur in one place. + + +* Chapter 4: Comments + +Comments are a necessary evil—they exist because code fails to express intent clearly. + +Outdated comments are dangerous; they can mislead more than help. + +Strive to write code that explains itself; comments should be minimised. + +Truth is always in the code, not in the comments. + +** Comments Do Not Make Up for Bad Code + +Don’t use comments to excuse messy, unclear code. Clean the code instead. + +Clear, expressive code with few comments > cluttered code with many comments. + +#+begin_src java + + // Check to see if the employee is eligible for full benefits + if ((employee.flags & HOURLY_FLAG) && (employee.age > 65)) + + // Better: + if (employee.isEligibleForFullBenefits()) + +#+end_src + +** Good Comments + +Only write them when unavoidable. Such as in the following instances: + +*** Legal Comments + +Sometimes required for copyright/licensing. + +Keep them short; refer to standard licenses rather than embedding full legal text. + +*** Informative Comments + +Explain return values, formats, or patterns. + +Prefer naming/structuring code to make such comments unnecessary. + +#+begin_src java + + // format matched kk:mm:ss EEE, MMM dd, yyyy + Pattern timeMatcher = Pattern.compile("\\d*:\\d*:\\d* \\w*, \\w* \\d*, \\d*"); + +#+end_src + + +*** Explanation of Intent + +Describe why a certain approach was chosen. + +Helps future maintainers understand reasoning behind code. + +#+begin_src java + + public int compareTo(Object o) + { + if(o instanceof WikiPagePath) + { WikiPagePath p = (WikiPagePath) o; + String compressedName = StringUtil.join(names, ""); + String compressedArgumentName = StringUtil.join(p.names, ""); + return compressedName.compareTo(compressedArgumentName); + } + return 1; // we are greater because we are the right type. + } + +#+end_src + +*** Clarification + +Translate obscure values into readable terms. + +Useful when working with unchangeable APIs/libraries, but risky if incorrect. + +*** Warning of Consequences + +Alert others about performance, thread-safety, or side effects. + +For example, in code you can say: + +~// SimpleDateFormat is not thread safe, so create each instance independently.~ + +*** \TODO\ Comments + +Mark incomplete work or planned improvements. + +Should be reviewed regularly; not an excuse for bad code. + +*** Amplification + +Highlight the importance of seemingly small details. + +~// the trim is real important. It removes starting spaces...~ + +*** Javadocs in Public APIs + +Public APIs should have clear documentation. + +Javadocs can also mislead. Keep them accurate and up-to-date. + +** Bad Comments +- Don't place a comment just because you feel like it. +- Remove redundant comments. +- Avoid misleading comments. +- Don't mandate everything (not every function needs a Javadoc). +- No need for journal comments, we have source control. +- Remove noise comments. + +*** Don’t Use a Comment When You Can Use a Function or Variable + +Replace explanatory comments with expressive variable or function names. + +Refactor code to remove comment redundancy. + +*** Position Markers + +Avoid decorative banners like ~// Actions ///////////////////////~, they add clutter. + +Use sparingly and only for meaningful grouping. + +Overuse makes them blend into background noise. + +*** Closing Brace Comments + +Comments on closing braces (} // while) are unnecessary for small, well structured functions. + +Prefer short, clear functions over brace markers. + +*** Attributions and Bylines + +Don’t add personal tags like ~/* Added by Rick */~, use version control for authorship history. + +Such comments become outdated and irrelevant over time. + +*** Commented Out Code + +Never keep old code commented out; delete it and rely on version control history. + +Commented-out code adds clutter and confuses future maintainers. + +#+begin_src java + + // Old cruft that should be deleted: + //hdrPos = bytePos; + //dataPos = bytePos; + +#+end_src + +*** HTML Comments + +Avoid HTML markup inside code comments, it makes them harder to read in the editor. + +Let documentation tools (like Javadoc) handle formatting. + +*** Nonlocal Information + +Comments should describe nearby code only, not unrelated parts of the system. + +Avoid embedding global/system details that the function can’t control. + +*** Too Much Information + +Avoid long, unnecessary historical or technical explanations. + +Keep only relevant context (e.g., “RFC 2045” reference is fine, not the full spec). + +*** Inobvious Connection + +Ensure the relationship between comment and code is clear. + +Don’t make readers guess what part of the code the comment refers to. + +#+begin_src java + + // plus filter bytes ... but which part is “filter”? + this.pngBytes = new byte[((this.width + 1) * this.height * 3) + 200]; + +#+end_src + +*** Function Headers + +Short, single purpose functions with good names don’t need header comments. + +Let the function name explain the purpose. + +*** Javadocs in Nonpublic Code + +Javadocs are useful for public APIs, but excessive formality in internal code is just noise. + +Internal methods should be self explanatory without full doc comments. +* Chapter 5: Formatting +** Vertical Formatting +- Vertical openness (blank lines) separates concepts and improves readability. +- Too much density makes code look like a muddle and harder to scan. + +*** Vertical Density +- Tightly related lines should appear vertically dense. +- Avoid useless comments that interrupt association. +- Example (bad): +#+BEGIN_SRC java + public class ReporterConfig { + /** + ,* The class name of the reporter listener + ,*/ + private String m_className; +#+END_SRC + +- Example (better): +#+BEGIN_SRC java +public class ReporterConfig { +private String m_className; +private List m_properties = new ArrayList<>(); +#+END_SRC + +*** Vertical Distance +- Related concepts should be kept close together to reduce scrolling and searching. +- Local variables → as close to use as possible, usually at top of function. +- Control variables → declared inside loop headers. +- Instance variables → declared at the top of class (common Java convention). + +#+BEGIN_SRC java +for (Test each : tests) { + count += each.countTestCases(); +} +#+END_SRC + +- Dependent functions: caller above callee for natural top-down reading. +#+BEGIN_SRC java +public Response makeResponse(...) { + String pageName = getPageNameOrDefault(request, "FrontPage"); + loadPage(pageName, context); + return makePageResponse(context); +} + +private String getPageNameOrDefault(Request request, String defaultPageName) { ... } +#+END_SRC + +*** Conceptual Affinity +- Group functions with similar naming or shared purpose. +- Example (JUnit assert methods): +#+BEGIN_SRC java +static public void assertTrue(String message, boolean condition) { ... } +static public void assertTrue(boolean condition) { ... } +static public void assertFalse(String message, boolean condition) { ... } +static public void assertFalse(boolean condition) { ... } +#+END_SRC + +*** Vertical Ordering +- Organise code top down: + - High-level concepts first (main logic). + - Lower-level details later. +- Readers can skim like a newspaper: important first, details last. +- Contrast: C/C++ require declarations before use, Java does not. + +*** Summary - vertical +- Use vertical openness to separate concepts. +- Use vertical density to group related ones. +- Keep related variables, methods, and concepts close together. +- Order code top down for natural readability. + +** Horizontal Formatting + +Keep lines short. Most professional code naturally stays within ~45 characters, with ~80 as an upper bound. Lines beyond 100–120 characters are generally careless. + +Avoid shrinking font or overly wide monitors to fit more code, readability > fitting more characters. + +Example limit guideline: + +#+begin_src java + // Good (short) + int sum = a + b + c; + + // Bad (too long) + int sum = a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p + q; +#+end_src +*** Horizontal Openness and Density + +Use spaces to separate low-precedence operators (e.g., +, -, =) and improve readability. + +Do not put spaces between function names and parentheses, they are closely related. + +Example (Quadratic formula formatting): + +#+begin_src java + return (-b + Math.sqrt(determinant)) / (2*a); +#+end_src + +Separate arguments with spaces after commas to show distinct parameters. + +*** Horizontal Alignment +Avoid aligning variable declarations or assignments in columns, it draws the eye to the wrong place. + +Long aligned lists usually mean the class is too large and should be split. + +Example (preferred unaligned): + +#+begin_src java + // Prefer this: + private Socket socket; + private InputStream input; + private OutputStream output; + + //instead of: + private Socket socket; + private InputStream input; + private OutputStream output; + +#+end_src + +*** Indentation + +Indent according to scope hierarchy: + +Classes → no indent + +Methods → 1 level + +Method bodies → 2 levels + +Inner blocks → +1 for each nesting + +Indentation makes scopes visually obvious; without it, code is hard to scan. + +Avoid collapsing scopes onto one line, always use braces and proper indenting. + +*** Dummy Scopes + +Avoid dummy bodies in loops (e.g., empty while or for loops). + +If unavoidable, place semicolon on its own indented line to make it visible. + +#+begin_src java + while (dis.read(buf, 0, size) != -1) + ; +#+end_src + +** Team Rules + +Teams must agree on a single formatting style for consistency. + +Use IDE formatters to enforce these rules across all files. + +Consistent formatting builds trust and reduces mental load for readers. + +** Uncle Bob’s Formatting Rules (Example in CodeAnalyzer.java) + +Short, clear methods with consistent spacing and indentation. + +Use spaces around assignment and low-precedence operators, no space for high precedence operators. + +Avoid deeply nested structures. Prefer clear, flat logic. + +Example snippet: + +#+begin_src java + private void measureLine(String line) { + lineCount++; + int lineSize = line.length(); + totalChars += lineSize; + lineWidthHistogram.addLine(lineSize, lineCount); + recordWidestLine(lineSize); + } +#+end_src diff --git a/build-site.el b/build-site.el index 87c30df..d8c2f5b 100644 --- a/build-site.el +++ b/build-site.el @@ -11,6 +11,14 @@ ;;; Code: (require 'package) + +(add-to-list 'load-path "~/master-folder/org_files/org_web/lisp/") + +(require 'recently-updated) +(require 'tags) +(require 'sidenotes) +(require 'sitemaps) + (setq package-user-dir (expand-file-name "./.packages")) (setq package-archives '(("melpa" . "https://melpa.org/packages/") ("elpa" . "https://elpa.gnu.org/packages/"))) @@ -21,23 +29,34 @@ (require 'ox) (require 'ox-html) -;; Initialize the package system (package-initialize) (unless package-archive-contents (package-refresh-contents)) -;; Install dependencies (package-install 'htmlize) (add-to-list 'load-path "~/master-folder/org_files/org_web/") (require 'htmlize) -(setq org-html-htmlize-output-type 'css) - - (defvar z-shared-head " - ") + + + + + + + + + + + + + + + +" + ) (setq org-export-global-macros (append @@ -49,6 +68,7 @@ ("kbd" . "@@html:$1@@@@latex:\\texttt{$1}@@") ("margimg" . "@@html:@@") +("countdown" . "@@html:@@") org-export-global-macros))) @@ -60,9 +80,10 @@ \"Site @@ -82,199 +103,117 @@ Created with %c on Arch ") -(defun z/posts-sitemap (title list) - "sitemap that lists post links as bullet points with dates and tags." - (concat - "#+TITLE: " title "\n" - "#+OPTIONS: toc:nil num:nil \n\n" - "See the categories: @@html:Categories@@\n\n" - "* Posts:\n" - (mapconcat - (lambda (entry) - (let* ((link (car entry)) - ;; extract relative file name from the link - (filename (if (string-match "\\[\\[file:\\([^]]+\\)\\]" link) - (match-string 1 link) - link)) - (full-path (expand-file-name filename "~/master-folder/org_files/org_web/posts/")) - (date-str "no date") - (tags-str "")) - ;; Get publish date - (let ((date (org-publish-find-date full-path org-publish-project-alist))) - (when date - (setq date-str (format-time-string "%Y-%m-%d" date)))) - ;; Get FILETAGS from file buffer - (when (file-exists-p full-path) - (with-temp-buffer - (insert-file-contents full-path) - (org-mode) - (let* ((tags (cadr (assoc "FILETAGS" (org-collect-keywords '("FILETAGS")))))) - (when tags - (setq tags-str (mapconcat (lambda (tag) - (format "@@html: %s @@" tag tag)) - (split-string tags ":" t) ;; <- Splits by ":" and removes empty strings - " ")))))) - ;; Final line output - (format "- %s @@html:%s@@ %s" link date-str tags-str))) - (cdr list) - "\n"))) + +(defun z/comments-file-p (file) + "Return non-nil if FILE has comments enabled. +A file has comments if: + - It has a #+COMMENTS: keyword with value \"t\" (case-insensitive)." + (when (file-exists-p file) + (with-temp-buffer + (insert-file-contents file) + (org-mode) + (let* ((keywords (org-collect-keywords '("COMMENTS"))) + (comments (cadr (assoc "COMMENTS" keywords)))) + (print keywords) + + (and comments + (string-match-p "^\\s-*t\\s-*$" + (downcase comments))))))) + +(defun z/comments-file-slug (file) + "Return page slug from #+SLUG: or fallback to filename." + (with-temp-buffer + (insert-file-contents file) + (org-mode) + (let* ((keywords (org-collect-keywords '("SLUG"))) + (slug (cadr (assoc "SLUG" keywords)))) + (if (and slug (string-match-p "\\S-" slug)) + slug + (file-name-base file))))) + +(defun z/org-html-insert-comments-into-body (body backend info) + "Insert comments section at the end of the document body." + (when (org-export-derived-backend-p backend 'html) + (let ((input-file (plist-get info :input-file))) + (if (and input-file + (z/comments-file-p input-file)) + (let* ((slug (z/comments-file-slug input-file)) + (comments-html + (format + " +
+

Comments

+ +
+ +
+ +
+ + + + + +
+
+" + slug))) + (concat body comments-html)) + body)))) -(defun z/blogs-sitemap (title list) - "sitemap that lists blog links as bullet points with dates and tags." - (concat - "#+TITLE: " title "\n" - "#+OPTIONS: toc:nil num:nil \n\n" - "See the categories: @@html:Categories@@\n\n" - "* Blogs:\n" - (mapconcat - (lambda (entry) - (let* ((link (car entry)) - ;; extract relative file name from the link - (filename (if (string-match "\\[\\[file:\\([^]]+\\)\\]" link) - (match-string 1 link) - link)) - (full-path (expand-file-name filename "~/master-folder/org_files/org_web/blogs/")) - (date-str "no date") - (tags-str "")) - ;; Get publish date - (let ((date (org-publish-find-date full-path org-publish-project-alist))) - (when date - (setq date-str (format-time-string "%Y-%m-%d" date)))) - ;; Get FILETAGS from file buffer - (when (file-exists-p full-path) - (with-temp-buffer - (insert-file-contents full-path) - (org-mode) - (let* ((tags (cadr (assoc "FILETAGS" (org-collect-keywords '("FILETAGS")))))) - (when tags - (setq tags-str (mapconcat (lambda (tag) - (format "@@html: %s @@" tag tag)) - (split-string tags ":" t) ;; <- Splits by ":" and removes empty strings - " ")))))) - ;; Final line output - (format "- %s @@html:%s@@ %s" link date-str tags-str))) - (cdr list) - "\n"))) +(defun z/org-html-add-body-classes (output backend info) + "Add layout-related classes to based on file metadata." + (when (org-export-derived-backend-p backend 'html) + (let* ((input-file (plist-get info :input-file)) + (classes + (delq nil + (list + (when (and input-file + (z/wip-file-p input-file)) + "wip") + (when (and input-file + (z/no-sidenotes-file-p input-file)) + "no-sidenotes"))))) + (if classes + (replace-regexp-in-string + "]*\\)>" + (format "" + (string-join classes " ")) + output) + output)))) + +(add-to-list 'org-export-filter-final-output-functions + #'z/org-html-add-body-classes) +(add-to-list 'org-export-filter-body-functions + #'z/org-html-insert-comments-into-body) -(defun z/categories-sitemap (title _list) - "Generate a categories page by scanning tags across org-posts and org-blogs." - (let* ((expanded (org-publish-expand-projects org-publish-project-alist)) - (posts (assoc "org-posts" expanded)) - (blogs (assoc "org-blogs" expanded)) - ;; DO NOT set :exclude to "" — it excludes everything - (files (cl-remove-duplicates - (append (and posts (org-publish-get-base-files posts)) - (and blogs (org-publish-get-base-files blogs))) - :test #'file-equal-p)) - ;; optional: drop the generated sitemaps - (files (cl-remove-if - (lambda (f) - (member (file-name-nondirectory f) - '("posts-list.org" "blogs-list.org" "sitemap.org" "categories.org"))) - files)) - (counts (make-hash-table :test 'equal))) - ;;(message "files I will scan: %S" files) - (dolist (f files) - (when (file-readable-p f) - (with-temp-buffer - (insert-file-contents f) - (org-mode) - (let* ((kw (org-collect-keywords '("FILETAGS" "TAGS"))) - (raw (car (or (cdr (assoc "FILETAGS" kw)) - (cdr (assoc "TAGS" kw)))))) - (when raw - (dolist (tag (split-string raw ":" t)) - (puthash tag (1+ (gethash tag counts 0)) counts))))))) +(setq org-html-htmlize-output-type 'css) - (let (tags) - (maphash (lambda (k _) (push k tags)) counts) - (setq tags (sort tags #'string-lessp)) - (concat - "#+TITLE: " title "\n#+OPTIONS: toc:nil num:nil title:nil\n\n* Categories (Includes both blogs and posts)\n" - (if tags - (mapconcat - (lambda (tag) - (format "- [[file:tags/%s.org][@@html:%s@@]] (%d)" - (z/tag-slug tag) tag (gethash tag counts)) - ) - tags - "\n") - "_No tags found yet._"))))) +(org-export-define-derived-backend 'z-html 'html + :filters-alist '((:filter-final-output . z/insert-filetags-after-title))) +(defun z/org-main-prep (_project) + (z/generate-recently-updated-org 26)) -(defun z/tag-slug (s) - "Turn a tag into a safe filename." - (let ((down (downcase s))) - (replace-regexp-in-string "[^a-z0-9]+" "-" down))) - -(defun z/collect-post-files () - "Return all .org files from org-posts and org-blogs." - (let* ((expanded (org-publish-expand-projects org-publish-project-alist)) - (posts (assoc "org-posts" expanded)) - (blogs (assoc "org-blogs" expanded))) - (cl-remove-duplicates - (append (and posts (org-publish-get-base-files posts)) - (and blogs (org-publish-get-base-files blogs))) - :test #'file-equal-p))) - -(defun z/gather-tag-index () - "Return hash: tag -> list of (FILE TITLE DATE-ISO)." - (let ((idx (make-hash-table :test 'equal))) - (dolist (f (z/collect-post-files)) - (when (and (string-match-p "\\.org\\'" f) - (file-readable-p f) - ;; ignore generated lists - (not (member (file-name-nondirectory f) - '("posts-list.org" "blogs-list.org" "sitemap.org" "categories.org")))) - (with-temp-buffer - (insert-file-contents f) - (org-mode) - (let* ((kw (org-collect-keywords '("TITLE" "FILETAGS" "TAGS" "DATE"))) - (title (or (car (cdr (assoc "TITLE" kw))) - (file-name-base f))) - (date (or (car (cdr (assoc "DATE" kw))) "")) ;; optional - (raw (car (or (cdr (assoc "FILETAGS" kw)) - (cdr (assoc "TAGS" kw)))))) - (when raw - (dolist (tag (split-string raw ":" t)) - (push (list f title date) (gethash tag idx)))))))) - idx)) - -(defun z/write-tag-pages () - "Generate tags/*.org pages listing posts for each tag." - (let* ((site-root (expand-file-name "~/master-folder/org_files/org_web/")) - (tags-dir (expand-file-name "tags" site-root))) - (unless (file-directory-p tags-dir) - (make-directory tags-dir t)) - (let ((idx (z/gather-tag-index))) - (maphash - (lambda (tag items) - (let* ((slug (z/tag-slug tag)) - (outfile (expand-file-name (format "%s.org" slug) tags-dir))) - (with-temp-file outfile - (insert (format "#+TITLE: Tag: %s\n#+OPTIONS: toc:nil num:nil title:nil \n\n* Posts tagged %s\n" - tag tag)) - ;; sort newest first if DATE present - (setq items (sort items (lambda (a b) (string> (nth 2 a) (nth 2 b))))) - (dolist (it items) - (let* ((file (nth 0 it)) - (title (nth 1 it)) - (rel (file-relative-name file tags-dir))) - ;; link to the source .org; org-publish will rewrite to the .html - (insert (format "- [[file:%s][%s]]\n" rel title))))))) - idx) - ))) - +(defun z/z-publish-to-html (plist filename pub-dir) + "Publish FILENAME to HTML using the z-html backend." + (org-publish-org-to 'z-html filename ".html" plist pub-dir)) ;; Define the publishing project (setq org-publish-project-alist `(("org-main" :recursive t :base-directory "~/master-folder/org_files/org_web/" - :publishing-function org-html-publish-to-html + :publishing-function z/z-publish-to-html :publishing-directory "~/master-folder/org_files/org_web/output" :base-extension "org" :auto-sitemap t @@ -283,25 +222,29 @@ Created with %c on Arch Arch Arch Arch +#+FILETAGS: :emacs:website: +#+NO_SIDENOTES: t +#+COMMENTS: t +#+SLUG: backlog + + +* TODO + +* DOING + +* DONE + +- Add a notes page <2025-12-18 Thu> +- Add a *move* function in the competencies board page for mobile <2025-12-18 Thu> +- Refactor the build script to extract functions into seperate files <2025-12-16 Tue> +- Add a WIP property in an org file. If the header contains the string "WIP" then create a list of all WIP items and add to it. Otherwise do nothing. +- Page for latest updated files. +- Give lima extra controls, and update the syntax file for her. +- Service for RDP +- Create the backlog page. + +* For another time + +- Instead of deleting the output directory each time the website is built, only delete the files that have been changed. diff --git a/home/backlog.org~ b/home/backlog.org~ new file mode 100644 index 0000000..cb5c01b --- /dev/null +++ b/home/backlog.org~ @@ -0,0 +1,25 @@ +#+TITLE: Backlog +#+OPTIONS: num:nil toc:nil +#+DATE: <2025-11-08 Sat 11:08> +#+FILETAGS: :emacs:website: +#+COMMENTS: t +#+SLUG: backlog + +* TODO + +* DOING + +* DONE + +- Refactor the build script to extract functions into seperate files <2025-12-16 Tue> +- Add a WIP property in an org file. If the header contains the string "WIP" then create a list of all WIP items and add to it. Otherwise do nothing. +- Page for latest updated files. +- Give lima extra controls, and update the syntax file for her. +- Service for RDP +- Create the backlog page. + +* For another time :( + +- Instead of deleting the output directory each time the website is built, only delete the files that have been changed. +- Remove lima's files from the sitemap (for some odd reason). + diff --git a/categories.org b/home/categories.org similarity index 57% rename from categories.org rename to home/categories.org index 935e7fb..4faa49c 100644 --- a/categories.org +++ b/home/categories.org @@ -4,7 +4,11 @@ * Categories (Includes both blogs and posts) - [[file:tags/education.org][@@html:@@]] (1) - [[file:tags/emacs.org][@@html:@@]] (2) -- [[file:tags/insights.org][@@html:@@]] (1) -- [[file:tags/introduction.org][@@html:@@]] (2) -- [[file:tags/review.org][@@html:@@]] (1) -- [[file:tags/website.org][@@html:@@]] (1) \ No newline at end of file +- [[file:tags/insights.org][@@html:@@]] (4) +- [[file:tags/introduction.org][@@html:@@]] (3) +- [[file:tags/learning.org][@@html:@@]] (10) +- [[file:tags/maths.org][@@html:@@]] (1) +- [[file:tags/notes.org][@@html:@@]] (11) +- [[file:tags/reading.org][@@html:@@]] (1) +- [[file:tags/review.org][@@html:@@]] (9) +- [[file:tags/website.org][@@html:@@]] (2) \ No newline at end of file diff --git a/contact.org b/home/contact.org similarity index 100% rename from contact.org rename to home/contact.org diff --git a/home/countdown.org b/home/countdown.org new file mode 100644 index 0000000..a28e61f --- /dev/null +++ b/home/countdown.org @@ -0,0 +1,14 @@ +#+TITLE: Countdown +#+OPTIONS: num:nil title:nil +#+DATE: <2025-08-04 Mon> +#+COMMENTS: t +#+SLUG: wedding-countdown + +* zaine and halima :) + + +#+ATTR_HTML: :class countdown-wrap +{{{countdown(2026-08-02T00:00:00Z,Nikah Day)}}} + +#+ATTR_HTML: :class countdown-wrap +{{{countdown(2026-08-09T00:00:00Z,Walima Day)}}} diff --git a/home/countdown.org~ b/home/countdown.org~ new file mode 100644 index 0000000..e209c9d --- /dev/null +++ b/home/countdown.org~ @@ -0,0 +1,12 @@ +#+TITLE: Countdown +#+OPTIONS: num:nil title:nil +#+DATE: <2025-08-04 Mon> + +* zaine and halima :) + + +#+ATTR_HTML: :class countdown-wrap +{{{countdown(2026-08-02T00:00:00Z,Nikah Day)}}} + +#+ATTR_HTML: :class countdown-wrap +{{{countdown(2026-08-09T00:00:00Z,Walima Day)}}} diff --git a/home/notes.org b/home/notes.org new file mode 100644 index 0000000..19f9b63 --- /dev/null +++ b/home/notes.org @@ -0,0 +1,34 @@ +#+TITLE: Notes +#+OPTIONS: toc:nil num:nil +#+NO_SIDENOTES: t +#+COMMENTS: t +#+SLUG: notes + +#+BEGIN_EXPORT html +
+

Loading notes…

+
+ +
+
+ + + + + + + +
+
+#+END_EXPORT diff --git a/home/notes.org~ b/home/notes.org~ new file mode 100644 index 0000000..40d66c2 --- /dev/null +++ b/home/notes.org~ @@ -0,0 +1,38 @@ +#+TITLE: Public Notes +#+OPTIONS: toc:nil num:nil +#+NO_SIDENOTES: t +#+COMMENTS: t +#+SLUG: notes + + +This page is a shared notes space — short thoughts, marginalia, and passing ideas. +Names are required; everything else is intentionally lightweight. + +#+BEGIN_EXPORT html +
+

Loading notes…

+
+ +
+
+ + + + + + + +
+
+#+END_EXPORT diff --git a/home/services.org b/home/services.org new file mode 100644 index 0000000..13caddb --- /dev/null +++ b/home/services.org @@ -0,0 +1,24 @@ +#+TITLE: Service +#+OPTIONS: toc:nil num:nil title:nil +#+COMMENTS: t +#+SLUG: services + + +| Service | Link | +|---------------+--------------| +| PgAdmin | [[https://pgadmin.zainezq.com/][PgAdmin]] | +| Stirling PDF | [[https://stirlingpdf.zainezq.com/][Stirling PDF]] | +| Portainer | [[https://portainer.zainezq.com/][Portainer]] | +| File Browser | [[https://filebrowser.zainezq.com/][File Browser]] | +| Calibre Web | [[https://calibre.zainezq.com/][Calibre Web]] | +| Miniflux | [[https://miniflux.zainezq.com/][Miniflux]] | +| JupyterLab | [[https://jupyter.zainezq.com/][JupyterLab]] | +| Guacamole | [[https://guac.zainezq.com/][Guacamole]] | +| Vaultwarden | [[https://bitwarden.zainezq.com/][Vaultwarden]] | +| Reader | [[https://reader.zainezq.com/][Reader]] | +| Netdata | [[https://netdata.zainezq.com/][Netdata]] | +| Books VNC | [[https://booksvnc.zainezq.com/][Books VNC]] | +| Actual | [[https://actual.zainezq.com/][Actual]] | +| Photo Library | [[https://photo.zainezq.com/][Immich]] | +| VPN | [[https://wgeasy.zainezq.com/][wg-easy]] | +| Glance | [[https://glance.zainezq.com/][Glance]] | diff --git a/home/services.org~ b/home/services.org~ new file mode 100644 index 0000000..87a30c6 --- /dev/null +++ b/home/services.org~ @@ -0,0 +1,21 @@ +#+TITLE: Service +#+OPTIONS: toc:nil num:nil title:nil + +| Service | Link | +|---------------+--------------| +| PgAdmin | [[https://pgadmin.zainezq.com/][PgAdmin]] | +| Stirling PDF | [[https://stirlingpdf.zainezq.com/][Stirling PDF]] | +| Portainer | [[https://portainer.zainezq.com/][Portainer]] | +| File Browser | [[https://filebrowser.zainezq.com/][File Browser]] | +| Calibre Web | [[https://calibre.zainezq.com/][Calibre Web]] | +| Miniflux | [[https://miniflux.zainezq.com/][Miniflux]] | +| JupyterLab | [[https://jupyter.zainezq.com/][JupyterLab]] | +| Guacamole | [[https://guac.zainezq.com/][Guacamole]] | +| Vaultwarden | [[https://bitwarden.zainezq.com/][Vaultwarden]] | +| Reader | [[https://reader.zainezq.com/][Reader]] | +| Netdata | [[https://netdata.zainezq.com/][Netdata]] | +| Books VNC | [[https://booksvnc.zainezq.com/][Books VNC]] | +| Actual | [[https://actual.zainezq.com/][Actual]] | +| Photo Library | [[https://photo.zainezq.com/][Immich]] | +| VPN | [[https://wgeasy.zainezq.com/][wg-easy]] | +| Glance | [[https://glance.zainezq.com/][Glance]] | diff --git a/home/setup.org b/home/setup.org new file mode 100644 index 0000000..6ccd082 --- /dev/null +++ b/home/setup.org @@ -0,0 +1,444 @@ +#+TITLE: Setup +#+OPTIONS: toc:nil num:nil +#+COMMENTS: t +#+SLUG: setup +#+NO_SIDENOTES: t + +* Introduction + +/Last updated: <2025-12-18 Thu 22:00>/ + +See this link[fn:1] for a more generic introduction on how to publish websites using ~org-publish~. + +This page will highlight the ~build-script.el~ used to generate this website. This entire website is generated through org files using the command: + +#+BEGIN_SRC bash +emacs -Q --script build-site.el +#+END_SRC + +* The script + +The script is as follows, at the start I have some metadata relating to the file, followed by package management,then the declaration of variables/functions and finally the ~org-publish-project-alist~ which handles nearly all of the project generation instructions. + +/The following code snippet will probably change often, as I can never seem to stick to a single configuration 😀/ + +#+BEGIN_SRC emacs-lisp + ;;; build-site.el --- Publish my website using org-publish -*- lexical-binding: t; -*- + + ;; Author: Zaine Qayyum + ;; Created: 2025-08-08 + ;; Purpose: Build and publish my static site from Org files. + + ;;; Commentary: + ;; Run this file with: + ;; emacs -Q --script build-site.el + + ;;; Code: + + (require 'package) + + (add-to-list 'load-path "~/master-folder/org_files/org_web/lisp/") + + (require 'recently-updated) + (require 'tags) + (require 'sidenotes) + (require 'sitemaps) + + (setq package-user-dir (expand-file-name "./.packages")) + (setq package-archives '(("melpa" . "https://melpa.org/packages/") + ("elpa" . "https://elpa.gnu.org/packages/"))) + + (require 'ox-publish) + (require 'cl-lib) + (require 'org) + (require 'ox) + (require 'ox-html) + + (package-initialize) + (unless package-archive-contents + (package-refresh-contents)) + + (package-install 'htmlize) + (add-to-list 'load-path "~/master-folder/org_files/org_web/") + (require 'htmlize) + + (defvar z-shared-head + " + + + + + + + + + + + + + + + + " + ) + + (setq org-export-global-macros + (append + '(("sidenote" + . "@@html:$2@@") + ("epigraph" . "@@html:
$1
$2
@@") + ("epigraph_single" . "@@html:
$1
@@") + ("epigraph3" . "@@html:
$1
$2, $3
@@") + ("kbd" . "@@html:$1@@@@latex:\\texttt{$1}@@") + ("margimg" + . "@@html:@@") + ("countdown" . "@@html:@@") + + + org-export-global-macros))) + + + (defvar z-preamble + " +
+ \"Site + + +
+
Updated: %C
+ + " + ) + + (defvar z-postamble + "
+
+
+ Copyright © 2022-2025 Zaine Qayyum. All rights reserved unless otherwise noted.
+
+ Created with %c on Arch GNU/Linux +
+
") + + + + (defun z/comments-file-p (file) + "Return non-nil if FILE has comments enabled. + A file has comments if: + - It has a #+COMMENTS: keyword with value \"t\" (case-insensitive)." + (when (file-exists-p file) + (with-temp-buffer + (insert-file-contents file) + (org-mode) + (let* ((keywords (org-collect-keywords '("COMMENTS"))) + (comments (cadr (assoc "COMMENTS" keywords)))) + (and comments + (string-match-p "^\\s-*t\\s-*$" + (downcase comments))))))) + + (defun z/comments-file-slug (file) + "Return page slug from #+SLUG: or fallback to filename." + (with-temp-buffer + (insert-file-contents file) + (org-mode) + (let* ((keywords (org-collect-keywords '("SLUG"))) + (slug (cadr (assoc "SLUG" keywords)))) + (if (and slug (string-match-p "\\S-" slug)) + slug + (file-name-base file))))) + + (defun z/org-html-insert-comments-into-body (body backend info) + "Insert comments section at the end of the document body." + (when (org-export-derived-backend-p backend 'html) + (let ((input-file (plist-get info :input-file))) + (if (and input-file + (z/comments-file-p input-file)) + (let* ((slug (z/comments-file-slug input-file)) + (comments-html + (format + " +
+

Comments

+ +
+ +
+ +
+ + + + + +
+
+ " + slug))) + (concat body comments-html)) + body)))) + + + (defun z/org-html-add-body-classes (output backend info) + "Add layout-related classes to based on file metadata." + (when (org-export-derived-backend-p backend 'html) + (let* ((input-file (plist-get info :input-file)) + (classes + (delq nil + (list + (when (and input-file + (z/wip-file-p input-file)) + "wip") + (when (and input-file + (z/no-sidenotes-file-p input-file)) + "no-sidenotes"))))) + (if classes + (replace-regexp-in-string + "]*\\)>" + (format "" + (string-join classes " ")) + output) + output)))) + + (add-to-list 'org-export-filter-final-output-functions + #'z/org-html-add-body-classes) + (add-to-list 'org-export-filter-body-functions + #'z/org-html-insert-comments-into-body) + + + + (setq org-html-htmlize-output-type 'css) + + (org-export-define-derived-backend 'z-html 'html + :filters-alist '((:filter-final-output . z/insert-filetags-after-title))) + + (defun z/org-main-prep (_project) + (z/generate-recently-updated-org 26)) + + (defun z/z-publish-to-html (plist filename pub-dir) + "Publish FILENAME to HTML using the z-html backend." + (org-publish-org-to 'z-html filename ".html" plist pub-dir)) + + ;; Define the publishing project + (setq org-publish-project-alist + `(("org-main" + :recursive t + :base-directory "~/master-folder/org_files/org_web/" + :publishing-function z/z-publish-to-html + :publishing-directory "~/master-folder/org_files/org_web/output" + :base-extension "org" + :auto-sitemap t + :sitemap-filename "sitemap.org" + :sitemap-title "Sitemap" + :sitemap-sort-files chronologically + :html-preamble ,z-preamble + :html-postamble ,z-postamble + :html-head ,z-shared-head + :preparation-function z/org-main-prep + ) + + ("org-categories-sitemap" + :recursive t + :base-directory "~/master-folder/org_files/org_web/home" + :publishing-directory "~/master-folder/org_files/org_web/output/home" + :base-extension "org" + :auto-sitemap t + :sitemap-filename "categories.org" + :sitemap-title "Categories" + :sitemap-function z/categories-sitemap + :html-preamble ,z-preamble + :html-postamble ,z-postamble + :html-head ,z-shared-head + ) + ("org-posts" + :base-directory "~/master-folder/org_files/org_web/posts" + :publishing-directory "~/master-folder/org_files/org_web/output/posts/" + :recursive t + :base-extension "org" + :publishing-function z/z-publish-to-html + :with-author nil + :with-creator nil + :html-validation-link nil + :with-toc t + :section-numbers t + :html-preamble ,z-preamble + :html-postamble ,z-postamble + :auto-sitemap t + :sitemap-filename "posts-list.org" + :sitemap-title "Posts List" + :sitemap-style list + :sitemap-function z/posts-sitemap + :sitemap-sort-files anti-chronologically + :html-head ,z-shared-head + ) + ("org-blogs" + :base-directory "~/master-folder/org_files/org_web/blogs" + :publishing-directory "~/master-folder/org_files/org_web/output/blogs/" + :recursive t + :base-extension "org" + :publishing-function z/z-publish-to-html + :with-author nil + :with-creator nil + :html-validation-link nil + :html-preamble ,z-preamble + :html-postamble ,z-postamble + :auto-sitemap t + :sitemap-filename "blogs-list.org" + :sitemap-title "Blogs List" + :sitemap-style list + :sitemap-function z/blogs-sitemap + :sitemap-sort-files anti-chronologically + :html-head ,z-shared-head + ) + ("org-books" + :base-directory "~/master-folder/org_files/org_web/books/" + :publishing-directory "~/master-folder/org_files/org_web/output/books/" + :recursive t + :base-extension "org" + :publishing-function z/z-publish-to-html + :with-author nil + :with-creator nil + :html-validation-link nil + :html-preamble ,z-preamble + :html-postamble ,z-postamble + :auto-sitemap t + :sitemap-filename "books-list.org" + :sitemap-title "Books List" + :sitemap-style list + :sitemap-function z/books-sitemap + :sitemap-sort-files anti-chronologically + :html-head ,z-shared-head + ) + ("org-2025" + :base-directory "~/master-folder/org_files/org_web/blogs/2025/" + :publishing-directory "~/master-folder/org_files/org_web/output/blogs/2025/" + :recursive t + :base-extension "org" + :publishing-function z/z-publish-to-html + :with-author nil + :with-creator nil + :html-validation-link nil + :html-preamble ,z-preamble + :html-postamble ,z-postamble + :auto-sitemap t + :sitemap-filename "2025-list.org" + :sitemap-title "2025 List" + :sitemap-style list + :sitemap-function z/2025-sitemap + :sitemap-sort-files anti-chronologically + :html-head ,z-shared-head + ) + ("org-career" + :base-directory "~/master-folder/org_files/org_web/posts/career/" + :publishing-directory "~/master-folder/org_files/org_web/output/posts/career/" + :recursive t + :base-extension "org" + :publishing-function z/z-publish-to-html + :with-author nil + :with-creator nil + :html-validation-link nil + :html-preamble ,z-preamble + :html-postamble ,z-postamble + :auto-sitemap t + :sitemap-filename "career-list.org" + :sitemap-title "Career List" + :sitemap-style list + :sitemap-function z/career-sitemap + :sitemap-sort-files anti-chronologically + :html-head ,z-shared-head + ) + ("wip-pages" + :base-directory "~/master-folder/org_files/org_web/" + :publishing-directory "~/master-folder/org_files/org_web/output/" + :recursive t + :base-extension "org" + :publishing-function z/z-publish-to-html + :with-author nil + :with-creator nil + :html-validation-link nil + :html-preamble ,z-preamble + :html-postamble ,z-postamble + :auto-sitemap t + :sitemap-filename "wip.org" + :sitemap-title "Work in progress" + :sitemap-style list + :sitemap-function z/wip-sitemap + :sitemap-sort-files anti-chronologically + :html-head ,z-shared-head + ) + ("org-tags" + :base-directory "~/master-folder/org_files/org_web/tags" + :publishing-directory "~/master-folder/org_files/org_web/output/tags" + :recursive t + :base-extension "org" + :publishing-function org-html-publish-to-html + :with-author nil + :with-creator nil + :html-preamble ,z-preamble + :html-postamble ,z-postamble + :html-head ,z-shared-head) + ("org-assets" + :base-directory "~/master-folder/org_files/org_web/assets/" + :base-extension "css\\|js\\|png\\|jpg\\|gif\\|svg\\|pdf\\|woff\\|woff2\\|ttf" + :publishing-directory "~/master-folder/org_files/org_web/output/assets/" + :recursive t + :publishing-function org-publish-attachment) + )) + + + (delete-directory "~/master-folder/org_files/org_web/output/" t) + (delete-directory "~/master-folder/org_files/org_web/tags/" t) + (message "Directory deleted") + + (z/write-tag-pages) + (org-publish-all t) + (message "Build complete!") + + ;;; build-site.el ends here +#+END_SRC + +* Publish Script +/Note: This is legacy, just keeping it here for logs/ + +As the project is hosted on Github, I have created a small script that is able to push changes to the remote repository, which Cloudflare will automatically detect and rebuild the website: + +#+BEGIN_SRC bash + + #!/bin/bash + + # Exit immediately if any command fails + set -e + + # Define paths + ORG_OUTPUT_DIR="$HOME/master-folder/org_files/org_web/output" + + cd "$ORG_OUTPUT_DIR" + + echo "HTML published to: $ORG_OUTPUT_DIR" + + # Git commands + echo "Adding changes to Git..." + git add . + + echo "Committing..." + git commit -m "Auto-publish on $(date)" || echo "Nothing to commit." + + echo "Pushing to GitHub..." + git push origin main + + echo "Done! Changes pushed and Cloudflare should rebuild the site." + +#+END_SRC + +[fn:1] See: [[../blogs/publish-pages.org][this link]] diff --git a/home/setup.org~ b/home/setup.org~ new file mode 100644 index 0000000..2614f13 --- /dev/null +++ b/home/setup.org~ @@ -0,0 +1,684 @@ +#+TITLE: Setup +#+OPTIONS: toc:nil num:nil +#+COMMENTS: t +#+SLUG: setup + +* Introduction + +/Last updated: <2025-11-06 Thu 21:16>/ + +See this link[fn:1] for a more generic introduction on how to publish websites using ~org-publish~. + +This page will highlight the ~build-script.el~ used to generate this website. This entire website is generated through org files using the command: + +#+BEGIN_SRC bash +emacs -Q --script build-site.el +#+END_SRC + +* The script + +The script is as follows, at the start I have some metadata relating to the file, followed by package management,then the declaration of variables/functions and finally the ~org-publish-project-alist~ which handles nearly all of the project generation instructions. + +/The following code snippet will probably change often, as I can never seem to stick to a single configuration 😀/ + +#+BEGIN_SRC emacs-lisp +;;; build-site.el --- Publish my website using org-publish -*- lexical-binding: t; -*- + +;; Author: Zaine Qayyum +;; Created: 2025-08-08 +;; Purpose: Build and publish my static site from Org files. + +;;; Commentary: +;; Run this file with: +;; emacs -Q --script build-site.el + +;;; Code: + +(require 'package) +(setq package-user-dir (expand-file-name "./.packages")) +(setq package-archives '(("melpa" . "https://melpa.org/packages/") + ("elpa" . "https://elpa.gnu.org/packages/"))) + +(require 'ox-publish) +(require 'cl-lib) +(require 'org) +(require 'ox) +(require 'ox-html) + +;; Initialize the package system +(package-initialize) +(unless package-archive-contents + (package-refresh-contents)) + +;; Install dependencies +(package-install 'htmlize) +(add-to-list 'load-path "~/master-folder/org_files/org_web/") +(require 'htmlize) + +(setq org-html-htmlize-output-type 'css) + +(defvar z-shared-head + " + + + + + + + +" + ) + +(setq org-export-global-macros + (append + '(("sidenote" + . "@@html:$2@@") + ("epigraph" . "@@html:
$1
$2
@@") + ("epigraph_single" . "@@html:
$1
@@") + ("epigraph3" . "@@html:
$1
$2, $3
@@") + ("kbd" . "@@html:$1@@@@latex:\\texttt{$1}@@") + ("margimg" + . "@@html:@@") +("countdown" . "@@html:@@") + + + org-export-global-macros))) + + +(defvar z-preamble + " +
+ \"Site + + +
+
Updated: %C
+ +" +) + +(defvar z-postamble + "
+
+
+Copyright © 2022-2025 Zaine Qayyum. All rights reserved unless otherwise noted.
+
+Created with %c on Arch GNU/Linux +
+
") + + +(defun z/posts-sitemap (title list) + "sitemap that lists post links as bullet points with dates and tags." + (concat + "#+TITLE: " title "\n" + "#+OPTIONS: toc:nil num:nil \n\n" + "See the categories: @@html:Categories@@\n\n" + "* Posts:\n" + (mapconcat + (lambda (entry) + (let* ((link (car entry)) + ;; extract relative file name from the link + (filename (if (string-match "\\[\\[file:\\([^]]+\\)\\]" link) + (match-string 1 link) + link)) + (full-path (expand-file-name filename "~/master-folder/org_files/org_web/posts/")) + (date-str "no date") + (tags-str "")) + ;; Get publish date + (let ((date (org-publish-find-date full-path org-publish-project-alist))) + (when date + (setq date-str (format-time-string "%d-%m-%Y %H:%M" date)))) + ;; Get FILETAGS from file buffer + (when (file-exists-p full-path) + (with-temp-buffer + (insert-file-contents full-path) + (org-mode) + (let* ((tags (cadr (assoc "FILETAGS" (org-collect-keywords '("FILETAGS")))))) + (when tags + (setq tags-str (mapconcat (lambda (tag) + (format "@@html: %s @@" tag tag)) + (split-string tags ":" t) ;; <- Splits by ":" and removes empty strings + " ")))))) + ;; Final line output + (format "- %s @@html:%s@@ %s" link date-str tags-str))) + (cdr list) + "\n"))) + + +(defun z/blogs-sitemap (title list) + "sitemap that lists blog links as bullet points with dates and tags." + (concat + "#+TITLE: " title "\n" + "#+OPTIONS: toc:nil num:nil \n\n" + "See the categories: @@html:Categories@@\n\n" + "* Blogs:\n" + (mapconcat + (lambda (entry) + (let* ((link (car entry)) + ;; extract relative file name from the link + (filename (if (string-match "\\[\\[file:\\([^]]+\\)\\]" link) + (match-string 1 link) + link)) + (full-path (expand-file-name filename "~/master-folder/org_files/org_web/blogs/")) + (date-str "no date") + (tags-str "")) + ;; Get publish date + (let ((date (org-publish-find-date full-path org-publish-project-alist))) + (when date + (setq date-str (format-time-string "%d-%m-%Y %H:%M" date)))) + ;; Get FILETAGS from file buffer + (when (file-exists-p full-path) + (with-temp-buffer + (insert-file-contents full-path) + (org-mode) + (let* ((tags (cadr (assoc "FILETAGS" (org-collect-keywords '("FILETAGS")))))) + (when tags + (setq tags-str (mapconcat (lambda (tag) + (format "@@html: %s @@" tag tag)) + (split-string tags ":" t) ;; <- Splits by ":" and removes empty strings + " ")))))) + ;; Final line output + (format "- %s @@html:%s@@ %s" link date-str tags-str))) + (cdr list) + "\n"))) + + +(defun z/books-sitemap (title list) + "sitemap that lists books." + (concat + "#+TITLE: " title "\n" + "#+OPTIONS: toc:nil num:nil \n\n" + "See the categories: @@html:Categories@@\n\n" + "* Book Notes:\n" + (mapconcat + (lambda (entry) + (let* ((link (car entry)) + ;; extract relative file name from the link + (filename (if (string-match "\\[\\[file:\\([^]]+\\)\\]" link) + (match-string 1 link) + link)) + (full-path (expand-file-name filename "~/master-folder/org_files/org_web/books/")) + (date-str "no date") + (tags-str "")) + ;; Get publish date + (let ((date (org-publish-find-date full-path org-publish-project-alist))) + (when date + (setq date-str (format-time-string "%d-%m-%Y %H:%M" date)))) + ;; Get FILETAGS from file buffer + (when (file-exists-p full-path) + (with-temp-buffer + (insert-file-contents full-path) + (org-mode) + (let* ((tags (cadr (assoc "FILETAGS" (org-collect-keywords '("FILETAGS")))))) + (when tags + (setq tags-str (mapconcat (lambda (tag) + (format "@@html: %s @@" tag tag)) + (split-string tags ":" t) ;; <- Splits by ":" and removes empty strings + " ")))))) + ;; Final line output + (format "- %s @@html:%s@@ %s" link date-str tags-str))) + (cdr list) + "\n"))) + +(defun z/2025-sitemap (title list) + "Sitemap that lists 2025 posts grouped by month, with dates and FILETAGS." + (let ((output (concat + "#+TITLE: " title "\n" + "#+OPTIONS: toc:nil num:nil \n\n" + "See the categories: @@html:Categories@@\n\n" + "* 2025\n")) + (current-month nil)) + + ;; `list` is what org-publish passes in; we skip its car (top-level title node) + (dolist (entry (cdr list)) + ;; In this setup each ENTRY is usually (LINK . OTHER-STUFF) + (when (consp entry) + (let* ((link (car entry)) + ;; Extract relative filename from the [[file:...][...]] link + (filename (if (string-match "\\[\\[file:\\([^]]+\\)\\]" link) + (match-string 1 link) + link)) + (full-path (expand-file-name + filename + "~/master-folder/org_files/org_web/blogs/2025/")) + (date (when (file-exists-p full-path) + (org-publish-find-date full-path org-publish-project-alist))) + (date-str (if date + (format-time-string "%d-%m-%Y %H:%M" date) + "no date")) + (month-str (if date + (format-time-string "%B %Y" date) ; e.g. "August 2025" + "No date")) + (tags-str "")) + + ;; Collect FILETAGS from the file + (when (file-exists-p full-path) + (with-temp-buffer + (insert-file-contents full-path) + (org-mode) + (let ((tags (cadr (assoc "FILETAGS" + (org-collect-keywords '("FILETAGS")))))) + (when tags + (setq tags-str + (mapconcat + (lambda (tag) + (format "@@html: \ +%s @@" tag tag)) + (split-string tags ":" t) + " ")))))) + + ;; Insert month heading whenever month changes + (unless (equal month-str current-month) + (setq current-month month-str) + (setq output (concat output "\n** " month-str "\n"))) + + ;; Insert the actual entry + (setq output + (concat output + (format "- %s @@html:%s@@ %s\n" + link date-str tags-str)))))) + + output)) + +(defun z/career-sitemap (title list) + "Sitemap that lists careers posts grouped by month, with dates and FILETAGS." + (let ((output (concat + "#+TITLE: " title "\n" + "#+OPTIONS: toc:nil num:nil \n\n" + "See the categories: @@html:Categories@@\n\n" + "* 2025\n" + "See the following page for more details: @@html:Career Intro@@\n" + )) + (current-month nil)) + + ;; `list` is what org-publish passes in; we skip its car (top-level title node) + (dolist (entry (cdr list)) + ;; In this setup each ENTRY is usually (LINK . OTHER-STUFF) + (when (consp entry) + (let* ((link (car entry)) + ;; Extract relative filename from the [[file:...][...]] link + (filename (if (string-match "\\[\\[file:\\([^]]+\\)\\]" link) + (match-string 1 link) + link)) + (full-path (expand-file-name + filename + "~/master-folder/org_files/org_web/posts/career/")) + (date (when (file-exists-p full-path) + (org-publish-find-date full-path org-publish-project-alist))) + (date-str (if date + (format-time-string "%d-%m-%Y %H:%M" date) + "no date")) + (month-str (if date + (format-time-string "%B %Y" date) ; e.g. "August 2025" + "No date")) + (tags-str "")) + + ;; Collect FILETAGS from the file + (when (file-exists-p full-path) + (with-temp-buffer + (insert-file-contents full-path) + (org-mode) + (let ((tags (cadr (assoc "FILETAGS" + (org-collect-keywords '("FILETAGS")))))) + (when tags + (setq tags-str + (mapconcat + (lambda (tag) + (format "@@html: \ +%s @@" tag tag)) + (split-string tags ":" t) + " ")))))) + + ;; Insert month heading whenever month changes + (unless (equal month-str current-month) + (setq current-month month-str) + (setq output (concat output "\n** " month-str "\n"))) + + ;; Insert the actual entry + (setq output + (concat output + (format "- %s @@html:%s@@ %s\n" + link date-str tags-str)))))) + + output)) + + + + + +(defun z/categories-sitemap (title _list) + "Generate a categories page by scanning tags across org-posts and org-blogs." + (let* ((expanded (org-publish-expand-projects org-publish-project-alist)) + (posts (assoc "org-posts" expanded)) + (blogs (assoc "org-blogs" expanded)) + ;; DO NOT set :exclude to "" — it excludes everything + (files (cl-remove-duplicates + (append (and posts (org-publish-get-base-files posts)) + (and blogs (org-publish-get-base-files blogs))) + :test #'file-equal-p)) + ;; optional: drop the generated sitemaps + (files (cl-remove-if + (lambda (f) + (member (file-name-nondirectory f) + '("posts-list.org" "blogs-list.org" "sitemap.org" "categories.org"))) + files)) + (counts (make-hash-table :test 'equal))) + ;;(message "files I will scan: %S" files) + (dolist (f files) + (when (file-readable-p f) + (with-temp-buffer + (insert-file-contents f) + (org-mode) + (let* ((kw (org-collect-keywords '("FILETAGS" "TAGS"))) + (raw (car (or (cdr (assoc "FILETAGS" kw)) + (cdr (assoc "TAGS" kw)))))) + (when raw + (dolist (tag (split-string raw ":" t)) + (puthash tag (1+ (gethash tag counts 0)) counts))))))) + + (let (tags) + (maphash (lambda (k _) (push k tags)) counts) + (setq tags (sort tags #'string-lessp)) + (concat + "#+TITLE: " title "\n#+OPTIONS: toc:nil num:nil title:nil\n\n* Categories (Includes both blogs and posts)\n" + (if tags + (mapconcat + (lambda (tag) + (format "- [[file:tags/%s.org][@@html:%s@@]] (%d)" + (z/tag-slug tag) tag (gethash tag counts)) + ) + tags + "\n") + "_No tags found yet._"))))) + + +(defun z/tag-slug (s) + "Turn a tag into a safe filename." + (let ((down (downcase s))) + (replace-regexp-in-string "[^a-z0-9]+" "-" down))) + +(defun z/collect-post-files () + "Return all .org files from org-posts and org-blogs." + (let* ((expanded (org-publish-expand-projects org-publish-project-alist)) + (posts (assoc "org-posts" expanded)) + (blogs (assoc "org-blogs" expanded))) + (cl-remove-duplicates + (append (and posts (org-publish-get-base-files posts)) + (and blogs (org-publish-get-base-files blogs))) + :test #'file-equal-p))) + +(defun z/gather-tag-index () + "Return hash: tag -> list of (FILE TITLE DATE-ISO)." + (let ((idx (make-hash-table :test 'equal))) + (dolist (f (z/collect-post-files)) + (when (and (string-match-p "\\.org\\'" f) + (file-readable-p f) + ;; ignore generated lists + (not (member (file-name-nondirectory f) + '("posts-list.org" "blogs-list.org" "sitemap.org" "categories.org")))) + (with-temp-buffer + (insert-file-contents f) + (org-mode) + (let* ((kw (org-collect-keywords '("TITLE" "FILETAGS" "TAGS" "DATE"))) + (title (or (car (cdr (assoc "TITLE" kw))) + (file-name-base f))) + (date (or (car (cdr (assoc "DATE" kw))) "")) ;; optional + (raw (car (or (cdr (assoc "FILETAGS" kw)) + (cdr (assoc "TAGS" kw)))))) + (when raw + (dolist (tag (split-string raw ":" t)) + (push (list f title date) (gethash tag idx)))))))) + idx)) + +(defun z/write-tag-pages () + "Generate tags/*.org pages listing posts for each tag." + (let* ((site-root (expand-file-name "~/master-folder/org_files/org_web/")) + (tags-dir (expand-file-name "tags" site-root))) + (unless (file-directory-p tags-dir) + (make-directory tags-dir t)) + (let ((idx (z/gather-tag-index))) + (maphash + (lambda (tag items) + (let* ((slug (z/tag-slug tag)) + (outfile (expand-file-name (format "%s.org" slug) tags-dir))) + (with-temp-file outfile + (insert (format "#+TITLE: Tag: %s\n#+OPTIONS: toc:nil num:nil title:nil \n\n* Posts tagged %s\n" + tag tag)) + ;; sort newest first if DATE present + (setq items (sort items (lambda (a b) (string> (nth 2 a) (nth 2 b))))) + (dolist (it items) + (let* ((file (nth 0 it)) + (title (nth 1 it)) + (rel (file-relative-name file tags-dir))) + ;; link to the source .org; org-publish will rewrite to the .html + (insert (format "- [[file:%s][%s]]\n" rel title))))))) + idx) + ))) + +(defun z/filetags-html (info) + "Return an HTML snippet for FILETAGS from INFO, or nil if none." + (let ((tags (plist-get info :filetags))) + (when tags + (format + "
%s
\n" + (mapconcat (lambda (tag) + (format " %s " tag)) + tags " "))))) + +(defun z/insert-filetags-after-title (output backend info) + "Insert FILETAGS after the first

in OUTPUT." + (if (org-export-derived-backend-p backend 'html) + (let ((block (z/filetags-html info))) + (if (and block + (string-match "\\(]*class=[\"']title[\"'][^>]*>.*?

\\)" output)) + (replace-match (concat "\\1\n" block) t nil output) + output)) + output)) + +(org-export-define-derived-backend 'z-html 'html + :filters-alist '((:filter-final-output . z/insert-filetags-after-title))) + +(defun z/z-publish-to-html (plist filename pub-dir) + "Publish FILENAME to HTML using the z-html backend." + (org-publish-org-to 'z-html filename ".html" plist pub-dir)) + + +;; Define the publishing project +(setq org-publish-project-alist + `(("org-main" + :recursive t + :base-directory "~/master-folder/org_files/org_web/" + :publishing-function z/z-publish-to-html + :publishing-directory "~/master-folder/org_files/org_web/output" + :base-extension "org" + :auto-sitemap t + :sitemap-filename "sitemap.org" + :sitemap-title "Sitemap" + :sitemap-sort-files chronologically + :html-preamble ,z-preamble + :html-postamble ,z-postamble + :html-head ,z-shared-head + ) + + ("org-categories-sitemap" + :recursive t + :base-directory "~/master-folder/org_files/org_web/" + :publishing-directory "~/master-folder/org_files/org_web/output" + :base-extension "org" + :auto-sitemap t + :sitemap-filename "categories.org" + :sitemap-title "Categories" + :sitemap-function z/categories-sitemap + :html-preamble ,z-preamble + :html-postamble ,z-postamble + :html-head ,z-shared-head + ) + ("org-posts" + :base-directory "~/master-folder/org_files/org_web/posts" + :publishing-directory "~/master-folder/org_files/org_web/output/posts/" + :recursive t + :base-extension "org" + :publishing-function z/z-publish-to-html + :with-author nil + :with-creator nil + :html-validation-link nil + :with-toc t + :section-numbers t + :html-preamble ,z-preamble + :html-postamble ,z-postamble + :auto-sitemap t + :sitemap-filename "posts-list.org" + :sitemap-title "Posts List" + :sitemap-style list + :sitemap-function z/posts-sitemap + :sitemap-sort-files anti-chronologically + :html-head ,z-shared-head + ) + ("org-blogs" + :base-directory "~/master-folder/org_files/org_web/blogs" + :publishing-directory "~/master-folder/org_files/org_web/output/blogs/" + :recursive t + :base-extension "org" + :publishing-function z/z-publish-to-html + :with-author nil + :with-creator nil + :html-validation-link nil + :html-preamble ,z-preamble + :html-postamble ,z-postamble + :auto-sitemap t + :sitemap-filename "blogs-list.org" + :sitemap-title "Blogs List" + :sitemap-style list + :sitemap-function z/blogs-sitemap + :sitemap-sort-files anti-chronologically + :html-head ,z-shared-head + ) + ("org-books" + :base-directory "~/master-folder/org_files/org_web/books/" + :publishing-directory "~/master-folder/org_files/org_web/output/books/" + :recursive t + :base-extension "org" + :publishing-function z/z-publish-to-html + :with-author nil + :with-creator nil + :html-validation-link nil + :html-preamble ,z-preamble + :html-postamble ,z-postamble + :auto-sitemap t + :sitemap-filename "books-list.org" + :sitemap-title "Books List" + :sitemap-style list + :sitemap-function z/books-sitemap + :sitemap-sort-files anti-chronologically + :html-head ,z-shared-head + ) + ("org-2025" + :base-directory "~/master-folder/org_files/org_web/blogs/2025/" + :publishing-directory "~/master-folder/org_files/org_web/output/blogs/2025/" + :recursive t + :base-extension "org" + :publishing-function z/z-publish-to-html + :with-author nil + :with-creator nil + :html-validation-link nil + :html-preamble ,z-preamble + :html-postamble ,z-postamble + :auto-sitemap t + :sitemap-filename "2025-list.org" + :sitemap-title "2025 List" + :sitemap-style list + :sitemap-function z/2025-sitemap + :sitemap-sort-files anti-chronologically + :html-head ,z-shared-head + ) + ("org-career" + :base-directory "~/master-folder/org_files/org_web/posts/career/" + :publishing-directory "~/master-folder/org_files/org_web/output/posts/career/" + :recursive t + :base-extension "org" + :publishing-function z/z-publish-to-html + :with-author nil + :with-creator nil + :html-validation-link nil + :html-preamble ,z-preamble + :html-postamble ,z-postamble + :auto-sitemap t + :sitemap-filename "career-list.org" + :sitemap-title "Career List" + :sitemap-style list + :sitemap-function z/career-sitemap + :sitemap-sort-files anti-chronologically + :html-head ,z-shared-head + ) + ("org-tags" + :base-directory "~/master-folder/org_files/org_web/tags" + :publishing-directory "~/master-folder/org_files/org_web/output/tags" + :recursive t + :base-extension "org" + :publishing-function org-html-publish-to-html + :with-author nil + :with-creator nil + :html-preamble ,z-preamble + :html-postamble ,z-postamble + :html-head ,z-shared-head) + ("org-assets" + :base-directory "~/master-folder/org_files/org_web/assets/" + :base-extension "css\\|js\\|png\\|jpg\\|gif\\|svg\\|pdf\\|woff\\|woff2\\|ttf" + :publishing-directory "~/master-folder/org_files/org_web/output/assets/" + :recursive t + :publishing-function org-publish-attachment) + )) + +(delete-directory "~/master-folder/org_files/org_web/output/" t) +(delete-directory "~/master-folder/org_files/org_web/tags/" t) +(message "Directory deleted") + +(z/write-tag-pages) +(org-publish-all t) + +(message "Build complete!") + +;;; build-site.el ends here + +#+END_SRC + +* Publish Script + +As the project is hosted on Github, I have created a small script that is able to push changes to the remote repository, which Cloudflare will automatically detect and rebuild the website: + +#+BEGIN_SRC bash + + #!/bin/bash + + # Exit immediately if any command fails + set -e + + # Define paths + ORG_OUTPUT_DIR="$HOME/master-folder/org_files/org_web/output" + + cd "$ORG_OUTPUT_DIR" + + echo "HTML published to: $ORG_OUTPUT_DIR" + + # Git commands + echo "Adding changes to Git..." + git add . + + echo "Committing..." + git commit -m "Auto-publish on $(date)" || echo "Nothing to commit." + + echo "Pushing to GitHub..." + git push origin main + + echo "Done! Changes pushed and Cloudflare should rebuild the site." + +#+END_SRC + +[fn:1] See: [[~/master-folder/org_files/org_web/blogs/publish-pages.org][this link]] diff --git a/home/status.org b/home/status.org new file mode 100644 index 0000000..db75ce9 --- /dev/null +++ b/home/status.org @@ -0,0 +1,21 @@ +#+TITLE: Competency Status Board +#+OPTIONS: toc:nil num:nil +#+NO_SIDENOTES: t +#+COMMENTS: t +#+SLUG: competency-board + + +This page shows the status of the competencies I am working on (*grad*) + +#+BEGIN_EXPORT html +
+

Loading status…

+
+ + +#+END_EXPORT diff --git a/home/status.org~ b/home/status.org~ new file mode 100644 index 0000000..5d72a0a --- /dev/null +++ b/home/status.org~ @@ -0,0 +1,16 @@ +#+TITLE: Competency Status Board +#+OPTIONS: toc:nil num:nil +#+NO_SIDENOTES: t +#+COMMENTS: t +#+SLUG: competency-board + + +This page shows the status of the competencies I am working on (*grad*) + +#+BEGIN_EXPORT html +
+

Loading status…

+
+ + +#+END_EXPORT diff --git a/index.org b/index.org index 9d84a53..06ab062 100644 --- a/index.org +++ b/index.org @@ -6,95 +6,24 @@ This is a website built using Emacs Org-mode and published using ~org-publish~. {{{sidenote(1, The very first iteration of this website used the Angular framework\, only after a while I realised (as every Emacs lover does) that I want to make this an Emacs-centric project)}}} - Feel free to explore: -- [[file:about.org][About Page]] -- [[file:posts/posts-list.org][Posts Page]] +[[file:lima/limas-play-area.org][lima's page]] + +** Main Pages: + +- [[file:posts/career/career-list.org][Career Page]] +- [[file:books/books-list.org][Books Page]] +- [[file:blogs/2025/2025-list.org][2025]] + +** Extra (reference pages) + +- [[file:home/status.org][Status of competencies]] +- [[file:wip.org][Work In Progress Files]] +- [[file:home/backlog.org][Backlog Page]] +- [[file:recently-updated.org][Recent Updates Page]] - [[file:blogs/blogs-list.org][Blogs Page]] -- [[file:setup.org][Setup Page]] +- [[file:posts/posts-list.org][Posts Page]] +- [[file:home/setup.org][Setup Page]] - [[file:sitemap.org][Sitemap]] - -* How to publish web pages using ~org-publish~ - -This website is heavily inspired by some people who have decided to use ~org-publish~ as a way to convert ~org~ files into ~html~. I came across a few that took the plunge and decided to migrate from platforms like Wordpress and instead opted for a more transparent, text-based workflow. - -The Org website[fn:1] now looks a lot better than it used to. - -There is a [[https://en.wikipedia.org/wiki/Unix_philosophy][Unix philosophy]] that emphasises building simple and compact code that can be easily maintainable, and following this approach (do one thing well) aligns with this philosophy. - -So how does one go about publishing web pages through Emacs's ~org-publish~? The answer is quite simple. You probably will need to have some familiarity with ~elisp~[fn:2] although it's not a must. - -The first thing we will want to do is edit our ~init.el~ file, which is the *initialization* file. In this file we want to add the following: - -/This is a simple and quick way of getting this setup, although there is a better and more scalable approach to this, where we have a build script that runs each time we want to generate the project. See [[file:setup.org][here]] for more details./ - -#+begin_src emacs-lisp - - (setq org-publish-project-alist - '(("org-notes" - :base-directory "~/web" - :base-extension "org" - :publishing-directory "~/web" - :recursive t - :publishing-function org-html-publish-to-html - :with-author nil - :with-creator nil - :html-validation-link nil - :with-toc t - :section-numbers t - :html-head "" - ) - ("org-static" - :base-directory "~/web" - :base-extension "css\\|js\\|png\\|jpg\\|gif" - :publishing-directory "~/web/output" - :recursive t - :publishing-function org-publish-attachment) - ("website" :components ("org-notes" "org-static")))) - -#+end_src - -What this snippet of code is doing is it is telling ~org-publish~ what **configurations** we want to use when we end up running the publish command. Under ~~/web~ is where we want to include our files. Let us take a simple example and create an ~index.org~ file: - -#+begin_src emacs-lisp - - ,#+TITLE: Website Name - ,#+OPTIONS: toc:nil num:nil - ,#+HTML_HEAD: - - ,* Welcome! - - This is a simple website - -#+end_src - -There are some metadata at the very top as we can see, these help us customise each page the way we want to. Here I disabled the Table of Contents and section numbers just for this file. We can also create a simple ~styles.css~ file in the same directory. -After doing this, we can run the command: - -#+begin_src emacs-lisp - - M-x org-publish RET website - -#+end_src - -This will generate a html file inside the ~~/web/output/~ directory, and we can serve this file using any static site generation tools; I recommend python due to it's simplicity: - -#+begin_src bash - - cd ~/web/output - python3 -m http.server 8000 - -#+end_src - -And there we go! The files are now being hosted on ~http://localhost:8000/~ and we have a fully functioning workflow for converting org files into a static website. - -* Contact - -Feel free to reach out on GitHub: https://github.com/zainezq - - - -[fn:1] See: https://orgmode.org/manual/Publishing.html - -[fn:2] Emacs Lisp is a Lisp dialect made for Emacs. See: https://en.wikipedia.org/wiki/Emacs_Lisp + diff --git a/lima/WAKEUPTHEALIENSARECOMING.org b/lima/WAKEUPTHEALIENSARECOMING.org new file mode 100644 index 0000000..d5e4b01 --- /dev/null +++ b/lima/WAKEUPTHEALIENSARECOMING.org @@ -0,0 +1,10 @@ +what would wake z up idk + +probably not my day yesterday + +oh but i'll tell you about my dream even though i don't really want to tell you + +it was my last day of school or something but it was in some big hall, looked like a mueseum or kinda like some theme park walk-through thing. saw some people that i didn't want to see blah blah but at the end of that school day (?) we all ended up in the main front area by this staircase ready to go, and then you came down the stairs to pick me up and i ran to you and hugged you ;( you asked me what happened, idk what i said to you anymore but this was all in front of everyone lmao and yeah we were newly married anddd one of my maths teachers asked us something and idk i think i said he's also a grandpa or something which was confusing because i also was talking about my sister's kids which they already knew about but everyone was still shocked + +okay ted talk over +basically i could feel the happiness i felt in it when i woke up and for some time after \ No newline at end of file diff --git a/lima/gems.org b/lima/gems.org new file mode 100644 index 0000000..d9af75d --- /dev/null +++ b/lima/gems.org @@ -0,0 +1 @@ +otherwise i'll never do it- probably won't but worth a try \ No newline at end of file diff --git a/lima/limas-play-area.org b/lima/limas-play-area.org new file mode 100644 index 0000000..43df492 --- /dev/null +++ b/lima/limas-play-area.org @@ -0,0 +1,19 @@ +* a pop of z! ⋆⭒˚.⋆ + +[[file:syntax.org][syntax101]] + +* caged, locked up, no escape + +[[file:WAKEUPTHEALIENSARECOMING.org][WAKEUPTHEALIENSARECOMING]] + +[[file:one.org][the real, and better version]] + +* 🌥 breathe + +[[file:braindump.org][braindump]] + +[[file:reminders.org][don't delete this silly]] + +* h will shock herself + +[[file:gems.org][h plans]] \ No newline at end of file diff --git a/lima/one.org b/lima/one.org new file mode 100644 index 0000000..e39f7e7 --- /dev/null +++ b/lima/one.org @@ -0,0 +1,13 @@ +twinkle twinkle little z + +how i wonder what you are + +up above the world so high + +like a diamond in the sky + +twinkle twinkle little z + +how i wonder what you are + +✩ˎˊ˗ \ No newline at end of file diff --git a/lima/syntax.org b/lima/syntax.org new file mode 100644 index 0000000..72af8f3 --- /dev/null +++ b/lima/syntax.org @@ -0,0 +1,65 @@ +#+OPTIONS: num:nil tags:t toc:t + +Okay the syntax for these files might take some time to get used to. But here is a quick summary (message me in case anything doesn't make sense). + +Headings / outlines are like: =* H1=, =** H2=, =*** H3= + +So you can create a new header like: + +#+BEGIN_SRC emacs-lisp + *** Example + // This is at level three (3 asterisks) + #+END_SRC + +- Emphasis: =*bold*= =/italic/= =_underline_= =+strike+= =~code~= ==verbatim== + +- Checkboxes: =- [ ] todo=, =- [X] done=, =- [-] partial= + +- TODO workflow: =* TODO Write post= → DONE; define states: =#+TODO: TODO DOING | DONE= + +- Timestamps: active =<2025-11-02 Sun 14:00>= ; inactive =[2025-11-02]= + +- Links: =[[https://example.com][desc]]= + +For a file tag, you can create a new file under the folder *lima*, make sure it has the ~.org~ extension. Then you simply can link it: + +#+begin_src emacs-lisp + +[[file:file_name.org][Alias]] + +#+end_src + +It will look like this: + +[[file:file_name.org][Alias]] + +This should be a quick start (once you get used to it, I'll update it with macros :)) + +ADD AS MANY FILES AS YOU WANT (just don't forget to link them!) + + +* New controls + +- I've changed your folder view on the file browser. Now you should see something like this: + + [[../assets/images/syntax/filebrowser.png]] + +- This is the entire directory for the website (shock). The folder that you used before was ~lima~, but now you can see all the other folders and files. If you make changes to any of them, the website doesn't update (due to scripting limitations). But if you edit anything inside the ~lima~ folder, it will update automatically as it was doing before. + +- If you want to add any images/drawings, go to the assets folder: + + [[../assets/images/syntax/assets.png]] + +- Here you can add images into the ~images~ folder, and any drawings in the ~sketchnotes~ folder. I would advise that you create a new folder inside the images/sketchnotes folder to group your assets that you want to include (solely so it doesn't look cluttered). For example: + + [[../assets/images/syntax/not_clutter.png]] + +- In order to actually use the asset in your file, you want to link the image or sketchnote using the following syntax: + + #+BEGIN_SRC emacs-lisp + + [[../assets/images/file_name.png]] + + [[../assets/sketchnotes/file_name.svg]] + + #+END_SRC diff --git a/lima/syntax.org~ b/lima/syntax.org~ new file mode 100644 index 0000000..4c64722 --- /dev/null +++ b/lima/syntax.org~ @@ -0,0 +1,35 @@ +Okay the syntax for these files might take some time to get used to. But here is a quick summary (message me in case anything doesn't make sense). + +Headings / outlines are like: =* H1=, =** H2=, =*** H3= + +So you can create a new header like: + +*** Example + +This is at level three (3 asterisks): + +- Emphasis: =*bold*= =/italic/= =_underline_= =+strike+= =~code~= ==verbatim== + +- Checkboxes: =- [ ] todo=, =- [X] done=, =- [-] partial= + +- TODO workflow: =* TODO Write post= → DONE; define states: =#+TODO: TODO DOING | DONE= + +- Timestamps: active =<2025-11-02 Sun 14:00>= ; inactive =[2025-11-02]= + +- Links: =[[https://example.com][desc]]= + +For a file tag, you can create a new file under the folder *lima*, make sure it has the ~.org~ extension. Then you simply can link it: + +#+begin_src emacs-lisp + +[[file:file_name.org][Alias]] + +#+end_src + +It will look like this: + +[[file:file_name.org][Alias]] + +This should be a quick start (once you get used to it, I'll update it with macros :)) + +ADD AS MANY FILES AS YOU WANT (just don't forget to link them!) \ No newline at end of file diff --git a/lisp/recently-updated.el b/lisp/recently-updated.el new file mode 100644 index 0000000..07b0a8d --- /dev/null +++ b/lisp/recently-updated.el @@ -0,0 +1,113 @@ +;;; recently-updated.el --- Generate recently-updated.org -*- lexical-binding: t; -*- + +(require 'cl-lib) +(require 'org) + +(defvar z-org-root + (file-name-as-directory + (expand-file-name "~/master-folder/org_files/org_web")) + "Root directory of the Org source files.") + +;; Which Org files should be skipped for Recently Updated +(defun z/recent-omit-org-p (rel-org) + "Return non-nil if REL-ORG (relative to `z-org-root`) should not be listed." + (let* ((name (file-name-nondirectory rel-org))) + (or + ;; assets-ish / tags + (string-prefix-p "assets/" rel-org) + (string-match-p "/assets/" rel-org) + (string-prefix-p "tags/" rel-org) + (string-match-p "/tags/" rel-org) + + ;; generated/meta orgs + (member name '("sitemap.org" + "categories.org" + "recently-updated.org" + "wip.org" + )) + + ;; any *-list.org sitemaps (posts-list.org, blogs-list.org, etc.) + (string-match-p "-list\\.org\\'" name)))) + +(defun z/read-org-title+mtime (file) + "Return (TITLE . MTIME) for Org FILE. + +TITLE from #+TITLE or file name. +MTIME is the file's modification time (real 'last updated')." + (with-temp-buffer + (insert-file-contents file) + (org-mode) + (let* ((props (org-collect-keywords '("TITLE"))) + (title-cell (assoc "TITLE" props)) + (title (or (and title-cell + (car (cdr title-cell))) + (file-name-base file))) + (mtime (nth 5 (file-attributes file)))) + (cons title mtime)))) + + +(defun z/read-org-title+date (file) + "Return (TITLE . TIME) for Org FILE. + +TITLE: +- from #+TITLE, or file-name if missing. + +TIME: +- from #+DATE if parseable as an org time string, +- otherwise from file's modification time." + (with-temp-buffer + (insert-file-contents file) + (org-mode) + (let* ((props (org-collect-keywords '("TITLE" "DATE"))) + ;; TITLE + (title-cell (assoc "TITLE" props)) + (title (or (and title-cell + (car (cdr title-cell))) + (file-name-base file))) + ;; DATE + (date-cell (assoc "DATE" props)) + (date-str (and date-cell + (car (cdr date-cell)))) + (time (or (and date-str + (ignore-errors + (org-time-string-to-time date-str))) + (nth 5 (file-attributes file))))) + (cons title time)))) + +(defun z/generate-recently-updated-org (&optional n) + "Generate recently-updated.org in `z-org-root`. + +Lists top N most recently updated Org pages +(excluding tags, sitemaps, *-list.org, etc). +Defaults to N=30." + (let* ((count (or n 30)) + (org-files (directory-files-recursively z-org-root "\\.org\\'")) + (items '())) + ;; Collect (REL PATH, TITLE, TIME) + (dolist (full org-files) + (let ((rel (file-relative-name full z-org-root))) + (unless (z/recent-omit-org-p rel) + (pcase-let* ((`(,title . ,time) (z/read-org-title+mtime full))) + (push (list rel title time) items))))) + ;; Sort newest first by TIME + (setq items + (sort items + (lambda (a b) + (time-less-p (nth 2 b) (nth 2 a))))) + ;; Trim + (setq items (cl-subseq items 0 (min count (length items)))) + ;; Write Org file + (with-temp-file (expand-file-name "recently-updated.org" z-org-root) + (insert "#+TITLE: Recently Updated\n" + "#+OPTIONS: toc:nil num:nil\n\n" + "* Recently Updated (top 26 files - per lima's request)\n") + (dolist (it items) + (let* ((rel (nth 0 it)) + (title (nth 1 it)) + (time (nth 2 it)) + (datestr (format-time-string "%Y-%m-%d %H:%M" time))) + (insert (format "- [[file:%s][%s]] @@html:%s@@\n" + rel title datestr)))))) + (message "Wrote recently-updated.org")) + +(provide 'recently-updated) diff --git a/lisp/sidenotes.el b/lisp/sidenotes.el new file mode 100644 index 0000000..1f9be7b --- /dev/null +++ b/lisp/sidenotes.el @@ -0,0 +1,26 @@ +(defun z/no-sidenotes-file-p (file) + "Return non-nil if FILE should be rendered without sidenotes. + +A file is no-sidenotes if: + - It has a #+NO_SIDENOTES: keyword, or + - Its FILETAGS contain :NO_SIDENOTES: or :KANBAN:, or + - Any top-level heading contains \"KANBAN\"." + (when (file-exists-p file) + (with-temp-buffer + (insert-file-contents file) + (org-mode) + (let* ((case-fold-search t) + (keywords (org-collect-keywords + '("NO_SIDENOTES" "FILETAGS"))) + (flag (cadr (assoc "NO_SIDENOTES" keywords))) + (filetags (cadr (assoc "FILETAGS" keywords)))) + (or + (and flag (string-match-p "\\S-" flag)) + (and filetags + (string-match-p + ":\\(NO_SIDENOTES\\|KANBAN\\):" + (concat ":" filetags ":"))) + (save-excursion + (goto-char (point-min)) + (re-search-forward "^\\*+ .*KANBAN.*" nil t))))))) +(provide 'sidenotes) diff --git a/lisp/sitemaps.el b/lisp/sitemaps.el new file mode 100644 index 0000000..fe0dc03 --- /dev/null +++ b/lisp/sitemaps.el @@ -0,0 +1,341 @@ +(defun z/posts-sitemap (title list) + "sitemap that lists post links as bullet points with dates and tags." + (concat + "#+TITLE: " title "\n" + "#+OPTIONS: toc:nil num:nil \n\n" + "See the categories: @@html:Categories@@\n\n" + "* Posts:\n" + (mapconcat + (lambda (entry) + (let* ((link (car entry)) + ;; extract relative file name from the link + (filename (if (string-match "\\[\\[file:\\([^]]+\\)\\]" link) + (match-string 1 link) + link)) + (full-path (expand-file-name filename "~/master-folder/org_files/org_web/posts/")) + (date-str "no date") + (tags-str "")) + ;; Get publish date + (let ((date (org-publish-find-date full-path org-publish-project-alist))) + (when date + (setq date-str (format-time-string "%d-%m-%Y %H:%M" date)))) + ;; Get FILETAGS from file buffer + (when (file-exists-p full-path) + (with-temp-buffer + (insert-file-contents full-path) + (org-mode) + (let* ((tags (cadr (assoc "FILETAGS" (org-collect-keywords '("FILETAGS")))))) + (when tags + (setq tags-str (mapconcat (lambda (tag) + (format "@@html: %s @@" tag tag)) + (split-string tags ":" t) ;; <- Splits by ":" and removes empty strings + " ")))))) + ;; Final line output + (format "- %s @@html:%s@@ %s" link date-str tags-str))) + (cdr list) + "\n"))) + + +(defun z/blogs-sitemap (title list) + "sitemap that lists blog links as bullet points with dates and tags." + (concat + "#+TITLE: " title "\n" + "#+OPTIONS: toc:nil num:nil \n\n" + "See the categories: @@html:Categories@@\n\n" + "* Blogs:\n" + (mapconcat + (lambda (entry) + (let* ((link (car entry)) + ;; extract relative file name from the link + (filename (if (string-match "\\[\\[file:\\([^]]+\\)\\]" link) + (match-string 1 link) + link)) + (full-path (expand-file-name filename "~/master-folder/org_files/org_web/blogs/")) + (date-str "no date") + (tags-str "")) + ;; Get publish date + (let ((date (org-publish-find-date full-path org-publish-project-alist))) + (when date + (setq date-str (format-time-string "%d-%m-%Y %H:%M" date)))) + ;; Get FILETAGS from file buffer + (when (file-exists-p full-path) + (with-temp-buffer + (insert-file-contents full-path) + (org-mode) + (let* ((tags (cadr (assoc "FILETAGS" (org-collect-keywords '("FILETAGS")))))) + (when tags + (setq tags-str (mapconcat (lambda (tag) + (format "@@html: %s @@" tag tag)) + (split-string tags ":" t) ;; <- Splits by ":" and removes empty strings + " ")))))) + ;; Final line output + (format "- %s @@html:%s@@ %s" link date-str tags-str))) + (cdr list) + "\n"))) + + +(defun z/books-sitemap (title list) + "sitemap that lists books." + (concat + "#+TITLE: " title "\n" + "#+OPTIONS: toc:nil num:nil \n\n" + "See the categories: @@html:Categories@@\n\n" + "* Book Notes:\n" + (mapconcat + (lambda (entry) + (let* ((link (car entry)) + ;; extract relative file name from the link + (filename (if (string-match "\\[\\[file:\\([^]]+\\)\\]" link) + (match-string 1 link) + link)) + (full-path (expand-file-name filename "~/master-folder/org_files/org_web/books/")) + (date-str "no date") + (tags-str "")) + ;; Get publish date + (let ((date (org-publish-find-date full-path org-publish-project-alist))) + (when date + (setq date-str (format-time-string "%d-%m-%Y %H:%M" date)))) + ;; Get FILETAGS from file buffer + (when (file-exists-p full-path) + (with-temp-buffer + (insert-file-contents full-path) + (org-mode) + (let* ((tags (cadr (assoc "FILETAGS" (org-collect-keywords '("FILETAGS")))))) + (when tags + (setq tags-str (mapconcat (lambda (tag) + (format "@@html: %s @@" tag tag)) + (split-string tags ":" t) ;; <- Splits by ":" and removes empty strings + " ")))))) + ;; Final line output + (format "- %s @@html:%s@@ %s" link date-str tags-str))) + (cdr list) + "\n"))) + +(defun z/2025-sitemap (title list) + "Sitemap that lists 2025 posts grouped by month, with dates and FILETAGS." + (let ((output (concat + "#+TITLE: " title "\n" + "#+OPTIONS: toc:nil num:nil \n\n" + "See the categories: @@html:Categories@@\n\n" + "* 2025\n")) + (current-month nil)) + + ;; `list` is what org-publish passes in; we skip its car (top-level title node) + (dolist (entry (cdr list)) + ;; In this setup each ENTRY is usually (LINK . OTHER-STUFF) + (when (consp entry) + (let* ((link (car entry)) + ;; Extract relative filename from the [[file:...][...]] link + (filename (if (string-match "\\[\\[file:\\([^]]+\\)\\]" link) + (match-string 1 link) + link)) + (full-path (expand-file-name + filename + "~/master-folder/org_files/org_web/blogs/2025/")) + (date (when (file-exists-p full-path) + (org-publish-find-date full-path org-publish-project-alist))) + (date-str (if date + (format-time-string "%d-%m-%Y %H:%M" date) + "no date")) + (month-str (if date + (format-time-string "%B %Y" date) ; e.g. "August 2025" + "No date")) + (tags-str "")) + + ;; Collect FILETAGS from the file + (when (file-exists-p full-path) + (with-temp-buffer + (insert-file-contents full-path) + (org-mode) + (let ((tags (cadr (assoc "FILETAGS" + (org-collect-keywords '("FILETAGS")))))) + (when tags + (setq tags-str + (mapconcat + (lambda (tag) + (format "@@html: \ +%s @@" tag tag)) + (split-string tags ":" t) + " ")))))) + + ;; Insert month heading whenever month changes + (unless (equal month-str current-month) + (setq current-month month-str) + (setq output (concat output "\n** " month-str "\n"))) + + ;; Insert the actual entry + (setq output + (concat output + (format "- %s @@html:%s@@ %s\n" + link date-str tags-str)))))) + + output)) + + +(defun z/career-sitemap (title list) + "Sitemap that lists careers posts grouped by month, with dates and FILETAGS." + (let ((output (concat + "#+TITLE: " title "\n" + "#+OPTIONS: toc:nil num:nil \n\n" + "See the categories: @@html:Categories@@\n\n" + "* 2025\n" + "See the following page for more details: @@html:Career Intro@@\n" + )) + (current-month nil)) + + ;; `list` is what org-publish passes in; we skip its car (top-level title node) + (dolist (entry (cdr list)) + ;; In this setup each ENTRY is usually (LINK . OTHER-STUFF) + (when (consp entry) + (let* ((link (car entry)) + ;; Extract relative filename from the [[file:...][...]] link + (filename (if (string-match "\\[\\[file:\\([^]]+\\)\\]" link) + (match-string 1 link) + link)) + (full-path (expand-file-name + filename + "~/master-folder/org_files/org_web/posts/career/")) + (date (when (file-exists-p full-path) + (org-publish-find-date full-path org-publish-project-alist))) + (date-str (if date + (format-time-string "%d-%m-%Y %H:%M" date) + "no date")) + (month-str (if date + (format-time-string "%B %Y" date) ; e.g. "August 2025" + "No date")) + (tags-str "")) + + ;; Collect FILETAGS from the file + (when (file-exists-p full-path) + (with-temp-buffer + (insert-file-contents full-path) + (org-mode) + (let ((tags (cadr (assoc "FILETAGS" + (org-collect-keywords '("FILETAGS")))))) + (when tags + (setq tags-str + (mapconcat + (lambda (tag) + (format "@@html: \ +%s @@" tag tag)) + (split-string tags ":" t) + " ")))))) + + ;; Insert month heading whenever month changes + (unless (equal month-str current-month) + (setq current-month month-str) + (setq output (concat output "\n** " month-str "\n"))) + + ;; Insert the actual entry + (setq output + (concat output + (format "- %s @@html:%s@@ %s\n" + link date-str tags-str)))))) + + output)) + + + +(defun z/categories-sitemap (title _list) + "Generate a categories page by scanning tags across org-posts and org-blogs." + (let* ((expanded (org-publish-expand-projects org-publish-project-alist)) + (posts (assoc "org-posts" expanded)) + (blogs (assoc "org-blogs" expanded)) + ;; DO NOT set :exclude to "" — it excludes everything + (files (cl-remove-duplicates + (append (and posts (org-publish-get-base-files posts)) + (and blogs (org-publish-get-base-files blogs))) + :test #'file-equal-p)) + ;; optional: drop the generated sitemaps + (files (cl-remove-if + (lambda (f) + (member (file-name-nondirectory f) + '("posts-list.org" "blogs-list.org" "sitemap.org" "categories.org"))) + files)) + (counts (make-hash-table :test 'equal))) + ;;(message "files I will scan: %S" files) + (dolist (f files) + (when (file-readable-p f) + (with-temp-buffer + (insert-file-contents f) + (org-mode) + (let* ((kw (org-collect-keywords '("FILETAGS" "TAGS"))) + (raw (car (or (cdr (assoc "FILETAGS" kw)) + (cdr (assoc "TAGS" kw)))))) + (when raw + (dolist (tag (split-string raw ":" t)) + (puthash tag (1+ (gethash tag counts 0)) counts))))))) + + (let (tags) + (maphash (lambda (k _) (push k tags)) counts) + (setq tags (sort tags #'string-lessp)) + (concat + "#+TITLE: " title "\n#+OPTIONS: toc:nil num:nil title:nil\n\n* Categories (Includes both blogs and posts)\n" + (if tags + (mapconcat + (lambda (tag) + (format "- [[file:tags/%s.org][@@html:%s@@]] (%d)" + (z/tag-slug tag) tag (gethash tag counts)) + ) + tags + "\n") + "_No tags found yet._"))))) + +(defun z/wip-file-p (file) + "Return non-nil if FILE should be treated as WIP. +A file is WIP if: + - It has a #+WIP: keyword with any non-empty value, or + - Its FILETAGS contain :WIP:, or + - Any top-level heading contains the string \"WIP\" (case-insensitive)." + (when (file-exists-p file) + (with-temp-buffer + (insert-file-contents file) + (org-mode) + (let* ((case-fold-search t) + (keywords (org-collect-keywords '("WIP" "FILETAGS"))) + (wip (cadr (assoc "WIP" keywords))) + (filetags (cadr (assoc "FILETAGS" keywords)))) + (or + (and wip (string-match-p "\\S-" wip)) + (and filetags + (string-match-p ":WIP:" (concat ":" filetags ":"))) + (save-excursion + (goto-char (point-min)) + (re-search-forward "^\\*+ .*WIP.*" nil t))))))) + + +(defun z/wip-sitemap (title list) + "Sitemap that lists only entries whose source files are WIP." + (let* ((root-dir "~/master-folder/org_files/org_web/") ;; adjust if needed + (items + (delq + nil + (mapcar + (lambda (entry) + (when (consp entry) + (let* ((link (car entry)) + ;; Extract relative filename from [[file:...]] link if present + (filename (if (string-match "\\[\\[file:\\([^]]+\\)\\]" link) + (match-string 1 link) + link)) + (full-path (expand-file-name filename root-dir))) + (when (z/wip-file-p full-path) + (let* ((date (org-publish-find-date full-path org-publish-project-alist)) + (date-str (if date + (format-time-string "%d-%m-%Y %H:%M" date) + ""))) + (format "- %s @@html:%s@@" + link date-str)))))) + (cdr list))))) ; skip sitemap root + (concat + "#+TITLE: " title "\n" + "#+OPTIONS: toc:nil num:nil\n\n" + "* Work in progress\n" + (if items + (mapconcat #'identity items "\n") + "No items currently marked as WIP.\n")))) + + + + +(provide 'sitemaps) diff --git a/lisp/tags.el b/lisp/tags.el new file mode 100644 index 0000000..61b25f0 --- /dev/null +++ b/lisp/tags.el @@ -0,0 +1,85 @@ +(defun z/tag-slug (s) + "Turn a tag into a safe filename." + (let ((down (downcase s))) + (replace-regexp-in-string "[^a-z0-9]+" "-" down))) + +(defun z/collect-post-files () + "Return all .org files from org-posts and org-blogs." + (let* ((expanded (org-publish-expand-projects org-publish-project-alist)) + (posts (assoc "org-posts" expanded)) + (blogs (assoc "org-blogs" expanded))) + (cl-remove-duplicates + (append (and posts (org-publish-get-base-files posts)) + (and blogs (org-publish-get-base-files blogs))) + :test #'file-equal-p))) + +(defun z/gather-tag-index () + "Return hash: tag -> list of (FILE TITLE DATE-ISO)." + (let ((idx (make-hash-table :test 'equal))) + (dolist (f (z/collect-post-files)) + (when (and (string-match-p "\\.org\\'" f) + (file-readable-p f) + ;; ignore generated lists + (not (member (file-name-nondirectory f) + '("posts-list.org" "blogs-list.org" "sitemap.org" "categories.org")))) + (with-temp-buffer + (insert-file-contents f) + (org-mode) + (let* ((kw (org-collect-keywords '("TITLE" "FILETAGS" "TAGS" "DATE"))) + (title (or (car (cdr (assoc "TITLE" kw))) + (file-name-base f))) + (date (or (car (cdr (assoc "DATE" kw))) "")) ;; optional + (raw (car (or (cdr (assoc "FILETAGS" kw)) + (cdr (assoc "TAGS" kw)))))) + (when raw + (dolist (tag (split-string raw ":" t)) + (push (list f title date) (gethash tag idx)))))))) + idx)) + +(defun z/write-tag-pages () + "Generate tags/*.org pages listing posts for each tag." + (let* ((site-root (expand-file-name "~/master-folder/org_files/org_web/")) + (tags-dir (expand-file-name "tags" site-root))) + (unless (file-directory-p tags-dir) + (make-directory tags-dir t)) + (let ((idx (z/gather-tag-index))) + (maphash + (lambda (tag items) + (let* ((slug (z/tag-slug tag)) + (outfile (expand-file-name (format "%s.org" slug) tags-dir))) + (with-temp-file outfile + (insert (format "#+TITLE: Tag: %s\n#+OPTIONS: toc:nil num:nil title:nil \n\n* Posts tagged %s\n" + tag tag)) + ;; sort newest first if DATE present + (setq items (sort items (lambda (a b) (string> (nth 2 a) (nth 2 b))))) + (dolist (it items) + (let* ((file (nth 0 it)) + (title (nth 1 it)) + (rel (file-relative-name file tags-dir))) + ;; link to the source .org; org-publish will rewrite to the .html + (insert (format "- [[file:%s][%s]]\n" rel title))))))) + idx) + ))) + +(defun z/filetags-html (info) + "Return an HTML snippet for FILETAGS from INFO, or nil if none." + (let ((tags (plist-get info :filetags))) + (when tags + (format + "
%s
\n" + (mapconcat (lambda (tag) + (format " %s " tag)) + tags " "))))) + +(defun z/insert-filetags-after-title (output backend info) + "Insert FILETAGS after the first

in OUTPUT." + (if (org-export-derived-backend-p backend 'html) + (let ((block (z/filetags-html info))) + (if (and block + (string-match "\\(]*class=[\"']title[\"'][^>]*>.*?

\\)" output)) + (replace-match (concat "\\1\n" block) t nil output) + output)) + output)) + + +(provide 'tags) diff --git a/output/assets/images/gr.png b/output/assets/images/gr.png deleted file mode 100644 index c8314b3..0000000 Binary files a/output/assets/images/gr.png and /dev/null differ diff --git a/output/assets/images/org-roam-ui-graph.png b/output/assets/images/org-roam-ui-graph.png deleted file mode 100644 index c46d6f9..0000000 Binary files a/output/assets/images/org-roam-ui-graph.png and /dev/null differ diff --git a/output/assets/scripts/script.js b/output/assets/scripts/script.js deleted file mode 100644 index 4436a66..0000000 --- a/output/assets/scripts/script.js +++ /dev/null @@ -1,184 +0,0 @@ - - -/* Event listener function for the COPY BUTTON */ -document.addEventListener("DOMContentLoaded", function () { - document.querySelectorAll("pre.src").forEach(function (codeBlock) { - const button = document.createElement("button"); - button.innerText = "Copy"; - button.className = "copy-btn"; - - // Append button inside
-	codeBlock.appendChild(button);
-
-	button.addEventListener("click", function () {
-	    const text = codeBlock.innerText.replace(button.innerText, ""); // exclude button text
-	    navigator.clipboard.writeText(text.trim()).then(() => {
-		button.innerText = "Copied!";
-		setTimeout(() => (button.innerText = "Copy"), 1500);
-	    });
-	});
-    });
-});
-
-
-/* Event listener for footnotes and sidenotes*/
-document.addEventListener("DOMContentLoaded", () => {
-    document.querySelectorAll('a.footref[href^="#fn"]').forEach(ref => {
-	const sup = ref.closest("sup") || ref;
-	// idempotent: don't insert twice
-	if (sup.nextElementSibling && sup.nextElementSibling.classList?.contains("footnote-sidenote")) return;
-	
-	const targetId = ref.getAttribute("href").replace(/^#/, ""); // works for fn.2 or fn2
-
-	const anchor   = document.getElementById(targetId);
-	if (!anchor) return;
-	
-	const footdef = anchor.closest(".footdef") || anchor.parentElement;
-	if (!footdef) return;
-	
-	// 1) Prefer leaf paragraphs to avoid div+p duplication
-	let paras = footdef.querySelectorAll("p.footpara");
-	if (!paras.length) {
-	    // fallback: any .footpara elements that don't contain another .footpara
-	    paras = footdef.querySelectorAll(".footpara:not(:has(.footpara))");
-	}
-
-	// 2) Build HTML, de-duplicating by text content
-	let parts = [];
-	if (paras.length) {
-	    const seen = new Set();
-	    parts = Array.from(paras).map(p => {
-		const txt = p.textContent.trim().replace(/\s+/g, " ");
-		if (seen.has(txt)) return "";
-		seen.add(txt);
-		return p.innerHTML.trim();
-	    }).filter(Boolean);
-	}
-
-	// 3) Fallback: clean full block if no paras found
-	if (!parts.length) {
-	    const clone = footdef.cloneNode(true);
-	    clone.querySelectorAll("sup.footnum, a[role='doc-backlink']").forEach(n => n.remove());
-	    parts = [clone.innerHTML.trim()];
-	}
-
-	// 4) Insert the sidenote
-	const sn = document.createElement("span");
-	sn.className = "sidenote footnote-sidenote";
-	sn.setAttribute("data-fn", (ref.textContent || "").trim());
-	sn.innerHTML = parts.join(" ");
-	
-	sup.insertAdjacentElement("afterend", sn);
-    });
-});
-
-
-/* Function for setting the theme */
-(function(){
-    const root = document.documentElement;
-    const storageKey = "theme";
-    const saved = localStorage.getItem(storageKey);
-    if (saved === "dark" || saved === "light") {
-	root.setAttribute("data-theme", saved);
-    }
-    const btn = document.getElementById("theme-toggle");
-    if (!btn) return;
-    btn.addEventListener("click", () => {
-	const current = root.getAttribute("data-theme");
-	const next = current === "dark" ? "light" : "dark";
-	// If no current (auto), assume we’re toggling to dark first
-	const target = current ? next : "dark";
-	root.setAttribute("data-theme", target);
-	localStorage.setItem(storageKey, target);
-    });
-})();
-
-
-/* Event listener for scrolling and changing the active label on the TOC */
-document.addEventListener("DOMContentLoaded", () => {
-    const toc = document.querySelector("#text-table-of-contents");
-    if (!toc) { console.warn("No #text-table-of-contents found"); return; }
-
-    const links = toc.querySelectorAll('a[href^="#"]'); // '^=' is a starts with operator.
-    // Intro   matches  
-    if (!links.length) { console.warn("No ToC links found"); return; }
-
-    // Map: id -> link
-    const linkById = new Map();
-    links.forEach(a => {
-	const id = decodeURIComponent(a.getAttribute("href").slice(1));
-	const el = document.getElementById(id);
-	if (el) linkById.set(id, a);
-    });
-    if (!linkById.size) { console.warn("No matching headings with IDs"); return; }
-
-    // Headings to observe (h2–h4 usually)
-    const headings = [...document.querySelectorAll("h2[id], h3[id], h4[id]")]
-	  .filter(h => linkById.has(h.id));
-
-    // Helper to mark active
-    const setActive = (id) => {
-	links.forEach(a => {
-	    const active = a.getAttribute("href") === `#${id}`;
-	    a.classList.toggle("is-active", active);
-	    if (active) a.setAttribute("aria-current", "true");
-	    else a.removeAttribute("aria-current");
-	});
-    };
-
-    // Calculate sticky header offset in px
-    const headerOffsetPx = 6.5 * parseFloat(getComputedStyle(document.documentElement).fontSize);
-
-    // Track visible headings (id -> distance from top)
-    const visible = new Map();
-
-    const observer = new IntersectionObserver((entries) => {
-	entries.forEach(entry => {
-	    const id = entry.target.id;
-	    if (entry.isIntersecting) {
-		// How far from the top (after header offset)
-		const dist = entry.target.getBoundingClientRect().top - headerOffsetPx;
-		visible.set(id, dist);
-	    } else {
-		visible.delete(id);
-	    }
-	});
-	
-	if (visible.size) {
-	    // Choose the heading closest to the top (>= -headerOffset)
-	    const topMost = [...visible.entries()]
-		  .sort((a,b) => Math.abs(a[1]) - Math.abs(b[1]))[0][0];
-	    setActive(topMost);
-	    // console.log("Active:", topMost, visible);
-	}
-    }, {
-	root: null,                                   // track relative to viewport
-	rootMargin: `-${headerOffsetPx}px 0px -70% 0px`,
-	threshold: [0, 0.01, 0.1]                     // fire as soon as it enters
-    });
-    
-    headings.forEach(h => observer.observe(h));
-
-    // Initial highlight (in case load mid‑page)
-    let bestId = null, bestDist = Infinity;
-    headings.forEach(h => {
-	const top = h.getBoundingClientRect().top - headerOffsetPx;
-	const dist = top < 0 ? Math.abs(top) : top + 1e6;
-	if (dist < bestDist) { bestDist = dist; bestId = h.id; }
-    });
-    if (bestId) setActive(bestId);
-    
-    // smooth-scroll ToC clicks
-    toc.addEventListener("click", (e) => {
-	const a = e.target.closest('a[href^="#"]');
-	if (!a) return;
-	const id = decodeURIComponent(a.hash.slice(1));
-	const el = document.getElementById(id);
-	if (!el) return;
-	e.preventDefault();
-	el.scrollIntoView({ behavior: "smooth", block: "start" });
-	el.setAttribute("tabindex", "-1");
-	el.focus({ preventScroll: true });
-	history.pushState(null, "", `#${id}`);
-    });
-});
diff --git a/output/assets/sketchnotes/2025-08-08-emacs.svg b/output/assets/sketchnotes/2025-08-08-emacs.svg
deleted file mode 100644
index 39862a6..0000000
--- a/output/assets/sketchnotes/2025-08-08-emacs.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/output/assets/styles/style.css b/output/assets/styles/style.css
deleted file mode 100644
index 8182c6c..0000000
--- a/output/assets/styles/style.css
+++ /dev/null
@@ -1,717 +0,0 @@
-/* =========================
-   Theme variables
-   ========================= */
-:root{
-    --gutter:  2rem;
-    --margin:  420px;
-    --body-pad: 1rem;
-    --content: clamp(
-	var(--content-min),
-	calc(100vi - 2*var(--body-pad) - 2*(var(--margin) + var(--gutter))),
-	var(--content-max)
-    );
-    --content-min: 60ch;
-    --content-max: 880px;
-    --bleed: 48px; 
-    --fullwidth-cap: 860px;
-    --bg: #faf9f6;
-    --fg: #000000;
-    --link: #1a0dab;
-    --heading: #004c99;
-    --code-bg: #f0f0f0;
-    --active-toc: #cacaca;
-    --note-color: #555;
-    --note-bg: transparent;
-    --border: #d7d7d7;
-    --chip-bg: #f0f0f0;
-    --chip-fg: #444;
-    --muted: #666;
-}
-
-
-/* =========================================
-   Dark mode — manual (data-theme) wins
-   ========================================= */
-
-:root[data-theme="dark"]{
-    --bg: #0f1115;
-    --fg: #e6e6e6;
-    --link: #8ab4ff;
-    --heading: #9ecbff;
-    --code-bg: #1a1d24;
-    
-    --note-color: #c2c7cf;
-    --note-bg: transparent;
-    
-    --border: #2a2f3a;
-    --chip-bg: #1f2330;
-    --chip-fg: #cfd3da;
-    --muted: #a9b0bb;
-    --active-toc: #313131;
-
-}
-
-/* =========================
-   Base
-   ========================= */
-html, body{
-    background-color: var(--bg);
-    color: var(--fg);
-    font-size: 16px;
-    transition: background-color .3s, color .3s;
-    margin: 0;
-}
-
-h1, h2, h3{ color: var(--heading); }
-
-a {
-  color: var(--link);
-  text-decoration: none;
-}
-
-a:hover {
-  text-decoration: underline;
-}
-
-/* =========================
-   Header / Banner
-   ========================= */
-.banner-header{
-    display: flex;
-    align-items: center;
-    justify-content: flex-start;
-    flex-wrap: wrap;
-    padding: .5rem 1rem;
-    border-radius: 6px;
-}
-.banner-logo{
-    height: 80px; width: auto;
-    margin-right: 1.5rem; border-radius: 50%;
-}
-nav{
-    display: flex; gap: 1rem;
-    font-weight: 600; font-size: 1.1rem;
-}
-#updated{
-    font-size: .8rem; color: var(--fg);
-    margin: .5rem 0 1rem; font-style: italic;
-}
-/* Mobile banner */
-@media (max-width: 600px){
-    .banner-header{ flex-direction: column; align-items: center; text-align: center; }
-    .banner-logo{ margin: 0 0 .5rem 0; }
-    nav{ flex-wrap: wrap; justify-content: center; gap: .5rem; font-size: 1rem; }
-}
-/* Make the preamble align with the content column + right rail */
-#preamble.status{
-    max-width: var(--content);
-    margin-left: auto;
-    margin-right: auto;
-    padding-left: var(--body-pad);
-    padding-right: var(--body-pad);
-    box-sizing: content-box;
-}
-
-#preamble .banner-header,
-#preamble #updated{
-    max-width: 100%;
-}
-
-
-
-/* =========================
-   Footer
-   ========================= */
-footer{
-    color: var(--fg);
-    padding: 1rem; margin-top: 2rem;
-    border-radius: 6px; text-align: center;
-    font-size: .9rem; font-style: italic;
-}
-
-/* =========================
-   Content column + reserved right margin
-   ========================= */
-#content.content{
-  max-width: var(--content);
-  margin-left: auto !important;
-  margin-right: auto !important;
-  padding-left: var(--body-pad);
-  padding-right: var(--body-pad);
-  box-sizing: content-box;
-  position: relative; /* fine to keep */
-}
-
-
-/* Figures & normal images obey text column */
-#content .figure,
-#content img:not(.fullwidth){
-    max-width: 100%;
-    height: auto;
-}
-
-/* =========================
-   Sidenotes (Tufte-like)
-   ========================= */
-/* HTML pattern:
-   label.margin-toggle.sidenote-number + input.margin-toggle + span.sidenote
-*/
-/* Sidenotes: float-right inside #content, then push them into the right gutter+edge */
-.sidenote,
-.marginnote{
-    float: right;
-    width: var(--margin);
-    //margin-right: calc(-1 * ( (95vw - var(--content)) / 2 ));
-    margin-right: calc(-1 * ((100vi - var(--content)) / 2));
-    padding-right: var(--gutter);
-    overflow-wrap: break-word;    /* preferred */
-    word-wrap: break-word;        /* legacy support */
-    word-break: break-word;       /* safety for stubborn cases */
-    margin-top: .3rem;
-    margin-bottom: .6rem;
-    font-size: .95rem;
-    line-height: 1.35;
-    color: var(--note-color);
-    background: var(--note-bg);
-    box-sizing: border-box;
-}
-
-/* Make margin notes stack instead of overlapping */
-.sidenote,
-.marginnote{
-  display: block;      /* spans -> blocks so clearing works predictably */
-  clear: right;        /* each note starts below the previous right-floated note */
-}
-
-/* Add a little spacing between consecutive notes in the margin */
-.sidenote + .sidenote,
-.sidenote + .marginnote,
-.marginnote + .sidenote,
-.marginnote + .marginnote{
-  margin-top: .4rem;
-}
-
-/* Optional: show the same footnote number in the margin */
-.footnote-sidenote::before{
-  content: attr(data-fn) " ";
-  font-size: 0.85em;
-  vertical-align: super;
-  margin-right: 0.2rem;
-  opacity: 0.85;
-}
-/* Sidenote numbering */
-body{ counter-reset: sidenote-counter; }
-.sidenote-number:after{
-    counter-increment: sidenote-counter;
-    content: counter(sidenote-counter);
-    font-size: .85em;
-    vertical-align: super;
-    margin-left: .15rem;
-}
-
-/* Hide checkbox toggles (kept for mobile patterns if added later) */
-.margin-toggle{
-    position: absolute;
-    opacity: 0;
-    pointer-events: none;
-}
-
-/* Safety: only big blocks should clear floats to avoid gaps */
-#content .figure,
-#content .org-src-container,
-#content img.fullwidth{
-    //clear: both;
-}
-
-/* =========================
-   Fullwidth media (centered, modest bleed, no collision)
-   ========================= */
-.fullwidth{
-    display: block;
-    position: relative;
-    left: 50%;
-    transform: translateX(-50%);
-    height: auto;
-    margin: 1.25rem 0 1.75rem;
-    width: 100%;
-    max-width: min(
-	calc(var(--content) + var(--bleed) * 2),     /* centered bleed */
-	var(--fullwidth-cap),                        /* absolute cap */
-	calc(100vw - 2 * var(--body-pad))            /* never cause horizontal scroll */
-    );
-}
-.figure .fullwidth{ width: inherit; }           /* if img sits inside a figure */
-.figure figcaption{
-    text-align: center; font-size: .95rem; color: #666; margin-top: .4rem;
-}
-
-
-
-
-/* Ultra-wide comfort tweaks */
-/* @media (min-width: 1600px){ */
-/*     :root{ --content: 720px; --margin: 360px; } */
-/* } */
-
-/* =========================
-   Code highlighting (Org-mode classes)
-   ========================= */
-.org-comment        { color: #b22222; }      /* comments */
-.org-string         { color: #8b2252; }      /* string literals */
-.org-keyword        { color: #a020f0; }      /* keywords */
-.org-builtin        { color: #483d8b; }      /* builtins */
-.org-constant       { color: #008b8b; }      /* constants / numbers */
-.org-function-name  { color: #0000ff; }      /* function names */
-.org-variable-name  { color: sienna; }       /* variables */
-.org-type           { color: #228b22; }      /* types/classes */
-.org-preprocessor   { color: #483d8b; }      /* #define, import, etc. */
-.org-doc            { color: #8b2252; }      /* docstrings */
-.org-warning        { color: #ff8c00; font-weight: 700; } /* warnings */
-
-/* Inline code */
-:not(pre) > code{
-    padding: 2px 5px; margin: 0 1px;
-    border: 1px solid #ddd; border-radius: 3px;
-    background-clip: padding-box;
-    color: #333; font-size: 80%;
-}
-pre {
-    background-color: var(--bg);
-}
-/* Code blocks */
-.org-src-container{
-    border: 1px solid #ccc;
-    box-shadow: 3px 3px 3px #eee;
-    font-family: Lucida Console, monospace;
-    font-size: 80%;
-    margin: 1em auto;
-    padding: .1em .5em;
-    position: relative;
-}
-.org-src-container > pre{ overflow: auto; }
-
-/* Language badges */
-.org-src-container > pre:before{
-    display: block; position: absolute; top: 0; right: 0;
-    background-color: #b3b3b3; color: #fff;
-    padding: 0 .5em; border-bottom-left-radius: 8px; border: 0;
-    font-size: 80%;
-}
-.org-src-container > pre.src-bash:before        { content: "bash"; }
-.org-src-container > pre.src-sh:before          { content: "sh"; }
-.org-src-container > pre.src-shell:before       { content: "shell"; }
-.org-src-container > pre.src-python:before      { content: "Python"; }
-.org-src-container > pre.src-emacs-lisp:before  { content: "Emacs Lisp"; }
-.org-src-container > pre.src-js:before,
-.org-src-container > pre.src-javascript:before  { content: "Javascript"; }
-.org-src-container > pre.src-typescript:before  { content: "Typescript"; }
-.org-src-container > pre.src-html:before        { content: "HTML"; }
-.org-src-container > pre.src-css:before         { content: "CSS"; }
-.org-src-container > pre.src-c:before           { content: "C"; }
-.org-src-container > pre.src-cpp:before         { content: "C++"; }
-.org-src-container > pre.src-java:before        { content: "Java"; }
-.org-src-container > pre.src-rust:before        { content: "Rust"; }
-.org-src-container > pre.src-R:before           { content: "R"; }
-
-/* Org copy button on blocks */
-.copy-btn{
-    position: absolute; top: .4em; right: .4em;
-    background-color: var(--code-bg); color: var(--heading);
-    border: 1px solid var(--heading); border-radius: 4px;
-    padding: .2em .6em; font-size: .8rem; cursor: pointer; z-index: 10;
-    transition: background-color .3s;
-}
-.copy-btn:hover{ background-color: var(--heading); color: var(--code-bg); }
-
-/* Org’s default pre label removal if duplicated elsewhere */
-pre.src::before{ content: none !important; }
-pre.src{
-    padding: 1.5em 1em 1em;
-    font-family: "Fira Mono","Courier New",monospace;
-    font-size: .9rem; line-height: 1.4;
-    overflow-x: auto; white-space: pre-wrap;
-}
-
-/* =========================
-   Lists with post meta chips
-   ========================= */
-.post-date{ color: pink; font-size: .9em; margin-left: 8px; }
-
-/* Post tag chip no longer needs a clearfix */
-.post-tag{
-    float: right;
-    display: inline-block;
-    background-color: #f0f0f0;
-    color: #444;
-    border-radius: 5px;
-    padding: 2px 6px;
-    margin-left: 6px;
-    font-size: 0.8em;
-}
-
-.filetags {
-  margin-top: .5rem;
-  display: flex;
-  flex-wrap: wrap;
-  gap: .3rem .4rem;
-  font-size: .9rem;
-  color: #666;
-}
-.filetags .tag {
-  display: inline-block;
-  padding: .1rem .45rem;
-  border-radius: .5rem;
-  background: #f0f0f0;
-  line-height: 1.6;
-}
-
-
-/* Shared base styles for all lists */
-ul, ol {
-  margin: 1rem 0 1.5rem 1.5rem; /* space above/below and indent */
-  padding: 0;
-  line-height: 1.5;
-}
-
-/* Unordered lists: custom bullet */
-ul {
-  list-style: none; /* remove default bullet */
-}
-ul li {
-  position: relative;
-  padding-left: 1.2em; /* space for custom bullet */
-}
-ul li::before {
-  content: "•"; /* bullet character */
-  position: absolute;
-  left: 0;
-  top: 0;
-  color: var(--heading); /* matches your headings color */
-  font-weight: bold;
-}
-
-/* Ordered lists: styled numbers */
-ol {
-  counter-reset: list-counter;
-  list-style: none; /* remove default numbering */
-}
-ol li {
-  counter-increment: list-counter;
-  position: relative;
-  padding-left: 1.8em; /* space for custom number */
-}
-ol li::before {
-  content: counter(list-counter) ".";
-  position: absolute;
-  left: 0;
-  top: 0;
-  color: var(--heading);
-  font-weight: bold;
-}
-
-/* List items: keep your float containment and spacing */
-li {
-  margin-bottom: 8px;
-  display: flow-root; /* contains floats like .post-tag */
-}
-
-/* Remove the old clearfix ::after */
-li::after {
-  content: none;
-}
-
-/* Nested lists: smaller bullet/number and reduced spacing */
-li ul,
-li ol {
-  margin-top: 0.5rem;
-  margin-bottom: 0.5rem;
-}
-li ul li::before {
-  content: "–"; /* en dash for nested bullets */
-  font-weight: normal;
-  color: var(--note-color);
-}
-li ol li::before {
-  font-weight: normal;
-  color: var(--note-color);
-}
-/* Epigraph container: center and limit width */
-.epigraph {
-  margin: 2rem auto;
-  max-width: 80%; /* narrower than full text width */
-  font-style: italic;
-}
-
-/* Main blockquote style */
-.epigraph blockquote {
-  margin: 0;
-  padding: 1rem 1.5rem;
-  border-left: 4px solid var(--heading);
-  background-color: rgba(0, 0, 0, 0.02); /* subtle background */
-  color: var(--fg);
-  line-height: 1.6;
-}
-
-/* Footer inside blockquote: author/source */
-.epigraph blockquote footer {
-  margin-top: 0.75rem;
-  font-style: normal; /* turn off italic for attribution */
-  font-size: 0.9em;
-  color: var(--note-color);
-  text-align: right;
-}
-
-/* Cite: make source title distinct */
-.epigraph blockquote cite {
-  font-style: italic;
-  font-weight: 500;
-  color: var(--link);
-}
-
-/* Optional: remove automatic quotes from 
in some browsers */ -.epigraph blockquote::before, -.epigraph blockquote::after { - content: none; -} - - -/* Auto scheme if no manual override is set */ -@media (prefers-color-scheme: dark){ - :root:not([data-theme="light"]){ - --bg: #0f1115; - --fg: #e6e6e6; - --link: #8ab4ff; - --heading: #9ecbff; - --code-bg: #1a1d24; - - --note-color: #c2c7cf; - --note-bg: transparent; - - --border: #2a2f3a; - --chip-bg: #1f2330; - --chip-fg: #cfd3da; - --muted: #a9b0bb; - } -} - -/* Tweak components that used fixed light colors */ -.figure figcaption{ color: var(--muted); } -.post-tag{ - background-color: var(--chip-bg); - color: var(--chip-fg); -} -.org-src-container{ - border: 1px solid var(--border); - box-shadow: 3px 3px 3px rgba(0,0,0,.15); -} -:not(pre) > code{ - border: 1px solid var(--border); - background-color: var(--code-bg); - color: var(--fg); -} -.epigraph blockquote{ - border-left-color: var(--heading); - background-color: color-mix(in oklab, var(--bg) 94%, var(--fg) 6%); - color: var(--fg); -} -.epigraph blockquote footer{ color: var(--muted); } - -/* Theme toggle button positioning + transparent background */ -.theme-toggle { - position: fixed; - top: 0.75rem; - right: 1rem; - z-index: 1000; - border: 1px solid var(--border, #ccc); - background: transparent; - color: var(--fg); - padding: .35rem .7rem; - border-radius: 6px; - cursor: pointer; - font: inherit; - transition: background-color 0.3s, color 0.3s, border-color 0.3s; -} -.theme-toggle:hover { - background-color: rgba(0,0,0,0.05); /* subtle hover tint */ - color: var(--heading); - border-color: var(--heading); -} - -/* Mobile: place next to nav links */ -@media (max-width: 600px) { - .theme-toggle { - position: static; - order: 2; /* after nav links in flex layout */ - margin-left: .5rem; - padding: .3rem .6rem; - background: transparent; - } - - .banner-header { - flex-wrap: wrap; - justify-content: center; - } - .banner-header nav { - display: flex; - align-items: center; - flex-wrap: wrap; - } -} -/* Margin/sidenote images */ -.sidenote .mn-fig, -.marginnote .mn-fig{ - margin: 0; -} -.sidenote .mn-img, -.marginnote .mn-img{ - display: block; - max-width: 100%; - height: auto; - border: 1px solid var(--border, #d7d7d7); - border-radius: 6px; - background: var(--bg); -} -.sidenote .mn-fig figcaption, -.marginnote .mn-fig figcaption{ - margin-top: .35rem; - font-size: .85rem; - color: var(--muted, #666); - line-height: 1.35; -} - - -.sidenote img { - margin-top: 0.35rem; - display: block; -} -/* .sidenote.footnote-sidenote img { */ -/* margin-top: 0.35rem; */ -/* display: block; */ -/* } */ - - -/* ========================= - Left sticky Table of Contents (desktop) - ========================= */ -#table-of-contents{ - /* push into the left gutter, mirroring .sidenote on the right */ - float: left; - width: var(--margin); - //margin-left: calc(-1 * ( (95vw - var(--content)) / 2 )); - margin-left: calc(-1 * ((100vi - var(--content)) / 2)); - padding-left: var(--gutter); - box-sizing: border-box; - - /* sticky behavior */ - position: sticky; - top: 5.5rem; /* clear your banner/header */ - max-height: calc(100vh - 6rem); - overflow: auto; - - /* look & feel */ - font-size: .95rem; - border-right: 1px solid var(--border, #d7d7d7); - padding-top: .25rem; -} - -#table-of-contents > h2{ - margin: 0 0 .5rem; - font-size: 1rem; - text-transform: uppercase; - letter-spacing: .02em; - color: var(--muted, #666); -} - -#text-table-of-contents ul{ - list-style: none; - margin: 0; - padding-left: 0; -} -#text-table-of-contents li{ - margin: .25rem 0; -} -#text-table-of-contents a{ - text-decoration: none; /* no underline by default */ -} -#text-table-of-contents a:hover{ - text-decoration: underline; -} - -/* Indent nested ToC levels a bit */ -#text-table-of-contents ul ul { margin-left: .75rem; opacity: .9; } - -/* Headings offset so anchors don’t hide under the sticky header */ -h2[id], h3[id], h4[id] { scroll-margin-top: 6.5rem; } - -#text-table-of-contents a.is-active{ - // font-weight: 700; - text-decoration: underline; - background-color: var(--active-toc); - //border: 1px solid var(--border, #ccc); - -} - -/*BP STUFF*/ - -/* theme removal */ -.hidden { display: none !important; } - -.bp-x{ right:72px; } - -.bp-next,.bp-prev{ right:8px; } -.bp-prev{ left:8px; } - -/* Smooth exit */ -.bp-wrap{ transition: opacity .18s ease; } -.bp-wrap.bp-fadeout{ opacity: 0; } - -/* Respect notches */ -.bp-controls{ - padding-top: env(safe-area-inset-top, 0); - padding-right: calc(env(safe-area-inset-right, 0) + 8px); -} - - -@media (max-width: 1250px){ - #preamble.status{ - padding-right: var(--body-pad); - } - - #content.content{ - padding-right: var(--body-pad); - } - .sidenote, - .marginnote{ - float: none; - clear: both; - width: auto; - display: block; - margin: 0.5rem 0 0.75rem; - padding-left: 0.75rem; - border-left: 3px solid rgba(0,0,0,.08); - margin-right: 0; - } - .fullwidth{ - max-width: calc(100vw - 2 * var(--body-pad)); - } - .sidenote, .marginnote{ - border-left: 3px solid color-mix(in oklab, var(--fg) 12%, transparent); - } - .sidenote .mn-img, - .marginnote .mn-img{ - border-color: color-mix(in oklab, var(--fg) 12%, transparent); - } - - #table-of-contents{ - float: none; - position: static; - width: auto; - margin: 0 0 1rem; - padding: .5rem .75rem; - border-right: 0; - border-left: 3px solid color-mix(in oklab, var(--fg) 12%, transparent); - background: color-mix(in oklab, var(--bg) 96%, var(--fg) 4%); - border-radius: 4px; - } - -} diff --git a/output/blogs/2025/08/benefits-of-reading.html b/output/blogs/2025/08/benefits-of-reading.html deleted file mode 100644 index f31057d..0000000 --- a/output/blogs/2025/08/benefits-of-reading.html +++ /dev/null @@ -1,309 +0,0 @@ - - - - - - - -Benefits of Reading - - - - - - - - - - - - -
- - -
Updated: 2025-08-14 Thu 23:50
-
-
-

Benefits of Reading

- - - -
-

Benefits 1

-
-

-At times, I find myself being able to digest material more quickly and efficiently, and so I thought to myself as to what the probable cause of this was. The answer transpired to be the inculcating of reading in my daily routine. As humans, we are constantly surrounded with information, some are noisy, some are useful. We can choose to filter out noisy information by limiting exposure of their avenues, however the topic I wanted to briefly touch upon is what are the (cognitive and non-cognitive) benefits of increasing the intake of useful information through the medium of reading? -

-
-
-

Mental Stimulation

-
-

-The brain is a muscle, in order to keep any muscle strong and healthy, it needs stimulation and attention. Thus, reading is an excellent way of ensuring the brain is fit and healthy. -

-
-
-
-

Stress Reduction

-
-

-A well written novel or informative book can go a long way in making you forget about the worries of the world. -

-
-
-
-

Increase in Knowledge

-
-

-Reading will always fill your head with new information. -

-
-
-
-

Entertainment

-
-

-A plethora of genres can keep anyone entertained! -

-
-
-
-

Better writing skills

-
-

-Being exposed to different writing styles allows you to be influenced to obtaining your own unique writing style. -

-
-
-
-
-

Footnotes:

-
- -
1

-Check the following link -

- - -
-
-
-
- -
-Created with Emacs 30.1 (Org mode 9.7.11) on Arch GNU/Linux -
-
-
- - diff --git a/output/blogs/2025/08/hilberts.hotel.html b/output/blogs/2025/08/hilberts.hotel.html deleted file mode 100644 index 13fa840..0000000 --- a/output/blogs/2025/08/hilberts.hotel.html +++ /dev/null @@ -1,242 +0,0 @@ - - - - - - - -Hilbert's Hotel - - - - - - - - - - - - -
- - -
Updated: 2025-08-11 Mon 18:40
-
-
-

Hilbert's Hotel

- - -

-I recently stumbled across a thought experiment in mathematics, where the counterintuitive nature of infinity is portrayed. There is a hotel with an infinite number of rooms, all of which are occupied with a guest. There is a paradox upon the arrival of a new guest, the hotel is still shown to be able to accomodate this guest, despite it being previously coined as full. -

- -

-See: this link for more details on the full experimental thought and the proofs alongside. -

-
-
-
- -
-Created with Emacs 30.1 (Org mode 9.7.11) on Arch GNU/Linux -
-
-
- - diff --git a/output/blogs/2025/08/spending-the-whole-day-on-this-website.html b/output/blogs/2025/08/spending-the-whole-day-on-this-website.html deleted file mode 100644 index 96471a3..0000000 --- a/output/blogs/2025/08/spending-the-whole-day-on-this-website.html +++ /dev/null @@ -1,291 +0,0 @@ - - - - - - - -09-08-2025: Website Changes - - - - - - - - - - - - -
- - -
Updated: 2025-08-10 Sun 13:05
-
-
-

09-08-2025: Website Changes

- - -
-

What was worked on

-
-

-I saw a few websites that used marginal notes on the right side of the screen like this one, so I decided to try and implement this feature in this wesbite. In my mind I thought of using an external pre-configured CSS that I can just insert and ta-da it would work. -

- -

-How wrong I was. -

- -

-See, this website is published with org-publish, meaning the org files that are written are converted to HTML through a transcoder. And as I already had a org.css1 stylesheet which was primarily used for syntax highlighting, it caused some layout and formatting issue when I tried to use something like tufte.css2. Anyways, long story short, the main thing I worked on today was styling this website, and ensuring certain macros worked: -

- -
-
-(setq org-export-global-macros
-      (append
-       '(("sidenote"
-          . "@@html:<label for=\"sn$1\" class=\"margin-toggle sidenote-number\"></label><input type=\"checkbox\" id=\"sn$1\" class=\"margin-toggle\"/><span class=\"sidenote\">$2</span>@@")
-         ("epigraph"  . "@@html:<div class=\"epigraph\"><blockquote>$1<footer>$2</footer></blockquote></div>@@")
-         ("epigraph_single"  . "@@html:<div class=\"epigraph\"><blockquote>$1</blockquote></div>@@")
-         ("epigraph3" . "@@html:<div class=\"epigraph\"><blockquote>$1<footer>$2, <cite>$3</cite></footer></blockquote></div>@@")
-         ("kbd"       . "@@html:<kbd>$1</kbd>@@@@latex:\\texttt{$1}@@")
-         ("margimg"
-          . "@@html:<aside class=\"marginnote\"><figure class=\"mn-fig\"><img src=\"$1\" alt=\"$2\" class=\"mn-img\" loading=\"lazy\" decoding=\"async\"/>$3</figure></aside>@@")
-
-         org-export-global-macros)))
-
-
-
- -

-This worked quite nicely, and I made sure to make these changes accessible on smaller devices. Anyways, I quite like this style of posting. Nothing formal nor technical, just a bunch of rambling on about random things. -

- -

-Oh, and also there's a darkmode theme switch now for you night owls. -

-
-
-
-
-
- -
-Created with Emacs 30.1 (Org mode 9.7.11) on Arch GNU/Linux -
-
-
- - diff --git a/output/blogs/2025/08/third-time.html b/output/blogs/2025/08/third-time.html deleted file mode 100644 index e767616..0000000 --- a/output/blogs/2025/08/third-time.html +++ /dev/null @@ -1,278 +0,0 @@ - - - - - - - -Third Time - - - - - - - - - - - - -
- - -
Updated: 2025-08-28 Thu 17:40
-
-
-

Third Time

- - -
-

Table of Contents

- -
-
-

A more structured way of working

-
-

- Useful for people who don't like the constrained working limit of 25 minutes for instance. -

- -

-Whilst researching for different ways to use pomodoro 1 on Emacs 2, I came across this package titled: pomm.el. In this package it had an interesting feature called pomm-third-time3. The ideology behind is as follows: -

-
    -
  • You work as long as you want.
  • -
  • Your break is calculated using the formula: Time of break = 1/3 x Time of work.
  • -
  • If you take a shorter break, the remaining break time is accumulated and added to the next break (within the same session).
  • -
-
-
-
-
-
- -
-Created with Emacs 30.1 (Org mode 9.7.11) on Arch GNU/Linux -
-
-
- - diff --git a/output/blogs/2025/08/wacom-with-arch.html b/output/blogs/2025/08/wacom-with-arch.html deleted file mode 100644 index 205d0db..0000000 --- a/output/blogs/2025/08/wacom-with-arch.html +++ /dev/null @@ -1,260 +0,0 @@ - - - - - - - -Wacom With Arch - - - - - - - - - - - - -
- - -
Updated: 2025-08-10 Sun 13:06
-
-
-

Wacom With Arch

- - -
-

Setting up Wacom Tablet in Arch Hyprland

-
-

-I purchased a One by Wacom tablet almost 2 years ago now and it works flawlessly in both Windows and MacOS and even in most Linux distros with some configuration. -

- -

-However with Arch and Hyprland, there were some issues related to the xserver it was running on. The X input driver module (e.g. xf86-input-wacom) is linked into the X server which in turn is in charge of rendering and generic input events. Hyprland on the other hand uses the Wayland display server protocol (replacement for the X protocol) hence why there is difficulty in input mapping. -

- -

-I came across this issue as I was able to get the tablet to work across multiple monitors (mapping to a single one) through the xsetwacom function - which, at the time I was using Ubuntu. But with Hyprland there needs to be some manual intervention. I came across this post which explains nicely how to map the tablet to a single monitor. I took this approach with a slight configuration; in my hyprland.conf file, I simply added the following line: -

- -
-
exec-once = hyprctl keyword "device[wacom-one-by-wacom-m-pen]:output" "DP-1"
-
-
- -

-which mapped the device to the primary monitor DP-1. -

-
-
-
-
-
- -
-Created with Emacs 30.1 (Org mode 9.7.11) on Arch GNU/Linux -
-
-
- - diff --git a/output/blogs/2025/08/what-do-i-want-to-do-with-emacs.html b/output/blogs/2025/08/what-do-i-want-to-do-with-emacs.html deleted file mode 100644 index 5a5e2c6..0000000 --- a/output/blogs/2025/08/what-do-i-want-to-do-with-emacs.html +++ /dev/null @@ -1,277 +0,0 @@ - - - - - - - -What Do I Want To Do With Emacs - - - - - - - - - - - - -
- - -
Updated: 2025-08-10 Sun 22:22
-
-
-

What Do I Want To Do With Emacs

- - -

-I first started using Emacs (and Linux for that matter) since around the end of 2023, and it's been well over a year of trial and error with this text editor. At first I was using this for all sorts of things such as: -

- -
    -
  • Dashboard
  • -
  • Agenda
  • -
  • Text editing
  • -
  • Magit (version controlling)
  • -
  • Syncthing Management
  • -
- -

-… and the list goes on. -

- -

-One thing I realised is just how powerful Emacs really is, just recently I found out that you can use the emoji-insert to insert emojis! Some have even gone to say it's an operating system in its own right. So this prompted the question today, what do I want from Emacs? -

- -

-After some thinking I narrowed it down and collated it in the following diagram: -

- - -
-

2025-08-08-emacs.svg -

-
- -

-Of course this is the current workflow, where the text in red are items I currently use Emacs for (high percentage likelihood this will increase), and the items in blue text are ones I plan on adding to my workflow. The main thing I want from this is to ensure consistency in daily practices. -

- -
    -
  • Org-roam bodies a huge aspect of the daily workflow, as it is a second brain where all information is stored.
  • -
  • Learning elisp is primarily done in the scratch buffer and through projects.
  • -
  • Java/Python IDE for when I'm leetcoding or doing any arbitrary project.
  • -
  • Text editing mainly relates to config files for the arch setup.
  • -
  • Org-agenda (more on this here).
  • -
  • Org-publish: see setup.
  • -
-
-
-
- -
-Created with Emacs 30.1 (Org mode 9.7.11) on Arch GNU/Linux -
-
-
- - diff --git a/output/blogs/2025/08/zettelkasten.html b/output/blogs/2025/08/zettelkasten.html deleted file mode 100644 index a8bd1f0..0000000 --- a/output/blogs/2025/08/zettelkasten.html +++ /dev/null @@ -1,267 +0,0 @@ - - - - - - - -Zettelkasten Method - - - - - - - - - - - - -
- - -
Updated: 2025-08-09 Sat 19:49
-
-
-

Zettelkasten Method

- - -
-

The Zettelkasten Method

-
-

-This Zettelkasten method is growing in popularity for taking notes. Ever felt as though there's too much pieces of information but you don't know how to deal with it? This method solves exactly that problem and more. Think of it as forming a second brain, where you can jot down ideas and information whilst simultaneously being able to connect them together. -

- -

-The official website describes in detail exactly what the Zettelkasten method is, providing use cases and benefits to this method. The part I wanted to highlighted in this is the following paragraph, where the author depicts how this concept would be explained to someone unfamiliar with it: -

- -

-

A Zettelkasten is a personal tool for thinking and writing. It has hypertextual features to make a web of thoughts possible. The difference to other systems is that you create a web of thoughts instead of notes of arbitrary size and form, and emphasize connection, not a collection.
-A Zettelkasten is a personal tool for thinking and writing. It has hypertextual features to make a web of thoughts possible. The difference to other systems is that you create a web of thoughts instead of notes of arbitrary size and form, and emphasize connection, not a collection. -

- - -

-This web of thoughts is exactly what makes it useful, it takes on the seemingly cluttered mess of information and is able to link the nodes together to create something meaningful and useful. -

- -

-There are many personal knowledge management systems' (PKMS) out there that allow you to practice this method; the most common ones being Obsidian and Org Roam. After using Org Roam with Emacs for about a year or so, I realised how useful this method really is. A random idea can be jotted down and later connected to a bigger project or note system. The following picture below is a screenshot of a graph generated UI for all the nodes created in my Org Roam folder: -

- - -
-

org-roam-ui-graph.png -

-
-
-
-
-
-
- -
-Created with Emacs 30.1 (Org mode 9.7.11) on Arch GNU/Linux -
-
-
- - diff --git a/output/blogs/blogs-intro.html b/output/blogs/blogs-intro.html deleted file mode 100644 index bd0709f..0000000 --- a/output/blogs/blogs-intro.html +++ /dev/null @@ -1,243 +0,0 @@ - - - - - - - -Blogs Introduction - - - - - - - - - - - - -
- - -
Updated: 2025-08-08 Fri 20:31
-
-
-

Blogs Introduction

- - -
-

Introduction

-
-

-In this section you will find posts that are not as structured as the ones found in here. Mainly these will deal with findings, research, assorted writings and random bits and blobs. -

-
-
-
-
-
- -
-Created with Emacs 30.1 (Org mode 9.7.11) on Arch GNU/Linux -
-
-
- - diff --git a/output/blogs/blogs-list.html b/output/blogs/blogs-list.html deleted file mode 100644 index f4fa6b7..0000000 --- a/output/blogs/blogs-list.html +++ /dev/null @@ -1,251 +0,0 @@ - - - - - - - -Blogs List - - - - - - - - - - - - -
- - -
Updated: 2025-09-26 Fri 15:56
-
-
-

Blogs List

-

-See the categories: Categories -

- -
-
-
- -
-Created with Emacs 30.1 (Org mode 9.7.11) on Arch GNU/Linux -
-
-
- - diff --git a/output/categories.html b/output/categories.html deleted file mode 100644 index 5277f66..0000000 --- a/output/categories.html +++ /dev/null @@ -1,249 +0,0 @@ - - - - - - - -Categories - - - - - - - - - - - - -
- - -
Updated: 2025-09-26 Fri 15:56
-
-
-
-

Categories (Includes both blogs and posts)

-
- -
-
-
-
-
- -
-Created with Emacs 30.1 (Org mode 9.7.11) on Arch GNU/Linux -
-
-
- - diff --git a/output/contact.html b/output/contact.html deleted file mode 100644 index d3ce4e2..0000000 --- a/output/contact.html +++ /dev/null @@ -1,259 +0,0 @@ - - - - - - - -Contact - - - - - - - - - - - - -
- - -
Updated: 2025-08-07 Thu 17:53
-
-
-

Contact

-
-

Contact Using the Following:

-
-

-> LinkedIn: LinkedIn -

- -

-> GitHub: Github -

- -

-> Email: -

- - - -

-Thanks for visiting! -

-
-
-
-
-
- -
-Created with Emacs 30.1 (Org mode 9.7.11) on Arch GNU/Linux -
-
-
- - diff --git a/output/index.html b/output/index.html deleted file mode 100644 index 849ecbf..0000000 --- a/output/index.html +++ /dev/null @@ -1,383 +0,0 @@ - - - - - - - -Home Page - - - - - - - - - - - - -
- - -
Updated: 2025-08-27 Wed 20:20
-
-
- -
-

Welcome! 🌱

-
-

-This is a website built using Emacs Org-mode and published using org-publish. The very first iteration of this website used the Angular framework, only after a while I realised (as every Emacs lover does) that I want to make this an Emacs-centric project -

- -

-Feel free to explore: -

- - -
-
-
-

How to publish web pages using org-publish

-
-

-This website is heavily inspired by some people who have decided to use org-publish as a way to convert org files into html. I came across a few that took the plunge and decided to migrate from platforms like Wordpress and instead opted for a more transparent, text-based workflow. -

- -

-The Org website1 now looks a lot better than it used to. -

- -

-There is a Unix philosophy that emphasises building simple and compact code that can be easily maintainable, and following this approach (do one thing well) aligns with this philosophy. -

- -

-So how does one go about publishing web pages through Emacs's org-publish? The answer is quite simple. You probably will need to have some familiarity with elisp2 although it's not a must. -

- -

-The first thing we will want to do is edit our init.el file, which is the initialization file. In this file we want to add the following: -

- -

-This is a simple and quick way of getting this setup, although there is a better and more scalable approach to this, where we have a build script that runs each time we want to generate the project. See here for more details. -

- -
-
-(setq org-publish-project-alist
-    '(("org-notes"
-       :base-directory "~/web"
-       :base-extension "org"
-       :publishing-directory "~/web"
-       :recursive t
-       :publishing-function org-html-publish-to-html
-       :with-author nil
-       :with-creator nil
-       :html-validation-link nil
-       :with-toc t
-       :section-numbers t
-       :html-head "<link rel=\"stylesheet\" href=\"style.css\" />"
-       )
-      ("org-static"
-       :base-directory "~/web"
-       :base-extension "css\\|js\\|png\\|jpg\\|gif"
-       :publishing-directory "~/web/output"
-       :recursive t
-       :publishing-function org-publish-attachment)
-      ("website" :components ("org-notes" "org-static"))))
-
-
-
- -

-What this snippet of code is doing is it is telling org-publish what configurations we want to use when we end up running the publish command. Under ~/web is where we want to include our files. Let us take a simple example and create an index.org file: -

- -
-
-#+TITLE: Website Name
-#+OPTIONS: toc:nil num:nil
-
-* Welcome!
-
-This is a simple website
-
-
-
- -

-There are some metadata at the very top as we can see, these help us customise each page the way we want to. Here I disabled the Table of Contents and section numbers just for this file. We can also create a simple styles.css file in the same directory. -After doing this, we can run the command: -

- -
-
-M-x org-publish RET website
-
-
-
- -

-This will generate a html file inside the ~/web/output/ directory, and we can serve this file using any static site generation tools; I recommend python due to it's simplicity: -

- -
-
-cd ~/web/output
-python3 -m http.server 8000 
-
-
-
- -

-And there we go! The files are now being hosted on http://localhost:8000/ and we have a fully functioning workflow for converting org files into a static website. -

-
-
-
-

Contact

-
-

-Feel free to reach out on GitHub: https://github.com/zainezq -

-
-
-
-

Footnotes:

-
- - - -
2

-Emacs Lisp is a Lisp dialect made for Emacs. See: https://en.wikipedia.org/wiki/Emacs_Lisp -

- - -
-
-
-
- -
-Created with Emacs 30.1 (Org mode 9.7.11) on Arch GNU/Linux -
-
-
- - diff --git a/output/posts/clean-code/clean-code-chapter-1.html b/output/posts/clean-code/clean-code-chapter-1.html deleted file mode 100644 index 8936481..0000000 --- a/output/posts/clean-code/clean-code-chapter-1.html +++ /dev/null @@ -1,263 +0,0 @@ - - - - - - - -Clean Code: Chapter 1 Notes - - - - - - - - - - - - -
- - -
Updated: 2025-08-10 Sun 20:35
-
-
-

Clean Code: Chapter 1 Notes

- - -
-

Table of Contents

- -
-

-Link to Chapter 2 -

-
-

Chapter 1: Clean Code

-
-

-Referenced Items: -

-
    -
  • Implementation Patterns, Kent Beck, Addison-Wesley, 2007.
  • -
  • Literate Programming, Donald E. Knuth, Center for the Study of Language and Information, Leland Stanford Junior University, 1992.
  • -
- -

-Principles mentioned: -Single Responsibility Principle (SRP), the Open Closed Principle (OCP), and the Dependency Inversion Principle (DIP) -

-
-
-
-
-
- -
-Created with Emacs 30.1 (Org mode 9.7.11) on Arch GNU/Linux -
-
-
- - diff --git a/output/posts/clean-code/clean-code-chapter-2.html b/output/posts/clean-code/clean-code-chapter-2.html deleted file mode 100644 index 0e9d183..0000000 --- a/output/posts/clean-code/clean-code-chapter-2.html +++ /dev/null @@ -1,420 +0,0 @@ - - - - - - - -Clean Code: Chapter 2 Notes - - - - - - - - - - - - -
- - -
Updated: 2025-08-28 Thu 16:52
-
-
-

Clean Code: Chapter 2 Notes

- - - -

-Link to Chapter 1 | Link to Chapter 3 -

-
-

Chapter 2: Meaningful Names

-
-
-
-

Use intention revealing names:

-
-

-Names should reveal intent, there is no revelation in naming an integer d, intending it stands for days. Instead, you should use the following names: -

-
-
int elapsedTimeInDays;
-int daysSinceCreation;
-int daysSinceModification;
-int fileAgeInDays;
-
-
-
-
-
-

Avoid disinformation

-
-

-Don't postfix the word 'list' to the name 'accounts' unless it's actually a list. This is because the reader will assume the data type of accountsList is indeed a list, instead choose a name like accountsGroup. -

-
-
-
-

Make Meaningful Distinctions

-
-

-While it is possible to name by being disinformative, it is also possible to name being non informative. Consider: -

- -
-
-public static void copyChars(char a1[], char a2[]) {
-    for (int i = 0; i < a1.length; i++) {
-        a2[i] = a1[i];
-    }
-}
-
-
-
- -

-What on earth does a[1] and a[2] even stand for? We are better off using names like source and destination (due to the function's intent of copying the array). -

- -

-Furthermore, noise words are redundant. We should never use the word variable when naming a variable, or table when naming a table. -

-
-
-
-

Use Pronouncable Names

-
-

-This is quite straightforward. Do not use a name like genymdhms to refer to generation date, year, month, day, hour, minute, -and second. Instead use generationTimeStamp. -

-
-
-
-

Use Searchable Names

-
-

-In modern IDE's, it is still quite difficult to search for single-lettered variables. The writer states a personal preference of using single-letter names only as local variables and inside short methods. The following principle is given: -

- -

-The length of a name should correspond to the size of its scope -

-
-
-
-

Avoid Encodings

-
-

-Don't prefix variables with letters like m_ as was done in the past. Do not type encode as well, an example of this is: PhoneNumber phoneString; we can see the reader being misled into thinking the phone number is a String. -

-
-
-
-

Avoid Mental Mappings

-
-

-Clarity is king, don't use a name for a variable that only you know what it stands for. For example: using the letter r as the lower-cased version of the url with the host and scheme -removed. That's being smart, not professional. -

-
-
-
-

Class Names

-
-

-

Classes and objects should have noun or noun phrase names like Customer, WikiPage, Account, and AddressParser. Avoid words like Manager, Processor, Data, or Info in the name of a class. A class name should not be a verb
-

-
-
-
-

Method Names

-
-

-Methods should have verb or verb phrase names. -

-
-
-
-

Don't be cute/Don't use puns

-
-

-Do not use names that are only understandable to people whom you share jokes etc with. Furthermore, do not use colloquialism and slang in names. -

-
    -
  • Example: HandGrenade instead of DeleteItems
  • -
  • Example: whack() instead of kill()
  • -
-
-
-
-

Pick one word per concept

-
-

-If you have multiple choices for naming a concept, use one and stick with it. For instance if your options are fetch, get and retrieve, use one and stick with it throughout. -

-
-
-
-

Solution Domain Names and Problem Domain Names

-
-

-Where possible use solution domain names, as the people that are going to be reading the code are programmers. Therefore, do not shy away from using CS terms, algorithm names, math names and so forth. -

- -

-However when it is not possible to use solution domain names (in other words, when there is no "programmer-eese" then use the name from the problem domain. The other programmers can ask the domain expert for clarification. If the code is more to do with the problem domain concepts, then the names should be drawn from them. -

-
-
-
-

Add Meaningful Context

-
-

-Enclose names with well-named classes, functions, or namespaces. When all else fails, then prefix with something that provides more context. -

-
-
-
-

Don't add gratuitous context

-
-

-Shorter names are better than longer ones, generally. This is so long as the context and intent is clear. Don't add redundant or irrelevant additions to the name in the for the sake of 'context'. -

-
-
-
-
-
-
- -
-Created with Emacs 30.1 (Org mode 9.7.11) on Arch GNU/Linux -
-
-
- - diff --git a/output/posts/clean-code/clean-code-chapter-3.html b/output/posts/clean-code/clean-code-chapter-3.html deleted file mode 100644 index 1049f26..0000000 --- a/output/posts/clean-code/clean-code-chapter-3.html +++ /dev/null @@ -1,493 +0,0 @@ - - - - - - - -Clean Code: Chapter 3 Notes - - - - - - - - - - - - -
- - -
Updated: 2025-08-28 Thu 16:52
-
-
-

Clean Code: Chapter 3 Notes

- - - -

-Link to Chapter 2 | Link to Chapter 4 -

-
-

Chapter 3: Functions

-
-
-
-

Functions should be small

-
-

-Functions should be extremely short—ideally just a few lines, so they remain easy to understand and maintain. -

- -

-Avoid deeply nested blocks; keep indentation shallow (1–2 levels), often replacing blocks with descriptive function calls. -

- -

-A small function tells a concise, self-contained story, making it easier for readers to follow the program’s intent. -

- -

-The smaller the function, the more descriptive and accurate its name can be, improving self-documentation. -

- -

-Large functions hide complexity and mix abstraction levels, making errors and duplication more likely. -

-
-
-
-

Do One Thing & One Level of Abstraction

-
-

-A function should do exactly one conceptual task, and all its statements should exist at the same abstraction level. -

- -

-Mixing details (like string concatenation) with high-level actions (like rendering a page) causes confusion. -

- -

-The Stepdown Rule: organise functions so they read like a top down narrative, each calling the next abstraction level. -

- -

-If you can extract a subfunction with a name that isn’t a restatement, the original function is doing too much. -

- -

-Functions that “do one thing” cannot be logically split into sections such as “initialize,” “process,” “finalize.” -

-
-
-
-

Switch Statements

-
-

-Switch statements naturally violate “do one thing” by handling multiple cases; they also grow in size over time. -

- -

-They break the Single Responsibility Principle (multiple reasons to change) and Open-Closed Principle (must change for new cases). -

- -

-Preferred approach: hide switch statements inside a factory and dispatch behavior polymorphically through an interface. -

- -

-Allow only one visible switch in your system, used solely for object creation, then encapsulate it. -

- -

-This removes duplication and keeps high-level code unaware of concrete type distinctions. -

- -

-Example: -

- -
-
public abstract class Employee {
-    public abstract boolean isPayday();
-    public abstract Money calculatePay();
-    public abstract void deliverPay(Money pay);
-}
------------------
-    public interface EmployeeFactory {
-        public Employee makeEmployee(EmployeeRecord r) throws InvalidEmployeeType;
-    }
------------------
-    public class EmployeeFactoryImpl implements EmployeeFactory {
-        public Employee makeEmployee(EmployeeRecord r) throws InvalidEmployeeType {
-            switch (r.type) {
-            case COMMISSIONED:
-                return new CommissionedEmployee(r) ;
-            case HOURLY:
-                return new HourlyEmployee(r);
-            case SALARIED:
-                return new SalariedEmploye(r);
-            default:
-                throw new InvalidEmployeeType(r.type);
-            }
-        }
-    }
-
-
-
-
-
-
-

Use Descriptive Names

-
-

-A function’s name should clearly state its purpose. Long, descriptive names beat short, cryptic ones. -

- -

-Consistent naming patterns (shared verbs/nouns) help code read like a coherent story and aid predictability. -

- -

-Descriptive names reduce the need for comments and improve comprehension without external documentation. -

- -

-Renaming functions can reveal design improvements, so try multiple options until the best emerges. -

- -

-IDE refactoring tools make renaming safe, encouraging experimentation. -

-
-
-
-

Function Arguments

-
-

-

The ideal number of arguments for a function is zero (niladic). Next comes one (monadic), followed closely by two (dyadic). Three arguments (triadic) should be avoided where possible. More than three (polyadic) requires very special justification—and then shouldn’t be used anyway.
-

- -

-Fewer arguments = better; aim for 0–2, avoid more than 3 unless absolutely necessary. -

- -

-Flag arguments (booleans) are a red flag—they imply the function does multiple things. -

- -

-Group related parameters into objects (e.g., Point for x and y) to reduce argument count and improve clarity. -

- -

-Output arguments are confusing—prefer returning values or mutating the owning object’s state. -

- -

-Match function/argument names in verb–noun or keyword style (e.g., writeField(name), assertExpectedEqualsActual). -

-
-
-
-

Have No Side Effects

-
-

-A function should do only what its name promises. Hidden state changes are misleading and dangerous. -

- -

-Side effects create temporal coupling, meaning the function must be called in a certain sequence to be safe. -

- -

-If unavoidable, make side effects explicit in the name (e.g., checkPasswordAndInitializeSession). -

- -

-Clear separation of command and query functions avoids ambiguity in meaning and intent. -

- -

-Functions that modify state and return information often cause confusion and should be split. -

-
-
-
-

Error Handling

-
-

-Error handling is a single responsibility—separate it from normal logic to keep both paths clear. -

- -

-Prefer exceptions over error codes to avoid cluttering the happy path and to reduce dependency magnets. -

- -

-Extract try/catch bodies into their own functions for cleaner structure. -

- -

-See below: -

- -
-
-public void delete(Page page) {
-      try {
-        deletePageAndAllReferences(page);
-      }
-      catch (Exception e) {
-        logError(e);
-      }
-  }
-
-private void deletePageAndAllReferences(Page page) throws Exception {
-    deletePage(page);
-    registry.deleteReference(page.name);
-    configKeys.deleteKey(page.name.makeKey());
-}
-private void logError(Exception e) {
-    logger.log(e.getMessage());
-}
-
-
-
- -

-Keep functions small enough that occasional multiple return or break statements are acceptable. -

- -

-Avoid duplication in error handling, and follow the DRY principle to ensure changes occur in one place. -

-
-
-
-
-
-
- -
-Created with Emacs 30.1 (Org mode 9.7.11) on Arch GNU/Linux -
-
-
- - diff --git a/output/posts/clean-code/clean-code-chapter-4.html b/output/posts/clean-code/clean-code-chapter-4.html deleted file mode 100644 index ed8222b..0000000 --- a/output/posts/clean-code/clean-code-chapter-4.html +++ /dev/null @@ -1,618 +0,0 @@ - - - - - - - -Clean Code: Chapter 4 Notes - - - - - - - - - - - - -
- - -
Updated: 2025-08-28 Thu 16:52
-
-
-

Clean Code: Chapter 4 Notes

- - - -

-Link to Chapter 3 | Link to Chapter 5 -

-
-

Chapter 4: Comments

-
-

-Comments are a necessary evil—they exist because code fails to express intent clearly. -

- -

-Outdated comments are dangerous; they can mislead more than help. -

- -

-Strive to write code that explains itself; comments should be minimised. -

- -

-Truth is always in the code, not in the comments. -

-
-
-

Comments Do Not Make Up for Bad Code

-
-

-Don’t use comments to excuse messy, unclear code. Clean the code instead. -

- -

-Clear, expressive code with few comments > cluttered code with many comments. -

- -
-
-// Check to see if the employee is eligible for full benefits
-if ((employee.flags & HOURLY_FLAG) && (employee.age > 65))
-
-// Better:
-if (employee.isEligibleForFullBenefits())
-
-
-
-
-
-
-

Good Comments

-
-

-Only write them when unavoidable. Such as in the following instances: -

-
-
-

Legal Comments

-
-

-Sometimes required for copyright/licensing. -

- -

-Keep them short; refer to standard licenses rather than embedding full legal text. -

-
-
-
-

Informative Comments

-
-

-Explain return values, formats, or patterns. -

- -

-Prefer naming/structuring code to make such comments unnecessary. -

- -
-
-// format matched kk:mm:ss EEE, MMM dd, yyyy
-Pattern timeMatcher = Pattern.compile("\\d*:\\d*:\\d* \\w*, \\w* \\d*, \\d*");
-
-
-
-
-
-
-

Explanation of Intent

-
-

-Describe why a certain approach was chosen. -

- -

-Helps future maintainers understand reasoning behind code. -

- -
-
-public int compareTo(Object o)
-{
-    if(o instanceof WikiPagePath)
-        { WikiPagePath p = (WikiPagePath) o;
-            String compressedName = StringUtil.join(names, "");
-            String compressedArgumentName = StringUtil.join(p.names, "");
-            return compressedName.compareTo(compressedArgumentName);
-        }
-    return 1; // we are greater because we are the right type.
-}
-
-
-
-
-
-
-

Clarification

-
-

-Translate obscure values into readable terms. -

- -

-Useful when working with unchangeable APIs/libraries, but risky if incorrect. -

-
-
-
-

Warning of Consequences

-
-

-Alert others about performance, thread-safety, or side effects. -

- -

-For example, in code you can say: -

- -

-// SimpleDateFormat is not thread safe, so create each instance independently. -

-
-
-
-

TODO Comments

-
-

-Mark incomplete work or planned improvements. -

- -

-Should be reviewed regularly; not an excuse for bad code. -

-
-
-
-

Amplification

-
-

-Highlight the importance of seemingly small details. -

- -

-// the trim is real important. It removes starting spaces... -

-
-
-
-

Javadocs in Public APIs

-
-

-Public APIs should have clear documentation. -

- -

-Javadocs can also mislead. Keep them accurate and up-to-date. -

-
-
-
-
-

Bad Comments

-
-
    -
  • Don't place a comment just because you feel like it.
  • -
  • Remove redundant comments.
  • -
  • Avoid misleading comments.
  • -
  • Don't mandate everything (not every function needs a Javadoc).
  • -
  • No need for journal comments, we have source control.
  • -
  • Remove noise comments.
  • -
-
-
-

Don’t Use a Comment When You Can Use a Function or Variable

-
-

-Replace explanatory comments with expressive variable or function names. -

- -

-Refactor code to remove comment redundancy. -

-
-
-
-

Position Markers

-
-

-Avoid decorative banners like // Actions ///////////////////////, they add clutter. -

- -

-Use sparingly and only for meaningful grouping. -

- -

-Overuse makes them blend into background noise. -

-
-
-
-

Closing Brace Comments

-
-

-Comments on closing braces (} // while) are unnecessary for small, well structured functions. -

- -

-Prefer short, clear functions over brace markers. -

-
-
-
-

Attributions and Bylines

-
-

-Don’t add personal tags like /* Added by Rick */, use version control for authorship history. -

- -

-Such comments become outdated and irrelevant over time. -

-
-
-
-

Commented Out Code

-
-

-Never keep old code commented out; delete it and rely on version control history. -

- -

-Commented-out code adds clutter and confuses future maintainers. -

- -
-
-// Old cruft that should be deleted:
-//hdrPos = bytePos;
-//dataPos = bytePos;
-
-
-
-
-
-
-

HTML Comments

-
-

-Avoid HTML markup inside code comments, it makes them harder to read in the editor. -

- -

-Let documentation tools (like Javadoc) handle formatting. -

-
-
-
-

Nonlocal Information

-
-

-Comments should describe nearby code only, not unrelated parts of the system. -

- -

-Avoid embedding global/system details that the function can’t control. -

-
-
-
-

Too Much Information

-
-

-Avoid long, unnecessary historical or technical explanations. -

- -

-Keep only relevant context (e.g., “RFC 2045” reference is fine, not the full spec). -

-
-
-
-

Inobvious Connection

-
-

-Ensure the relationship between comment and code is clear. -

- -

-Don’t make readers guess what part of the code the comment refers to. -

- -
-
-// plus filter bytes ... but which part is “filter”?
-this.pngBytes = new byte[((this.width + 1) * this.height * 3) + 200];
-
-
-
-
-
-
-

Function Headers

-
-

-Short, single purpose functions with good names don’t need header comments. -

- -

-Let the function name explain the purpose. -

-
-
-
-

Javadocs in Nonpublic Code

-
-

-Javadocs are useful for public APIs, but excessive formality in internal code is just noise. -

- -

-Internal methods should be self explanatory without full doc comments. -

-
-
-
-
-
-
-
- -
-Created with Emacs 30.1 (Org mode 9.7.11) on Arch GNU/Linux -
-
-
- - diff --git a/output/posts/clean-code/clean-code-chapter-5.html b/output/posts/clean-code/clean-code-chapter-5.html deleted file mode 100644 index 5f2a6dc..0000000 --- a/output/posts/clean-code/clean-code-chapter-5.html +++ /dev/null @@ -1,560 +0,0 @@ - - - - - - - -Clean Code: Chapter 5 Notes - - - - - - - - - - - - -
- - -
Updated: 2025-08-28 Thu 17:03
-
-
-

Clean Code: Chapter 5 Notes

- - - -

-Link to Chapter 4 | Link to Chapter 6 -

-
-

Chapter 5: Formatting

-
-
-
-

Vertical Formatting

-
-
    -
  • Vertical openness (blank lines) separates concepts and improves readability.
  • -
  • Too much density makes code look like a muddle and harder to scan.
  • -
-
-
-

Vertical Density

-
-
    -
  • Tightly related lines should appear vertically dense.
  • -
  • Avoid useless comments that interrupt association.
  • -
  • Example (bad):
  • -
-
-
public class ReporterConfig {
-/**
-* The class name of the reporter listener
-*/
-private String m_className;
-
-
- -
    -
  • Example (better):
  • -
-
-
public class ReporterConfig {
-private String m_className;
-private List<Property> m_properties = new ArrayList<>();
-
-
-
-
-
-

Vertical Distance

-
-
    -
  • Related concepts should be kept close together to reduce scrolling and searching.
  • -
  • Local variables → as close to use as possible, usually at top of function.
  • -
  • Control variables → declared inside loop headers.
  • -
  • Instance variables → declared at the top of class (common Java convention).
  • -
- -
-
for (Test each : tests) {
-    count += each.countTestCases();
-}
-
-
- -
    -
  • Dependent functions: caller above callee for natural top-down reading.
  • -
-
-
public Response makeResponse(...) {
-    String pageName = getPageNameOrDefault(request, "FrontPage");
-    loadPage(pageName, context);
-    return makePageResponse(context);
-}
-
-private String getPageNameOrDefault(Request request, String defaultPageName) { ... }
-
-
-
-
-
-

Conceptual Affinity

-
-
    -
  • Group functions with similar naming or shared purpose.
  • -
  • Example (JUnit assert methods):
  • -
-
-
static public void assertTrue(String message, boolean condition) { ... }
-static public void assertTrue(boolean condition) { ... }
-static public void assertFalse(String message, boolean condition) { ... }
-static public void assertFalse(boolean condition) { ... }
-
-
-
-
-
-

Vertical Ordering

-
-
    -
  • Organise code top down: -
      -
    • High-level concepts first (main logic).
    • -
    • Lower-level details later.
    • -
  • -
  • Readers can skim like a newspaper: important first, details last.
  • -
  • Contrast: C/C++ require declarations before use, Java does not.
  • -
-
-
-
-

Summary - vertical

-
-
    -
  • Use vertical openness to separate concepts.
  • -
  • Use vertical density to group related ones.
  • -
  • Keep related variables, methods, and concepts close together.
  • -
  • Order code top down for natural readability.
  • -
-
-
-
-
-

Horizontal Formatting

-
-

-Keep lines short. Most professional code naturally stays within ~45 characters, with ~80 as an upper bound. Lines beyond 100–120 characters are generally careless. -

- -

-Avoid shrinking font or overly wide monitors to fit more code, readability > fitting more characters. -

- -

-Example limit guideline: -

- -
-
// Good (short)
-int sum = a + b + c;
-
-// Bad (too long)
-int sum = a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p + q;
-
-
-
-
-

Horizontal Openness and Density

-
-

-Use spaces to separate low-precedence operators (e.g., +, -, =) and improve readability. -

- -

-Do not put spaces between function names and parentheses, they are closely related. -

- -

-Example (Quadratic formula formatting): -

- -
-
return (-b + Math.sqrt(determinant)) / (2*a);
-
-
- -

-Separate arguments with spaces after commas to show distinct parameters. -

-
-
-
-

Horizontal Alignment

-
-

-Avoid aligning variable declarations or assignments in columns, it draws the eye to the wrong place. -

- -

-Long aligned lists usually mean the class is too large and should be split. -

- -

-Example (preferred unaligned): -

- -
-
// Prefer this:
-private Socket socket;
-private InputStream input;
-private OutputStream output;
-
-//instead of:
-private Socket       socket;
-private InputStream  input;
-private OutputStream output;
-
-
-
-
-
-
-

Indentation

-
-

-Indent according to scope hierarchy: -

- -

-Classes → no indent -

- -

-Methods → 1 level -

- -

-Method bodies → 2 levels -

- -

-Inner blocks → +1 for each nesting -

- -

-Indentation makes scopes visually obvious; without it, code is hard to scan. -

- -

-Avoid collapsing scopes onto one line, always use braces and proper indenting. -

-
-
-
-

Dummy Scopes

-
-

-Avoid dummy bodies in loops (e.g., empty while or for loops). -

- -

-If unavoidable, place semicolon on its own indented line to make it visible. -

- -
-
while (dis.read(buf, 0, size) != -1)
-    ;
-
-
-
-
-
-
-

Team Rules

-
-

-Teams must agree on a single formatting style for consistency. -

- -

-Use IDE formatters to enforce these rules across all files. -

- -

-Consistent formatting builds trust and reduces mental load for readers. -

-
-
-
-

Uncle Bob’s Formatting Rules (Example in CodeAnalyzer.java)

-
-

-Short, clear methods with consistent spacing and indentation. -

- -

-Use spaces around assignment and low-precedence operators, no space for high precedence operators. -

- -

-Avoid deeply nested structures. Prefer clear, flat logic. -

- -

-Example snippet: -

- -
-
private void measureLine(String line) {
-  lineCount++;
-  int lineSize = line.length();
-  totalChars += lineSize;
-  lineWidthHistogram.addLine(lineSize, lineCount);
-  recordWidestLine(lineSize);
-}
-
-
-
-
-
-
-
-
- -
-Created with Emacs 30.1 (Org mode 9.7.11) on Arch GNU/Linux -
-
-
- - diff --git a/output/posts/posts-intro.html b/output/posts/posts-intro.html deleted file mode 100644 index 9b5bd2f..0000000 --- a/output/posts/posts-intro.html +++ /dev/null @@ -1,271 +0,0 @@ - - - - - - - -Posts Introduction - - - - - - - - - - - - -
- - -
Updated: 2025-08-09 Sat 19:27
-
-
-

Posts Introduction

- - -
-

Introduction

-
-

-In this section you will find posts related to both technical and non-technical topics. As Einstein said: “If you can’t explain it simply you don’t understand it well enough”, thus the goal with these posts is to develop the skill of being able to deliver habitual high quality explanations as well as reinforcing the topic(s) learnt. -

- -

-Generally, for teachers, this is done by taking the time to develop their own capacity to explain key concepts simply. In some institutions, departmental CPD is well spent rehearsing ways to improve the ability to explain the more difficult material in a more simpler way. -

- -

-When we study a topic, we get a grasp of the thing we are trying to learn, and then later on one may realise that they didn't actually learn it. The context of this differs for every person, for some it may be in the field of medicine, others in the field of software development; we may find ourselves with a solution without knowing the why. -

- -

-Explaining topics is a fantastic method of learning, in an article titled Explaining it helps you learn it, the author Juha-Matti Santal says: -

- -

-

The act of teaching (and more accurately, the act of preparing to teach) forces us to discover the knowledge gaps, fill them and improve our communication skills to a level where we know enough to confidently, comfortably and successfully transfer that knowledge from our mind to another mind.
Juha-Matti Santala, Explaining it helps you learn it
-

- -

-The article further quotes Henrik Jernevad in whose blog post writes: -

- -

-

I think the magic comes when you need to put words to things. It is quite easy to have a vague picture of something in your mind without realizing how vague it actually is. Putting things in words exposes that vagueness.
Henrik Jernevad
-

- -

-The final line Putting things in words exposes that vagueness is profound in the context of learning. In the pursuit of any academic discipline, we're often confronted with vagueness. Yet, it’s often through explaining a concept to someone else, using your own words, analogies, and diagrams, that one ends up actually understanding it. -

-
-
-
-
-
- -
-Created with Emacs 30.1 (Org mode 9.7.11) on Arch GNU/Linux -
-
-
- - diff --git a/output/posts/posts-list.html b/output/posts/posts-list.html deleted file mode 100644 index 2d25859..0000000 --- a/output/posts/posts-list.html +++ /dev/null @@ -1,249 +0,0 @@ - - - - - - - -Posts List - - - - - - - - - - - - -
- - -
Updated: 2025-09-26 Fri 15:56
-
- -
-
- -
-Created with Emacs 30.1 (Org mode 9.7.11) on Arch GNU/Linux -
-
-
- - diff --git a/output/setup.html b/output/setup.html deleted file mode 100644 index c636316..0000000 --- a/output/setup.html +++ /dev/null @@ -1,702 +0,0 @@ - - - - - - - -Setup - - - - - - - - - - - - -
- - -
Updated: 2025-08-10 Sun 20:55
-
-
-

Setup

-
-

Introduction

-
-

-Last updated: <2025-08-10 Sun 20:42> -

- -

-This page will highlight the build-script.el used to generate this website. This entire website is generated through org files using the command: -

- -
-
emacs -Q --script build-site.el
-
-
-
-
-
-

The script

-
-

-The script is as follows, at the start I have some metadata relating to the file, followed by package management,then the declaration of variables/functions and finally the org-publish-project-alist which handles nearly all of the project generation instructions. -

- -

-The following code snippet will probably change often, as I can never seem to stick to a single configuration 😀 -

- -
-
-;;; build-site.el --- Publish my website using org-publish -*- lexical-binding: t; -*-
-
-;; Author: Zaine Qayyum <zaineulabideen@outlook.com>
-;; Created: 2025-08-08
-;; Purpose: Build and publish my static site from Org files.
-
-;;; Commentary:
-;; Run this file with:
-;; emacs -Q --script build-site.el
-
-;;; Code:
-
-(require 'package)
-(setq package-user-dir (expand-file-name "./.packages"))
-(setq package-archives '(("melpa" . "https://melpa.org/packages/")
-                         ("elpa" . "https://elpa.gnu.org/packages/")))
-
-(require 'ox-publish)
-(require 'cl-lib)
-(require 'org)
-(require 'ox)
-(require 'ox-html)
-
-;; Initialize the package system
-(package-initialize)
-(unless package-archive-contents
-  (package-refresh-contents))
-
-;; Install dependencies
-(package-install 'htmlize)
-(add-to-list 'load-path "~/master-folder/org_files/org_web/")
-(require 'htmlize)
-
-(setq org-html-htmlize-output-type 'css)
-
-(defvar z-shared-head
-  "
-<link rel=\"stylesheet\" href=\"/assets/styles/style.css\" />
-<script src=\"/assets/scripts/script.js\" defer></script> ")
-
-(setq org-export-global-macros
-      (append
-       '(("sidenote"
-          . "@@html:<label for=\"sn$1\" class=\"margin-toggle sidenote-number\"></label><input type=\"checkbox\" id=\"sn$1\" class=\"margin-toggle\"/><span class=\"sidenote\">$2</span>@@")
-         ("epigraph"  . "@@html:<div class=\"epigraph\"><blockquote>$1<footer>$2</footer></blockquote></div>@@")
-         ("epigraph_single"  . "@@html:<div class=\"epigraph\"><blockquote>$1</blockquote></div>@@")
-         ("epigraph3" . "@@html:<div class=\"epigraph\"><blockquote>$1<footer>$2, <cite>$3</cite></footer></blockquote></div>@@")
-         ("kbd"       . "@@html:<kbd>$1</kbd>@@@@latex:\\texttt{$1}@@")
-         ("margimg"
-          . "@@html:<aside class=\"marginnote\"><figure class=\"mn-fig\"><img src=\"$1\" alt=\"$2\" class=\"mn-img\" loading=\"lazy\" decoding=\"async\"/>$3</figure></aside>@@")
-
-
-         org-export-global-macros)))
-
-
-(defvar z-preamble
-  "
-<div class=\"banner-header\">
-  <a href=\"/\"> <img src=\"/assets/images/gr.png\" alt=\"Site Logo\" class=\"banner-logo\" /> </a>
-  <nav>
-    <a href=\"/\">Home | </a>
-    <a href=\"/posts/posts-list.html\">Posts | </a>
-    <a href=\"/blogs/blogs-list.html\">Blogs | </a>
-    <a href=\"/contact.html\">Contact</a>
-  </nav>
-  <button class=\"theme-toggle\" id=\"theme-toggle\" type=\"button\" aria-label=\"Toggle dark mode\">🌗 Theme</button>
-  </div>
-  <div id=\"updated\">Updated: %C</div>
-
-"
-  )
-
-(defvar z-postamble
-  "<footer>
-<div class=\"copyright-container\">
-<div class=\"copyright\">
-Copyright &copy; 2022-2025 Zaine Qayyum. All rights reserved unless otherwise noted.</div></div>
-<div class=\"generated\">
-Created with %c on <a href=\"https://www.archlinux.org/\">Arch</a> <a href=\"https://www.gnu.org\">GNU</a>/<a href=\"https://www.kernel.org/\">Linux</a>
-</div>
-</footer>")
-
-
-(defun z/posts-sitemap (title list)
-  "sitemap that lists post links as bullet points with dates and tags."
-  (concat
-   "#+TITLE: " title "\n"
-   "#+OPTIONS: toc:nil num:nil \n\n"
-   "See the categories: @@html:<a href=\"../categories.html\">Categories</a>@@\n\n"
-   "* Posts:\n"
-   (mapconcat
-    (lambda (entry)
-      (let* ((link (car entry))
-             ;; extract relative file name from the link
-             (filename (if (string-match "\\[\\[file:\\([^]]+\\)\\]" link)
-                           (match-string 1 link)
-                         link))
-             (full-path (expand-file-name filename "~/master-folder/org_files/org_web/posts/"))
-             (date-str "no date")
-             (tags-str ""))
-        ;; Get publish date
-        (let ((date (org-publish-find-date full-path org-publish-project-alist)))
-          (when date
-            (setq date-str (format-time-string "%d-%m-%Y %H:%M" date))))
-        ;; Get FILETAGS from file buffer
-        (when (file-exists-p full-path)
-          (with-temp-buffer
-            (insert-file-contents full-path)
-            (org-mode)
-            (let* ((tags (cadr (assoc "FILETAGS" (org-collect-keywords '("FILETAGS"))))))
-              (when tags
-                (setq tags-str (mapconcat (lambda (tag)
-                                            (format "@@html:<a href=\"/tags/%s.html\"> <span class=\"post-tag\">%s</span> </a>@@" tag tag))
-                                          (split-string tags ":" t)  ;; <- Splits by ":" and removes empty strings
-                                          " "))))))
-        ;; Final line output
-        (format "- %s @@html:<span class=\"post-date\">%s</span>@@ %s" link date-str tags-str)))
-    (cdr list)
-    "\n")))
-
-
-(defun z/blogs-sitemap (title list)
-  "sitemap that lists blog links as bullet points with dates and tags."
-  (concat
-   "#+TITLE: " title "\n"
-   "#+OPTIONS: toc:nil num:nil \n\n"
-   "See the categories: @@html:<a href=\"../categories.html\">Categories</a>@@\n\n"
-   "* Blogs:\n"
-   (mapconcat
-    (lambda (entry)
-      (let* ((link (car entry))
-             ;; extract relative file name from the link
-             (filename (if (string-match "\\[\\[file:\\([^]]+\\)\\]" link)
-                           (match-string 1 link)
-                         link))
-             (full-path (expand-file-name filename "~/master-folder/org_files/org_web/blogs/"))
-             (date-str "no date")
-             (tags-str ""))
-        ;; Get publish date
-        (let ((date (org-publish-find-date full-path org-publish-project-alist)))
-          (when date
-            (setq date-str (format-time-string "%d-%m-%Y %H:%M" date))))
-        ;; Get FILETAGS from file buffer
-        (when (file-exists-p full-path)
-          (with-temp-buffer
-            (insert-file-contents full-path)
-            (org-mode)
-            (let* ((tags (cadr (assoc "FILETAGS" (org-collect-keywords '("FILETAGS"))))))
-              (when tags
-                (setq tags-str (mapconcat (lambda (tag)
-                                            (format "@@html:<a href=\"/tags/%s.html\"> <span class=\"post-tag\">%s</span> </a>@@" tag tag))
-                                          (split-string tags ":" t)  ;; <- Splits by ":" and removes empty strings
-                                          " "))))))
-        ;; Final line output
-        (format "- %s @@html:<span class=\"post-date\">%s</span>@@ %s" link date-str tags-str)))
-    (cdr list)
-    "\n")))
-
-
-
-(defun z/categories-sitemap (title _list)
-  "Generate a categories page by scanning tags across org-posts and org-blogs."
-  (let* ((expanded (org-publish-expand-projects org-publish-project-alist))
-         (posts    (assoc "org-posts" expanded))
-         (blogs    (assoc "org-blogs" expanded))
-         ;; DO NOT set :exclude to "" — it excludes everything
-         (files (cl-remove-duplicates
-                 (append (and posts (org-publish-get-base-files posts))
-                         (and blogs (org-publish-get-base-files blogs)))
-                 :test #'file-equal-p))
-         ;; optional: drop the generated sitemaps
-         (files (cl-remove-if
-                 (lambda (f)
-                   (member (file-name-nondirectory f)
-                           '("posts-list.org" "blogs-list.org" "sitemap.org" "categories.org")))
-                 files))
-         (counts (make-hash-table :test 'equal)))
-    ;;(message "files I will scan: %S" files)
-    (dolist (f files)
-      (when (file-readable-p f)
-        (with-temp-buffer
-          (insert-file-contents f)
-          (org-mode)
-          (let* ((kw (org-collect-keywords '("FILETAGS" "TAGS")))
-                 (raw (car (or (cdr (assoc "FILETAGS" kw))
-                               (cdr (assoc "TAGS" kw))))))
-            (when raw
-              (dolist (tag (split-string raw ":" t))
-                (puthash tag (1+ (gethash tag counts 0)) counts)))))))
-
-    (let (tags)
-      (maphash (lambda (k _) (push k tags)) counts)
-      (setq tags (sort tags #'string-lessp))
-      (concat
-       "#+TITLE: " title "\n#+OPTIONS: toc:nil num:nil title:nil\n\n* Categories (Includes both blogs and posts)\n"
-       (if tags
-           (mapconcat
-            (lambda (tag)
-              (format "- [[file:tags/%s.org][@@html:<span class=\"post-tag\">%s</span>@@]] (%d)"
-                      (z/tag-slug tag) tag (gethash tag counts))
-              )
-            tags
-            "\n")
-         "_No tags found yet._")))))
-
-
-(defun z/tag-slug (s)
-  "Turn a tag into a safe filename."
-  (let ((down (downcase s)))
-    (replace-regexp-in-string "[^a-z0-9]+" "-" down)))
-
-(defun z/collect-post-files ()
-  "Return all .org files from org-posts and org-blogs."
-  (let* ((expanded (org-publish-expand-projects org-publish-project-alist))
-         (posts    (assoc "org-posts" expanded))
-         (blogs    (assoc "org-blogs" expanded)))
-    (cl-remove-duplicates
-     (append (and posts (org-publish-get-base-files posts))
-             (and blogs (org-publish-get-base-files blogs)))
-     :test #'file-equal-p)))
-
-(defun z/gather-tag-index ()
-  "Return hash: tag -> list of (FILE TITLE DATE-ISO)."
-  (let ((idx (make-hash-table :test 'equal)))
-    (dolist (f (z/collect-post-files))
-      (when (and (string-match-p "\\.org\\'" f)
-                 (file-readable-p f)
-                 ;; ignore generated lists
-                 (not (member (file-name-nondirectory f)
-                              '("posts-list.org" "blogs-list.org" "sitemap.org" "categories.org"))))
-        (with-temp-buffer
-          (insert-file-contents f)
-          (org-mode)
-          (let* ((kw (org-collect-keywords '("TITLE" "FILETAGS" "TAGS" "DATE")))
-                 (title (or (car (cdr (assoc "TITLE" kw)))
-                            (file-name-base f)))
-                 (date  (or (car (cdr (assoc "DATE" kw))) "")) ;; optional
-                 (raw   (car (or (cdr (assoc "FILETAGS" kw))
-                                 (cdr (assoc "TAGS" kw))))))
-            (when raw
-              (dolist (tag (split-string raw ":" t))
-                (push (list f title date) (gethash tag idx))))))))
-    idx))
-
-(defun z/write-tag-pages ()
-  "Generate tags/*.org pages listing posts for each tag."
-  (let* ((site-root (expand-file-name "~/master-folder/org_files/org_web/"))
-         (tags-dir  (expand-file-name "tags" site-root)))
-    (unless (file-directory-p tags-dir)
-      (make-directory tags-dir t))
-    (let ((idx (z/gather-tag-index)))
-      (maphash
-       (lambda (tag items)
-         (let* ((slug (z/tag-slug tag))
-                (outfile (expand-file-name (format "%s.org" slug) tags-dir)))
-           (with-temp-file outfile
-             (insert (format "#+TITLE: Tag: %s\n#+OPTIONS: toc:nil num:nil title:nil \n\n* Posts tagged %s\n"
-                             tag tag))
-             ;; sort newest first if DATE present
-             (setq items (sort items (lambda (a b) (string> (nth 2 a) (nth 2 b)))))
-             (dolist (it items)
-               (let* ((file (nth 0 it))
-                      (title (nth 1 it))
-                      (rel   (file-relative-name file tags-dir)))
-                 ;; link to the source .org; org-publish will rewrite to the .html
-                 (insert (format "- [[file:%s][%s]]\n" rel title)))))))
-       idx)
-      )))
-
-(defun z/filetags-html (info)
-  "Return an HTML snippet for FILETAGS from INFO, or nil if none."
-  (let ((tags (plist-get info :filetags)))
-    (when tags
-      (format
-       "<div class=\"filetags\">%s</div>\n"
-       (mapconcat (lambda (tag)
-                    (format "<a href=\"/categories.html\"> <span class=\"post-tag\">%s</span> </a>" tag))
-                  tags " ")))))
-
-(defun z/insert-filetags-after-title (output backend info)
-  "Insert FILETAGS after the first <h1 class=\"title\"> in OUTPUT."
-  (if (org-export-derived-backend-p backend 'html)
-      (let ((block (z/filetags-html info)))
-        (if (and block
-                 (string-match "\\(<h1[^>]*class=[\"']title[\"'][^>]*>.*?</h1>\\)" output))
-            (replace-match (concat "\\1\n" block) t nil output)
-          output))
-    output))
-
-(org-export-define-derived-backend 'z-html 'html
-                                   :filters-alist '((:filter-final-output . z/insert-filetags-after-title)))
-
-(defun z/z-publish-to-html (plist filename pub-dir)
-  "Publish FILENAME to HTML using the z-html backend."
-  (org-publish-org-to 'z-html filename ".html" plist pub-dir))
-
-
-;; Define the publishing project
-(setq org-publish-project-alist
-      `(("org-main"
-         :recursive t
-         :base-directory "~/master-folder/org_files/org_web/"
-         :publishing-function z/z-publish-to-html
-         :publishing-directory "~/master-folder/org_files/org_web/output"
-         :base-extension "org"
-         :auto-sitemap t
-         :sitemap-filename "sitemap.org"
-         :sitemap-title "Sitemap"
-         :sitemap-sort-files chronologically
-         :html-preamble ,z-preamble
-         :html-postamble ,z-postamble
-         :html-head ,z-shared-head
-         )
-
-        ("org-categories-sitemap"
-         :recursive t
-         :base-directory "~/master-folder/org_files/org_web/"
-         :publishing-directory "~/master-folder/org_files/org_web/output"
-         :base-extension "org"
-         :auto-sitemap t
-         :sitemap-filename "categories.org"
-         :sitemap-title "Categories"
-         :sitemap-function z/categories-sitemap
-         :html-preamble ,z-preamble
-         :html-postamble ,z-postamble
-         :html-head ,z-shared-head
-         )
-        ("org-posts"
-         :base-directory "~/master-folder/org_files/org_web/posts"
-         :publishing-directory "~/master-folder/org_files/org_web/output/posts/"
-         :recursive t
-         :base-extension "org"
-         :publishing-function z/z-publish-to-html
-         :with-author nil
-         :with-creator nil
-         :html-validation-link nil
-         :with-toc t
-         :section-numbers t
-         :html-preamble ,z-preamble
-         :html-postamble ,z-postamble
-         :auto-sitemap t
-         :sitemap-filename "posts-list.org"
-         :sitemap-title "Posts List"
-         :sitemap-style list
-         :sitemap-function z/posts-sitemap
-         :sitemap-sort-files anti-chronologically
-         :html-head ,z-shared-head
-         )
-        ("org-blogs"
-         :base-directory "~/master-folder/org_files/org_web/blogs"
-         :publishing-directory "~/master-folder/org_files/org_web/output/blogs/"
-         :recursive t
-         :base-extension "org"
-         :publishing-function z/z-publish-to-html
-         :with-author nil
-         :with-creator nil
-         :html-validation-link nil
-         :html-preamble ,z-preamble
-         :html-postamble ,z-postamble
-         :auto-sitemap t
-         :sitemap-filename "blogs-list.org"
-         :sitemap-title "Blogs List"
-         :sitemap-style list
-         :sitemap-function z/blogs-sitemap
-         :sitemap-sort-files anti-chronologically
-         :html-head ,z-shared-head
-         )
-        ("org-tags"
-         :base-directory "~/master-folder/org_files/org_web/tags"
-         :publishing-directory "~/master-folder/org_files/org_web/output/tags"
-         :recursive t
-         :base-extension "org"
-         :publishing-function org-html-publish-to-html
-         :with-author nil
-         :with-creator nil
-         :html-preamble ,z-preamble
-         :html-postamble ,z-postamble
-         :html-head ,z-shared-head)
-        ("org-assets"
-         :base-directory "~/master-folder/org_files/org_web/assets/"
-         :base-extension "css\\|js\\|png\\|jpg\\|gif\\|svg\\|pdf\\|woff\\|woff2\\|ttf"
-         :publishing-directory "~/master-folder/org_files/org_web/output/assets/"
-         :recursive t
-         :publishing-function org-publish-attachment)
-        ))
-
-(delete-directory "~/master-folder/org_files/org_web/output/" t)
-(delete-directory "~/master-folder/org_files/org_web/tags/" t)
-(message "Directory deleted")
-
-;; Generate the site output
-(z/write-tag-pages)
-(org-publish-all t)
-
-(message "Build complete!")
-
-;;; build-site.el ends here
-
-
-
-
-
-
-

Publish Script

-
-

-As the project is hosted on Github, I have created a small script that is able to push changes to the remote repository, which Cloudflare will automatically detect and rebuild the website: -

- -
-
-#!/bin/bash
-
-# Exit immediately if any command fails
-set -e
-
-# Define paths
-ORG_OUTPUT_DIR="$HOME/master-folder/org_files/org_web/output"
-
-cd "$ORG_OUTPUT_DIR"
-
-echo "HTML published to: $ORG_OUTPUT_DIR"
-
-# Git commands
-echo "Adding changes to Git..."
-git add .
-
-echo "Committing..."
-git commit -m "Auto-publish on $(date)" || echo "Nothing to commit."
-
-echo "Pushing to GitHub..."
-git push origin main
-
-echo "Done! Changes pushed and Cloudflare should rebuild the site."
-
-
-
-
-
-
-
-
- -
-Created with Emacs 30.1 (Org mode 9.7.11) on Arch GNU/Linux -
-
-
- - diff --git a/output/sitemap.html b/output/sitemap.html deleted file mode 100644 index 4e70162..0000000 --- a/output/sitemap.html +++ /dev/null @@ -1,270 +0,0 @@ - - - - - - - -Sitemap - - - - - - - - - - - - -
- - -
Updated: 2025-09-26 Fri 15:56
-
- -
-
- -
-Created with Emacs 30.1 (Org mode 9.7.11) on Arch GNU/Linux -
-
-
- - diff --git a/output/tags/books.html b/output/tags/books.html deleted file mode 100644 index 3406a9a..0000000 --- a/output/tags/books.html +++ /dev/null @@ -1,244 +0,0 @@ - - - - - - - -Tag: books - - - - - - - - - - - - -
- - -
Updated: 2025-09-26 Fri 15:56
-
- -
-
- -
-Created with Emacs 30.1 (Org mode 9.7.11) on Arch GNU/Linux -
-
-
- - diff --git a/output/tags/education.html b/output/tags/education.html deleted file mode 100644 index 0da5c4a..0000000 --- a/output/tags/education.html +++ /dev/null @@ -1,240 +0,0 @@ - - - - - - - -Tag: education - - - - - - - - - - - - -
- - -
Updated: 2025-09-26 Fri 15:56
-
-
-
-

Posts tagged education

- -
-
-
-
- -
-Created with Emacs 30.1 (Org mode 9.7.11) on Arch GNU/Linux -
-
-
- - diff --git a/output/tags/emacs.html b/output/tags/emacs.html deleted file mode 100644 index e71919c..0000000 --- a/output/tags/emacs.html +++ /dev/null @@ -1,241 +0,0 @@ - - - - - - - -Tag: emacs - - - - - - - - - - - - -
- - -
Updated: 2025-09-26 Fri 15:56
-
- -
-
- -
-Created with Emacs 30.1 (Org mode 9.7.11) on Arch GNU/Linux -
-
-
- - diff --git a/output/tags/insights.html b/output/tags/insights.html deleted file mode 100644 index eef0a29..0000000 --- a/output/tags/insights.html +++ /dev/null @@ -1,243 +0,0 @@ - - - - - - - -Tag: insights - - - - - - - - - - - - -
- - -
Updated: 2025-09-26 Fri 15:56
-
-
-
-

Posts tagged insights

- -
-
-
-
- -
-Created with Emacs 30.1 (Org mode 9.7.11) on Arch GNU/Linux -
-
-
- - diff --git a/output/tags/introduction.html b/output/tags/introduction.html deleted file mode 100644 index 5780ec7..0000000 --- a/output/tags/introduction.html +++ /dev/null @@ -1,241 +0,0 @@ - - - - - - - -Tag: introduction - - - - - - - - - - - - -
- - -
Updated: 2025-09-26 Fri 15:56
-
-
-
-

Posts tagged introduction

- -
-
-
-
- -
-Created with Emacs 30.1 (Org mode 9.7.11) on Arch GNU/Linux -
-
-
- - diff --git a/output/tags/maths.html b/output/tags/maths.html deleted file mode 100644 index 1c13713..0000000 --- a/output/tags/maths.html +++ /dev/null @@ -1,240 +0,0 @@ - - - - - - - -Tag: maths - - - - - - - - - - - - -
- - -
Updated: 2025-09-26 Fri 15:56
-
-
-
-

Posts tagged maths

- -
-
-
-
- -
-Created with Emacs 30.1 (Org mode 9.7.11) on Arch GNU/Linux -
-
-
- - diff --git a/output/tags/notes.html b/output/tags/notes.html deleted file mode 100644 index 233bb45..0000000 --- a/output/tags/notes.html +++ /dev/null @@ -1,244 +0,0 @@ - - - - - - - -Tag: notes - - - - - - - - - - - - -
- - -
Updated: 2025-09-26 Fri 15:56
-
- -
-
- -
-Created with Emacs 30.1 (Org mode 9.7.11) on Arch GNU/Linux -
-
-
- - diff --git a/output/tags/reading.html b/output/tags/reading.html deleted file mode 100644 index f8c349f..0000000 --- a/output/tags/reading.html +++ /dev/null @@ -1,240 +0,0 @@ - - - - - - - -Tag: reading - - - - - - - - - - - - -
- - -
Updated: 2025-09-26 Fri 15:56
-
-
-
-

Posts tagged reading

- -
-
-
-
- -
-Created with Emacs 30.1 (Org mode 9.7.11) on Arch GNU/Linux -
-
-
- - diff --git a/output/tags/review.html b/output/tags/review.html deleted file mode 100644 index 0ccbdfd..0000000 --- a/output/tags/review.html +++ /dev/null @@ -1,240 +0,0 @@ - - - - - - - -Tag: review - - - - - - - - - - - - -
- - -
Updated: 2025-09-26 Fri 15:56
-
-
-
-

Posts tagged review

- -
-
-
-
- -
-Created with Emacs 30.1 (Org mode 9.7.11) on Arch GNU/Linux -
-
-
- - diff --git a/output/tags/website.html b/output/tags/website.html deleted file mode 100644 index b7a6831..0000000 --- a/output/tags/website.html +++ /dev/null @@ -1,240 +0,0 @@ - - - - - - - -Tag: website - - - - - - - - - - - - -
- - -
Updated: 2025-09-26 Fri 15:56
-
-
-
-

Posts tagged website

- -
-
-
-
- -
-Created with Emacs 30.1 (Org mode 9.7.11) on Arch GNU/Linux -
-
-
- - diff --git a/posts.org b/posts.org deleted file mode 100644 index 1ba3462..0000000 --- a/posts.org +++ /dev/null @@ -1,12 +0,0 @@ -#+TITLE: Posts -#+OPTIONS: toc:nil num:nil -#+SELECT_TAGS: export - -* Links -*** [[file:posts/posts-intro.org][Posts Introduction]] :intro: - - -*** [[file:posts/zettelkasten.org][Zettelkasten Method]] :education: - - -<2025-08-06 Wed> diff --git a/posts/career/airflow.org b/posts/career/airflow.org new file mode 100644 index 0000000..7e9006a --- /dev/null +++ b/posts/career/airflow.org @@ -0,0 +1,65 @@ +#+TITLE: Datamarts, Airflow and DAG's +#+OPTIONS: num:nil +#+DATE:<2025-11-15 Sat 18:37> +#+filetags: :learning:notes: +#+COMMENTS: t +#+SLUG: airflow + +* What they are: +Summary from Filippo: +- A DataMart it's simply a database that is made ad-hoc to contain a certain subset of data (Specific tables with specific columns). +- Once that is created Airflow is used to put data in that database. So it extract data from other sources (APIS, databases) and loads it into the datamart we have created. +- Once you get Airflow locally working and a new Datamart (deployed locally) you can then write a dag to load data into the Mart. + +A data warehouse is where *all* the data is stored (sales, marketing, HR etc.). A data mart is just what one group needs, so for example, a finance data mart would contain invoices, payments and budgets data. + +** Apache Airflow +It is a tool that lets you automate and schedule data tasks. It: +- Runs jobs on a schedule (e.g. every night at 1am). +- Knows what needs to run before what. +- Shows you a nice UI so you can see: what succeeded, what failed and where it’s stuck. + +So: Airflow = a smart scheduler and organiser for data workflows. + +** DAG +DAG stands for directed acyclic graph. In simple terms, it is just a workflow made of tasks in a certain order. For example, a DAG can be: ~get_data~, then ~clean_data~, then ~load_data_into_datamart~, and finally ~send_email~. DAG is the whole workflow, the tasks are the individual steps inside the DAG. + +*** ELT (Extract → Load → Transform) + +Do the cleaning after loading into the warehouse. Flow: +- Extract from source systems +- Load raw data directly into the warehouse +- Transform inside the warehouse (usually with SQL) + +Here is a diagram that explains this process: + +[[../../assets/sketchnotes/elt-yed.svg]] + +** Diagram +*Sam has written the following and made this diagram* + +[[../../../assets/images/diagrams/airflow.png]] + +1. AZDO repo for our datamart to go in. Clone the data mart example template +2. Datamart, can use one big one that has different tables for everything that we need. +3. Local airflow that calls api and gets data. continuation tokens can be used. there is a good example +4. Upload everything to microlise servers where it is run automatically +5. Power bi, change data sources to be references to our sql. Can have all tables in power bi. Use filters + +** Summary that I wrote in slack: + +- https://azdo.microlise.com/MicroliseCollection/Microlise/_git/DataMart.SQLServer: very useful link explaining what datamarts are, ELT (extract, load, transform) orchestration and how to set this up + +- https://azdo.microlise.com/MicroliseCollection/Microlise/_git/DataMart.DataEngineering.InsightReadiness: an example of what a datamart repo looks like. in the template repo, the two important files are tsqlt and DataMartTemplate (in this instance its called InsightReadiness ) + +- tsqlt coverage should be above 60% + +- https://microliseuk.sharepoint.com/sites/msteams_bb7636: This link is the CoP (community of practice) for the airflow. You'll find useful information regarding what apache airflow is and how to set things up. + +- https://microliseuk.sharepoint.com/sites/msteams_bb7636/SitePages/Getting-started-with-Airflow.aspx: This link tells you how to set airflow up locally. Once you're done, this will be published to an airflow scheduler platform. Keep in mind that when you want to publish this, document that it succeeds in the PR. + +- http://airmastqa01.mms.local:8080/home: this link shows all the DAGs that are currently running in a testing environment, and when they are scheduled. Login using MSUP (edited) + +- http://airmastprd01.mms.local:8080/home: This link is the prod version + +- https://azdo.microlise.com/MicroliseCollection/Microlise/_git/DevDocs?path=/Airflow/HowTo/PowerBiReportRefresh.md&version=GBmain&_a=preview: Might prove to be useful when we do the powerbi refresh thing... diff --git a/posts/career/airflow.org~ b/posts/career/airflow.org~ new file mode 100644 index 0000000..cb12bb7 --- /dev/null +++ b/posts/career/airflow.org~ @@ -0,0 +1,63 @@ +#+TITLE: Datamarts, Airflow and DAG's +#+OPTIONS: num:nil +#+DATE:<2025-11-15 Sat 18:37> +#+filetags: :learning:notes: + +* What they are: +Summary from Filippo: +- A DataMart it's simply a database that is made ad-hoc to contain a certain subset of data (Specific tables with specific columns). +- Once that is created Airflow is used to put data in that database. So it extract data from other sources (APIS, databases) and loads it into the datamart we have created. +- Once you get Airflow locally working and a new Datamart (deployed locally) you can then write a dag to load data into the Mart. + +A data warehouse is where *all* the data is stored (sales, marketing, HR etc.). A data mart is just what one group needs, so for example, a finance data mart would contain invoices, payments and budgets data. + +** Apache Airflow +It is a tool that lets you automate and schedule data tasks. It: +- Runs jobs on a schedule (e.g. every night at 1am). +- Knows what needs to run before what. +- Shows you a nice UI so you can see: what succeeded, what failed and where it’s stuck. + +So: Airflow = a smart scheduler and organiser for data workflows. + +** DAG +DAG stands for directed acyclic graph. In simple terms, it is just a workflow made of tasks in a certain order. For example, a DAG can be: ~get_data~, then ~clean_data~, then ~load_data_into_datamart~, and finally ~send_email~. DAG is the whole workflow, the tasks are the individual steps inside the DAG. + +*** ELT (Extract → Load → Transform) + +Do the cleaning after loading into the warehouse. Flow: +- Extract from source systems +- Load raw data directly into the warehouse +- Transform inside the warehouse (usually with SQL) + +Here is a diagram that explains this process: + +[[../../assets/sketchnotes/elt-yed.svg]] + +** Diagram +*Sam has written the following and made this diagram* + +[[../../../assets/images/diagrams/airflow.png]] + +1. AZDO repo for our datamart to go in. Clone the data mart example template +2. Datamart, can use one big one that has different tables for everything that we need. +3. Local airflow that calls api and gets data. continuation tokens can be used. there is a good example +4. Upload everything to microlise servers where it is run automatically +5. Power bi, change data sources to be references to our sql. Can have all tables in power bi. Use filters + +** Summary that I wrote in slack: + +- https://azdo.microlise.com/MicroliseCollection/Microlise/_git/DataMart.SQLServer: very useful link explaining what datamarts are, ELT (extract, load, transform) orchestration and how to set this up + +- https://azdo.microlise.com/MicroliseCollection/Microlise/_git/DataMart.DataEngineering.InsightReadiness: an example of what a datamart repo looks like. in the template repo, the two important files are tsqlt and DataMartTemplate (in this instance its called InsightReadiness ) + +- tsqlt coverage should be above 60% + +- https://microliseuk.sharepoint.com/sites/msteams_bb7636: This link is the CoP (community of practice) for the airflow. You'll find useful information regarding what apache airflow is and how to set things up. + +- https://microliseuk.sharepoint.com/sites/msteams_bb7636/SitePages/Getting-started-with-Airflow.aspx: This link tells you how to set airflow up locally. Once you're done, this will be published to an airflow scheduler platform. Keep in mind that when you want to publish this, document that it succeeds in the PR. + +- http://airmastqa01.mms.local:8080/home: this link shows all the DAGs that are currently running in a testing environment, and when they are scheduled. Login using MSUP (edited) + +- http://airmastprd01.mms.local:8080/home: This link is the prod version + +- https://azdo.microlise.com/MicroliseCollection/Microlise/_git/DevDocs?path=/Airflow/HowTo/PowerBiReportRefresh.md&version=GBmain&_a=preview: Might prove to be useful when we do the powerbi refresh thing... diff --git a/posts/career/career-intro.org b/posts/career/career-intro.org new file mode 100644 index 0000000..22df173 --- /dev/null +++ b/posts/career/career-intro.org @@ -0,0 +1,9 @@ +#+TITLE: Career Introduction +#+OPTIONS: num:nil tags:t toc:t +#+DATE: <2025-11-06 Thu 21:29> +#+FILETAGS: :introduction: +#+WIP: +#+COMMENTS: t +#+SLUG: career-intro + +Here lies posts that relate to the technicalities and intricacies of my career. They will include information regarding my competencies, career growth, technical components and many other things. The goal is to have a single designated area where I can read, learn and relearn different things. diff --git a/posts/career/career-intro.org~ b/posts/career/career-intro.org~ new file mode 100644 index 0000000..1856c9f --- /dev/null +++ b/posts/career/career-intro.org~ @@ -0,0 +1,7 @@ +#+TITLE: Career Introduction +#+OPTIONS: num:nil tags:t toc:t +#+DATE: <2025-11-06 Thu 21:29> +#+FILETAGS: :introduction: +#+WIP: + +Here lies posts that relate to the technicalities and intricacies of my career. They will include information regarding my competencies, career growth, technical components and many other things. The goal is to have a single designated area where I can read, learn and relearn different things. diff --git a/posts/career/career-list.org b/posts/career/career-list.org new file mode 100644 index 0000000..82b8641 --- /dev/null +++ b/posts/career/career-list.org @@ -0,0 +1,25 @@ +#+TITLE: Career List +#+OPTIONS: toc:nil num:nil + +See the categories: @@html:Categories@@ + +* 2025 +See the following page for more details: @@html:Career Intro@@ + +** December 2025 +- [[file:probation-objectives.org][Probation Objectives:]] @@html:@@ @@html: @@ @@html: @@ + +** November 2025 +- [[file:airflow.org][Datamarts, Airflow and DAG's]] @@html:@@ @@html: @@ @@html: @@ +- [[file:normalisation.org][Benefits of Normalisation]] @@html:@@ @@html: @@ @@html: @@ +- [[file:management-of-self.org][Management of self training]] @@html:@@ @@html: @@ @@html: @@ +- [[file:wireframe-designs.org][Wireframe Designs]] @@html:@@ @@html: @@ @@html: @@ +- [[file:requirements-features.org][Requirements, features, user stories, tasks, walking skeletons]] @@html:@@ @@html: @@ @@html: @@ +- [[file:career-intro.org][Career Introduction]] @@html:@@ @@html: @@ +- [[file:invest-principles.org][Invest Principles]] @@html:@@ @@html: @@ @@html: @@ +- [[file:retrospectives.org][Retrospectives]] @@html:@@ @@html: @@ @@html: @@ +- [[file:lean.org][Lean]] @@html:@@ @@html: @@ @@html: @@ + +** October 2025 +- [[file:owasp.org][OWASP Top Ten]] @@html:@@ @@html: @@ @@html: @@ +- [[file:solid-principles.org][SOLID Principles]] @@html:@@ @@html: @@ @@html: @@ diff --git a/posts/career/invest-principles.org b/posts/career/invest-principles.org new file mode 100644 index 0000000..5af8ba9 --- /dev/null +++ b/posts/career/invest-principles.org @@ -0,0 +1,9 @@ +#+title: Invest Principles +#+date: <2025-11-06 Thu 21:08> +#+OPTIONS: num:nil tags:t toc:t +#+filetags: :learning:notes: +#+WIP: t +#+COMMENTS: t +#+SLUG: invest-principles + +* TODO diff --git a/posts/career/invest-principles.org~ b/posts/career/invest-principles.org~ new file mode 100644 index 0000000..f182149 --- /dev/null +++ b/posts/career/invest-principles.org~ @@ -0,0 +1,6 @@ +#+title: Invest Principles +#+date: <2025-11-06 Thu 21:08> +#+OPTIONS: num:nil tags:t toc:t +#+filetags: :learning:notes: + +* TODO diff --git a/posts/career/lean.org b/posts/career/lean.org new file mode 100644 index 0000000..2ad80d5 --- /dev/null +++ b/posts/career/lean.org @@ -0,0 +1,12 @@ +#+title: Lean +#+date: <2025-11-05 wed 20:46> +#+OPTIONS: num:nil tags:t toc:t +#+filetags: :learning:notes: +#+WIP: t +#+COMMENTS: t +#+SLUG: lean + +* What Lean aims to do + +Deliver more value with less waste by shortening feedback loops, improving flow, and continually learning. + diff --git a/posts/career/lean.org~ b/posts/career/lean.org~ new file mode 100644 index 0000000..6e9d7b0 --- /dev/null +++ b/posts/career/lean.org~ @@ -0,0 +1,10 @@ +#+title: Lean +#+date: <2025-11-05 wed 20:46> +#+OPTIONS: num:nil tags:t toc:t +#+filetags: :learning:notes: +#+WIP: t + +* What Lean aims to do + +Deliver more value with less waste by shortening feedback loops, improving flow, and continually learning. + diff --git a/posts/career/management-of-self.org b/posts/career/management-of-self.org new file mode 100644 index 0000000..23eb7eb --- /dev/null +++ b/posts/career/management-of-self.org @@ -0,0 +1,20 @@ +#+TITLE: Management of self training +#+OPTIONS: num:nil tags:t toc:t +#+DATE: <2025-11-06 Thu 22:01> +#+FILETAGS: :learning:notes: +#+WIP: +#+COMMENTS: t +#+SLUG: management-of-self + +Management of self in the workplace refers to how individuals regulate their behavior, emotions, and productivity to perform effectively and professionally. Here's a brief overview: + +- *Time Management*: Prioritising tasks, meeting deadlines, and avoiding procrastination. +- *Emotional Regulation*: Staying calm under pressure, handling stress, and maintaining a positive attitude. +- *Accountability*: Taking responsibility for actions and outcomes, owning mistakes, and learning from them. +- *Adaptability*: Being flexible with changes, open to feedback, and willing to improve. +- *Professionalism*: Maintaining ethical standards, being respectful, and demonstrating integrity. +- *Goal Setting*: Defining clear objectives and staying focused on achieving them. + +It is important as it helps build trust, improve performance and creates a healthy work environment. + +[[../../assets/images/stress-in-workplace.png]] diff --git a/posts/career/management-of-self.org~ b/posts/career/management-of-self.org~ new file mode 100644 index 0000000..3706bf6 --- /dev/null +++ b/posts/career/management-of-self.org~ @@ -0,0 +1,18 @@ +#+TITLE: Management of self training +#+OPTIONS: num:nil tags:t toc:t +#+DATE: <2025-11-06 Thu 22:01> +#+FILETAGS: :learning:notes: +#+WIP: + +Management of self in the workplace refers to how individuals regulate their behavior, emotions, and productivity to perform effectively and professionally. Here's a brief overview: + +- *Time Management*: Prioritising tasks, meeting deadlines, and avoiding procrastination. +- *Emotional Regulation*: Staying calm under pressure, handling stress, and maintaining a positive attitude. +- *Accountability*: Taking responsibility for actions and outcomes, owning mistakes, and learning from them. +- *Adaptability*: Being flexible with changes, open to feedback, and willing to improve. +- *Professionalism*: Maintaining ethical standards, being respectful, and demonstrating integrity. +- *Goal Setting*: Defining clear objectives and staying focused on achieving them. + +It is important as it helps build trust, improve performance and creates a healthy work environment. + +[[../../assets/images/stress-in-workplace.png]] diff --git a/posts/career/normalisation.org b/posts/career/normalisation.org new file mode 100644 index 0000000..91b9fcc --- /dev/null +++ b/posts/career/normalisation.org @@ -0,0 +1,14 @@ +#+TITLE: Benefits of Normalisation +#+OPTIONS: num:nil tags:t toc:t +#+DATE: <2025-11-10 Mon 18:04> +#+FILETAGS: :learning:notes: +#+WIP: +#+COMMENTS: t +#+SLUG: normalisation + + +For some reason it didn't include one of the bullet points, here it is: + +- It simplifies maintenance, so it’s easier to update, delete or insert data without anomalies. + +[[../../assets/images/competencies/Aware-of-the-Benefits-of-Normalisation.png]] diff --git a/posts/career/normalisation.org~ b/posts/career/normalisation.org~ new file mode 100644 index 0000000..10d5ac6 --- /dev/null +++ b/posts/career/normalisation.org~ @@ -0,0 +1,11 @@ +#+TITLE: Benefits of Normalisation +#+OPTIONS: num:nil tags:t toc:t +#+DATE: <2025-11-10 Mon 18:04> +#+FILETAGS: :learning:notes: +#+WIP: + +For some reason it didn't include one of the bullet points, here it is: + +- It simplifies maintenance, so it’s easier to update, delete or insert data without anomalies. + +[[../../assets/images/competencies/Aware-of-the-Benefits-of-Normalisation.png]] diff --git a/posts/career/owasp.org b/posts/career/owasp.org new file mode 100644 index 0000000..c9d0f41 --- /dev/null +++ b/posts/career/owasp.org @@ -0,0 +1,86 @@ +#+TITLE: OWASP Top Ten +#+OPTIONS: num:nil tags:t toc:t +#+DATE: <2025-10-19 Sun 13:21> +#+FILETAGS: :learning:notes: +#+COMMENTS: t +#+SLUG: owasp-top-10 + +* Competency notes: + +[[../../assets/images/owasp/Screenshot 2025-10-16 134403.png]] +[[../../assets/images/owasp/Screenshot 2025-10-16 134438.png]] +[[../../assets/images/owasp/Screenshot 2025-10-16 134457.png]] +[[../../assets/images/owasp/Screenshot 2025-10-16 134403.png]] +[[../../assets/images/owasp/Screenshot 2025-10-16 134512.png]] + +* OWASP Top 10 (2021) + +** A01: Broken Access Control +- Users can act outside their permissions (e.g., IDOR, privilege escalation). +- Mitigations: + - Enforce server-side authorization on every request. + - Deny by default; allow only explicit permissions. + - Test with role and privilege matrices. + +** A02: Cryptographic Failures +- Sensitive data is exposed due to weak or misused cryptography. +- Mitigations: + - Use modern TLS everywhere. + - Encrypt sensitive data in transit and at rest. + - Manage keys securely and avoid homegrown crypto. + +** A03: Injection +- Untrusted input alters queries or commands (SQLi, XSS, command injection). +- Mitigations: + - Use parameterized queries and prepared statements. + - Sanitize and validate input. + - Escape output appropriately for each context. + +** A04: Insecure Design +- Security flaws in system design or architecture. +- Mitigations: + - Perform threat modeling early. + - Use secure design patterns and reference architectures. + - Enforce defense-in-depth and least privilege. + +** A05: Security Misconfiguration +- Insecure defaults, unnecessary features, verbose errors, misconfigured services. +- Mitigations: + - Harden configurations and use secure defaults. + - Automate configuration management. + - Disable unused features and accounts. + +** A06: Vulnerable and Outdated Components +- Using outdated software or libraries with known vulnerabilities. +- Mitigations: + - Maintain an inventory (SBOM) of components. + - Monitor CVEs and patch frequently. + - Use trusted package sources. + +** A07: Identification and Authentication Failures +- Broken login, session management, or weak authentication. +- Mitigations: + - Use strong authentication (MFA recommended). + - Protect session tokens; implement logout and timeouts. + - Avoid exposing sensitive session IDs in URLs. + +** A08: Software and Data Integrity Failures +- Relying on untrusted software or pipelines without integrity verification. +- Mitigations: + - Sign and verify code and dependencies. + - Secure CI/CD pipelines. + - Use integrity checks for data and updates. + +** A09: Security Logging and Monitoring Failures +- Inadequate logging or monitoring prevents detection of attacks. +- Mitigations: + - Log critical events (auth, access, errors). + - Centralize and monitor logs. + - Establish alerting and response procedures. + +** A10: Server-Side Request Forgery (SSRF) +- The application fetches URLs from untrusted sources on behalf of users. +- Mitigations: + - Deny outbound network access by default. + - Use allowlists for external requests. + - Protect metadata and internal services. diff --git a/posts/career/owasp.org~ b/posts/career/owasp.org~ new file mode 100644 index 0000000..f831c0f --- /dev/null +++ b/posts/career/owasp.org~ @@ -0,0 +1,84 @@ +#+TITLE: OWASP Top Ten +#+OPTIONS: num:nil tags:t toc:t +#+DATE: <2025-10-19 Sun 13:21> +#+FILETAGS: :learning:notes: + +* Competency notes: + +[[../../assets/images/owasp/Screenshot 2025-10-16 134403.png]] +[[../../assets/images/owasp/Screenshot 2025-10-16 134438.png]] +[[../../assets/images/owasp/Screenshot 2025-10-16 134457.png]] +[[../../assets/images/owasp/Screenshot 2025-10-16 134403.png]] +[[../../assets/images/owasp/Screenshot 2025-10-16 134512.png]] + +* OWASP Top 10 (2021) + +** A01: Broken Access Control +- Users can act outside their permissions (e.g., IDOR, privilege escalation). +- Mitigations: + - Enforce server-side authorization on every request. + - Deny by default; allow only explicit permissions. + - Test with role and privilege matrices. + +** A02: Cryptographic Failures +- Sensitive data is exposed due to weak or misused cryptography. +- Mitigations: + - Use modern TLS everywhere. + - Encrypt sensitive data in transit and at rest. + - Manage keys securely and avoid homegrown crypto. + +** A03: Injection +- Untrusted input alters queries or commands (SQLi, XSS, command injection). +- Mitigations: + - Use parameterized queries and prepared statements. + - Sanitize and validate input. + - Escape output appropriately for each context. + +** A04: Insecure Design +- Security flaws in system design or architecture. +- Mitigations: + - Perform threat modeling early. + - Use secure design patterns and reference architectures. + - Enforce defense-in-depth and least privilege. + +** A05: Security Misconfiguration +- Insecure defaults, unnecessary features, verbose errors, misconfigured services. +- Mitigations: + - Harden configurations and use secure defaults. + - Automate configuration management. + - Disable unused features and accounts. + +** A06: Vulnerable and Outdated Components +- Using outdated software or libraries with known vulnerabilities. +- Mitigations: + - Maintain an inventory (SBOM) of components. + - Monitor CVEs and patch frequently. + - Use trusted package sources. + +** A07: Identification and Authentication Failures +- Broken login, session management, or weak authentication. +- Mitigations: + - Use strong authentication (MFA recommended). + - Protect session tokens; implement logout and timeouts. + - Avoid exposing sensitive session IDs in URLs. + +** A08: Software and Data Integrity Failures +- Relying on untrusted software or pipelines without integrity verification. +- Mitigations: + - Sign and verify code and dependencies. + - Secure CI/CD pipelines. + - Use integrity checks for data and updates. + +** A09: Security Logging and Monitoring Failures +- Inadequate logging or monitoring prevents detection of attacks. +- Mitigations: + - Log critical events (auth, access, errors). + - Centralize and monitor logs. + - Establish alerting and response procedures. + +** A10: Server-Side Request Forgery (SSRF) +- The application fetches URLs from untrusted sources on behalf of users. +- Mitigations: + - Deny outbound network access by default. + - Use allowlists for external requests. + - Protect metadata and internal services. diff --git a/posts/career/probation-objectives.org b/posts/career/probation-objectives.org new file mode 100644 index 0000000..1b2c59f --- /dev/null +++ b/posts/career/probation-objectives.org @@ -0,0 +1,79 @@ +#+TITLE: Probation Objectives: +#+OPTIONS: num:nil +#+DATE: <2025-12-08 Mon 17:55> +#+filetags: :review:notes: +#+WIP: t +#+COMMENTS: t +#+SLUG: probation-objectives + +* Checklists: +** Customer Service Report +- Introduction +- Service Desk Experience +- Customer Experience +- Lessons Learnt +- Conclusion + +** Personal Development Plan +- Create a SWOT +- Create a Personal Development Plan with SMART objectives +- Include strengths and weaknesses of your team role +- Define short-, medium-, and long-term objectives +- Keep traction on the competency tasks and dashboard + +** Process Best Practice Report +- Introduction +- Lean Overview +- Software Development Overview +- Lessons Learnt +- Conclusion + +** Competencies +- +20% End of Nov+ +- 20% End of Dec +- 20% End of Jan + +** Books +- +The Toyota Way+ +- +The Clean Coder+ +- Agile Testing (in progress) +- Code Complete 2 +- Clean Code (in progress) +- Working Effectively with Legacy Code +- Headfirst into Design Patterns + +** Public Code Review (Demonstrate development best practice) +- Training on appropriate languages (C#, PHP Laravel, JavaScript, SQL, PowerShell, NUnit, PHPUnit, Jest, tSQLt, pester) +- Read books and do training on development best practices (SOLID, Design Patterns, Continuous Monitoring, TDD, BDD, CI/CD) +- Training around Requirements (Feature writing, User Story writing, Task writing, User Story Mapping, Low Fidelity UI Designs) +- Daily Kata’s and regular Dojo’s +- Present software developed by the team in a Public Code Review + +** Deployment Pipeline (Integrate a build and deployment pipeline) +- Implement a TFS build and deployment pipeline with pester tests +- Implement and maintain CI/CD +- Continuously monitor environments daily +- Build and/or maintain an environment using PowerShell, Vagrant, Ansible, and/or Terraform + +** Misc +- DevOps Skills for Developers with VS and TFS +- Getting Started with Ansible +- Introduction to Versioning Environments with Vagrant +- Terraform – Getting Started + +* Rotations +Rotations start on: <2026-01-14 Wed> + +* Probation meetings: +1. <2025-11-06 Thu> +2. <2025-12-11 Thu> +3. + +* Document: + +[[../../assets/images/reviews/probation-objectives/probation-objectives-1.png]] +[[../../assets/images/reviews/probation-objectives/probation-objectives-2.png]] +[[../../assets/images/reviews/probation-objectives/probation-objectives-3.png]] +[[../../assets/images/reviews/probation-objectives/probation-objectives-4.png]] +[[../../assets/images/reviews/probation-objectives/probation-objectives-5.png]] + diff --git a/posts/career/probation-objectives.org~ b/posts/career/probation-objectives.org~ new file mode 100644 index 0000000..9e2eacb --- /dev/null +++ b/posts/career/probation-objectives.org~ @@ -0,0 +1,77 @@ +#+TITLE: Probation Objectives: +#+OPTIONS: num:nil +#+DATE: <2025-12-08 Mon 17:55> +#+filetags: :review:notes: +#+WIP: t + +* Checklists: +** Customer Service Report +- Introduction +- Service Desk Experience +- Customer Experience +- Lessons Learnt +- Conclusion + +** Personal Development Plan +- Create a SWOT +- Create a Personal Development Plan with SMART objectives +- Include strengths and weaknesses of your team role +- Define short-, medium-, and long-term objectives +- Keep traction on the competency tasks and dashboard + +** Process Best Practice Report +- Introduction +- Lean Overview +- Software Development Overview +- Lessons Learnt +- Conclusion + +** Competencies +- +20% End of Nov+ +- 20% End of Dec +- 20% End of Jan + +** Books +- +The Toyota Way+ +- +The Clean Coder+ +- Agile Testing (in progress) +- Code Complete 2 +- Clean Code (in progress) +- Working Effectively with Legacy Code +- Headfirst into Design Patterns + +** Public Code Review (Demonstrate development best practice) +- Training on appropriate languages (C#, PHP Laravel, JavaScript, SQL, PowerShell, NUnit, PHPUnit, Jest, tSQLt, pester) +- Read books and do training on development best practices (SOLID, Design Patterns, Continuous Monitoring, TDD, BDD, CI/CD) +- Training around Requirements (Feature writing, User Story writing, Task writing, User Story Mapping, Low Fidelity UI Designs) +- Daily Kata’s and regular Dojo’s +- Present software developed by the team in a Public Code Review + +** Deployment Pipeline (Integrate a build and deployment pipeline) +- Implement a TFS build and deployment pipeline with pester tests +- Implement and maintain CI/CD +- Continuously monitor environments daily +- Build and/or maintain an environment using PowerShell, Vagrant, Ansible, and/or Terraform + +** Misc +- DevOps Skills for Developers with VS and TFS +- Getting Started with Ansible +- Introduction to Versioning Environments with Vagrant +- Terraform – Getting Started + +* Rotations +Rotations start on: <2026-01-14 Wed> + +* Probation meetings: +1. <2025-11-06 Thu> +2. <2025-12-11 Thu> +3. + +* Document: + +[[../../assets/images/reviews/probation-objectives/probation-objectives-1.png]] +[[../../assets/images/reviews/probation-objectives/probation-objectives-2.png]] +[[../../assets/images/reviews/probation-objectives/probation-objectives-3.png]] +[[../../assets/images/reviews/probation-objectives/probation-objectives-4.png]] +[[../../assets/images/reviews/probation-objectives/probation-objectives-5.png]] + diff --git a/posts/career/requirements-features.org b/posts/career/requirements-features.org new file mode 100644 index 0000000..f759a70 --- /dev/null +++ b/posts/career/requirements-features.org @@ -0,0 +1,77 @@ +#+TITLE: Requirements, features, user stories, tasks, walking skeletons +#+OPTIONS: num:nil tags:t toc:t +#+DATE: <2025-11-06 Thu 22:01> +#+FILETAGS: :learning:notes: +#+WIP: +#+COMMENTS: t +#+SLUG: requirements-features + +** Requirements +- Definition: High-level descriptions of what the system must do or how it should behave. +- Types: + - *Functional*: Specific behaviors (e.g., “The system must allow users to reset passwords”). + - *Non-functional*: Quality attributes (e.g., performance, security, usability). +- Agile Context: Requirements are often evolving and are captured incrementally through user stories and feedback loops. + +** Epic +An epic is a high-level objective or initiative that’s too big to complete in a single sprint. It represents a major business goal or capability and is broken down into smaller deliverables: + +- Purpose: Organise related work under a common theme. + +- Scope: Broad and strategic; spans multiple sprints. + +- Example: “Enable secure user access” could be an epic that includes login, signup, password reset, and multi-factor authentication. + + “An epic serves to manage tasks. It's a defined body of work that is segmented into specific tasks (called ‘stories’) based on the needs/requests of customers or end-users" + +** Features +- Definition: Distinct pieces of functionality that deliver value to users. +- Scope: Larger than user stories but smaller than epics. +- Example: “User profile management” could be a feature encompassing login, profile editing, and avatar upload. + +** User Stories +- Definition: Short, simple descriptions of a feature told from the perspective of the user. +- Format: “As a [user], I want [goal] so that [reason].” +- Purpose: Helps teams focus on user needs and prioritize work. +- Agile Role: Central to backlog grooming, sprint planning, and iterative delivery. + +** Tasks +- Definition: Technical or operational steps needed to implement a user story. +- Granularity: Smallest unit of work, often assigned to individual team members. +- Example: For a story like “User can upload avatar,” tasks might include: + - Create upload UI + - Validate image format + - Store image in database + +** Walking Skeleton +- Definition: A minimal, end-to-end version of the system that includes the core architecture and basic functionality. +- Purpose: Validates the system’s structure early and allows incremental development. +- Agile Benefit: Enables early integration, testing, and feedback while avoiding big-bang delivery. + +** Hierarchy Overview + +| Level | Description | Agile Artifact | +|----------------+----------------------------+------------------| +| Vision | Broad business goals | Epics | +| Feature | Functional chunks of value | Features | +| User Need | Specific user goals | User Stories | +| Implementation | Technical execution steps | Tasks | +| Skeleton | Minimal working system | Walking Skeleton | + + +Here is a summary of the agile hierarchy: + + +| Level | Example | Description | +|----------------+--------------------------------------+----------------------------------------| +| **Epic** | Enable secure user access | Broad goal spanning multiple features | +| **Feature** | User Login | Functional unit delivering user value | +| **User Story** | “Log in with email/password” | Specific user goal within the feature | +| **Task** | Build login form, connect to backend | Technical steps to implement the story | + +* Example from work: +<2025-10-09 Thu> +The user story creation we did for a particular made up project (not the proudest but team voted on this sadly). + + +[[../../assets/images/user_stories.jpg]] diff --git a/posts/career/requirements-features.org~ b/posts/career/requirements-features.org~ new file mode 100644 index 0000000..b200662 --- /dev/null +++ b/posts/career/requirements-features.org~ @@ -0,0 +1,75 @@ +#+TITLE: Requirements, features, user stories, tasks, walking skeletons +#+OPTIONS: num:nil tags:t toc:t +#+DATE: <2025-11-06 Thu 22:01> +#+FILETAGS: :learning:notes: +#+WIP: + +** Requirements +- Definition: High-level descriptions of what the system must do or how it should behave. +- Types: + - *Functional*: Specific behaviors (e.g., “The system must allow users to reset passwords”). + - *Non-functional*: Quality attributes (e.g., performance, security, usability). +- Agile Context: Requirements are often evolving and are captured incrementally through user stories and feedback loops. + +** Epic +An epic is a high-level objective or initiative that’s too big to complete in a single sprint. It represents a major business goal or capability and is broken down into smaller deliverables: + +- Purpose: Organise related work under a common theme. + +- Scope: Broad and strategic; spans multiple sprints. + +- Example: “Enable secure user access” could be an epic that includes login, signup, password reset, and multi-factor authentication. + + “An epic serves to manage tasks. It's a defined body of work that is segmented into specific tasks (called ‘stories’) based on the needs/requests of customers or end-users" + +** Features +- Definition: Distinct pieces of functionality that deliver value to users. +- Scope: Larger than user stories but smaller than epics. +- Example: “User profile management” could be a feature encompassing login, profile editing, and avatar upload. + +** User Stories +- Definition: Short, simple descriptions of a feature told from the perspective of the user. +- Format: “As a [user], I want [goal] so that [reason].” +- Purpose: Helps teams focus on user needs and prioritize work. +- Agile Role: Central to backlog grooming, sprint planning, and iterative delivery. + +** Tasks +- Definition: Technical or operational steps needed to implement a user story. +- Granularity: Smallest unit of work, often assigned to individual team members. +- Example: For a story like “User can upload avatar,” tasks might include: + - Create upload UI + - Validate image format + - Store image in database + +** Walking Skeleton +- Definition: A minimal, end-to-end version of the system that includes the core architecture and basic functionality. +- Purpose: Validates the system’s structure early and allows incremental development. +- Agile Benefit: Enables early integration, testing, and feedback while avoiding big-bang delivery. + +** Hierarchy Overview + +| Level | Description | Agile Artifact | +|----------------+----------------------------+------------------| +| Vision | Broad business goals | Epics | +| Feature | Functional chunks of value | Features | +| User Need | Specific user goals | User Stories | +| Implementation | Technical execution steps | Tasks | +| Skeleton | Minimal working system | Walking Skeleton | + + +Here is a summary of the agile hierarchy: + + +| Level | Example | Description | +|----------------+--------------------------------------+----------------------------------------| +| **Epic** | Enable secure user access | Broad goal spanning multiple features | +| **Feature** | User Login | Functional unit delivering user value | +| **User Story** | “Log in with email/password” | Specific user goal within the feature | +| **Task** | Build login form, connect to backend | Technical steps to implement the story | + +* Example from work: +<2025-10-09 Thu> +The user story creation we did for a particular made up project (not the proudest but team voted on this sadly). + + +[[../../assets/images/user_stories.jpg]] diff --git a/posts/career/retrospectives.org b/posts/career/retrospectives.org new file mode 100644 index 0000000..085f7dd --- /dev/null +++ b/posts/career/retrospectives.org @@ -0,0 +1,55 @@ +#+title: Retrospectives +#+date: <2025-11-05 wed 20:46> +#+OPTIONS: num:nil tags:t toc:t +#+filetags: :learning:notes: +#+WIP: +#+COMMENTS: t +#+SLUG: retrospectives + +* Dates: +- First retrospective meeting held at the end of sprint 1: <2025-11-04 Tue> +- Second retrospective (and subsequent biweekly ones): <2025-11-18 Tue> + +** Retros from <2025-12-02 Tue> + [[../../assets/images/reviews/02-12-25-retros.jpg]] + + +* Introduction: + + +Retrospectives are part of the agile methodology of working, and is a way for the team to discuss and reflect on the way the sprint went. + +** Purpose of Sprint Retrospectives +- Continuous improvement: Teams inspect their work and processes to enhance quality and effectiveness. +- Reflection and learning: Members discuss what went well, what didn’t, and why. +- Actionable change: The goal is to identify specific improvements and implement them in the next sprint. + +** Key Activities During a Retrospective +- Review the sprint: Examine interactions, tools, processes, and outcomes. +- Identify successes and challenges: Celebrate wins and analyse problems. +- Generate insights: Explore root causes of issues and assumptions that led to missteps. +- Plan improvements: Decide on changes to try in the next sprint, often adding them to the backlog. + +** Who Participates +- Entire Scrum Team: Product Owner, Scrum Master, and Developers. +- Optional stakeholders if their input is valuable, but the focus remains on the team’s internal dynamics. + +** Common Retrospective Techniques +- Start, Stop, Continue: What to begin, cease, or keep doing. +- Mad, Sad, Glad: Emotional reflection on the sprint. +- 4Ls (Liked, Learned, Lacked, Longed for): A deeper dive into team sentiment and learning. + +** Timing and Format +- Held at the end of each sprint, typically lasting 60–90 minutes. +- Can be in-person, remote, or hybrid, using tools like sticky notes, voting widgets, and templates. + +** Best Practices +- Keep it constructive and focused. +- Ensure psychological safety so team members feel comfortable sharing. +- Follow through on action items to avoid stagnation and disillusionment. + +* Frameworks used + + Boat model: + +[[../../assets/images/retrospective_boat_model.png]] diff --git a/posts/career/retrospectives.org~ b/posts/career/retrospectives.org~ new file mode 100644 index 0000000..39d8804 --- /dev/null +++ b/posts/career/retrospectives.org~ @@ -0,0 +1,54 @@ +#+title: Retrospectives +#+date: <2025-11-05 wed 20:46> +#+OPTIONS: num:nil tags:t toc:t +#+filetags: :learning:notes: +#+WIP: + + +* Dates: +- First retrospective meeting held at the end of sprint 1: <2025-11-04 Tue> +- Second retrospective (and subsequent biweekly ones): <2025-11-18 Tue> + +** Retros from <2025-12-02 Tue> + [[../../assets/images/reviews/02-12-25-retros.jpg]] + + +* Introduction: + + +Retrospectives are part of the agile methodology of working, and is a way for the team to discuss and reflect on the way the sprint went. + +** Purpose of Sprint Retrospectives +- Continuous improvement: Teams inspect their work and processes to enhance quality and effectiveness. +- Reflection and learning: Members discuss what went well, what didn’t, and why. +- Actionable change: The goal is to identify specific improvements and implement them in the next sprint. + +** Key Activities During a Retrospective +- Review the sprint: Examine interactions, tools, processes, and outcomes. +- Identify successes and challenges: Celebrate wins and analyse problems. +- Generate insights: Explore root causes of issues and assumptions that led to missteps. +- Plan improvements: Decide on changes to try in the next sprint, often adding them to the backlog. + +** Who Participates +- Entire Scrum Team: Product Owner, Scrum Master, and Developers. +- Optional stakeholders if their input is valuable, but the focus remains on the team’s internal dynamics. + +** Common Retrospective Techniques +- Start, Stop, Continue: What to begin, cease, or keep doing. +- Mad, Sad, Glad: Emotional reflection on the sprint. +- 4Ls (Liked, Learned, Lacked, Longed for): A deeper dive into team sentiment and learning. + +** Timing and Format +- Held at the end of each sprint, typically lasting 60–90 minutes. +- Can be in-person, remote, or hybrid, using tools like sticky notes, voting widgets, and templates. + +** Best Practices +- Keep it constructive and focused. +- Ensure psychological safety so team members feel comfortable sharing. +- Follow through on action items to avoid stagnation and disillusionment. + +* Frameworks used + + Boat model: + +[[../../assets/images/retrospective_boat_model.png]] diff --git a/posts/career/solid-principles.org b/posts/career/solid-principles.org new file mode 100644 index 0000000..9c63c72 --- /dev/null +++ b/posts/career/solid-principles.org @@ -0,0 +1,21 @@ +#+TITLE: SOLID Principles +#+OPTIONS: num:nil tags:t toc:t +#+DATE: <2025-10-18 Sat 19:14> +#+FILETAGS: :learning:notes: +#+COMMENTS: t +#+SLUG: solid-principles + +The SOLID principles are five design guidelines for writing maintainable, flexible, and scalable object-oriented code. Here's a quick summary: + +1. Single Responsibility Principle (SRP): A class should have only one reason to change, meaning it should have a single responsibility or purpose. + +2. Open/Closed Principle (OCP): Classes should be open for extension (e.g., adding new functionality) but closed for modification (no need to change existing code). + +3. Liskov Substitution Principle (LSP): Subclasses should be substitutable for their base classes without breaking the program's behavior. + +4. Interface Segregation Principle (ISP): Clients should not be forced to depend on interfaces they don't use, favoring specific, small interfaces over large, general ones. + +5. Dependency Inversion Principle (DIP): High-level modules should not depend on low-level modules; both should depend on abstractions (e.g., interfaces), and abstractions should not depend on details. + +These principles promote cleaner, more modular, and testable code. + diff --git a/posts/career/solid-principles.org~ b/posts/career/solid-principles.org~ new file mode 100644 index 0000000..ae9168c --- /dev/null +++ b/posts/career/solid-principles.org~ @@ -0,0 +1,19 @@ +#+TITLE: SOLID Principles +#+OPTIONS: num:nil tags:t toc:t +#+DATE: <2025-10-18 Sat 19:14> +#+FILETAGS: :learning:notes: + +The SOLID principles are five design guidelines for writing maintainable, flexible, and scalable object-oriented code. Here's a quick summary: + +1. Single Responsibility Principle (SRP): A class should have only one reason to change, meaning it should have a single responsibility or purpose. + +2. Open/Closed Principle (OCP): Classes should be open for extension (e.g., adding new functionality) but closed for modification (no need to change existing code). + +3. Liskov Substitution Principle (LSP): Subclasses should be substitutable for their base classes without breaking the program's behavior. + +4. Interface Segregation Principle (ISP): Clients should not be forced to depend on interfaces they don't use, favoring specific, small interfaces over large, general ones. + +5. Dependency Inversion Principle (DIP): High-level modules should not depend on low-level modules; both should depend on abstractions (e.g., interfaces), and abstractions should not depend on details. + +These principles promote cleaner, more modular, and testable code. + diff --git a/posts/career/wireframe-designs.org b/posts/career/wireframe-designs.org new file mode 100644 index 0000000..ca83a1b --- /dev/null +++ b/posts/career/wireframe-designs.org @@ -0,0 +1,53 @@ +#+TITLE: Wireframe Designs +#+OPTIONS: num:nil tags:t toc:t +#+DATE: <2025-11-06 Thu 22:01> +#+FILETAGS: :learning:notes: +#+WIP: +#+COMMENTS: t +#+SLUG: wireframe-designs + +* What are wireframes? + +Wireframes are basic visual representations of a website, app, or digital product that focus on structure and layout rather than design details like colors, fonts, or images. They typically include elements such as: + +- Navigation menus + +- Buttons + +- Content blocks + +- Headers and footers + +Wireframes range from low-fidelity sketches to high-fidelity {{{sidenote(1, Low-fidelity (lo-fi) refers to a basic\, quick prototype that focuses on core concepts with minimal detail\, while high-fidelity (hi-fi) refers to a realistic\, detailed prototype that closely mimics the final product's look\, feel\, and functionality)}}} mockups, depending on the stage of the design process. + +* Why wireframes are useful + +Wireframes are essential tools in UX and UI design for several reasons: + +- Clarify layout and functionality: They help designers and stakeholders visualise how users will interact with the product + +- Facilitate collaboration: Wireframes serve as a communication tool between designers, developers, and clients, aligning everyone on structure and goals (close the feedback loop) + +- Identify usability issues early: By focusing on user flow and interface logic, wireframes allow teams to spot and fix problems before investing in detailed design + +- Support iterative design: Wireframes are easy to update and adapt, making them ideal for testing and refining ideas + +* When to use wireframes? + +Wireframes are typically created in the early stages of product development, before visual design and coding begin. They’re especially helpful during: + +- Initial concept development + +- Stakeholder presentations + +- User testing and feedback sessions + +* Competency example: + +[[../../assets/images/wireframes/Screenshot 2025-10-31 112139.png]] +[[../../assets/images/wireframes/Screenshot 2025-10-31 112206.png]] +[[../../assets/images/wireframes/Screenshot 2025-10-31 112427.png]] +[[../../assets/images/wireframes/Screenshot 2025-10-31 115621.png]] +[[../../assets/images/wireframes/Screenshot 2025-10-31 115949.png]] +[[../../assets/images/wireframes/Screenshot 2025-10-31 120038.png]] +[[../../assets/images/wireframes/Screenshot 2025-10-31 120203.png]] diff --git a/posts/career/wireframe-designs.org~ b/posts/career/wireframe-designs.org~ new file mode 100644 index 0000000..362404d --- /dev/null +++ b/posts/career/wireframe-designs.org~ @@ -0,0 +1,51 @@ +#+TITLE: Wireframe Designs +#+OPTIONS: num:nil tags:t toc:t +#+DATE: <2025-11-06 Thu 22:01> +#+FILETAGS: :learning:notes: +#+WIP: + +* What are wireframes? + +Wireframes are basic visual representations of a website, app, or digital product that focus on structure and layout rather than design details like colors, fonts, or images. They typically include elements such as: + +- Navigation menus + +- Buttons + +- Content blocks + +- Headers and footers + +Wireframes range from low-fidelity sketches to high-fidelity {{{sidenote(1, Low-fidelity (lo-fi) refers to a basic\, quick prototype that focuses on core concepts with minimal detail\, while high-fidelity (hi-fi) refers to a realistic\, detailed prototype that closely mimics the final product's look\, feel\, and functionality)}}} mockups, depending on the stage of the design process. + +* Why wireframes are useful + +Wireframes are essential tools in UX and UI design for several reasons: + +- Clarify layout and functionality: They help designers and stakeholders visualise how users will interact with the product + +- Facilitate collaboration: Wireframes serve as a communication tool between designers, developers, and clients, aligning everyone on structure and goals (close the feedback loop) + +- Identify usability issues early: By focusing on user flow and interface logic, wireframes allow teams to spot and fix problems before investing in detailed design + +- Support iterative design: Wireframes are easy to update and adapt, making them ideal for testing and refining ideas + +* When to use wireframes? + +Wireframes are typically created in the early stages of product development, before visual design and coding begin. They’re especially helpful during: + +- Initial concept development + +- Stakeholder presentations + +- User testing and feedback sessions + +* Competency example: + +[[../../assets/images/wireframes/Screenshot 2025-10-31 112139.png]] +[[../../assets/images/wireframes/Screenshot 2025-10-31 112206.png]] +[[../../assets/images/wireframes/Screenshot 2025-10-31 112427.png]] +[[../../assets/images/wireframes/Screenshot 2025-10-31 115621.png]] +[[../../assets/images/wireframes/Screenshot 2025-10-31 115949.png]] +[[../../assets/images/wireframes/Screenshot 2025-10-31 120038.png]] +[[../../assets/images/wireframes/Screenshot 2025-10-31 120203.png]] diff --git a/posts/posts-list.org b/posts/posts-list.org index f384668..ff941ad 100644 --- a/posts/posts-list.org +++ b/posts/posts-list.org @@ -4,4 +4,17 @@ See the categories: @@html:Categories@@ * Posts: -- [[file:posts-intro.org][Posts Introduction]] @@html:@@ @@html: @@ \ No newline at end of file +- [[file:career/career-list.org][Career List]] @@html:@@ +- [[file:career/probation-objectives.org][Probation Objectives:]] @@html:@@ @@html: @@ @@html: @@ +- [[file:career/airflow.org][Datamarts, Airflow and DAG's]] @@html:@@ @@html: @@ @@html: @@ +- [[file:career/normalisation.org][Benefits of Normalisation]] @@html:@@ @@html: @@ @@html: @@ +- [[file:career/management-of-self.org][Management of self training]] @@html:@@ @@html: @@ @@html: @@ +- [[file:career/wireframe-designs.org][Wireframe Designs]] @@html:@@ @@html: @@ @@html: @@ +- [[file:career/requirements-features.org][Requirements, features, user stories, tasks, walking skeletons]] @@html:@@ @@html: @@ @@html: @@ +- [[file:career/career-intro.org][Career Introduction]] @@html:@@ @@html: @@ +- [[file:career/invest-principles.org][Invest Principles]] @@html:@@ @@html: @@ @@html: @@ +- [[file:career/retrospectives.org][Retrospectives]] @@html:@@ @@html: @@ @@html: @@ +- [[file:career/lean.org][Lean]] @@html:@@ @@html: @@ @@html: @@ +- [[file:career/owasp.org][OWASP Top Ten]] @@html:@@ @@html: @@ @@html: @@ +- [[file:career/solid-principles.org][SOLID Principles]] @@html:@@ @@html: @@ @@html: @@ +- [[file:posts-intro.org][Posts Introduction]] @@html:@@ @@html: @@ \ No newline at end of file diff --git a/publish-and-push.sh b/publish-and-push.sh new file mode 100755 index 0000000..8d92197 --- /dev/null +++ b/publish-and-push.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +# Exit immediately if any command fails +set -e + +# Define paths +ORG_OUTPUT_DIR="$HOME/master-folder/org_files/org_web/output" + +cd "$ORG_OUTPUT_DIR" + +echo "HTML published to: $ORG_OUTPUT_DIR" + +# Git commands +echo "Adding changes to Git..." +git add . + +echo "Committing..." +git commit -m "Auto-publish on $(date)" || echo "Nothing to commit." + +echo "Pushing to GitHub..." +git push origin main + +echo "Done! Changes pushed and Cloudflare should rebuild the site." diff --git a/recently-updated.org b/recently-updated.org new file mode 100644 index 0000000..eaa9c57 --- /dev/null +++ b/recently-updated.org @@ -0,0 +1,30 @@ +#+TITLE: Recently Updated +#+OPTIONS: toc:nil num:nil + +* Recently Updated (top 26 files - per lima's request) +- [[file:home/backlog.org][Backlog]] @@html:@@ +- [[file:home/notes.org][Notes]] @@html:@@ +- [[file:home/setup.org][Setup]] @@html:@@ +- [[file:home/status.org][Competency Status Board]] @@html:@@ +- [[file:lima/braindump.org][braindump]] @@html:@@ +- [[file:posts/career/wireframe-designs.org][Wireframe Designs]] @@html:@@ +- [[file:posts/career/solid-principles.org][SOLID Principles]] @@html:@@ +- [[file:posts/career/retrospectives.org][Retrospectives]] @@html:@@ +- [[file:posts/career/requirements-features.org][Requirements, features, user stories, tasks, walking skeletons]] @@html:@@ +- [[file:posts/career/probation-objectives.org][Probation Objectives:]] @@html:@@ +- [[file:posts/career/owasp.org][OWASP Top Ten]] @@html:@@ +- [[file:posts/career/normalisation.org][Benefits of Normalisation]] @@html:@@ +- [[file:posts/career/management-of-self.org][Management of self training]] @@html:@@ +- [[file:posts/career/lean.org][Lean]] @@html:@@ +- [[file:posts/career/invest-principles.org][Invest Principles]] @@html:@@ +- [[file:posts/career/career-intro.org][Career Introduction]] @@html:@@ +- [[file:posts/career/airflow.org][Datamarts, Airflow and DAG's]] @@html:@@ +- [[file:home/services.org][Service]] @@html:@@ +- [[file:home/countdown.org][Countdown]] @@html:@@ +- [[file:blogs/publish-pages.org][How to publish pages using Org Publish]] @@html:@@ +- [[file:blogs/2025/12-december/14-12-week-review.org][[14-12-2025] - Weekly Review]] @@html:@@ +- [[file:blogs/2025/12-december/07-12-week-review.org][[07-12-2025] - Weekly Review]] @@html:@@ +- [[file:blogs/2025/11-november/30-11-week-review.org][[30-11-2025] - Weekly Review]] @@html:@@ +- [[file:blogs/2025/11-november/23-11-week-review.org][[23-11-2025] - Weekly Review]] @@html:@@ +- [[file:blogs/2025/11-november/16-11-week-review.org][[16-11-2025] - Weekly Review]] @@html:@@ +- [[file:blogs/2025/11-november/09-11-week-review.org][[09-11-2025] - Weekly Review]] @@html:@@ diff --git a/setup.org b/setup.org deleted file mode 100644 index 1887990..0000000 --- a/setup.org +++ /dev/null @@ -1,409 +0,0 @@ -#+TITLE: Setup -#+OPTIONS: toc:nil num:nil - -* Introduction - -This page will highlight the ~build-script.el~ used to generate this website. This entire website is generated through org files using the command: - -#+BEGIN_SRC bash -emacs -Q --script build-site.el -#+END_SRC - -* The script - -The script is as follows, at the start I have some metadata relating to the file, followed by package management,then the declaration of variables/functions and finally the ~org-publish-project-alist~ which handles nearly all of the project generation instructions. - -/The following code snippet will probably change often, as I can never seem to stick to a single configuration 😀/ - -#+BEGIN_SRC emacs-lisp - - ;;; build-site.el --- Publish my website using org-publish -*- lexical-binding: t; -*- - - ;; Author: Zaine Qayyum - ;; Created: 2025-08-08 - ;; Purpose: Build and publish my static site from Org files. - - ;;; Commentary: - ;; Run this file with: - ;; emacs -Q --script build-site.el - - ;;; Code: - - (require 'package) - (setq package-user-dir (expand-file-name "./.packages")) - (setq package-archives '(("melpa" . "https://melpa.org/packages/") - ("elpa" . "https://elpa.gnu.org/packages/"))) - - (require 'ox-publish) - (require 'cl-lib) - (require 'org) - - ;; Initialize the package system - (package-initialize) - (unless package-archive-contents - (package-refresh-contents)) - - ;; Install dependencies - (package-install 'htmlize) - - ;; Load the publishing system - (setq org-html-htmlize-output-type 'css) - (setq org-html-head-include-default-style nil) - - (defvar z-shared-head - " - ") - - (defvar z-preamble - " - -
Updated: %C
" - ) - - (defvar z-postamble - "
-
-
- Copyright © 2022-2025 Zaine Qayyum. All rights reserved unless otherwise noted.
-
- Created with %c on Arch GNU/Linux -
-
") - - - - (defun z/posts-sitemap (title list) - "sitemap that lists post links as bullet points with dates and tags." - (concat - "#+TITLE: " title "\n" - "#+OPTIONS: toc:nil num:nil \n\n" - "See the categories: @@html:Categories@@\n\n" - "* Posts:\n" - (mapconcat - (lambda (entry) - (let* ((link (car entry)) - ;; extract relative file name from the link - (filename (if (string-match "\\[\\[file:\\([^]]+\\)\\]" link) - (match-string 1 link) - link)) - (full-path (expand-file-name filename "~/master-folder/org_files/org_web/posts/")) - (date-str "no date") - (tags-str "")) - ;; Get publish date - (let ((date (org-publish-find-date full-path org-publish-project-alist))) - (when date - (setq date-str (format-time-string "%Y-%m-%d" date)))) - ;; Get FILETAGS from file buffer - (when (file-exists-p full-path) - (with-temp-buffer - (insert-file-contents full-path) - (org-mode) - (let* ((tags (cadr (assoc "FILETAGS" (org-collect-keywords '("FILETAGS")))))) - (when tags - (setq tags-str (mapconcat (lambda (tag) - (format "@@html:%s@@" tag)) - (split-string tags ":" t) ;; <- Splits by ":" and removes empty strings - " ")))))) - ;; Final line output - (format "- %s @@html:%s@@ %s" link date-str tags-str))) - (cdr list) - "\n"))) - - - (defun z/blogs-sitemap (title list) - "sitemap that lists blog links as bullet points with dates and tags." - (concat - "#+TITLE: " title "\n" - "#+OPTIONS: toc:nil num:nil \n\n" - "See the categories: @@html:Categories@@\n\n" - "* Blogs:\n" - (mapconcat - (lambda (entry) - (let* ((link (car entry)) - ;; extract relative file name from the link - (filename (if (string-match "\\[\\[file:\\([^]]+\\)\\]" link) - (match-string 1 link) - link)) - (full-path (expand-file-name filename "~/master-folder/org_files/org_web/blogs/")) - (date-str "no date") - (tags-str "")) - ;; Get publish date - (let ((date (org-publish-find-date full-path org-publish-project-alist))) - (when date - (setq date-str (format-time-string "%Y-%m-%d" date)))) - ;; Get FILETAGS from file buffer - (when (file-exists-p full-path) - (with-temp-buffer - (insert-file-contents full-path) - (org-mode) - (let* ((tags (cadr (assoc "FILETAGS" (org-collect-keywords '("FILETAGS")))))) - (when tags - (setq tags-str (mapconcat (lambda (tag) - (format "@@html:%s@@" tag)) - (split-string tags ":" t) ;; <- Splits by ":" and removes empty strings - " ")))))) - ;; Final line output - (format "- %s @@html:%s@@ %s" link date-str tags-str))) - (cdr list) - "\n"))) - - - - (defun z/categories-sitemap (title _list) - "Generate a categories page by scanning tags across org-posts and org-blogs." - (let* ((expanded (org-publish-expand-projects org-publish-project-alist)) - (posts (assoc "org-posts" expanded)) - (blogs (assoc "org-blogs" expanded)) - ;; DO NOT set :exclude to "" — it excludes everything - (files (cl-remove-duplicates - (append (and posts (org-publish-get-base-files posts)) - (and blogs (org-publish-get-base-files blogs))) - :test #'file-equal-p)) - ;; optional: drop the generated sitemaps - (files (cl-remove-if - (lambda (f) - (member (file-name-nondirectory f) - '("posts-list.org" "blogs-list.org" "sitemap.org" "categories.org"))) - files)) - (counts (make-hash-table :test 'equal))) - ;;(message "files I will scan: %S" files) - (dolist (f files) - (when (file-readable-p f) - (with-temp-buffer - (insert-file-contents f) - (org-mode) - (let* ((kw (org-collect-keywords '("FILETAGS" "TAGS"))) - (raw (car (or (cdr (assoc "FILETAGS" kw)) - (cdr (assoc "TAGS" kw)))))) - (when raw - (dolist (tag (split-string raw ":" t)) - (puthash tag (1+ (gethash tag counts 0)) counts))))))) - - (let (tags) - (maphash (lambda (k _) (push k tags)) counts) - (setq tags (sort tags #'string-lessp)) - (concat - "#+TITLE: " title "\n#+OPTIONS: toc:nil num:nil title:nil\n\n* Categories (Includes both blogs and posts)\n" - (if tags - (mapconcat - (lambda (tag) - (format "- [[file:tags/%s.org][@@html:%s@@]] (%d)" - (z/tag-slug tag) tag (gethash tag counts)) - ) - tags - "\n") - "_No tags found yet._"))))) - - - (defun z/tag-slug (s) - "Turn a tag into a safe filename." - (let ((down (downcase s))) - (replace-regexp-in-string "[^a-z0-9]+" "-" down))) - - (defun z/collect-post-files () - "Return all .org files from org-posts and org-blogs." - (let* ((expanded (org-publish-expand-projects org-publish-project-alist)) - (posts (assoc "org-posts" expanded)) - (blogs (assoc "org-blogs" expanded))) - (cl-remove-duplicates - (append (and posts (org-publish-get-base-files posts)) - (and blogs (org-publish-get-base-files blogs))) - :test #'file-equal-p))) - - (defun z/gather-tag-index () - "Return hash: tag -> list of (FILE TITLE DATE-ISO)." - (let ((idx (make-hash-table :test 'equal))) - (dolist (f (z/collect-post-files)) - (when (and (string-match-p "\\.org\\'" f) - (file-readable-p f) - ;; ignore generated lists - (not (member (file-name-nondirectory f) - '("posts-list.org" "blogs-list.org" "sitemap.org" "categories.org")))) - (with-temp-buffer - (insert-file-contents f) - (org-mode) - (let* ((kw (org-collect-keywords '("TITLE" "FILETAGS" "TAGS" "DATE"))) - (title (or (car (cdr (assoc "TITLE" kw))) - (file-name-base f))) - (date (or (car (cdr (assoc "DATE" kw))) "")) ;; optional - (raw (car (or (cdr (assoc "FILETAGS" kw)) - (cdr (assoc "TAGS" kw)))))) - (when raw - (dolist (tag (split-string raw ":" t)) - (push (list f title date) (gethash tag idx)))))))) - idx)) - - (defun z/write-tag-pages () - "Generate tags/*.org pages listing posts for each tag." - (let* ((site-root (expand-file-name "~/master-folder/org_files/org_web/")) - (tags-dir (expand-file-name "tags" site-root))) - (unless (file-directory-p tags-dir) - (make-directory tags-dir t)) - (let ((idx (z/gather-tag-index))) - (maphash - (lambda (tag items) - (let* ((slug (z/tag-slug tag)) - (outfile (expand-file-name (format "%s.org" slug) tags-dir))) - (with-temp-file outfile - (insert (format "#+TITLE: Tag: %s\n#+OPTIONS: toc:nil num:nil title:nil \n\n* Posts tagged %s\n" - tag tag)) - ;; sort newest first if DATE present - (setq items (sort items (lambda (a b) (string> (nth 2 a) (nth 2 b))))) - (dolist (it items) - (let* ((file (nth 0 it)) - (title (nth 1 it)) - (rel (file-relative-name file tags-dir))) - ;; link to the source .org; org-publish will rewrite to the .html - (insert (format "- [[file:%s][%s]]\n" rel title))))))) - idx) - ))) - - - ;; Define the publishing project - (setq org-publish-project-alist - `(("org-main" - :recursive t - :base-directory "~/master-folder/org_files/org_web/" - :publishing-function org-html-publish-to-html - :publishing-directory "~/master-folder/org_files/org_web/output" - :with-author nil - :with-creator t - :with-toc t - :base-extension "org" - :section-numbers nil - :time-stamp-file nil - :auto-sitemap t - :sitemap-filename "sitemap.org" - :sitemap-title "Sitemap" - :sitemap-sort-files chronologically - :html-preamble ,z-preamble - :html-postamble ,z-postamble - :html-head ,z-shared-head) - ("org-categories-sitemap" - :recursive t - :base-directory "~/master-folder/org_files/org_web/" - :publishing-directory "~/master-folder/org_files/org_web/output" - :base-extension "org" - :auto-sitemap t - :sitemap-filename "categories.org" - :sitemap-title "Categories" - :sitemap-function z/categories-sitemap - :html-preamble ,z-preamble - :html-postamble ,z-postamble - :html-head ,z-shared-head) - ("org-posts" - :base-directory "~/master-folder/org_files/org_web/posts" - :publishing-directory "~/master-folder/org_files/org_web/output/posts/" - :recursive t - :base-extension "org" - :publishing-function org-html-publish-to-html - :with-author nil - :with-creator nil - :html-validation-link nil - :with-toc t - :section-numbers t - :html-preamble ,z-preamble - :html-postamble ,z-postamble - :auto-sitemap t - :sitemap-filename "posts-list.org" - :sitemap-title "Posts List" - :sitemap-style list - :sitemap-function z/posts-sitemap - :html-head ,z-shared-head) - ("org-blogs" - :base-directory "~/master-folder/org_files/org_web/blogs" - :publishing-directory "~/master-folder/org_files/org_web/output/blogs/" - :recursive t - :base-extension "org" - :publishing-function org-html-publish-to-html - :with-author nil - :with-creator nil - :html-validation-link nil - :html-preamble ,z-preamble - :html-postamble ,z-postamble - :auto-sitemap t - :sitemap-filename "blogs-list.org" - :sitemap-title "Blogs List" - :sitemap-style list - :sitemap-function z/blogs-sitemap - :html-head ,z-shared-head) - ("org-tags" - :base-directory "~/master-folder/org_files/org_web/tags" - :publishing-directory "~/master-folder/org_files/org_web/output/tags" - :recursive t - :base-extension "org" - :publishing-function org-html-publish-to-html - :with-author nil - :with-creator nil - :html-preamble ,z-preamble - :html-postamble ,z-postamble - :html-head ,z-shared-head) - ("org-assets" - :base-directory "~/master-folder/org_files/org_web/assets/" - :base-extension "css\\|js\\|png\\|jpg\\|gif\\|svg\\|pdf\\|woff\\|woff2\\|ttf" - :publishing-directory "~/master-folder/org_files/org_web/output/assets/" - :recursive t - :publishing-function org-publish-attachment) - )) - - (delete-directory "~/master-folder/org_files/org_web/output/" t) - (delete-directory "~/master-folder/org_files/org_web/tags/" t) - (message "Directory deleted") - - ;; Generate the site output - (z/write-tag-pages) - (org-publish-all t) - - (message "Build complete!") - - ;;; build-site.el ends here - -#+END_SRC - -* Publish Script - -As the project is hosted on Github, I have created a small script that is able to push changes to the remote repository, which Cloudflare will automatically detect and rebuild the website: - -#+BEGIN_SRC bash - - #!/bin/bash - - # Exit immediately if any command fails - set -e - - # Define paths - ORG_PROJECT_NAME="website" - ORG_OUTPUT_DIR="$HOME/master-folder/org_files/org_web/output" - - # commands used prior to having the `build-script.el` - # echo "Running org-publish..." - # emacs --batch \ - # --eval "(require 'org)" \ - # --eval "(require 'ox-publish)" \ - # --eval "(org-publish \"$ORG_PROJECT_NAME\" t)" - - cd "$ORG_OUTPUT_DIR" - - echo "HTML published to: $ORG_OUTPUT_DIR" - - # Git commands - echo "Adding changes to Git..." - git add . - - echo "Committing..." - git commit -m "Auto-publish on $(date)" || echo "Nothing to commit." - - echo "Pushing to GitHub..." - git push origin main - - echo "Done! Changes pushed and Cloudflare should rebuild the site." - -#+END_SRC diff --git a/sitemap.org b/sitemap.org index 7a4a7f0..113fa69 100644 --- a/sitemap.org +++ b/sitemap.org @@ -1,21 +1,77 @@ #+TITLE: Sitemap -- [[file:about.org][About Page]] - [[file:index.org][Home Page]] -- [[file:contact.org][Contact]] -- [[file:setup.org][Setup]] -- [[file:posts.org][Posts]] -- [[file:categories.org][Categories]] +- [[file:wip.org][Work in progress]] +- [[file:recently-updated.org][Recently Updated]] +- lima + - [[file:lima/syntax.org][syntax]] + - [[file:lima/WAKEUPTHEALIENSARECOMING.org][WAKEUPTHEALIENSARECOMING]] + - [[file:lima/gems.org][gems]] + - [[file:lima/one.org][one]] + - [[file:lima/reminders.org][reminders]] + - [[file:lima/limas-play-area.org][limas-play-area]] + - [[file:lima/braindump.org][braindump]] - posts - [[file:posts/posts-intro.org][Posts Introduction]] - [[file:posts/posts-list.org][Posts List]] + - career + - [[file:posts/career/solid-principles.org][SOLID Principles]] + - [[file:posts/career/owasp.org][OWASP Top Ten]] + - [[file:posts/career/retrospectives.org][Retrospectives]] + - [[file:posts/career/lean.org][Lean]] + - [[file:posts/career/invest-principles.org][Invest Principles]] + - [[file:posts/career/career-intro.org][Career Introduction]] + - [[file:posts/career/management-of-self.org][Management of self training]] + - [[file:posts/career/wireframe-designs.org][Wireframe Designs]] + - [[file:posts/career/requirements-features.org][Requirements, features, user stories, tasks, walking skeletons]] + - [[file:posts/career/normalisation.org][Benefits of Normalisation]] + - [[file:posts/career/airflow.org][Datamarts, Airflow and DAG's]] + - [[file:posts/career/probation-objectives.org][Probation Objectives:]] + - [[file:posts/career/career-list.org][Career List]] - blogs - [[file:blogs/blogs-intro.org][Blogs Introduction]] + - [[file:blogs/publish-pages.org][How to publish pages using Org Publish]] - [[file:blogs/blogs-list.org][Blogs List]] + - 2025 + - [[file:blogs/2025/2025-list.org][2025 List]] + - 08-august + - [[file:blogs/2025/08-august/zettelkasten.org][Zettelkasten Method]] + - [[file:blogs/2025/08-august/what-do-i-want-to-do-with-emacs.org][What Do I Want To Do With Emacs]] + - [[file:blogs/2025/08-august/wacom-with-arch.org][Wacom With Arch]] + - [[file:blogs/2025/08-august/spending-the-whole-day-on-this-website.org][09-08-2025: Website Changes]] + - [[file:blogs/2025/08-august/hilberts.hotel.org][Hilbert's Hotel]] + - [[file:blogs/2025/08-august/benefits-of-reading.org][Benefits of Reading]] + - [[file:blogs/2025/08-august/third-time.org][Third Time]] + - 11-november + - [[file:blogs/2025/11-november/02-11-week-review.org][[02-11-2025] - Weekly Review]] + - [[file:blogs/2025/11-november/09-11-week-review.org][[09-11-2025] - Weekly Review]] + - [[file:blogs/2025/11-november/16-11-week-review.org][[16-11-2025] - Weekly Review]] + - [[file:blogs/2025/11-november/23-11-week-review.org][[23-11-2025] - Weekly Review]] + - [[file:blogs/2025/11-november/30-11-week-review.org][[30-11-2025] - Weekly Review]] + - 12-december + - [[file:blogs/2025/12-december/07-12-week-review.org][[07-12-2025] - Weekly Review]] + - [[file:blogs/2025/12-december/14-12-week-review.org][[14-12-2025] - Weekly Review]] +- books + - [[file:books/books-list.org][Books List]] + - clean-code + - [[file:books/clean-code/clean-code-notes.org][Clean Code Notes]] +- home + - [[file:home/countdown.org][Countdown]] + - [[file:home/contact.org][Contact]] + - [[file:home/backlog.org][Backlog]] + - [[file:home/services.org][Service]] + - [[file:home/status.org][Competency Status Board]] + - [[file:home/setup.org][Setup]] + - [[file:home/notes.org][Notes]] + - [[file:home/categories.org][Categories]] - tags - [[file:tags/introduction.org][Tag: introduction]] - - [[file:tags/insights.org][Tag: insights]] - - [[file:tags/emacs.org][Tag: emacs]] - - [[file:tags/website.org][Tag: website]] + - [[file:tags/learning.org][Tag: learning]] + - [[file:tags/review.org][Tag: review]] + - [[file:tags/notes.org][Tag: notes]] - [[file:tags/education.org][Tag: education]] - - [[file:tags/review.org][Tag: review]] \ No newline at end of file + - [[file:tags/insights.org][Tag: insights]] + - [[file:tags/website.org][Tag: website]] + - [[file:tags/emacs.org][Tag: emacs]] + - [[file:tags/maths.org][Tag: maths]] + - [[file:tags/reading.org][Tag: reading]] \ No newline at end of file diff --git a/tags/education.org b/tags/education.org index 1e192ba..5308a6b 100644 --- a/tags/education.org +++ b/tags/education.org @@ -2,4 +2,4 @@ #+OPTIONS: toc:nil num:nil title:nil * Posts tagged education -- [[file:../blogs/2025/08/zettelkasten.org][Zettelkasten Method]] +- [[file:../blogs/2025/08-august/zettelkasten.org][Zettelkasten Method]] diff --git a/tags/emacs.org b/tags/emacs.org index 6e568ec..8bb8b15 100644 --- a/tags/emacs.org +++ b/tags/emacs.org @@ -2,5 +2,5 @@ #+OPTIONS: toc:nil num:nil title:nil * Posts tagged emacs -- [[file:../blogs/2025/08/spending-the-whole-day-on-this-website.org][09-08-2025: Website Changes]] -- [[file:../blogs/2025/08/what-do-i-want-to-do-with-emacs.org][What Do I Want To Do With Emacs]] +- [[file:../blogs/2025/08-august/spending-the-whole-day-on-this-website.org][09-08-2025: Website Changes]] +- [[file:../blogs/2025/08-august/what-do-i-want-to-do-with-emacs.org][What Do I Want To Do With Emacs]] diff --git a/tags/insights.org b/tags/insights.org index 4e47eac..83a87f1 100644 --- a/tags/insights.org +++ b/tags/insights.org @@ -2,4 +2,7 @@ #+OPTIONS: toc:nil num:nil title:nil * Posts tagged insights -- [[file:../blogs/2025/08/wacom-with-arch.org][Wacom With Arch]] +- [[file:../blogs/2025/08-august/third-time.org][Third Time]] +- [[file:../blogs/2025/08-august/benefits-of-reading.org][Benefits of Reading]] +- [[file:../blogs/2025/08-august/hilberts.hotel.org][Hilbert's Hotel]] +- [[file:../blogs/2025/08-august/wacom-with-arch.org][Wacom With Arch]] diff --git a/tags/introduction.org b/tags/introduction.org index d0215e1..5a78313 100644 --- a/tags/introduction.org +++ b/tags/introduction.org @@ -2,5 +2,6 @@ #+OPTIONS: toc:nil num:nil title:nil * Posts tagged introduction +- [[file:../posts/career/career-intro.org][Career Introduction]] - [[file:../blogs/blogs-intro.org][Blogs Introduction]] - [[file:../posts/posts-intro.org][Posts Introduction]] diff --git a/tags/learning.org b/tags/learning.org new file mode 100644 index 0000000..b8198e4 --- /dev/null +++ b/tags/learning.org @@ -0,0 +1,14 @@ +#+TITLE: Tag: learning +#+OPTIONS: toc:nil num:nil title:nil + +* Posts tagged learning +- [[file:../posts/career/airflow.org][Datamarts, Airflow and DAG's]] +- [[file:../posts/career/normalisation.org][Benefits of Normalisation]] +- [[file:../posts/career/management-of-self.org][Management of self training]] +- [[file:../posts/career/wireframe-designs.org][Wireframe Designs]] +- [[file:../posts/career/requirements-features.org][Requirements, features, user stories, tasks, walking skeletons]] +- [[file:../posts/career/invest-principles.org][Invest Principles]] +- [[file:../posts/career/retrospectives.org][Retrospectives]] +- [[file:../posts/career/lean.org][Lean]] +- [[file:../posts/career/owasp.org][OWASP Top Ten]] +- [[file:../posts/career/solid-principles.org][SOLID Principles]] diff --git a/tags/maths.org b/tags/maths.org new file mode 100644 index 0000000..8174081 --- /dev/null +++ b/tags/maths.org @@ -0,0 +1,5 @@ +#+TITLE: Tag: maths +#+OPTIONS: toc:nil num:nil title:nil + +* Posts tagged maths +- [[file:../blogs/2025/08-august/hilberts.hotel.org][Hilbert's Hotel]] diff --git a/tags/notes.org b/tags/notes.org new file mode 100644 index 0000000..49171f2 --- /dev/null +++ b/tags/notes.org @@ -0,0 +1,15 @@ +#+TITLE: Tag: notes +#+OPTIONS: toc:nil num:nil title:nil + +* Posts tagged notes +- [[file:../posts/career/probation-objectives.org][Probation Objectives:]] +- [[file:../posts/career/airflow.org][Datamarts, Airflow and DAG's]] +- [[file:../posts/career/normalisation.org][Benefits of Normalisation]] +- [[file:../posts/career/management-of-self.org][Management of self training]] +- [[file:../posts/career/wireframe-designs.org][Wireframe Designs]] +- [[file:../posts/career/requirements-features.org][Requirements, features, user stories, tasks, walking skeletons]] +- [[file:../posts/career/invest-principles.org][Invest Principles]] +- [[file:../posts/career/retrospectives.org][Retrospectives]] +- [[file:../posts/career/lean.org][Lean]] +- [[file:../posts/career/owasp.org][OWASP Top Ten]] +- [[file:../posts/career/solid-principles.org][SOLID Principles]] diff --git a/tags/reading.org b/tags/reading.org new file mode 100644 index 0000000..2cff7a3 --- /dev/null +++ b/tags/reading.org @@ -0,0 +1,5 @@ +#+TITLE: Tag: reading +#+OPTIONS: toc:nil num:nil title:nil + +* Posts tagged reading +- [[file:../blogs/2025/08-august/benefits-of-reading.org][Benefits of Reading]] diff --git a/tags/review.org b/tags/review.org index 23cc213..0d75471 100644 --- a/tags/review.org +++ b/tags/review.org @@ -2,4 +2,12 @@ #+OPTIONS: toc:nil num:nil title:nil * Posts tagged review -- [[file:../blogs/2025/08/what-do-i-want-to-do-with-emacs.org][What Do I Want To Do With Emacs]] +- [[file:../blogs/2025/12-december/14-12-week-review.org][[14-12-2025] - Weekly Review]] +- [[file:../posts/career/probation-objectives.org][Probation Objectives:]] +- [[file:../blogs/2025/12-december/07-12-week-review.org][[07-12-2025] - Weekly Review]] +- [[file:../blogs/2025/11-november/30-11-week-review.org][[30-11-2025] - Weekly Review]] +- [[file:../blogs/2025/11-november/23-11-week-review.org][[23-11-2025] - Weekly Review]] +- [[file:../blogs/2025/11-november/16-11-week-review.org][[16-11-2025] - Weekly Review]] +- [[file:../blogs/2025/11-november/09-11-week-review.org][[09-11-2025] - Weekly Review]] +- [[file:../blogs/2025/11-november/02-11-week-review.org][[02-11-2025] - Weekly Review]] +- [[file:../blogs/2025/08-august/what-do-i-want-to-do-with-emacs.org][What Do I Want To Do With Emacs]] diff --git a/tags/website.org b/tags/website.org index 89c5a83..6d60268 100644 --- a/tags/website.org +++ b/tags/website.org @@ -2,4 +2,5 @@ #+OPTIONS: toc:nil num:nil title:nil * Posts tagged website -- [[file:../blogs/2025/08/spending-the-whole-day-on-this-website.org][09-08-2025: Website Changes]] +- [[file:../blogs/publish-pages.org][How to publish pages using Org Publish]] +- [[file:../blogs/2025/08-august/spending-the-whole-day-on-this-website.org][09-08-2025: Website Changes]] diff --git a/watcher.sh b/watcher.sh new file mode 100755 index 0000000..9058bbc --- /dev/null +++ b/watcher.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +# run a single command once per save in a subfolder + +WATCH="/home/zaine/master-folder/org_files/org_web/lima" # subfolder to watch +RUN_IN="/home/zaine/master-folder/org_files/org_web" # run command from here + +# pass the command to run as args: +# ./watch.sh emacs -Q --script build-site.el +[[ $# -gt 0 ]] || { echo "Usage: $0 "; exit 1; } +CMD=("$@") + +# ignore common temp/swap files +EXCLUDE='(^|/)(#.*#|\.#.*|.*~|.*\.tmp|.*\.sw[pox])($|/)' + +inotifywait -m -r -q \ + -e close_write,moved_to \ + --exclude "$EXCLUDE" \ + --format 'Triggered by: %w%f' \ + "$WATCH" | +while read -r path; do + echo "$path" + ( cd "$RUN_IN" && "${CMD[@]}" ) +done diff --git a/wip.org b/wip.org new file mode 100644 index 0000000..73312b9 --- /dev/null +++ b/wip.org @@ -0,0 +1,7 @@ +#+TITLE: Work in progress +#+OPTIONS: toc:nil num:nil + +* Work in progress +- [[file:posts/career/probation-objectives.org][Probation Objectives:]] @@html:@@ +- [[file:posts/career/invest-principles.org][Invest Principles]] @@html:@@ +- [[file:posts/career/lean.org][Lean]] @@html:@@ \ No newline at end of file