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 @@ + + + diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..e05d682 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "target": "es2019", + "strict": true, + "rootDir": "./src/main/js/", + "outDir": "./dist/js/" + }, + "include": [ + "src/main/js/**/*.ts" + ] +}