+
+ `
+}
\ No newline at end of file
diff --git a/day5/src/components/routerLink.ts b/day5/src/components/routerLink.ts
new file mode 100644
index 0000000..838d907
--- /dev/null
+++ b/day5/src/components/routerLink.ts
@@ -0,0 +1,5 @@
+export const RouterLink = (link: string, name: string) => {
+ return `
+ ${name}
+ `
+}
\ No newline at end of file
diff --git a/day5/src/components/textInput.ts b/day5/src/components/textInput.ts
new file mode 100644
index 0000000..b00c227
--- /dev/null
+++ b/day5/src/components/textInput.ts
@@ -0,0 +1,10 @@
+export const TextInput = () => {
+ return `
+
+
Input is: {text}
+
+
+
+
+ `
+}
\ No newline at end of file
diff --git a/day5/src/icons/minus.svg b/day5/src/icons/minus.svg
new file mode 100644
index 0000000..3d681cb
--- /dev/null
+++ b/day5/src/icons/minus.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/day5/src/icons/plus.svg b/day5/src/icons/plus.svg
new file mode 100644
index 0000000..6d61262
--- /dev/null
+++ b/day5/src/icons/plus.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/day5/src/icons/refresh.svg b/day5/src/icons/refresh.svg
new file mode 100644
index 0000000..482bede
--- /dev/null
+++ b/day5/src/icons/refresh.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/day5/src/lib/ReactiveObject.js b/day5/src/lib/ReactiveObject.js
new file mode 100644
index 0000000..d98c166
--- /dev/null
+++ b/day5/src/lib/ReactiveObject.js
@@ -0,0 +1,38 @@
+export class Reactive {
+ constructor(obj) {
+ this.contents = obj;
+ this.listeners = {};
+ this.makeReactive(obj);
+ }
+
+ makeReactive(obj) {
+ Object.keys(obj).forEach(prop => this.makePropReactive(obj, prop));
+ }
+
+ makePropReactive(obj, key) {
+ let value = obj[key];
+
+ // Gotta be careful with this here
+ const that = this;
+
+ Object.defineProperty(obj, key, {
+ get() {
+ return value;
+ },
+ set(newValue) {
+ value = newValue;
+ that.notify(key)
+ }
+ })
+ }
+
+ listen(prop, handler) {
+ if (!this.listeners[prop]) this.listeners[prop] = [];
+
+ this.listeners[prop].push(handler);
+ }
+
+ notify(prop) {
+ this.listeners[prop].forEach(listener => listener(this.contents[prop]));
+ }
+}
\ No newline at end of file
diff --git a/day5/src/lib/templateRenderer.js b/day5/src/lib/templateRenderer.js
new file mode 100644
index 0000000..a0954af
--- /dev/null
+++ b/day5/src/lib/templateRenderer.js
@@ -0,0 +1,44 @@
+export const compileToString = (template) => {
+ const ast = parse(template);
+ let fnStr = `\`\``;
+
+ ast.map(t => {
+ // checking to see if it is an interpolation
+ if (t.startsWith("{") && t.endsWith("}")) {
+ // so first we calculate the hex value of the variable, which is needed so we can use reactivity properly
+ // then after that we append a span element with the data-token- attribute to the span
+ // finally we add the appState.contents.variables to the string so we have the value of the variable in the raw html.
+ const uuid = t.split(/{|}/).filter(Boolean)[0].trim().split("").map(c => c.charCodeAt(0).toString(16).padStart(2, "0")).join("");
+ fnStr = fnStr.substring(0, fnStr.length - 1) + `\``;
+ fnStr += `+appState.contents.${t.split(/{|}/).filter(Boolean)[0].trim()}` + `+\`\``;
+ } else {
+ // append the string to the fnStr
+ fnStr += `+\`${t}\``;
+ }
+ });
+
+ return fnStr;
+}
+
+// this function will turn a string like "hi {user}" into an array that looks something like "["hi", "{user}"] so we can loop over the array elements
+// when compiling the template
+var parse = (template) => {
+ let result = /{(.*?)}/g.exec(template);
+ const arr = [];
+ let firstPos;
+
+ while (result) {
+ firstPos = result.index;
+ if (firstPos !== 0) {
+ arr.push(template.substring(0, firstPos));
+ template = template.slice(firstPos);
+ }
+
+ arr.push(result[0]);
+ template = template.slice(result[0].length);
+ result = /{(.*?)}/g.exec(template);
+ }
+
+ if (template) arr.push(template);
+ return arr;
+}
\ No newline at end of file
diff --git a/day5/src/main.ts b/day5/src/main.ts
new file mode 100644
index 0000000..6712bbf
--- /dev/null
+++ b/day5/src/main.ts
@@ -0,0 +1,93 @@
+import { Reactive } from './lib/ReactiveObject'
+import { compileToString } from './lib/templateRenderer'
+import '/src/style.css';
+
+const documentBody = document.getElementById('app')
+const appState = new Reactive({
+ count: 0,
+ text: '',
+ year: ''
+});
+
+// Global function to handle rendering a page and navigation
+async function loadPage(route?: string) {
+ let templatedVirtualDom
+ let fileName: any = window.location.pathname.split('/');
+ if (!documentBody) {
+ throw new Error
+ }
+ if (fileName[1] === '') {
+ fileName = '/index'
+ } else {
+ fileName = fileName.join('/').toLowerCase().trim()
+ }
+ if (route) {
+ // we assume the user is navigating to another page so we change the windows path to the give route
+ // then we dynamically import the page from the pages directory and then we change the templatedVirtualDom
+ // to the new files content
+ history.pushState('', '', route)
+ fileName = window.location.pathname.split('/');
+ if (fileName[1] === '') {
+ fileName = '/index'
+ } else {
+ fileName = fileName.join('/').toLowerCase().trim()
+ }
+ const file = await import(/* @vite-ignore */ './pages' + fileName)
+ templatedVirtualDom = await eval(compileToString(eval(file.default)()));
+ } else {
+ const file = await import(/* @vite-ignore */ './pages' + fileName)
+ templatedVirtualDom = await eval(compileToString(eval(file.default)()))
+ }
+ documentBody.innerHTML = templatedVirtualDom
+ // here we hydrate/re-hydrate the page content
+ await hydratePage()
+}
+
+// function to turn the template into reactive content "hydating" a page
+async function hydratePage() {
+ if (!documentBody) {
+ throw new Error
+ }
+ // for every item in the appState lets check for any element with the "checksum", a hex code equivalent of the item name
+ Object.keys(appState.contents).forEach((e) => {
+ if (e === undefined) return
+ // here we check for elements with the name of "data-token-"
+ const querySelector = "data-token-" + e.split("").map(c => c.charCodeAt(0).toString(16).padStart(2, "0")).join("")
+ const listeningElements = document.querySelectorAll(`[${querySelector}]`)
+ listeningElements.forEach((elm) => {
+ appState.listen(e, (change) => elm.textContent = change);
+ })
+ })
+ // here we look for elements with the d-click attribute and on click run the function in the attribute
+ let elms = documentBody.querySelectorAll('*[d-click]')
+ elms.forEach((e) => {
+ const clickFunction = e.getAttribute("d-click")
+ if (!clickFunction) return;
+ e.addEventListener('click', () => {
+ eval(clickFunction)
+ })
+ })
+
+ // here we look for elements with the d-model attribute and if there is any input in the element then we update the appState item with the name
+ // of the attribute value
+ // example: if the user types "hello" into a text field with the d-model attribute of "text" then we update the appState item with the name "text"
+ // to "hello"
+ // similar to vue.js v-model attribute
+ let modelElms = document.querySelectorAll('*[d-model]')
+ modelElms.forEach((e) => {
+ const modelName = e.getAttribute("d-model")
+ if (!modelName) return;
+ e.addEventListener('input', (event: any) => {
+ appState.contents[modelName] = event.target.value
+ })
+ })
+}
+
+// equivalent to mounted() on svelte or vue
+window.addEventListener('load', async () => {
+ // loadPage after all the index is loaded
+ await loadPage()
+ window.onpopstate = async () => {
+ await loadPage()
+ }
+})
\ No newline at end of file
diff --git a/day5/src/pages/index.ts b/day5/src/pages/index.ts
new file mode 100644
index 0000000..1971cf2
--- /dev/null
+++ b/day5/src/pages/index.ts
@@ -0,0 +1,23 @@
+import { Counter } from '/src/components/counter';
+import { TextInput } from '/src/components/textInput';
+import { MyDad } from '/src/components/myDad';
+import { RouterLink } from '/src/components/routerLink';
+
+
+export default () => {
+ return `
+