diff --git a/.eslintrc.yml b/.eslintrc.yml index f2a33eaa..2b8a75cf 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -8,6 +8,7 @@ extends: - plugin:sonarjs/recommended - plugin:unicorn/recommended - plugin:perfectionist/recommended-natural + - plugin:jsdoc/recommended-typescript-flavor-error plugins: - simple-import-sort @@ -17,6 +18,8 @@ parserOptions: sourceType: module settings: + jsdoc: + mode: typescript import/resolver: alias: extensions: @@ -111,6 +114,25 @@ rules: import/extensions: - error - ignorePackages + jsdoc/require-jsdoc: + - error + - checkConstructors: false + checkGetters: true + checkSetters: true + contexts: + - ArrowFunctionExpression + - ClassProperty + - FunctionDeclaration + - FunctionExpression + - MethodDefinition + jsdoc/valid-types: + - off + jsdoc/require-returns-description: + - off + jsdoc/require-param-description: + - off + jsdoc/require-property-description: + - off overrides: - files: diff --git a/.lintstagedrc.yml b/.lintstagedrc.yml index 7272cf7b..3ef658c0 100644 --- a/.lintstagedrc.yml +++ b/.lintstagedrc.yml @@ -1,4 +1,4 @@ -'*.{json,md}': prettier --write +'*.{json,yml,css,js,cjs,d.ts}': prettier --write '*': make lint_editorconfig lint_fs '*.css': make lint_css -'*.js': make lint_js +'*.js': make lint_js lint_type diff --git a/.prettierrc.yml b/.prettierrc.yml index e5524cb7..0d2af67b 100644 --- a/.prettierrc.yml +++ b/.prettierrc.yml @@ -1,3 +1,5 @@ +plugins: + - 'prettier-plugin-jsdoc' printWidth: 80 tabWidth: 2 useTabs: false diff --git a/Makefile b/Makefile index 4ddbdb49..3cbbe07f 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,7 @@ build: clean # Prettify prettify: - npx prettier --write "**/*.{json,yml,css,js,cjs}" + npx prettier --write "**/*.{json,yml,css,js,cjs,d.ts}" # Lint @@ -23,11 +23,13 @@ lint_fs: lint_editorconfig: npx editorconfig-checker lint_prettify: - npx prettier --check "**/*.{json,yml,css,js,cjs}" + npx prettier --check "**/*.{json,yml,css,js,cjs,d.ts}" lint_html: npx node-w3c-validator -i build/*.html -f lint -evH lint_css: npx stylelint "source/styles/**/*.css" lint_js: npx eslint --ext .js "**/*.{js,cjs}" -lint: lint_fs lint_editorconfig lint_prettify lint_html lint_css lint_js +lint_type: + npx tsc --noEmit +lint: lint_fs lint_editorconfig lint_prettify lint_html lint_css lint_js lint_type diff --git a/eleventy.config.cjs b/eleventy.config.cjs index 7e291caa..5821c9bf 100644 --- a/eleventy.config.cjs +++ b/eleventy.config.cjs @@ -1,3 +1,5 @@ +let fs = require(`node:fs/promises`) +let process = require(`node:process`) let esbuild = require(`esbuild`) let lightningcss = require(`lightningcss`) let htmlMin = require(`html-minifier-terser`) @@ -8,7 +10,6 @@ let packageJson = require(`./package.json`) let Image = require(`@11ty/eleventy-img`) let svgo = require(`svgo`) let path = require(`node:path`) -let fs = require(`node:fs/promises`) let isDevelopment = process.env.NODE_ENV === `development` @@ -28,7 +29,10 @@ let Path = { }, } -/** @param {import("@11ty/eleventy").UserConfig} config */ +/** + * @param {import('@11ty/eleventy').UserConfig} config + * @returns {ReturnType} + */ let init = (config) => { // ignores if (!isDevelopment) { @@ -44,6 +48,7 @@ let init = (config) => { config.addTemplateFormats(`json`) config.addExtension(`json`, { + /** @type {import('@11ty/eleventy/src/Engines/TemplateEngine')['compile']} */ compile: async (_content, url) => { if (url !== Path.DB) { return @@ -84,6 +89,7 @@ let init = (config) => { config.addTemplateFormats(`css`) config.addExtension(`css`, { + /** @type {import('@11ty/eleventy/src/Engines/TemplateEngine')['compile']} */ compile: async (_content, url) => { if (url !== Path.CSS) { return @@ -114,6 +120,7 @@ let init = (config) => { config.addTemplateFormats(`js`) config.addExtension(`js`, { + /** @type {import('@11ty/eleventy/src/Engines/TemplateEngine')['compile']} */ compile: async (_content, url) => { if (url !== Path.JS.MAIN) { return @@ -162,6 +169,7 @@ let init = (config) => { config.addTemplateFormats(`png`) config.addExtension(`png`, { + /** @type {import('@11ty/eleventy/src/Engines/TemplateEngine')['compile']} */ compile: async (_content, url) => { return async () => { let { @@ -173,6 +181,7 @@ let init = (config) => { if (url.includes(`.photo.`)) { await Image(url, { + /** @type {import('@11ty/eleventy-img').BaseImageOptions['filenameFormat']} */ filenameFormat: (_id, source, _width, format) => { let extension = path.extname(source) let name = path.basename(source, extension) @@ -194,6 +203,7 @@ let init = (config) => { config.addTemplateFormats(`svg`) config.addExtension(`svg`, { + /** @type {import('@11ty/eleventy/src/Engines/TemplateEngine')['compile']} */ compile: (content, url) => { return () => { if (url === `./source/images/icons/icon.svg`) { diff --git a/jsconfig.json b/jsconfig.json deleted file mode 100644 index 69e21a52..00000000 --- a/jsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "include": ["source/scripts/**/*"], - "exclude": ["node_modules/*"], - "compilerOptions": { - "baseUrl": ".", - "paths": { - "~/*": ["source/scripts/*"] - } - } -} diff --git a/package-lock.json b/package-lock.json index 2ba37d42..9a35d3cd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "eslint": "8.53.0", "eslint-import-resolver-alias": "1.1.2", "eslint-plugin-import": "2.29.0", + "eslint-plugin-jsdoc": "46.9.0", "eslint-plugin-perfectionist": "2.3.0", "eslint-plugin-simple-import-sort": "10.0.0", "eslint-plugin-sonarjs": "0.23.0", @@ -30,11 +31,13 @@ "lint-staged": "15.1.0", "node-w3c-validator": "2.0.2", "prettier": "3.1.0", + "prettier-plugin-jsdoc": "1.1.1", "simple-git-hooks": "2.9.0", "stylelint": "15.11.0", "stylelint-config-standard": "34.0.0", "stylelint-order": "6.0.3", - "svgo": "3.0.3" + "svgo": "3.0.3", + "typescript": "5.2.2" }, "engines": { "node": "18.x.x", @@ -964,6 +967,20 @@ "postcss-selector-parser": "^6.0.13" } }, + "node_modules/@es-joy/jsdoccomment": { + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.41.0.tgz", + "integrity": "sha512-aKUhyn1QI5Ksbqcr3fFJj16p99QdjUxXAEuFst1Z47DRyoiMwivIH9MV/ARcJOCXVjPfjITciej8ZD2O/6qUmw==", + "dev": true, + "dependencies": { + "comment-parser": "1.4.1", + "esquery": "^1.5.0", + "jsdoc-type-pratt-parser": "~4.0.0" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/@esbuild/android-arm": { "version": "0.19.5", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.5.tgz", @@ -1609,6 +1626,15 @@ "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", "dev": true }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "dev": true, + "dependencies": { + "@types/ms": "*" + } + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -1621,6 +1647,15 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, + "node_modules/@types/mdast": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.3.tgz", + "integrity": "sha512-LsjtqsyF+d2/yFOYaN22dHZI1Cpwkrj+g06G8+qtUKlhovPW89YhqSnfKtMbkgmEtYpH2gydRNULd6y8mciAFg==", + "dev": true, + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/minimatch": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", @@ -1633,6 +1668,12 @@ "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==", "dev": true }, + "node_modules/@types/ms": { + "version": "0.7.34", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz", + "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==", + "dev": true + }, "node_modules/@types/node": { "version": "20.5.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.1.tgz", @@ -1651,6 +1692,12 @@ "integrity": "sha512-+d+WYC1BxJ6yVOgUgzK8gWvp5qF8ssV5r4nsDcZWKRWcDQLQ619tvWAxJQYGgBrO1MnLJC7a5GtiYsAoQ47dJg==", "dev": true }, + "node_modules/@types/unist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", + "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==", + "dev": true + }, "node_modules/@typescript-eslint/scope-manager": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.11.0.tgz", @@ -1888,6 +1935,15 @@ "node": ">= 8" } }, + "node_modules/are-docs-informative": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/are-docs-informative/-/are-docs-informative-0.0.2.tgz", + "integrity": "sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==", + "dev": true, + "engines": { + "node": ">=14" + } + }, "node_modules/arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", @@ -2193,6 +2249,12 @@ "node": ">=8" } }, + "node_modules/binary-searching": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/binary-searching/-/binary-searching-2.0.5.tgz", + "integrity": "sha512-v4N2l3RxL+m4zDxyxz3Ne2aTmiPn8ZUpKFpdPtO+ItW1NcTCXA7JeHG5GMBSvoKSkQZ9ycS+EouDVxYB9ufKWA==", + "dev": true + }, "node_modules/bl": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", @@ -2514,6 +2576,16 @@ "node": ">=4" } }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/character-parser": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz", @@ -2764,6 +2836,15 @@ "node": ">=14" } }, + "node_modules/comment-parser": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.1.tgz", + "integrity": "sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==", + "dev": true, + "engines": { + "node": ">= 12.0.0" + } + }, "node_modules/compare-func": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", @@ -3371,6 +3452,19 @@ "node": ">=0.10.0" } }, + "node_modules/decode-named-character-reference": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz", + "integrity": "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==", + "dev": true, + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", @@ -3453,6 +3547,15 @@ "node": ">= 0.6.0" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/destroy": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", @@ -3483,6 +3586,19 @@ "node": ">= 0.8.0" } }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "dev": true, + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -4177,6 +4293,41 @@ "semver": "bin/semver.js" } }, + "node_modules/eslint-plugin-jsdoc": { + "version": "46.9.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-46.9.0.tgz", + "integrity": "sha512-UQuEtbqLNkPf5Nr/6PPRCtr9xypXY+g8y/Q7gPa0YK7eDhh0y2lWprXRnaYbW7ACgIUvpDKy9X2bZqxtGzBG9Q==", + "dev": true, + "dependencies": { + "@es-joy/jsdoccomment": "~0.41.0", + "are-docs-informative": "^0.0.2", + "comment-parser": "1.4.1", + "debug": "^4.3.4", + "escape-string-regexp": "^4.0.0", + "esquery": "^1.5.0", + "is-builtin-module": "^3.2.1", + "semver": "^7.5.4", + "spdx-expression-parse": "^3.0.1" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/eslint-plugin-jsdoc/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/eslint-plugin-perfectionist": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/eslint-plugin-perfectionist/-/eslint-plugin-perfectionist-2.3.0.tgz", @@ -6223,6 +6374,15 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsdoc-type-pratt-parser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.0.0.tgz", + "integrity": "sha512-YtOli5Cmzy3q4dP26GraSOeAhqecewG04hoO8DY56CH4KJ9Fvv5qKWUCCo3HZob7esJQHCv6/+bnTy72xZZaVQ==", + "dev": true, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/jsesc": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", @@ -7495,6 +7655,43 @@ "node": ">=0.10.0" } }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.0.tgz", + "integrity": "sha512-n7MTOr/z+8NAX/wmhhDji8O3bRvPTV/U0oTCaZJkjhPSKTPhS3xufVhKGF8s1pJ7Ox4QgoIU7KHseh09S+9rTA==", + "dev": true, + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "dev": true, + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/mdn-data": { "version": "2.0.30", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", @@ -7746,6 +7943,448 @@ "node": ">= 0.6" } }, + "node_modules/micromark": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.0.tgz", + "integrity": "sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.0.tgz", + "integrity": "sha512-jThOz/pVmAYUtkroV3D5c1osFXAMv9e0ypGDOIZuCeAe91/sD6BoE2Sjzt30yuXtwOYUmySOhMas/PVyh02itA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.0.tgz", + "integrity": "sha512-j9DGrQLm/Uhl2tCzcbLhy5kXsgkHUrjJHg4fFAeoMRwJmJerT9aw4FEhIbZStWN8A3qMwOp1uzHr4UL8AInxtA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.0.tgz", + "integrity": "sha512-RR3i96ohZGde//4WSe/dJsxOX6vxIg9TimLAS3i4EhBAFx8Sm5SmqVfR8E87DPSR31nEAjZfbt91OMZWcNgdZw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", + "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.0.tgz", + "integrity": "sha512-jY8CSxmpWLOxS+t8W+FG3Xigc0RDQA9bKMY/EwILvsesiRniiVMejYTE4wumNc2f4UbAa4WsHqe3J1QS1sli+A==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.0.tgz", + "integrity": "sha512-28kbwaBjc5yAI1XadbdPYHX/eDnqaUFVikLwrO7FDnKG7lpgxnvk/XGRhX/PN0mOZ+dBSZ+LgunHS+6tYQAzhA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", + "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.0.tgz", + "integrity": "sha512-anK8SWmNphkXdaKgz5hJvGa7l00qmcaUQoMYsBwDlSKFKjc6gjGXPDw3FNL3Nbwq5L8gE+RCbGqTw49FK5Qyvg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.0.tgz", + "integrity": "sha512-S0ze2R9GH+fu41FA7pbSqNWObo/kzwf8rN/+IGlW/4tC6oACOs8B++bh+i9bVyNnwCcuksbFwsBme5OCKXCwIw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.0.tgz", + "integrity": "sha512-vZZio48k7ON0fVS3CUgFatWHoKbbLTK/rT7pzpJ4Bjp5JjkZeasRfrS9wsBdDJK2cJLHMckXZdzPSSr1B8a4oQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.1.tgz", + "integrity": "sha512-bmkNc7z8Wn6kgjZmVHOX3SowGmVdhYS7yBpMnuMnPzDq/6xwVA604DuOXMZTO1lvq01g+Adfa0pE2UKGlxL1XQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.0.tgz", + "integrity": "sha512-r4Sc6leeUTn3P6gk20aFMj2ntPwn6qpDZqWvYmAG6NgvFTIlj4WtrAudLi65qYoaGdXYViXYw2pkmn7QnIFasA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.0.tgz", + "integrity": "sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.0.tgz", + "integrity": "sha512-xNn4Pqkj2puRhKdKTm8t1YHC/BAjx6CEwRFXntTaRf/x16aqka6ouVoutm+QdkISTlT7e2zU7U4ZdlDLJd2Mcw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.0.tgz", + "integrity": "sha512-2xhYT0sfo85FMrUPtHcPo2rrp1lwbDEEzpx7jiH2xXJLqBuy4H0GgXk5ToU8IEwoROtXuL8ND0ttVa4rNqYK3w==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.0.tgz", + "integrity": "sha512-6KU6qO7DZ7GJkaCgwBNtplXCvGkJToU86ybBAUdavvgsCiG8lSSvYxr9MhwmQ+udpzywHsl4RpGJsYWG1pDOcA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.0.tgz", + "integrity": "sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.0.0.tgz", + "integrity": "sha512-vc93L1t+gpR3p8jxeVdaYlbV2jTYteDje19rNSS/H5dlhxUYll5Fy6vJ2cDwP8RnsXi818yGty1ayP55y3W6fg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", + "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.0.tgz", + "integrity": "sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, "node_modules/micromatch": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", @@ -8972,6 +9611,23 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/prettier-plugin-jsdoc": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/prettier-plugin-jsdoc/-/prettier-plugin-jsdoc-1.1.1.tgz", + "integrity": "sha512-yA13k0StQ+g0RJBrmo2IldVSp3ANXlJdsNzQNhGtQ0LY7JFC+u01No/1Z9xp0ZhT4u98BXlPAc4SC0iambqy5A==", + "dev": true, + "dependencies": { + "binary-searching": "^2.0.5", + "comment-parser": "^1.4.0", + "mdast-util-from-markdown": "^2.0.0" + }, + "engines": { + "node": ">=14.13.1 || >=16.0.0" + }, + "peerDependencies": { + "prettier": "^3.0.0" + } + }, "node_modules/promise": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", @@ -10048,9 +10704,9 @@ "dev": true }, "node_modules/spdx-expression-parse": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", - "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", "dev": true, "dependencies": { "spdx-exceptions": "^2.1.0", @@ -10995,6 +11651,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dev": true, + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/universalify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", diff --git a/package.json b/package.json index 6df33fb7..1eff93d9 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "eslint": "8.53.0", "eslint-import-resolver-alias": "1.1.2", "eslint-plugin-import": "2.29.0", + "eslint-plugin-jsdoc": "46.9.0", "eslint-plugin-perfectionist": "2.3.0", "eslint-plugin-simple-import-sort": "10.0.0", "eslint-plugin-sonarjs": "0.23.0", @@ -31,11 +32,13 @@ "lint-staged": "15.1.0", "node-w3c-validator": "2.0.2", "prettier": "3.1.0", + "prettier-plugin-jsdoc": "1.1.1", "simple-git-hooks": "2.9.0", "stylelint": "15.11.0", "stylelint-config-standard": "34.0.0", "stylelint-order": "6.0.3", - "svgo": "3.0.3" + "svgo": "3.0.3", + "typescript": "5.2.2" }, "browserslist": [ "last 2 version", diff --git a/source/pages/index.njk b/source/pages/index.njk index 4bec3011..ad5e5691 100644 --- a/source/pages/index.njk +++ b/source/pages/index.njk @@ -157,7 +157,7 @@ permalink: 'index.html'

Experience

My path

-
+
Skills type: diff --git a/source/scripts/common/enums/api/api-error-message.enum.js b/source/scripts/common/enums/api/api-error-message.enum.js index 326d6eba..ff94bd55 100644 --- a/source/scripts/common/enums/api/api-error-message.enum.js +++ b/source/scripts/common/enums/api/api-error-message.enum.js @@ -1,5 +1,5 @@ -let ApiErrorMessage = { +let ApiErrorMessage = /** @type {const} */ ({ NETWORK_ERROR: `Network Error`, -} +}) export { ApiErrorMessage } diff --git a/source/scripts/common/enums/api/api-path.enum.js b/source/scripts/common/enums/api/api-path.enum.js index 2dfbcbf9..34350f24 100644 --- a/source/scripts/common/enums/api/api-path.enum.js +++ b/source/scripts/common/enums/api/api-path.enum.js @@ -1,5 +1,5 @@ -let ApiPath = { +let ApiPath = /** @type {const} */ ({ TIMELINE: `/timeline`, -} +}) export { ApiPath } diff --git a/source/scripts/common/enums/api/timeline-api-path.enum.js b/source/scripts/common/enums/api/timeline-api-path.enum.js index 9a7b4684..2c714f4c 100644 --- a/source/scripts/common/enums/api/timeline-api-path.enum.js +++ b/source/scripts/common/enums/api/timeline-api-path.enum.js @@ -1,5 +1,5 @@ -let TimelineApiPath = { +let TimelineApiPath = /** @type {const} */ ({ ROOT: `/`, -} +}) export { TimelineApiPath } diff --git a/source/scripts/common/enums/app/app-config.enum.js b/source/scripts/common/enums/app/app-config.enum.js index a9717c0b..4f65cc44 100644 --- a/source/scripts/common/enums/app/app-config.enum.js +++ b/source/scripts/common/enums/app/app-config.enum.js @@ -1,7 +1,7 @@ -let AppConfig = { +let AppConfig = /** @type {const} */ ({ FILES_API_PATH: `/api`, SERVER_HOST: `http://localhost:`, SERVER_PORT: 3001, -} +}) export { AppConfig } diff --git a/source/scripts/common/enums/event/keyboard-key.enum.js b/source/scripts/common/enums/event/keyboard-key.enum.js index 8a35685b..d7df1f17 100644 --- a/source/scripts/common/enums/event/keyboard-key.enum.js +++ b/source/scripts/common/enums/event/keyboard-key.enum.js @@ -1,6 +1,6 @@ -let KeyboardKey = { +let KeyboardKey = /** @type {const} */ ({ ESCAPE: `Escape`, TAB: `Tab`, -} +}) export { KeyboardKey } diff --git a/source/scripts/common/enums/exception/custom-exception-name.enum.js b/source/scripts/common/enums/exception/custom-exception-name.enum.js index 83885d5d..e68f65f4 100644 --- a/source/scripts/common/enums/exception/custom-exception-name.enum.js +++ b/source/scripts/common/enums/exception/custom-exception-name.enum.js @@ -1,5 +1,5 @@ -let CustomExceptionName = { +let CustomExceptionName = /** @type {const} */ ({ HTTP_ERROR: `HttpError`, -} +}) export { CustomExceptionName } diff --git a/source/scripts/common/enums/file/content-type.enum.js b/source/scripts/common/enums/file/content-type.enum.js index 99d252ef..cd8a70db 100644 --- a/source/scripts/common/enums/file/content-type.enum.js +++ b/source/scripts/common/enums/file/content-type.enum.js @@ -1,5 +1,5 @@ -let ContentType = { +let ContentType = /** @type {const} */ ({ JSON: `application/json`, -} +}) export { ContentType } diff --git a/source/scripts/common/enums/http/http-code.enum.js b/source/scripts/common/enums/http/http-code.enum.js index 556d3fd5..ecfb58f4 100644 --- a/source/scripts/common/enums/http/http-code.enum.js +++ b/source/scripts/common/enums/http/http-code.enum.js @@ -1,5 +1,5 @@ -let HttpCode = { +let HttpCode = /** @type {const} */ ({ INTERNAL_SERVER_ERROR: 500, -} +}) export { HttpCode } diff --git a/source/scripts/common/enums/http/http-header.enum.js b/source/scripts/common/enums/http/http-header.enum.js index ac6c1414..ead0cbc0 100644 --- a/source/scripts/common/enums/http/http-header.enum.js +++ b/source/scripts/common/enums/http/http-header.enum.js @@ -1,5 +1,5 @@ -let HttpHeader = { +let HttpHeader = /** @type {const} */ ({ CONTENT_TYPE: `content-type`, -} +}) export { HttpHeader } diff --git a/source/scripts/common/enums/http/http-method.enum.js b/source/scripts/common/enums/http/http-method.enum.js index c2284f6f..232f7b48 100644 --- a/source/scripts/common/enums/http/http-method.enum.js +++ b/source/scripts/common/enums/http/http-method.enum.js @@ -1,6 +1,6 @@ -let HttpMethod = { +let HttpMethod = /** @type {const} */ ({ GET: `GET`, POST: `POST`, -} +}) export { HttpMethod } diff --git a/source/scripts/common/enums/notification/notification-message.enum.js b/source/scripts/common/enums/notification/notification-message.enum.js index a455e1af..a658c186 100644 --- a/source/scripts/common/enums/notification/notification-message.enum.js +++ b/source/scripts/common/enums/notification/notification-message.enum.js @@ -1,5 +1,5 @@ -let NotificationMessage = { +let NotificationMessage = /** @type {const} */ ({ LOVE: `You found something... maybe you should turn down the sound (or maybe add) ❤️`, -} +}) export { NotificationMessage } diff --git a/source/scripts/common/enums/setting/setting-button-label.enum.js b/source/scripts/common/enums/setting/setting-button-label.enum.js index 4cc7fd4c..65d60c84 100644 --- a/source/scripts/common/enums/setting/setting-button-label.enum.js +++ b/source/scripts/common/enums/setting/setting-button-label.enum.js @@ -1,5 +1,5 @@ -let SettingButtonLabel = { +let SettingButtonLabel = /** @type {const} */ ({ SWITCH_LOVE: `Switch playing of love`, -} +}) export { SettingButtonLabel } diff --git a/source/scripts/common/enums/setting/setting-name.enum.js b/source/scripts/common/enums/setting/setting-name.enum.js index daf76cdd..5132d3c4 100644 --- a/source/scripts/common/enums/setting/setting-name.enum.js +++ b/source/scripts/common/enums/setting/setting-name.enum.js @@ -1,7 +1,7 @@ -let SettingName = { +let SettingName = /** @type {const} */ ({ MOTION: `motion`, THEME: `theme`, WHATISLOVE: `whatislove`, -} +}) export { SettingName } diff --git a/source/scripts/common/enums/timeline/skill-type.enum.js b/source/scripts/common/enums/timeline/skill-type.enum.js index 2e29d6de..f73745d1 100644 --- a/source/scripts/common/enums/timeline/skill-type.enum.js +++ b/source/scripts/common/enums/timeline/skill-type.enum.js @@ -1,6 +1,6 @@ -let SkillType = { +let SkillType = /** @type {const} */ ({ HARD_SKILL: `hardSkill`, SOFT_SKILL: `softSkill`, -} +}) export { SkillType } diff --git a/source/scripts/common/enums/timeline/timeline-type.enum.js b/source/scripts/common/enums/timeline/timeline-type.enum.js index 98370043..5e083317 100644 --- a/source/scripts/common/enums/timeline/timeline-type.enum.js +++ b/source/scripts/common/enums/timeline/timeline-type.enum.js @@ -1,8 +1,8 @@ -let TimelineType = { +let TimelineType = /** @type {const} */ ({ BOOK: `book`, COURSE: `course`, MEETUP: `meetup`, POSITION: `position`, -} +}) export { TimelineType } diff --git a/source/scripts/common/maps/number/boolean-to-number.map.js b/source/scripts/common/maps/number/boolean-to-number.map.js index 5b6eb743..f6db7b1a 100644 --- a/source/scripts/common/maps/number/boolean-to-number.map.js +++ b/source/scripts/common/maps/number/boolean-to-number.map.js @@ -1,6 +1,6 @@ -let booleanToNumber = { +let booleanToNumber = /** @type {const} */ ({ 'false': 0, 'true': 1, -} +}) export { booleanToNumber } diff --git a/source/scripts/common/types/settings/settings-button-payload.type.d.ts b/source/scripts/common/types/settings/settings-button-payload.type.d.ts new file mode 100644 index 00000000..9db48c2d --- /dev/null +++ b/source/scripts/common/types/settings/settings-button-payload.type.d.ts @@ -0,0 +1,13 @@ +import { SettingButtonLabel, SettingName } from '~/common/enums/enums.js' + +type SettingButtonPayload = { + isDefaultChecked: boolean + label: (typeof SettingButtonLabel)[keyof typeof SettingButtonLabel] + name: (typeof SettingName)[keyof typeof SettingName] + onClick: ( + name: (typeof SettingName)[keyof typeof SettingName], + isCheck: boolean, + ) => void +} + +export { type SettingButtonPayload } diff --git a/source/scripts/common/types/settings/settings.d.ts b/source/scripts/common/types/settings/settings.d.ts new file mode 100644 index 00000000..82e84265 --- /dev/null +++ b/source/scripts/common/types/settings/settings.d.ts @@ -0,0 +1 @@ +export { type SettingButtonPayload } from './settings-button-payload.type.d.js' diff --git a/source/scripts/common/types/timeline/timeline-create-payload.type.d.ts b/source/scripts/common/types/timeline/timeline-create-payload.type.d.ts new file mode 100644 index 00000000..5f633899 --- /dev/null +++ b/source/scripts/common/types/timeline/timeline-create-payload.type.d.ts @@ -0,0 +1,16 @@ +import { type TimelineType, type SkillType } from '~/common/enums/enums.js' + +type TimelineCreatePayload = { + date: string + desc: string + endDate: string + link: string + linkDesc: string + origin: string + originDesc: string + title: string + skillType: (typeof SkillType)[keyof typeof SkillType] + type: (typeof TimelineType)[keyof typeof TimelineType] +} + +export { type TimelineCreatePayload } diff --git a/source/scripts/common/types/timeline/timeline-filter.d.type.d.ts b/source/scripts/common/types/timeline/timeline-filter.d.type.d.ts new file mode 100644 index 00000000..a321b0b8 --- /dev/null +++ b/source/scripts/common/types/timeline/timeline-filter.d.type.d.ts @@ -0,0 +1,8 @@ +import { type TimelineType, type SkillType } from '~/common/enums/enums.js' + +type TimelineFilter = { + skillTypes: Record<(typeof SkillType)[keyof typeof SkillType], boolean> + types: Record<(typeof TimelineType)[keyof typeof TimelineType], boolean> +} + +export { type TimelineFilter } diff --git a/source/scripts/common/types/timeline/timeline.d.ts b/source/scripts/common/types/timeline/timeline.d.ts new file mode 100644 index 00000000..58607ed4 --- /dev/null +++ b/source/scripts/common/types/timeline/timeline.d.ts @@ -0,0 +1,3 @@ +export { type Timeline } from './timeline.type.d.js' +export { type TimelineCreatePayload } from './timeline-create-payload.type.js' +export { type TimelineFilter } from './timeline-filter.d.type.js' diff --git a/source/scripts/common/types/timeline/timeline.type.d.ts b/source/scripts/common/types/timeline/timeline.type.d.ts new file mode 100644 index 00000000..d1549f79 --- /dev/null +++ b/source/scripts/common/types/timeline/timeline.type.d.ts @@ -0,0 +1,17 @@ +import { type TimelineType, type SkillType } from '~/common/enums/enums.js' + +type Timeline = { + id: string + date: string + desc: string + endDate: string + link: string + linkDesc: string + origin: string + originDesc: string + title: string + skillType: (typeof SkillType)[keyof typeof SkillType] + type: (typeof TimelineType)[keyof typeof TimelineType] +} + +export { type Timeline } diff --git a/source/scripts/common/types/toast/toast-message-payload.type.d.ts b/source/scripts/common/types/toast/toast-message-payload.type.d.ts new file mode 100644 index 00000000..9c80a86e --- /dev/null +++ b/source/scripts/common/types/toast/toast-message-payload.type.d.ts @@ -0,0 +1,7 @@ +type ToastMessagePayload = { + cb?: () => void + duration?: number + message: string +} + +export { type ToastMessagePayload } diff --git a/source/scripts/common/types/toast/toast.d.ts b/source/scripts/common/types/toast/toast.d.ts new file mode 100644 index 00000000..8078e096 --- /dev/null +++ b/source/scripts/common/types/toast/toast.d.ts @@ -0,0 +1 @@ +export { type ToastMessagePayload } from './toast-message-payload.type.js' diff --git a/source/scripts/components/common/loader/helpers/get-loader-element/get-loader-element.helper.js b/source/scripts/components/common/loader/helpers/get-loader-element/get-loader-element.helper.js index 79aa302e..78c96638 100644 --- a/source/scripts/components/common/loader/helpers/get-loader-element/get-loader-element.helper.js +++ b/source/scripts/components/common/loader/helpers/get-loader-element/get-loader-element.helper.js @@ -1,5 +1,6 @@ import { createElement } from '~/helpers/helpers.js' +/** @returns {HTMLElement} */ let getLoaderElement = () => { return createElement(` diff --git a/source/scripts/components/common/loader/loader.js b/source/scripts/components/common/loader/loader.js index 7967877d..22a80bd7 100644 --- a/source/scripts/components/common/loader/loader.js +++ b/source/scripts/components/common/loader/loader.js @@ -1,20 +1,30 @@ import { getLoaderElement } from './helpers/helpers.js' class Loader { + /** + * @param {{ + * containerNode: HTMLElement + * }} constructor + */ constructor({ containerNode }) { this._containerNode = containerNode + /** @type {HTMLElement | undefined} */ this._loaderNode = undefined } + /** @returns {void} */ init() { this._loaderNode = getLoaderElement() this._containerNode.prepend(this._loaderNode) } + /** @returns {void} */ remove() { - this._loaderNode.remove() + let loaderNode = /** @type {HTMLElement} */ (this._loaderNode) + + loaderNode.remove() } } diff --git a/source/scripts/components/common/toast/common/constants.js b/source/scripts/components/common/toast/common/constants.js index 4f5b9f25..b799d6da 100644 --- a/source/scripts/components/common/toast/common/constants.js +++ b/source/scripts/components/common/toast/common/constants.js @@ -1,7 +1,7 @@ -let TOAST_DEFAULT_DURATION = 3000 +let TOAST_DEFAULT_DURATION = /** @type {const} */ (3000) -let CLEAN_MESSAGE_DELAY = 100 +let CLEAN_MESSAGE_DELAY = /** @type {const} */ (100) -let TOAST_SHOW_CLASS_NAME = `toast--show` +let TOAST_SHOW_CLASS_NAME = /** @type {const} */ (`toast--show`) export { CLEAN_MESSAGE_DELAY, TOAST_DEFAULT_DURATION, TOAST_SHOW_CLASS_NAME } diff --git a/source/scripts/components/common/toast/toast.js b/source/scripts/components/common/toast/toast.js index 949f6322..35e5d679 100644 --- a/source/scripts/components/common/toast/toast.js +++ b/source/scripts/components/common/toast/toast.js @@ -6,32 +6,45 @@ import { TOAST_SHOW_CLASS_NAME, } from './common/constants.js' +/** @typedef {import('~/common/types/toast/toast').ToastMessagePayload} ToastMessagePayload */ + class Toast { constructor() { + /** @type {HTMLElement | undefined} */ this._toastNode = undefined + /** @type {ToastMessagePayload[]} */ this._messages = [] + /** @type {boolean} */ this._isShowingMessage = false } + /** + * @param {ToastMessagePayload} messagePayload + * @returns {Promise} + */ async _displayToastMessage(messagePayload) { + let toastNode = /** @type {HTMLElement} */ (this._toastNode) let { cb, duration = TOAST_DEFAULT_DURATION, message } = messagePayload - this._toastNode.classList.add(TOAST_SHOW_CLASS_NAME) - this._toastNode.textContent = message + toastNode.classList.add(TOAST_SHOW_CLASS_NAME) + toastNode.textContent = message await setAsyncTimeout(() => { - this._toastNode.classList.remove(TOAST_SHOW_CLASS_NAME) + toastNode.classList.remove(TOAST_SHOW_CLASS_NAME) }, duration) await setAsyncTimeout(() => { - this._toastNode.textContent = `` + toastNode.textContent = `` }, CLEAN_MESSAGE_DELAY) cb?.() } + /** @returns {Promise} */ async _initShowingMessages() { - let messagePayload = this._messages.shift() + let messagePayload = /** @type {ToastMessagePayload} */ ( + this._messages.shift() + ) this._isShowingMessage = true @@ -48,10 +61,17 @@ class Toast { this._isShowingMessage = false } + /** @returns {void} */ init() { - this._toastNode = document.querySelector(`.toast`) + this._toastNode = /** @type {HTMLElement} */ ( + document.querySelector(`.toast`) + ) } + /** + * @param {ToastMessagePayload} message + * @returns {void} + */ pushMessage(message) { this._messages.push(message) diff --git a/source/scripts/components/form/form.js b/source/scripts/components/form/form.js index 845a6387..0836ee56 100644 --- a/source/scripts/components/form/form.js +++ b/source/scripts/components/form/form.js @@ -3,45 +3,74 @@ import { fillSelectOptions, getFormValues } from '~/helpers/helpers.js' import { getTransformedTimeline } from './helpers/helpers.js' +/** @typedef {import('~/services/timeline-api/timeline-api.service.js').TimelineApi} TimelineApi */ +/** @typedef {import('~/common/types/timeline/timeline').TimelineCreatePayload} TimelineCreatePayload */ + let skillTypeOptions = Object.values(SkillType) let TimelineTypeOptions = Object.values(TimelineType) class Form { + /** + * @param {{ + * timelineApi: TimelineApi + * }} constructor + */ constructor({ timelineApi }) { this._timelineApi = timelineApi + /** @type {HTMLFormElement | undefined} */ this._formNode = undefined this._handleSubmit = this._handleSubmit.bind(this) } + /** + * @param {SubmitEvent} event_ + * @returns {Promise} + */ async _handleSubmit(event_) { event_.preventDefault() - let formValues = getFormValues(this._formNode) + let formNode = /** @type {HTMLFormElement} */ (this._formNode) + let formValues = /** @type {TimelineCreatePayload} */ ( + getFormValues(formNode) + ) await this._timelineApi.saveTimeline(getTransformedTimeline(formValues)) - this._formNode.reset() + formNode.reset() } + /** @returns {void} */ _initListeners() { - this._formNode.addEventListener(`submit`, this._handleSubmit) + let formNode = /** @type {HTMLFormElement} */ (this._formNode) + + formNode.addEventListener(`submit`, this._handleSubmit) } + /** @returns {void} */ _initSelects() { + let formNode = /** @type {HTMLFormElement} */ (this._formNode) + fillSelectOptions( - this._formNode.querySelector(`select[name="skillType"]`), + /** @type {HTMLSelectElement} */ ( + formNode.querySelector(`select[name="skillType"]`) + ), skillTypeOptions, ) fillSelectOptions( - this._formNode.querySelector(`select[name="type"]`), + /** @type {HTMLSelectElement} */ ( + formNode.querySelector(`select[name="type"]`) + ), TimelineTypeOptions, ) } + /** @returns {void} */ init() { - this._formNode = document.querySelector(`form[name="timeline"]`) + this._formNode = /** @type {HTMLFormElement} */ ( + document.querySelector(`form[name="timeline"]`) + ) this._initSelects() diff --git a/source/scripts/components/form/helpers/get-transformed-timeline/get-transformed-timeline.helper.js b/source/scripts/components/form/helpers/get-transformed-timeline/get-transformed-timeline.helper.js index e08f5307..3d3e3be0 100644 --- a/source/scripts/components/form/helpers/get-transformed-timeline/get-transformed-timeline.helper.js +++ b/source/scripts/components/form/helpers/get-transformed-timeline/get-transformed-timeline.helper.js @@ -1,11 +1,21 @@ +/** @typedef {import('~/common/types/timeline/timeline').TimelineCreatePayload} TimelineCreatePayload */ + +/** + * @param {TimelineCreatePayload} timeline + * @returns {TimelineCreatePayload} + */ let getTransformedTimeline = (timeline) => { - let transformedTimeline = { ...timeline } + let transformedTimeline = /** @type {Record} */ ({ + ...timeline, + }) - for (let key of Object.keys(transformedTimeline)) { + for (let key of /** @type {(keyof TimelineCreatePayload)[]} */ ( + Object.keys(timeline) + )) { transformedTimeline[key] = transformedTimeline[key] ?? `` } - return transformedTimeline + return /** @type {TimelineCreatePayload} */ (transformedTimeline) } export { getTransformedTimeline } diff --git a/source/scripts/components/home/components/easter-egg/common/constants.js b/source/scripts/components/home/components/easter-egg/common/constants.js index 136973be..abbc7826 100644 --- a/source/scripts/components/home/components/easter-egg/common/constants.js +++ b/source/scripts/components/home/components/easter-egg/common/constants.js @@ -1,7 +1,7 @@ -let SOUND_SRC = `/sounds/haddaway-whatislove.mp3` +let SOUND_SRC = /** @type {const} */ (`/sounds/haddaway-whatislove.mp3`) -let RESIZE_DELAY = 400 +let RESIZE_DELAY = /** @type {const} */ (400) -let NOTIFICATION_DELAY = 5000 +let NOTIFICATION_DELAY = /** @type {const} */ (5000) export { NOTIFICATION_DELAY, RESIZE_DELAY, SOUND_SRC } diff --git a/source/scripts/components/home/components/easter-egg/easter-egg.js b/source/scripts/components/home/components/easter-egg/easter-egg.js index 1e0002f7..185acb69 100644 --- a/source/scripts/components/home/components/easter-egg/easter-egg.js +++ b/source/scripts/components/home/components/easter-egg/easter-egg.js @@ -12,14 +12,28 @@ import { } from './common/constants.js' import { getNodeRandomCoords, getPlayerElement } from './helpers/helpers.js' +/** @typedef {import('~/common/types/toast/toast').ToastMessagePayload} ToastMessagePayload */ +/** @typedef {import('~/common/types/settings/settings').SettingButtonPayload} SettingButtonPayload */ +/** @typedef {typeof import('~/common/enums/enums.js').SettingName} SettingName */ + class EasterEgg { + /** + * @param {{ + * onNotificationAdd: (payload: ToastMessagePayload) => void + * onSettingBtnAppend: (payload: SettingButtonPayload) => HTMLButtonElement + * }} constructor + */ + constructor({ onNotificationAdd, onSettingBtnAppend }) { - this._onSettingBtnAppend = onSettingBtnAppend this._onNotificationAdd = onNotificationAdd + this._onSettingBtnAppend = onSettingBtnAppend - this._easterEggContainer = undefined - this._easterEggBtn = undefined - this._audio = undefined + /** @type {HTMLElement | undefined} */ + this._easterEggContainerNode = undefined + /** @type {HTMLButtonElement | undefined} */ + this._easterEggButtonNode = undefined + /** @type {HTMLAudioElement | undefined} */ + this._audioNode = undefined this._handleEasterEggClick = this._handleEasterEggClick.bind(this) this._handleSettingBtnClick = this._handleSettingBtnClick.bind(this) @@ -29,8 +43,14 @@ class EasterEgg { ) } + /** @returns {void} */ _handleEasterEggClick() { + let easterEggContainerNode = /** @type {HTMLElement} */ ( + this._easterEggContainerNode + ) + this._onNotificationAdd({ + /** @returns {void} */ cb: () => { let buttonNode = this._onSettingBtnAppend({ isDefaultChecked: true, @@ -49,43 +69,71 @@ class EasterEgg { this._removeListeners() - this._easterEggContainer.remove() + easterEggContainerNode.remove() } - _handleSettingBtnClick({ isChecked }) { - isChecked ? this._audio.play() : this._audio.pause() + /** + * @param {(typeof SettingName)[keyof typeof SettingName]} _name + * @param {boolean} isChecked + * @returns {void} + */ + _handleSettingBtnClick(_name, isChecked) { + let audioNode = /** @type {HTMLAudioElement} */ (this._audioNode) + + isChecked ? audioNode.play() : audioNode.pause() } + /** @returns {void} */ _handleWindowResize() { this._setRandomPosition() } + /** @returns {void} */ _initListeners() { - this._easterEggBtn.addEventListener(`click`, this._handleEasterEggClick) - window.addEventListener(`resize`, this._handleWindowResize) + let easterEggButtonNode = /** @type {HTMLElement} */ ( + this._easterEggButtonNode + ) + + easterEggButtonNode.addEventListener(`click`, this._handleEasterEggClick) + globalThis.addEventListener(`resize`, this._handleWindowResize) } + /** @returns {void} */ _removeListeners() { - this._easterEggBtn.removeEventListener(`click`, this._handleEasterEggClick) - window.removeEventListener(`resize`, this._handleWindowResize) + let easterEggButtonNode = /** @type {HTMLElement} */ ( + this._easterEggButtonNode + ) + + easterEggButtonNode.removeEventListener(`click`, this._handleEasterEggClick) + globalThis.removeEventListener(`resize`, this._handleWindowResize) } + /** @returns {void} */ _renderPlayer() { - this._audio = getPlayerElement(SOUND_SRC) + this._audioNode = getPlayerElement(SOUND_SRC) - document.body.append(this._audio) + document.body.append(this._audioNode) } + /** @returns {void} */ _setRandomPosition() { - let { x, y } = getNodeRandomCoords(this._easterEggContainer) + let easterEggContainerNode = /** @type {HTMLElement} */ ( + this._easterEggContainerNode + ) + let { x, y } = getNodeRandomCoords(easterEggContainerNode) - this._easterEggContainer.style.top = `${y}px` - this._easterEggContainer.style.left = `${x}px` + easterEggContainerNode.style.top = `${y}px` + easterEggContainerNode.style.left = `${x}px` } + /** @returns {void} */ init() { - this._easterEggContainer = document.querySelector(`.not-easter-egg`) - this._easterEggBtn = document.querySelector(`.not-easter-egg__button`) + this._easterEggContainerNode = /** @type {HTMLElement} */ ( + document.querySelector(`.not-easter-egg`) + ) + this._easterEggButtonNode = /** @type {HTMLButtonElement} */ ( + document.querySelector(`.not-easter-egg__button`) + ) this._setRandomPosition() diff --git a/source/scripts/components/home/components/easter-egg/helpers/get-node-random-coords/get-node-random-coords.helper.js b/source/scripts/components/home/components/easter-egg/helpers/get-node-random-coords/get-node-random-coords.helper.js index a5506fcd..4276dbef 100644 --- a/source/scripts/components/home/components/easter-egg/helpers/get-node-random-coords/get-node-random-coords.helper.js +++ b/source/scripts/components/home/components/easter-egg/helpers/get-node-random-coords/get-node-random-coords.helper.js @@ -1,7 +1,14 @@ import { getRandomNumber } from '~/helpers/helpers.js' -let MIN_COORD_NUMBER = 0 +let MIN_COORD_NUMBER = /** @type {const} */ (0) +/** + * @param {HTMLElement} node + * @returns {{ + * x: number + * y: number + * }} + */ let getNodeRandomCoords = (node) => { let y = getRandomNumber( MIN_COORD_NUMBER, diff --git a/source/scripts/components/home/components/easter-egg/helpers/get-player-element/get-player-element.helper.js b/source/scripts/components/home/components/easter-egg/helpers/get-player-element/get-player-element.helper.js index c38b6c91..bfe13127 100644 --- a/source/scripts/components/home/components/easter-egg/helpers/get-player-element/get-player-element.helper.js +++ b/source/scripts/components/home/components/easter-egg/helpers/get-player-element/get-player-element.helper.js @@ -1,9 +1,15 @@ import { createElement } from '~/helpers/helpers.js' +/** + * @param {string} source + * @returns {HTMLAudioElement} + */ let getPlayerElement = (source) => { - return createElement(` + return /** @type {HTMLAudioElement} */ ( + createElement(`
  • diff --git a/source/scripts/components/home/components/settings/settings.js b/source/scripts/components/home/components/settings/settings.js index a2281a38..9501e586 100644 --- a/source/scripts/components/home/components/settings/settings.js +++ b/source/scripts/components/home/components/settings/settings.js @@ -4,18 +4,32 @@ import { getCustomAttributeName } from '~/helpers/helpers.js' import { Control, Switch } from './components/components.js' import { getSettingItemElement } from './helpers/helpers.js' -let RESULT_VALUE = `auto` +let RESULT_VALUE = /** @type {const} */ (`auto`) + +/** @typedef {import('~/common/types/settings/settings').SettingButtonPayload} SettingButtonPayload */ +/** @typedef {import('~/services/storage/storage.service').Storage} Storage */ class Settings { + /** + * @param {{ + * storage: Storage + * }} constructor + */ constructor({ storage }) { this._storage = storage + /** @type {HTMLElement | undefined} */ this._settingListNode = undefined this._handleControlChange = this._handleControlChange.bind(this) } - _handleControlChange({ name, value }) { + /** + * @param {(typeof SettingName)[keyof typeof SettingName]} name + * @param {string} value + * @returns {void} + */ + _handleControlChange(name, value) { if (value === RESULT_VALUE) { this._removeSettingAttr(name) @@ -27,6 +41,10 @@ class Settings { this._storage.setItem(name, value) } + /** + * @param {(typeof SettingName)[keyof typeof SettingName]} name + * @returns {void} + */ _initControl(name) { let defaultValue = this._setInitialSettingAttr(name) @@ -37,31 +55,49 @@ class Settings { }).init(`.settings__item-fieldset--${name}`) } + /** + * @param {(typeof SettingName)[keyof typeof SettingName]} name + * @returns {void} + */ _removeSettingAttr(name) { document.documentElement.removeAttribute(getCustomAttributeName(name)) } + /** + * @param {(typeof SettingName)[keyof typeof SettingName]} name + * @returns {string | null} + */ _setInitialSettingAttr(name) { let value = this._storage.getItem(name) let hasValue = Boolean(value) if (hasValue) { - this._setSettingAttr(name, value) + this._setSettingAttr(name, /** @type {string} */ (value)) } return value } + /** + * @param {(typeof SettingName)[keyof typeof SettingName]} name + * @param {string} value + * @returns {void} + */ _setSettingAttr(name, value) { document.documentElement.setAttribute(getCustomAttributeName(name), value) } + /** + * @param {SettingButtonPayload} settings + * @returns {HTMLButtonElement} + */ appendNewBtn(settings) { let { isDefaultChecked, label, name, onClick } = settings let newSettingItemNode = getSettingItemElement({ label, name }) + let settingListNode = /** @type {HTMLElement} */ (this._settingListNode) - this._settingListNode.prepend(newSettingItemNode) + settingListNode.prepend(newSettingItemNode) return new Switch({ isDefaultChecked, @@ -70,8 +106,11 @@ class Settings { }).init(`.settings__item-switch--${name}`) } + /** @returns {void} */ init() { - this._settingListNode = document.querySelector(`.settings`) + this._settingListNode = /** @type {HTMLElement} */ ( + document.querySelector(`.settings`) + ) this._initControl(SettingName.THEME) this._initControl(SettingName.MOTION) diff --git a/source/scripts/components/home/components/timeline/components/timeline-form/common/constants.js b/source/scripts/components/home/components/timeline/components/timeline-form/common/constants.js index eb2a379e..563ab3c7 100644 --- a/source/scripts/components/home/components/timeline/components/timeline-form/common/constants.js +++ b/source/scripts/components/home/components/timeline/components/timeline-form/common/constants.js @@ -1,3 +1,3 @@ -let DEBOUNCE_DELAY = 200 +let DEBOUNCE_DELAY = /** @type {const} */ (200) export { DEBOUNCE_DELAY } diff --git a/source/scripts/components/home/components/timeline/components/timeline-form/timeline-form.js b/source/scripts/components/home/components/timeline/components/timeline-form/timeline-form.js index 974a9113..ccb65c8a 100644 --- a/source/scripts/components/home/components/timeline/components/timeline-form/timeline-form.js +++ b/source/scripts/components/home/components/timeline/components/timeline-form/timeline-form.js @@ -2,34 +2,50 @@ import { debounce, getFormValues } from '~/helpers/helpers.js' import { DEBOUNCE_DELAY } from './common/constants.js' +/** @typedef {import('~/common/types/timeline/timeline').TimelineFilter} TimelineFilter */ + class TimelineForm { + /** + * @param {{ + * onChange: (formValues: TimelineFilter) => void + * }} constructor + */ constructor({ onChange }) { this._onChange = onChange + /** @type {HTMLFormElement | undefined} */ this._formNode = undefined this._handleFormChange = this._handleFormChange.bind(this) } + /** @returns {void} */ _handleFormChange() { this._onChange(this.formValues) } + /** @returns {void} */ _initListeners() { - this._formNode.addEventListener( + let formNode = /** @type {HTMLFormElement} */ (this._formNode) + + formNode.addEventListener( `change`, debounce(this._handleFormChange, DEBOUNCE_DELAY), ) } + /** @returns {void} */ init() { - this._formNode = document.forms.timeline + this._formNode = /** @type {HTMLFormElement} */ ( + document.querySelector(`.timeline__filter`) + ) this._initListeners() } + /** @returns {TimelineFilter} */ get formValues() { - return getFormValues(this._formNode) + return getFormValues(/** @type {HTMLFormElement} */ (this._formNode)) } } diff --git a/source/scripts/components/home/components/timeline/components/timeline-list/helpers/get-timeline-template/get-timeline-template.helper.js b/source/scripts/components/home/components/timeline/components/timeline-list/helpers/get-timeline-template/get-timeline-template.helper.js index 490ba428..31d78d18 100644 --- a/source/scripts/components/home/components/timeline/components/timeline-list/helpers/get-timeline-template/get-timeline-template.helper.js +++ b/source/scripts/components/home/components/timeline/components/timeline-list/helpers/get-timeline-template/get-timeline-template.helper.js @@ -1,5 +1,11 @@ import { getFormattedDate, getStringWitCheck } from '~/helpers/helpers.js' +/** @typedef {import('~/common/types/timeline/timeline').Timeline} Timeline */ + +/** + * @param {Timeline} timeline + * @returns {string} + */ let getTimelineTemplate = (timeline) => { let { date, desc, endDate, link, linkDesc, origin, originDesc, title, type } = timeline diff --git a/source/scripts/components/home/components/timeline/components/timeline-list/helpers/get-timeline-templates/get-timeline-templates.helper.js b/source/scripts/components/home/components/timeline/components/timeline-list/helpers/get-timeline-templates/get-timeline-templates.helper.js index 42c40b42..31a3f5e5 100644 --- a/source/scripts/components/home/components/timeline/components/timeline-list/helpers/get-timeline-templates/get-timeline-templates.helper.js +++ b/source/scripts/components/home/components/timeline/components/timeline-list/helpers/get-timeline-templates/get-timeline-templates.helper.js @@ -1,5 +1,11 @@ import { getTimelineTemplate } from '../get-timeline-template/get-timeline-template.helper.js' +/** @typedef {import('~/common/types/timeline/timeline').Timeline} Timeline */ + +/** + * @param {Timeline[]} timelines + * @returns {string} + */ let getTimelineTemplates = (timelines) => { let timelineTemplates = `` diff --git a/source/scripts/components/home/components/timeline/components/timeline-list/timeline-list.js b/source/scripts/components/home/components/timeline/components/timeline-list/timeline-list.js index a6209d12..657b9dcc 100644 --- a/source/scripts/components/home/components/timeline/components/timeline-list/timeline-list.js +++ b/source/scripts/components/home/components/timeline/components/timeline-list/timeline-list.js @@ -1,16 +1,28 @@ import { getTimelineTemplates } from './helpers/helpers.js' +/** @typedef {import('~/common/types/timeline/timeline').Timeline} Timeline */ + class TimelineList { constructor() { + /** @type {HTMLElement | undefined} */ this._listNode = undefined } + /** @returns {void} */ init() { - this._listNode = document.querySelector(`.timeline__list`) + this._listNode = /** @type {HTMLElement} */ ( + document.querySelector(`.timeline__list`) + ) } + /** + * @param {Timeline[]} timelines + * @returns {void} + */ renderTimelines(timelines) { - this._listNode.innerHTML = getTimelineTemplates(timelines) + let listNode = /** @type {HTMLElement} */ (this._listNode) + + listNode.innerHTML = getTimelineTemplates(timelines) } } diff --git a/source/scripts/components/home/components/timeline/helpers/check-is-timeline-suit/check-is-timeline-suit.helper.js b/source/scripts/components/home/components/timeline/helpers/check-is-timeline-suit/check-is-timeline-suit.helper.js index 5e5caf6b..3b068662 100644 --- a/source/scripts/components/home/components/timeline/helpers/check-is-timeline-suit/check-is-timeline-suit.helper.js +++ b/source/scripts/components/home/components/timeline/helpers/check-is-timeline-suit/check-is-timeline-suit.helper.js @@ -1,3 +1,11 @@ +/** @typedef {import('~/common/types/timeline/timeline').Timeline} Timeline */ +/** @typedef {import('~/common/types/timeline/timeline').TimelineFilter} TimelineFilter */ + +/** + * @param {Timeline} timelineItem + * @param {TimelineFilter} filterValues + * @returns {boolean} + */ let checkIsTimelineSuit = (timelineItem, filterValues) => { let hasSkillType = filterValues.skillTypes[timelineItem.skillType] let hasType = filterValues.types[timelineItem.type] diff --git a/source/scripts/components/home/components/timeline/helpers/get-suit-timelines/get-suit-timelines.helper.js b/source/scripts/components/home/components/timeline/helpers/get-suit-timelines/get-suit-timelines.helper.js index 33d386b4..f5ac68ae 100644 --- a/source/scripts/components/home/components/timeline/helpers/get-suit-timelines/get-suit-timelines.helper.js +++ b/source/scripts/components/home/components/timeline/helpers/get-suit-timelines/get-suit-timelines.helper.js @@ -1,5 +1,13 @@ import { checkIsTimelineSuit } from '../check-is-timeline-suit/check-is-timeline-suit.helper.js' +/** @typedef {import('~/common/types/timeline/timeline').Timeline} Timeline */ +/** @typedef {import('~/common/types/timeline/timeline').TimelineFilter} TimelineFilter */ + +/** + * @param {Timeline[]} timelines + * @param {TimelineFilter} filterValues + * @returns {Timeline[]} + */ let getSuitTimelines = (timelines, filterValues) => { return timelines.filter((timeline) => { return checkIsTimelineSuit(timeline, filterValues) diff --git a/source/scripts/components/home/components/timeline/timeline.js b/source/scripts/components/home/components/timeline/timeline.js index 2a12bcc4..8dbbbd6f 100644 --- a/source/scripts/components/home/components/timeline/timeline.js +++ b/source/scripts/components/home/components/timeline/timeline.js @@ -4,12 +4,24 @@ import { checkIsBeforeElement } from '~/helpers/helpers.js' import { TimelineForm, TimelineList } from './components/components.js' import { getSuitTimelines } from './helpers/helpers.js' +/** @typedef {import('~/common/types/timeline/timeline').Timeline} TTimeline */ +/** @typedef {import('~/common/types/timeline/timeline').TimelineFilter} TimelineFilter */ +/** @typedef {import('~/services/timeline-api/timeline-api.service.js').TimelineApi} TimelineApi */ + class Timeline { + /** + * @param {{ + * timelineApi: TimelineApi + * }} constructor + */ constructor({ timelineApi }) { this._timelineApi = timelineApi + /** @type {HTMLElement | undefined} */ this._containerNode = undefined + /** @type {TTimeline[]} */ this._timelines = [] + /** @type {boolean} */ this._isLoading = false this._handleFormChange = this._handleFormChange.bind(this) @@ -19,22 +31,30 @@ class Timeline { onChange: this._handleFormChange, }) this._loaderComponent = new Loader({ - containerNode: document.querySelector(`.timeline__list-wrapper`), + containerNode: /** @type {HTMLElement} */ ( + document.querySelector(`.timeline__list-wrapper`) + ), }) this._timelineListComponent = new TimelineList() } + /** @returns {Promise} */ async _fetchTimelines() { this._timelines = await this._timelineApi.getTimelines() } + /** + * @param {TimelineFilter} formValues + * @returns {void} + */ _handleFormChange(formValues) { this._renderTimelines(formValues) } + /** @returns {Promise} */ async _handleTimelineShow() { let shouldLoadTimelines = checkIsBeforeElement( - this._containerNode.offsetTop, + /** @type {HTMLElement} */ (this._containerNode).offsetTop, ) if (shouldLoadTimelines && !this._isLoading) { @@ -50,18 +70,26 @@ class Timeline { } } + /** @returns {void} */ _initListeners() { document.addEventListener(`scroll`, this._handleTimelineShow) } + /** + * @param {TimelineFilter} formValues + * @returns {void} + */ _renderTimelines(formValues) { let timelines = getSuitTimelines(this._timelines, formValues) this._timelineListComponent.renderTimelines(timelines) } + /** @returns {void} */ init() { - this._containerNode = document.querySelector(`.timeline`) + this._containerNode = /** @type {HTMLElement} */ ( + document.querySelector(`.timeline`) + ) this._loaderComponent.init() this._timelineFormComponent.init() diff --git a/source/scripts/components/home/home.js b/source/scripts/components/home/home.js index 2c3a7054..829223ff 100644 --- a/source/scripts/components/home/home.js +++ b/source/scripts/components/home/home.js @@ -7,7 +7,18 @@ import { Timeline, } from './components/components.js' +/** @typedef {import('~/common/types/settings/settings').SettingButtonPayload} SettingButtonPayload */ +/** @typedef {import('~/common/types/toast/toast').ToastMessagePayload} ToastMessagePayload */ +/** @typedef {import('~/services/storage/storage.service').Storage} Storage */ +/** @typedef {import('~/services/timeline-api/timeline-api.service').TimelineApi} TimelineApi */ + class Home { + /** + * @param {{ + * storage: Storage + * timelineApi: TimelineApi + * }} constructor + */ constructor({ storage, timelineApi }) { this._handleSettingBtnAppend = this._handleSettingBtnAppend.bind(this) this._handleNotificationAdd = this._handleNotificationAdd.bind(this) @@ -26,14 +37,23 @@ class Home { this._toastComponent = new Toast() } + /** + * @param {ToastMessagePayload} message + * @returns {void} + */ _handleNotificationAdd(message) { this._toastComponent.pushMessage(message) } + /** + * @param {SettingButtonPayload} settings + * @returns {HTMLButtonElement} + */ _handleSettingBtnAppend(settings) { return this._settingsComponent.appendNewBtn(settings) } + /** @returns {void} */ init() { this._toastComponent.init() this._settingsComponent.init() diff --git a/source/scripts/exceptions/http-error/http-error.exception.js b/source/scripts/exceptions/http-error/http-error.exception.js index 471cc780..40432523 100644 --- a/source/scripts/exceptions/http-error/http-error.exception.js +++ b/source/scripts/exceptions/http-error/http-error.exception.js @@ -5,6 +5,12 @@ import { } from '~/common/enums/enums.js' class HttpError extends Error { + /** + * @param {{ + * message?: (typeof ApiErrorMessage)[keyof typeof ApiErrorMessage] + * status?: (typeof HttpCode)[keyof typeof HttpCode] + * }} [properties] + */ constructor({ message = ApiErrorMessage.NETWORK_ERROR, status = HttpCode.INTERNAL_SERVER_ERROR, diff --git a/source/scripts/globals.d.ts b/source/scripts/globals.d.ts new file mode 100644 index 00000000..200414bf --- /dev/null +++ b/source/scripts/globals.d.ts @@ -0,0 +1,5 @@ +import { WhatisloveMath } from '~/services/whatislove-math/whatislove-math.service.js' + +declare global { + var WhatisloveMath: WhatisloveMath +} diff --git a/source/scripts/helpers/api/get-server-api-url/get-server-api-url.helper.js b/source/scripts/helpers/api/get-server-api-url/get-server-api-url.helper.js index 53058fcd..24cc9320 100644 --- a/source/scripts/helpers/api/get-server-api-url/get-server-api-url.helper.js +++ b/source/scripts/helpers/api/get-server-api-url/get-server-api-url.helper.js @@ -1,3 +1,11 @@ +/** + * @param {{ + * apiPrefix?: string + * host?: string + * port?: number + * }} params + * @returns {string} + */ let getServerApiUrl = ({ apiPrefix = ``, host = ``, port = 0 } = {}) => { return `${host}${port}${apiPrefix}` } diff --git a/source/scripts/helpers/boolean/check-is-before-element/check-is-before-element.helper.js b/source/scripts/helpers/boolean/check-is-before-element/check-is-before-element.helper.js index ea124603..2fe5fe19 100644 --- a/source/scripts/helpers/boolean/check-is-before-element/check-is-before-element.helper.js +++ b/source/scripts/helpers/boolean/check-is-before-element/check-is-before-element.helper.js @@ -1,5 +1,9 @@ +/** + * @param {number} elementOffset + * @returns {boolean} + */ let checkIsBeforeElement = (elementOffset) => { - let breakPoint = window.scrollY + document.documentElement.clientHeight + let breakPoint = globalThis.scrollY + document.documentElement.clientHeight return breakPoint >= elementOffset } diff --git a/source/scripts/helpers/boolean/check-is-one-of/check-is-one-of.helper.js b/source/scripts/helpers/boolean/check-is-one-of/check-is-one-of.helper.js index b52b8146..4dd6dd29 100644 --- a/source/scripts/helpers/boolean/check-is-one-of/check-is-one-of.helper.js +++ b/source/scripts/helpers/boolean/check-is-one-of/check-is-one-of.helper.js @@ -1,3 +1,9 @@ +/** + * @template T + * @param {T} checkItem + * @param {...T} checksItems + * @returns {boolean} + */ let checkIsOneOf = (checkItem, ...checksItems) => { return checksItems.includes(checkItem) } diff --git a/source/scripts/helpers/date/get-formatted-date/get-formatted-date.helper.js b/source/scripts/helpers/date/get-formatted-date/get-formatted-date.helper.js index 0d44a698..4810b656 100644 --- a/source/scripts/helpers/date/get-formatted-date/get-formatted-date.helper.js +++ b/source/scripts/helpers/date/get-formatted-date/get-formatted-date.helper.js @@ -1,3 +1,7 @@ +/** + * @param {string | null} date + * @returns {string} + */ let getFormattedDate = (date) => { if (!date) { return `` diff --git a/source/scripts/helpers/dom/create-element/create-element.helper.js b/source/scripts/helpers/dom/create-element/create-element.helper.js index 2ac64e95..82085ec4 100644 --- a/source/scripts/helpers/dom/create-element/create-element.helper.js +++ b/source/scripts/helpers/dom/create-element/create-element.helper.js @@ -1,9 +1,13 @@ +/** + * @param {string} template + * @returns {HTMLElement} + */ let createElement = (template) => { let newElement = document.createElement(`div`) newElement.innerHTML = template - return newElement.firstElementChild + return /** @type {HTMLElement} */ (newElement.firstElementChild) } export { createElement } diff --git a/source/scripts/helpers/dom/get-custom-attribute-name/get-custom-attribute-name.helper.js b/source/scripts/helpers/dom/get-custom-attribute-name/get-custom-attribute-name.helper.js index 73ed6e6d..0385de43 100644 --- a/source/scripts/helpers/dom/get-custom-attribute-name/get-custom-attribute-name.helper.js +++ b/source/scripts/helpers/dom/get-custom-attribute-name/get-custom-attribute-name.helper.js @@ -1,3 +1,7 @@ +/** + * @param {string} name + * @returns {string} + */ let getCustomAttributeName = (name) => { return `data-${name}` } diff --git a/source/scripts/helpers/dom/subscribe-focus-trap/subscribe-focus-trap.helper.js b/source/scripts/helpers/dom/subscribe-focus-trap/subscribe-focus-trap.helper.js index 1ae378f7..e9d1d851 100644 --- a/source/scripts/helpers/dom/subscribe-focus-trap/subscribe-focus-trap.helper.js +++ b/source/scripts/helpers/dom/subscribe-focus-trap/subscribe-focus-trap.helper.js @@ -1,9 +1,17 @@ import { KeyboardKey } from '~/common/enums/enums.js' +/** + * @param {HTMLElement[]} elements + * @returns {() => void} + */ let subscribeFocusTrap = (...elements) => { - let [firstNode] = elements - let lastNode = elements.at(-1) + let firstNode = /** @type {HTMLElement} */ (elements.at(0)) + let lastNode = /** @type {HTMLElement} */ (elements.at(-1)) + /** + * @param {KeyboardEvent} event_ + * @returns {void} + */ let onFirstElementFocus = (event_) => { if (event_.key === KeyboardKey.TAB && event_.shiftKey) { event_.preventDefault() @@ -12,6 +20,10 @@ let subscribeFocusTrap = (...elements) => { } } + /** + * @param {KeyboardEvent} event_ + * @returns {void} + */ let onLastElementFocus = (event_) => { if (event_.key === KeyboardKey.TAB && !event_.shiftKey) { event_.preventDefault() diff --git a/source/scripts/helpers/form/add-select-options/add-select-options.helper.js b/source/scripts/helpers/form/add-select-options/add-select-options.helper.js index 0feb5447..04f4bd86 100644 --- a/source/scripts/helpers/form/add-select-options/add-select-options.helper.js +++ b/source/scripts/helpers/form/add-select-options/add-select-options.helper.js @@ -1,3 +1,8 @@ +/** + * @param {HTMLSelectElement} selectNode + * @param {HTMLOptionElement[]} options + * @returns {void} + */ let addSelectOptions = (selectNode, options) => { for (let option of options) { selectNode.add(option) diff --git a/source/scripts/helpers/form/create-select-options/create-select-options.helper.js b/source/scripts/helpers/form/create-select-options/create-select-options.helper.js index 2809a025..ea50bc52 100644 --- a/source/scripts/helpers/form/create-select-options/create-select-options.helper.js +++ b/source/scripts/helpers/form/create-select-options/create-select-options.helper.js @@ -1,3 +1,7 @@ +/** + * @param {string[]} options + * @returns {HTMLOptionElement[]} + */ let createSelectOptions = (options) => { return options.map((option) => new Option(option, option)) } diff --git a/source/scripts/helpers/form/fill-select-options/fill-select-options.helper.js b/source/scripts/helpers/form/fill-select-options/fill-select-options.helper.js index 33482368..06fc880d 100644 --- a/source/scripts/helpers/form/fill-select-options/fill-select-options.helper.js +++ b/source/scripts/helpers/form/fill-select-options/fill-select-options.helper.js @@ -1,6 +1,11 @@ import { addSelectOptions } from '~/helpers/form/add-select-options/add-select-options.helper.js' import { createSelectOptions } from '~/helpers/form/create-select-options/create-select-options.helper.js' +/** + * @param {HTMLSelectElement} selectNode + * @param {string[]} options + * @returns {void} + */ let fillSelectOptions = (selectNode, options) => { let selectOptions = createSelectOptions(options) diff --git a/source/scripts/helpers/number/get-random-number/get-random-number.helper.js b/source/scripts/helpers/number/get-random-number/get-random-number.helper.js index 7b328058..a304489a 100644 --- a/source/scripts/helpers/number/get-random-number/get-random-number.helper.js +++ b/source/scripts/helpers/number/get-random-number/get-random-number.helper.js @@ -1,5 +1,10 @@ -let RANDOM_NUMBER_INCREMENT = 1 +let RANDOM_NUMBER_INCREMENT = /** @type {const} */ (1) +/** + * @param {number} min + * @param {number} max + * @returns {number} + */ let getRandomNumber = (min, max) => { return Math.floor(Math.random() * (max - min + RANDOM_NUMBER_INCREMENT)) + min } diff --git a/source/scripts/helpers/string/get-string-with-check/get-string-with-check.helper.js b/source/scripts/helpers/string/get-string-with-check/get-string-with-check.helper.js index 7f267486..63d9a387 100644 --- a/source/scripts/helpers/string/get-string-with-check/get-string-with-check.helper.js +++ b/source/scripts/helpers/string/get-string-with-check/get-string-with-check.helper.js @@ -1,3 +1,8 @@ +/** + * @param {unknown} checkValue + * @param {string} payload + * @returns {string} + */ let getStringWitCheck = (checkValue, payload) => { return checkValue ? payload : `` } diff --git a/source/scripts/helpers/timeout/debounce/debounce.helper.js b/source/scripts/helpers/timeout/debounce/debounce.helper.js index cdffb96c..c8d5c925 100644 --- a/source/scripts/helpers/timeout/debounce/debounce.helper.js +++ b/source/scripts/helpers/timeout/debounce/debounce.helper.js @@ -1,5 +1,10 @@ +/** + * @param {(...args: unknown[]) => unknown} callback + * @param {number} delay + * @returns {(...args: unknown[]) => unknown} + */ let debounce = (callback, delay) => { - let timeout + let /** @type {undefined | number} */ timeout return (...arguments_) => { clearTimeout(timeout) diff --git a/source/scripts/helpers/timeout/set-async-timeout/set-async-timeout.helper.js b/source/scripts/helpers/timeout/set-async-timeout/set-async-timeout.helper.js index 02769d8e..dd31843d 100644 --- a/source/scripts/helpers/timeout/set-async-timeout/set-async-timeout.helper.js +++ b/source/scripts/helpers/timeout/set-async-timeout/set-async-timeout.helper.js @@ -1,3 +1,8 @@ +/** + * @param {(...args: unknown[]) => unknown} callback + * @param {number} [timeout] + * @returns {Promise} + */ let setAsyncTimeout = (callback, timeout = 0) => { return new Promise((resolve) => { setTimeout(() => { diff --git a/source/scripts/index.js b/source/scripts/index.js index 22a0fcfe..8d9b3c84 100644 --- a/source/scripts/index.js +++ b/source/scripts/index.js @@ -6,10 +6,11 @@ let home = new Home({ timelineApi, }) +/** @returns {void} */ let init = () => { home.init() - window.WhatisloveMath = WhatisloveMath + globalThis.WhatisloveMath = WhatisloveMath } init() diff --git a/source/scripts/services/http/http.service.js b/source/scripts/services/http/http.service.js index f580bc4f..444e5ca7 100644 --- a/source/scripts/services/http/http.service.js +++ b/source/scripts/services/http/http.service.js @@ -1,26 +1,55 @@ -import { ContentType, HttpHeader, HttpMethod } from '~/common/enums/enums.js' +import { + ApiErrorMessage, + ContentType, + HttpHeader, + HttpMethod, +} from '~/common/enums/enums.js' import { HttpError } from '~/exceptions/exceptions.js' import { checkIsOneOf } from '~/helpers/helpers.js' class Http { + /** + * @param {Response} response + * @returns {Response | never} + * @throws {HttpError} + */ static checkStatus(response) { if (!response.ok) { throw new HttpError({ - message: response.statusText, + message: + /** @type {(typeof ApiErrorMessage)[keyof typeof ApiErrorMessage]} */ ( + response.statusText ?? ApiErrorMessage.NETWORK_ERROR + ), }) } return response } + /** + * @template T + * @param {Response} response + * @returns {Promise} + */ static parseJSON(response) { return response.json() } + /** + * @param {Error} error + * @returns {never} + * @throws {Error} + */ static throwError(error) { throw error } + /** + * @param {{ + * contentType?: (typeof ContentType)[keyof typeof ContentType] | undefined + * }} options + * @returns {Headers} + */ _getHeaders({ contentType }) { let headers = new Headers() @@ -31,6 +60,16 @@ class Http { return headers } + /** + * @template T + * @param {string} url + * @param {{ + * contentType?: (typeof ContentType)[keyof typeof ContentType] + * method?: (typeof HttpMethod)[keyof typeof HttpMethod] + * payload?: unknown + * }} [options] + * @returns {Promise} + */ load(url, options = {}) { let { contentType, method = HttpMethod.GET, payload } = options let headers = this._getHeaders({ @@ -39,13 +78,15 @@ class Http { let isJSON = checkIsOneOf(contentType, ContentType.JSON) return fetch(url, { - body: isJSON ? JSON.stringify(payload) : payload, + body: /** @type {BodyInit} */ ( + isJSON ? JSON.stringify(payload) : undefined + ), headers, method, }) - .then(Http.checkStatus) - .then(Http.parseJSON) - .catch(Http.throwError) + .then((response) => Http.checkStatus(response)) + .then((response) => Http.parseJSON(response)) + .catch((error) => Http.throwError(error)) } } diff --git a/source/scripts/services/storage/storage.service.js b/source/scripts/services/storage/storage.service.js index 2f269756..b94777ee 100644 --- a/source/scripts/services/storage/storage.service.js +++ b/source/scripts/services/storage/storage.service.js @@ -1,20 +1,43 @@ class Storage { + /** + * @param {{ + * storage: globalThis.Storage + * }} constructor + */ constructor({ storage }) { + /** + * @private + * @type {globalThis.Storage} + */ this._storage = storage } + /** @returns {void} */ clear() { return this._storage.clear() } + /** + * @param {string} key + * @returns {string | null} + */ getItem(key) { return this._storage.getItem(key) } + /** + * @param {string} key + * @returns {void} + */ removeItem(key) { return this._storage.removeItem(key) } + /** + * @param {string} key + * @param {string} value + * @returns {void} + */ setItem(key, value) { return this._storage.setItem(key, value) } diff --git a/source/scripts/services/timeline-api/timeline-api.service.js b/source/scripts/services/timeline-api/timeline-api.service.js index 42e78208..5eac2234 100644 --- a/source/scripts/services/timeline-api/timeline-api.service.js +++ b/source/scripts/services/timeline-api/timeline-api.service.js @@ -5,7 +5,18 @@ import { TimelineApiPath, } from '~/common/enums/enums.js' +/** @typedef {import('~/common/types/timeline/timeline').Timeline} Timeline */ +/** @typedef {import('~/services/http/http.service').Http} Http */ +/** @typedef {import('~/common/types/timeline/timeline')} TimelineCreatePayload */ + class TimelineApi { + /** + * @param {{ + * baseUrl: string + * filesApiPath: string + * http: Http + * }} config + */ constructor({ baseUrl, filesApiPath, http }) { this._http = http this._baseUrl = baseUrl @@ -13,20 +24,33 @@ class TimelineApi { this._apiPath = ApiPath.TIMELINE } + /** + * @param {string} path + * @returns {string} + */ _getApiUrl(path) { return `${this._baseUrl}${this._apiPath}${path}` } + /** + * @param {string} path + * @returns {string} + */ _getFileUrl(path) { return `${this._filesApiPath}${path}.json` } + /** @returns {Promise} */ getTimelines() { return this._http.load(this._getFileUrl(this._apiPath), { method: HttpMethod.GET, }) } + /** + * @param {TimelineCreatePayload} payload + * @returns {Promise} + */ saveTimeline(payload) { return this._http.load(this._getApiUrl(TimelineApiPath.ROOT), { contentType: ContentType.JSON, diff --git a/source/scripts/services/whatislove-math/whatislove-math.service.js b/source/scripts/services/whatislove-math/whatislove-math.service.js index d347ab4b..6e532bf2 100644 --- a/source/scripts/services/whatislove-math/whatislove-math.service.js +++ b/source/scripts/services/whatislove-math/whatislove-math.service.js @@ -1,20 +1,26 @@ class WhatisloveMath { - static BoostEntity = { + static BoostEntity = /** @type {const} */ ({ BOOK: `book`, COURSE: `course`, MENTEE: `mentee`, - } + }) - static NOTHING_TODO_VALUE = 10 + static NOTHING_TODO_VALUE = /** @type {const} */ (10) - static boostEntityToProfessionalValue = { + static boostEntityToProfessionalValue = /** @type {const} */ ({ [WhatisloveMath.BoostEntity.BOOK]: 1, [WhatisloveMath.BoostEntity.COURSE]: 3, [WhatisloveMath.BoostEntity.MENTEE]: 10, - } + }) constructor() {} + /** + * @param {number} initialValue + * @param {(typeof WhatisloveMath.BoostEntity)[keyof typeof WhatisloveMath.BoostEntity][]} boostEntities + * @returns {number} + * @throws {SyntaxError} + */ static calculateProfessionalLevel(initialValue = 0, ...boostEntities) { let { BoostEntity, NOTHING_TODO_VALUE, boostEntityToProfessionalValue } = WhatisloveMath @@ -44,6 +50,11 @@ class WhatisloveMath { return professionalLevel } + /** + * @param {(typeof WhatisloveMath.BoostEntity)[keyof typeof WhatisloveMath.BoostEntity]} boostEntity + * @param {number} count + * @returns {WhatisloveMath.BoostEntity[]} + */ static multiplyBoostEntities(boostEntity, count = 0) { return Array.from({ length: count }).fill(boostEntity) } diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..76dae753 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,22 @@ +{ + "include": ["**/*.js"], + "exclude": ["node_modules", "build"], + "compilerOptions": { + "baseUrl": ".", + "paths": { + "~/*": ["source/scripts/*"] + }, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "types": ["./source/scripts/globals.d.ts"], + "esModuleInterop": true, + "moduleResolution": "Bundler", + "target": "ESNext", + "module": "ESNext", + "allowJs": true, + "checkJs": true, + "strict": true, + "noUncheckedIndexedAccess": true, + "exactOptionalPropertyTypes": true, + "noPropertyAccessFromIndexSignature": true + } +}