diff --git a/Gruntfile.js b/Gruntfile.js
index 9acc492..3aac6fe 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -14,13 +14,33 @@ module.exports = grunt => {
},
},
focus: {
- dev: {
- include: ["css", "js"],
+ deploy: {
+ include: ["css", "storage", "template"],
},
},
webpack: {
- options: {
- entry: "./src/main/js/main.js",
+ storage: {
+ entry: "./src/main/js/Storage.ts",
+ module: {
+ rules: [
+ {
+ test: /\.ts$/,
+ use: "ts-loader",
+ exclude: /node_modules/,
+ },
+ ],
+ },
+ resolve: {
+ extensions: [".ts"],
+ },
+ output: {
+ filename: "storage.js",
+ path: path.resolve(__dirname, "dist/"),
+ },
+ mode: "production",
+ },
+ template: {
+ entry: "./src/main/js/template.js",
module: {
rules: [
{
@@ -30,20 +50,14 @@ module.exports = grunt => {
],
},
resolve: {
- extensions: [".js"],
+ extensions: [".ts"],
},
output: {
library: "fwdekker-template",
libraryTarget: "umd",
filename: "template.js",
path: path.resolve(__dirname, "dist"),
- }
- },
- dev: {
- mode: "development",
- devtool: "inline-source-map",
- },
- deploy: {
+ },
mode: "production",
},
},
@@ -52,9 +66,13 @@ module.exports = grunt => {
files: ["src/main/**/*.css"],
tasks: ["cssmin"],
},
- js: {
+ storage: {
+ files: ["src/main/**/*.ts"],
+ tasks: ["webpack:storage"],
+ },
+ template: {
files: ["src/main/**/*.js"],
- tasks: ["webpack:dev"],
+ tasks: ["webpack:template"],
},
},
});
@@ -65,9 +83,8 @@ module.exports = grunt => {
grunt.loadNpmTasks("grunt-focus");
grunt.loadNpmTasks("grunt-webpack");
- grunt.registerTask("dev", ["webpack:dev", "cssmin"]);
- grunt.registerTask("dev:server", ["dev", "focus:dev"]);
- grunt.registerTask("deploy", ["webpack:deploy", "cssmin"]);
+ grunt.registerTask("deploy", ["webpack:storage", "webpack:template", "cssmin"]);
+ grunt.registerTask("deploy:server", ["deploy", "focus:deploy"]);
- grunt.registerTask("default", ["dev"]);
+ grunt.registerTask("default", ["deploy"]);
};
diff --git a/package-lock.json b/package-lock.json
index 707099f..b68c3f2 100644
Binary files a/package-lock.json and b/package-lock.json differ
diff --git a/package.json b/package.json
index a0a1d68..f80d88d 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@fwdekker/template",
- "version": "2.5.8",
+ "version": "2.6.0",
"description": "The base template for pages on fwdekker.com.",
"author": "Florine W. Dekker",
"license": "MIT",
@@ -19,9 +19,8 @@
],
"scripts": {
"clean": "grunt clean",
- "dev": "grunt dev",
- "dev:server": "grunt dev:server",
- "deploy": "grunt deploy"
+ "deploy": "grunt deploy",
+ "deploy:server": "grunt deploy:server"
},
"dependencies": {
"milligram": "^1.4.1",
@@ -35,7 +34,10 @@
"grunt-contrib-watch": "^1.1.0",
"grunt-focus": "^1.0.0",
"grunt-webpack": "^5.0.0",
- "webpack": "^5.69.1",
+ "ts-loader": "^9.2.8",
+ "ts-node": "^10.7.0",
+ "typescript": "^4.6.3",
+ "webpack": "^5.70.0",
"webpack-cli": "^4.9.2"
}
}
diff --git a/src/main/js/Storage.ts b/src/main/js/Storage.ts
new file mode 100644
index 0000000..65121c0
--- /dev/null
+++ b/src/main/js/Storage.ts
@@ -0,0 +1,186 @@
+/**
+ * Stores key-value pairs.
+ */
+export interface Storage {
+ /**
+ * Removes the data from storage.
+ */
+ clear(): void
+
+ /**
+ * Retrieves an array from storage.
+ *
+ * @param name the name of the array to retrieve
+ * @param def the value to return if no array is stored with the given name
+ */
+ getArray(name: string, def: any[]): any[]
+
+ /**
+ * Stores an array.
+ *
+ * @param name the name of the array to store
+ * @param value the array to store under the given name
+ * @protected
+ */
+ setArray(name: string, value: any[]): void
+
+ /**
+ * Retrieves a boolean from storage.
+ *
+ * @param name the name of the boolean to retrieve
+ * @param def the value to return if no boolean is stored with the given name
+ * @protected
+ */
+ getBoolean(name: string, def: boolean): boolean
+
+ /**
+ * Stores a boolean.
+ *
+ * @param name the name of the boolean to store
+ * @param value the boolean to store under the given name
+ * @protected
+ */
+ setBoolean(name: string, value: boolean): void
+
+ /**
+ * Retrieves a number from storage.
+ *
+ * @param name the name of the number to retrieve
+ * @param def the value to return if no number is stored with the given name
+ * @protected
+ */
+ getNumber(name: string, def: number): number
+
+ /**
+ * Stores a number.
+ *
+ * @param name the name of the number to store
+ * @param value the number to store under the given name
+ * @protected
+ */
+ setNumber(name: string, value: number): void
+}
+
+/**
+ * Stores key-value pairs in a single entry in `localStorage`.
+ */
+export class LocalStorage implements Storage {
+ private readonly key: string;
+ private cache: { [key: string]: string } | null = null;
+
+
+ /**
+ * Constructs a new persistent storage item under the given key.
+ *
+ * @param key the unique identifier to store the data under
+ */
+ constructor(key: string) {
+ this.key = key;
+ }
+
+
+ /**
+ * Reads the object stored in local storage.
+ *
+ * @return the object stored in local storage, or an empty object if there is nothing in the local storage
+ * @private
+ */
+ private read(): { [key: string]: string } {
+ if (this.cache === null)
+ this.cache = JSON.parse(localStorage.getItem(this.key) ?? "{}");
+
+ return this.cache!;
+ }
+
+ /**
+ * Writes the given object to local storage.
+ *
+ * @param item the object to write to local storage
+ * @private
+ */
+ private write(item: { [key: string]: string }): void {
+ this.cache = item;
+ localStorage.setItem(this.key, JSON.stringify(item));
+ }
+
+
+ clear(): void {
+ this.cache = null;
+ localStorage.removeItem(this.key);
+ }
+
+ getArray(name: string, def: any[] = []): any[] {
+ const array = this.read()[name];
+ return array === undefined ? def : JSON.parse(array);
+ }
+
+ setArray(name: string, value: any[]): void {
+ const item = this.read();
+ item[name] = JSON.stringify(value);
+ this.write(item);
+ }
+
+ getBoolean(name: string, def: boolean = false): boolean {
+ return (this.read()[name] ?? `${def}`) === "true";
+ }
+
+ setBoolean(name: string, value: boolean): void {
+ const item = this.read();
+ item[name] = "" + value;
+ this.write(item);
+ }
+
+ getNumber(name: string, def: number = 0): number {
+ return +(this.read()[name] ?? def);
+ }
+
+ setNumber(name: string, value: number): void {
+ const item = this.read();
+ item[name] = "" + value;
+ this.write(item);
+ }
+}
+
+/**
+ * Stores key-value pairs in an object.
+ */
+export class MemoryStorage implements Storage {
+ private storage: { [key: string]: any } = {};
+
+
+ clear(): void {
+ this.storage = {};
+ }
+
+ setArray(name: string, value: any[] = []): void {
+ this.storage[name] = value;
+ }
+
+ getArray(name: string, def: any[]): any[] {
+ return this.storage[name] ?? def;
+ }
+
+ setBoolean(name: string, value: boolean): void {
+ this.storage[name] = value;
+ }
+
+ getBoolean(name: string, def: boolean): boolean {
+ return this.storage[name] ?? def;
+ }
+
+ setNumber(name: string, value: number): void {
+ this.storage[name] = value;
+ }
+
+ getNumber(name: string, def: number): number {
+ return this.storage[name] ?? def;
+ }
+}
+
+
+// @ts-ignore
+if (typeof window.fwdekker === "undefined")
+ // @ts-ignore
+ window.fwdekker = {};
+// @ts-ignore
+window.fwdekker.storage = {Storage, LocalStorage, MemoryStorage};
diff --git a/src/main/js/main.js b/src/main/js/template.js
similarity index 99%
rename from src/main/js/main.js
rename to src/main/js/template.js
index 0d61158..ed0c12a 100644
--- a/src/main/js/main.js
+++ b/src/main/js/template.js
@@ -257,4 +257,4 @@ doAfterLoad(() => {
// Export to namespace
-fwdekker = {stringToHtml, $, $a, doAfterLoad, nav, header, footer};
+window.fwdekker = {stringToHtml, $, $a, doAfterLoad, nav, header, footer};
diff --git a/src/test/index.html b/src/test/index.html
index 421334f..d904238 100644
--- a/src/test/index.html
+++ b/src/test/index.html
@@ -47,5 +47,13 @@
+
+
+