diff --git a/add_stuff.jl b/add_stuff.jl
index c069394..dc7b846 100644
--- a/add_stuff.jl
+++ b/add_stuff.jl
@@ -98,4 +98,3 @@ end
if abspath(PROGRAM_FILE) == @__FILE__
run_wizard(isempty(ARGS) ? missing : first(ARGS))
end
-
diff --git a/blog/__template/index.html b/blog/__template/index.html
index 695d618..c6dabfa 100644
--- a/blog/__template/index.html
+++ b/blog/__template/index.html
@@ -4,7 +4,7 @@
Blog: {{ BLOG_TITLE }}
-
+
diff --git a/blog/hello-world/index.html b/blog/hello-world/index.html
index b567d29..021ffff 100644
--- a/blog/hello-world/index.html
+++ b/blog/hello-world/index.html
@@ -4,7 +4,7 @@
Blog: Hello world
-
+
diff --git a/blog/index.html b/blog/index.html
index bf23461..b021798 100644
--- a/blog/index.html
+++ b/blog/index.html
@@ -10,7 +10,7 @@
-
+
@hannahilea: blog
diff --git a/img/github-logo-styled.svg b/img/github-logo-styled.svg
new file mode 100644
index 0000000..60636b1
--- /dev/null
+++ b/img/github-logo-styled.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/img/linkedin-logo-styled.svg b/img/linkedin-logo-styled.svg
new file mode 100644
index 0000000..6efa444
--- /dev/null
+++ b/img/linkedin-logo-styled.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/index.html b/index.html
index db0ad48..5bbce3c 100755
--- a/index.html
+++ b/index.html
@@ -49,13 +49,15 @@ DSP ENGINEER | MAKER
This website is currently under active development!
-
+
+
+
-
+
+
+
diff --git a/scripts/md-block.js b/scripts/md-block.js
new file mode 100644
index 0000000..a2f47d1
--- /dev/null
+++ b/scripts/md-block.js
@@ -0,0 +1,305 @@
+// https://md-block.verou.me/md-block.js
+/**
+ *
custom element
+ * @author Lea Verou
+ */
+
+let marked = window.marked;
+let DOMPurify = window.DOMPurify;
+let Prism = window.Prism;
+
+export const URLs = {
+ marked: "https://cdn.jsdelivr.net/npm/marked/src/marked.min.js",
+ DOMPurify: "https://cdn.jsdelivr.net/npm/dompurify@2.3.3/dist/purify.es.min.js"
+}
+
+// Fix indentation
+function deIndent(text) {
+ let indent = text.match(/^[\r\n]*([\t ]+)/);
+
+ if (indent) {
+ indent = indent[1];
+
+ text = text.replace(RegExp("^" + indent, "gm"), "");
+ }
+
+ return text;
+}
+
+export class MarkdownElement extends HTMLElement {
+ constructor() {
+ super();
+
+ this.renderer = Object.assign({}, this.constructor.renderer);
+
+ for (let property in this.renderer) {
+ this.renderer[property] = this.renderer[property].bind(this);
+ }
+ }
+
+ get rendered() {
+ return this.getAttribute("rendered");
+ }
+
+ get mdContent () {
+ return this._mdContent;
+ }
+
+ set mdContent (html) {
+ this._mdContent = html;
+ this._contentFromHTML = false;
+
+ this.render();
+ }
+
+ connectedCallback() {
+ Object.defineProperty(this, "untrusted", {
+ value: this.hasAttribute("untrusted"),
+ enumerable: true,
+ configurable: false,
+ writable: false
+ });
+
+ if (this._mdContent === undefined) {
+ this._contentFromHTML = true;
+ this._mdContent = deIndent(this.innerHTML);
+ // https://github.com/markedjs/marked/issues/874#issuecomment-339995375
+ // marked expects markdown quotes (>) to be un-escaped, otherwise they won't render correctly
+ this._mdContent = this._mdContent.replace(/>/g, '>');
+ }
+
+ this.render();
+ }
+
+ async render () {
+ if (!this.isConnected || this._mdContent === undefined) {
+ return;
+ }
+
+ if (!marked) {
+ marked = import(URLs.marked).then(m => m.marked);
+ }
+
+ marked = await marked;
+
+ marked.setOptions({
+ gfm: true,
+ smartypants: true,
+ langPrefix: "language-",
+ });
+
+ marked.use({renderer: this.renderer});
+
+ let html = this._parse();
+
+ if (this.untrusted) {
+ let mdContent = this._mdContent;
+ html = await MarkdownElement.sanitize(html);
+ if (this._mdContent !== mdContent) {
+ // While we were running this async call, the content changed
+ // We don’t want to overwrite with old data. Abort mission!
+ return;
+ }
+ }
+
+ this.innerHTML = html;
+
+ if (!Prism && URLs.Prism && this.querySelector("code")) {
+ Prism = import(URLs.Prism);
+
+ if (URLs.PrismCSS) {
+ let link = document.createElement("link");
+ link.rel = "stylesheet";
+ link.href = URLs.PrismCSS;
+ document.head.appendChild(link);
+ }
+ }
+
+ if (Prism) {
+ await Prism; // in case it's still loading
+ Prism.highlightAllUnder(this);
+ }
+
+ if (this.src) {
+ this.setAttribute("rendered", this._contentFromHTML? "fallback" : "remote");
+ }
+ else {
+ this.setAttribute("rendered", this._contentFromHTML? "content" : "property");
+ }
+
+ // Fire event
+ let event = new CustomEvent("md-render", {bubbles: true, composed: true});
+ this.dispatchEvent(event);
+ }
+
+ static async sanitize(html) {
+ if (!DOMPurify) {
+ DOMPurify = import(URLs.DOMPurify).then(m => m.default);
+ }
+
+ DOMPurify = await DOMPurify; // in case it's still loading
+
+ return DOMPurify.sanitize(html);
+ }
+};
+
+export class MarkdownSpan extends MarkdownElement {
+ constructor() {
+ super();
+ }
+
+ _parse () {
+ return marked.parseInline(this._mdContent);
+ }
+
+ static renderer = {
+ codespan (code) {
+ if (this._contentFromHTML) {
+ // Inline HTML code needs to be escaped to not be parsed as HTML by the browser
+ // This results in marked double-escaping it, so we need to unescape it
+ code = code.replace(/&(?=[lg]t;)/g, "&");
+ }
+ else {
+ // Remote code may include characters that need to be escaped to be visible in HTML
+ code = code.replace(/${code}`;
+ }
+ }
+}
+
+export class MarkdownBlock extends MarkdownElement {
+ constructor() {
+ super();
+ }
+
+ get src() {
+ return this._src;
+ }
+
+ set src(value) {
+ this.setAttribute("src", value);
+ }
+
+ get hmin() {
+ return this._hmin || 1;
+ }
+
+ set hmin(value) {
+ this.setAttribute("hmin", value);
+ }
+
+ get hlinks() {
+ return this._hlinks ?? null;
+ }
+
+ set hlinks(value) {
+ this.setAttribute("hlinks", value);
+ }
+
+ _parse () {
+ return marked.parse(this._mdContent);
+ }
+
+ static renderer = Object.assign({
+ heading (text, level, _raw, slugger) {
+ level = Math.min(6, level + (this.hmin - 1));
+ const id = slugger.slug(text);
+ const hlinks = this.hlinks;
+
+ let content;
+
+ if (hlinks === null) {
+ // No heading links
+ content = text;
+ }
+ else {
+ content = ``;
+
+ if (hlinks === "") {
+ // Heading content is the link
+ content += text + "";
+ }
+ else {
+ // Headings are prepended with a linked symbol
+ content += hlinks + "" + text;
+ }
+ }
+
+ return `
+
+ ${content}
+ `;
+ },
+
+ code (code, language, escaped) {
+ if (this._contentFromHTML) {
+ // Inline HTML code needs to be escaped to not be parsed as HTML by the browser
+ // This results in marked double-escaping it, so we need to unescape it
+ code = code.replace(/&(?=[lg]t;)/g, "&");
+ }
+ else {
+ // Remote code may include characters that need to be escaped to be visible in HTML
+ code = code.replace(/${code}
`;
+ }
+ }, MarkdownSpan.renderer);
+
+ static get observedAttributes() {
+ return ["src", "hmin", "hlinks"];
+ }
+
+ attributeChangedCallback(name, oldValue, newValue) {
+ if (oldValue === newValue) {
+ return;
+ }
+
+ switch (name) {
+ case "src":
+ let url;
+ try {
+ url = new URL(newValue, location);
+ }
+ catch (e) {
+ return;
+ }
+
+ let prevSrc = this.src;
+ this._src = url;
+
+ if (this.src !== prevSrc) {
+ fetch(this.src)
+ .then(response => {
+ if (!response.ok) {
+ throw new Error(`Failed to fetch ${this.src}: ${response.status} ${response.statusText}`);
+ }
+
+ return response.text();
+ })
+ .then(text => {
+ this.mdContent = text;
+ })
+ .catch(e => {});
+ }
+
+ break;
+ case "hmin":
+ if (newValue > 0) {
+ this._hmin = +newValue;
+
+ this.render();
+ }
+ break;
+ case "hlinks":
+ this._hlinks = newValue;
+ this.render();
+ }
+ }
+}
+
+
+customElements.define("md-block", MarkdownBlock);
+customElements.define("md-span", MarkdownSpan);