From 46b6b831b1a7e96c4f47874ec470de2915ccb11c Mon Sep 17 00:00:00 2001 From: Richard Tibbles Date: Fri, 24 Apr 2026 17:51:46 -0700 Subject: [PATCH 01/11] Add eslint-plugin-jsdoc to kolibri-format, bump to 2.4.0 Registers eslint-plugin-jsdoc with flat/recommended-error as the base, plus opt-in rules targeting .js and .vue files. Adds `affects` to definedTags so the existing `@affects` convention used in Kolibri composables for documenting outer-state side-effects keeps validating. Version bumped to 2.4.0 (skipping 2.3.0 to avoid collision with the upstream import-x PR). Co-Authored-By: Claude Opus 4.7 (1M context) --- packages/kolibri-format/eslint.config.mjs | 21 +++ packages/kolibri-format/package.json | 3 +- pnpm-lock.yaml | 184 ++++++++++++++++++++-- 3 files changed, 191 insertions(+), 17 deletions(-) diff --git a/packages/kolibri-format/eslint.config.mjs b/packages/kolibri-format/eslint.config.mjs index af6d568a408..b72e54179f2 100644 --- a/packages/kolibri-format/eslint.config.mjs +++ b/packages/kolibri-format/eslint.config.mjs @@ -3,6 +3,7 @@ import eslintConfigPrettier from 'eslint-config-prettier'; import pluginImportX from 'eslint-plugin-import-x'; import pluginJest from 'eslint-plugin-jest'; import pluginJestDom from 'eslint-plugin-jest-dom'; +import jsdoc from 'eslint-plugin-jsdoc'; import pluginKolibri from 'eslint-plugin-kolibri'; import pluginN from 'eslint-plugin-n'; import pluginSmallImport from 'eslint-plugin-small-import'; @@ -11,6 +12,7 @@ import globals from 'globals'; const OFF = 0; const ERROR = 2; +const jsdocRecommended = jsdoc.configs['flat/recommended-error']; export default [ js.configs.recommended, @@ -19,6 +21,25 @@ export default [ pluginImportX.flatConfigs.warnings, pluginJestDom.configs['flat/recommended'], eslintConfigPrettier, + // JSDoc linting for .js and .vue files + { + ...jsdocRecommended, + files: ['**/*.js', '**/*.vue'], + rules: { + ...jsdocRecommended.rules, + 'jsdoc/require-jsdoc': OFF, + 'jsdoc/reject-function-type': OFF, + 'jsdoc/no-blank-blocks': ERROR, + 'jsdoc/no-blank-block-descriptions': ERROR, + 'jsdoc/informative-docs': ERROR, + 'jsdoc/sort-tags': ERROR, + 'jsdoc/require-description': ERROR, + 'jsdoc/require-hyphen-before-param-description': ERROR, + 'jsdoc/require-throws': ERROR, + // `@affects` documents side-effects on outer state; used widely in Kolibri composables. + 'jsdoc/check-tag-names': [ERROR, { definedTags: ['affects'] }], + }, + }, { plugins: { kolibri: pluginKolibri, diff --git a/packages/kolibri-format/package.json b/packages/kolibri-format/package.json index 67bfa00b37e..87e1f3cca05 100644 --- a/packages/kolibri-format/package.json +++ b/packages/kolibri-format/package.json @@ -1,6 +1,6 @@ { "name": "kolibri-format", - "version": "2.3.0", + "version": "2.4.0", "description": "A module for formatting frontend code to Kolibri standards", "repository": { "type": "git", @@ -23,6 +23,7 @@ "eslint-plugin-import-x": "^4.0.0", "eslint-plugin-jest": "^29.15.1", "eslint-plugin-jest-dom": "^5.5.0", + "eslint-plugin-jsdoc": "^62.9.0", "eslint-plugin-kolibri": "workspace:*", "eslint-plugin-n": "^17.24.0", "eslint-plugin-small-import": "^1.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e0e538e4bb2..05dc611eb92 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1207,6 +1207,9 @@ importers: enhanced-resolve: specifier: ^5.20.1 version: 5.20.1 + kolibri: + specifier: workspace:* + version: link:../kolibri kolibri-glob: specifier: workspace:* version: link:../kolibri-glob @@ -1385,6 +1388,9 @@ importers: eslint-plugin-jest-dom: specifier: ^5.5.0 version: 5.5.0(@testing-library/dom@9.3.4)(eslint@9.39.4(jiti@2.6.1)) + eslint-plugin-jsdoc: + specifier: ^62.9.0 + version: 62.9.0(eslint@9.39.4(jiti@2.6.1)) eslint-plugin-kolibri: specifier: workspace:* version: link:../eslint-plugin-kolibri @@ -2578,6 +2584,14 @@ packages: '@emnapi/wasi-threads@1.1.0': resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==} + '@es-joy/jsdoccomment@0.86.0': + resolution: {integrity: sha512-ukZmRQ81WiTpDWO6D/cTBM7XbrNtutHKvAVnZN/8pldAwLoJArGOvkNyxPTBGsPjsoaQBJxlH+tE2TNA/92Qgw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@es-joy/resolve.exports@1.2.0': + resolution: {integrity: sha512-Q9hjxWI5xBM+qW2enxfe8wDKdFWMfd0Z29k5ZJnuBqD/CasY5Zryj09aCA6owbGATWz+39p5uIdaHXpopOcG8g==} + engines: {node: '>=10'} + '@eslint-community/eslint-utils@4.9.1': resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -3381,6 +3395,10 @@ packages: '@sinclair/typebox@0.34.47': resolution: {integrity: sha512-ZGIBQ+XDvO5JQku9wmwtabcVTHJsgSWAHYtVuM9pBNNR5E88v6Jcj/llpmsjivig5X8A8HHOb4/mbEKPS5EvAw==} + '@sindresorhus/base62@1.0.0': + resolution: {integrity: sha512-TeheYy0ILzBEI/CO55CP6zJCSdSWeRtGnHy8U8dWSUH4I68iqTsy7HkMktR4xakThc9jotkPQUXT4ITdbV7cHA==} + engines: {node: '>=18'} + '@sinonjs/commons@3.0.1': resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} @@ -3681,6 +3699,10 @@ packages: resolution: {integrity: sha512-jr/swrr2aRmUAUjW5/zQHbMaui//vQlsZcJKijZf3M26bnmLj8LyZUpj8/Rd6uzaek06OWsqdofN/Thenm5O8A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/types@8.58.2': + resolution: {integrity: sha512-9TukXyATBQf/Jq9AMQXfvurk+G5R2MwfqQGDR2GzGz28HvY/lXNKGhkY+6IOubwcquikWk5cjlgPvD2uAA7htQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/typescript-estree@8.53.1': resolution: {integrity: sha512-RGlVipGhQAG4GxV1s34O91cxQ/vWiHJTDHbXRr0li2q/BGg3RR/7NM8QDWgkEgrwQYCvmJV9ichIwyoKCQ+DTg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -4057,6 +4079,10 @@ packages: aproba@2.1.0: resolution: {integrity: sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==} + are-docs-informative@0.0.2: + resolution: {integrity: sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==} + engines: {node: '>=14'} + are-we-there-yet@3.0.1: resolution: {integrity: sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} @@ -4582,6 +4608,10 @@ packages: resolution: {integrity: sha512-aRDkn3uyIlCFfk5NUA+VdwMmMsh8JGhc4hapfV4yxymHGQ3BVskMQfoXGpCo5IoBuQ9tS5iiVKhCpTcB4pW4qw==} engines: {node: '>= 12.0.0'} + comment-parser@1.4.6: + resolution: {integrity: sha512-ObxuY6vnbWTN6Od72xfwN9DbzC7Y2vv8u1Soi9ahRKL37gb6y1qk6/dgjs+3JWuXJHWvsg3BXIwzd/rkmAwavg==} + engines: {node: '>= 12.0.0'} + commondir@1.0.1: resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} @@ -5483,6 +5513,12 @@ packages: typescript: optional: true + eslint-plugin-jsdoc@62.9.0: + resolution: {integrity: sha512-PY7/X4jrVgoIDncUmITlUqK546Ltmx/Pd4Hdsu4CvSjryQZJI2mEV4vrdMufyTetMiZ5taNSqvK//BTgVUlNkA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 + eslint-plugin-n@17.24.0: resolution: {integrity: sha512-/gC7/KAYmfNnPNOb3eu8vw+TdVnV0zhdQwexsw6FLXbhzroVj20vRn2qL8lDWDGnAQ2J8DhdfvXxX9EoxvERvw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -5532,6 +5568,10 @@ packages: resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + eslint@7.32.0: resolution: {integrity: sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==} engines: {node: ^10.12.0 || >=12.0.0} @@ -5560,6 +5600,10 @@ packages: resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + espree@11.2.0: + resolution: {integrity: sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + espree@7.3.1: resolution: {integrity: sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==} engines: {node: ^10.12.0 || >=12.0.0} @@ -6052,6 +6096,9 @@ packages: resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} engines: {node: '>=18'} + html-entities@2.6.0: + resolution: {integrity: sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==} + html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} @@ -6734,6 +6781,10 @@ packages: '@babel/preset-env': optional: true + jsdoc-type-pratt-parser@7.2.0: + resolution: {integrity: sha512-dh140MMgjyg3JhJZY/+iEzW+NO5xR2gpbDFKHqotCmexElVntw7GjWjt511+C/Ef02RU5TKYrJo/Xlzk+OLaTw==} + engines: {node: '>=20.0.0'} + jsdom@26.1.0: resolution: {integrity: sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==} engines: {node: '>=18'} @@ -7319,6 +7370,9 @@ packages: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} + object-deep-merge@2.0.0: + resolution: {integrity: sha512-3DC3UMpeffLTHiuXSy/UG4NOIYTLlY9u3V82+djSCLYClWobZiS4ivYzpIUWrRY/nfsJ8cWsKyG3QfyLePmhvg==} + object-fit-images@3.2.4: resolution: {integrity: sha512-G+7LzpYfTfqUyrZlfrou/PLLLAPNC52FTy5y1CBywX+1/FkxIloOyQXBmZ3Zxa2AWO+lMF0JTuvqbr7G5e5CWg==} @@ -7429,10 +7483,16 @@ packages: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} + parse-imports-exports@0.2.4: + resolution: {integrity: sha512-4s6vd6dx1AotCx/RCI2m7t7GCh5bDRUtGNvRfHSP2wbBQdMi67pPe7mtzmgwcaQ8VKK/6IB7Glfyu3qdZJPybQ==} + parse-json@5.2.0: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} + parse-statements@1.0.11: + resolution: {integrity: sha512-HlsyYdMBnbPQ9Jr/VgJ1YF4scnldvJpJxCVx6KgqPL4dxppsWrJHCIIxQXMJrqGnsRkNPATbeMJ8Yxu7JMsYcA==} + parse5@7.3.0: resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} @@ -8106,6 +8166,10 @@ packages: requires-port@1.0.0: resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} + reserved-identifiers@1.2.0: + resolution: {integrity: sha512-yE7KUfFvaBFzGPs5H3Ops1RevfUEsDc5Iz65rOwWg4lE8HJSYtle77uul3+573457oHvBKuHYDl/xqUkKpEEdw==} + engines: {node: '>=18'} + resolve-cwd@3.0.0: resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} engines: {node: '>=8'} @@ -8293,6 +8357,11 @@ packages: engines: {node: '>=10'} hasBin: true + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + engines: {node: '>=10'} + hasBin: true + send@0.19.2: resolution: {integrity: sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==} engines: {node: '>= 0.8.0'} @@ -8444,6 +8513,9 @@ packages: spdx-expression-parse@3.0.1: resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} + spdx-expression-parse@4.0.0: + resolution: {integrity: sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==} + spdx-license-ids@3.0.22: resolution: {integrity: sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==} @@ -8864,6 +8936,10 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + to-valid-identifier@1.0.0: + resolution: {integrity: sha512-41wJyvKep3yT2tyPqX/4blcfybknGB4D+oETKLs7Q76UiPqRpUJK3hr1nxelyYO0PHKVzJwlu0aCeEAsGI6rpw==} + engines: {node: '>=20'} + toidentifier@1.0.1: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} @@ -10486,6 +10562,16 @@ snapshots: tslib: 2.8.1 optional: true + '@es-joy/jsdoccomment@0.86.0': + dependencies: + '@types/estree': 1.0.8 + '@typescript-eslint/types': 8.58.2 + comment-parser: 1.4.6 + esquery: 1.7.0 + jsdoc-type-pratt-parser: 7.2.0 + + '@es-joy/resolve.exports@1.2.0': {} + '@eslint-community/eslint-utils@4.9.1(eslint@9.39.4(jiti@2.6.1))': dependencies: eslint: 9.39.4(jiti@2.6.1) @@ -11538,12 +11624,12 @@ snapshots: '@npmcli/fs@1.1.1': dependencies: '@gar/promisify': 1.1.3 - semver: 7.7.3 + semver: 7.7.4 '@npmcli/fs@2.1.2': dependencies: '@gar/promisify': 1.1.3 - semver: 7.7.3 + semver: 7.7.4 '@npmcli/move-file@1.1.2': dependencies: @@ -11668,6 +11754,8 @@ snapshots: '@sinclair/typebox@0.34.47': {} + '@sindresorhus/base62@1.0.0': {} + '@sinonjs/commons@3.0.1': dependencies: type-detect: 4.0.8 @@ -11956,7 +12044,7 @@ snapshots: '@typescript-eslint/project-service@8.53.1(typescript@5.9.3)': dependencies: '@typescript-eslint/tsconfig-utils': 8.53.1(typescript@5.9.3) - '@typescript-eslint/types': 8.53.1 + '@typescript-eslint/types': 8.58.2 debug: 4.4.3 typescript: 5.9.3 transitivePeerDependencies: @@ -11973,6 +12061,8 @@ snapshots: '@typescript-eslint/types@8.53.1': {} + '@typescript-eslint/types@8.58.2': {} + '@typescript-eslint/typescript-estree@8.53.1(typescript@5.9.3)': dependencies: '@typescript-eslint/project-service': 8.53.1(typescript@5.9.3) @@ -11981,7 +12071,7 @@ snapshots: '@typescript-eslint/visitor-keys': 8.53.1 debug: 4.4.3 minimatch: 9.0.5 - semver: 7.7.3 + semver: 7.7.4 tinyglobby: 0.2.15 ts-api-utils: 2.4.0(typescript@5.9.3) typescript: 5.9.3 @@ -12308,6 +12398,10 @@ snapshots: dependencies: acorn: 8.15.0 + acorn-jsx@5.3.2(acorn@8.16.0): + dependencies: + acorn: 8.16.0 + acorn@7.4.1: {} acorn@8.15.0: {} @@ -12426,6 +12520,8 @@ snapshots: aproba@2.1.0: {} + are-docs-informative@0.0.2: {} + are-we-there-yet@3.0.1: dependencies: delegates: 1.0.0 @@ -13093,6 +13189,8 @@ snapshots: comment-parser@1.4.5: {} + comment-parser@1.4.6: {} + commondir@1.0.1: {} compressible@2.0.18: @@ -13614,7 +13712,7 @@ snapshots: '@one-ini/wasm': 0.1.1 commander: 10.0.1 minimatch: 9.0.1 - semver: 7.7.3 + semver: 7.7.4 ee-first@1.1.1: {} @@ -13811,7 +13909,7 @@ snapshots: eslint-compat-utils@0.5.1(eslint@9.39.4(jiti@2.6.1)): dependencies: eslint: 9.39.4(jiti@2.6.1) - semver: 7.7.3 + semver: 7.7.4 eslint-config-prettier@10.1.8(eslint@9.39.4(jiti@2.6.1)): dependencies: @@ -13929,6 +14027,26 @@ snapshots: transitivePeerDependencies: - supports-color + eslint-plugin-jsdoc@62.9.0(eslint@9.39.4(jiti@2.6.1)): + dependencies: + '@es-joy/jsdoccomment': 0.86.0 + '@es-joy/resolve.exports': 1.2.0 + are-docs-informative: 0.0.2 + comment-parser: 1.4.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint: 9.39.4(jiti@2.6.1) + espree: 11.2.0 + esquery: 1.7.0 + html-entities: 2.6.0 + object-deep-merge: 2.0.0 + parse-imports-exports: 0.2.4 + semver: 7.7.4 + spdx-expression-parse: 4.0.0 + to-valid-identifier: 1.0.0 + transitivePeerDependencies: + - supports-color + eslint-plugin-n@17.24.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3): dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.6.1)) @@ -13989,6 +14107,8 @@ snapshots: eslint-visitor-keys@4.2.1: {} + eslint-visitor-keys@5.0.1: {} + eslint@7.32.0: dependencies: '@babel/code-frame': 7.12.11 @@ -14025,7 +14145,7 @@ snapshots: optionator: 0.9.4 progress: 2.0.3 regexpp: 3.2.0 - semver: 7.7.3 + semver: 7.7.4 strip-ansi: 6.0.1 strip-json-comments: 3.1.1 table: 6.9.0 @@ -14090,6 +14210,12 @@ snapshots: acorn-jsx: 5.3.2(acorn@8.15.0) eslint-visitor-keys: 4.2.1 + espree@11.2.0: + dependencies: + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) + eslint-visitor-keys: 5.0.1 + espree@7.3.1: dependencies: acorn: 7.4.1 @@ -14098,8 +14224,8 @@ snapshots: espree@9.6.1: dependencies: - acorn: 8.15.0 - acorn-jsx: 5.3.2(acorn@8.15.0) + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) eslint-visitor-keys: 3.4.3 esprima@4.0.1: {} @@ -14616,6 +14742,8 @@ snapshots: dependencies: whatwg-encoding: 3.1.1 + html-entities@2.6.0: {} + html-escaper@2.0.2: {} html-minifier-terser@6.1.0: @@ -15034,7 +15162,7 @@ snapshots: '@babel/parser': 7.28.6 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 - semver: 7.7.3 + semver: 7.7.4 transitivePeerDependencies: - supports-color @@ -15405,7 +15533,7 @@ snapshots: jest-message-util: 30.2.0 jest-util: 30.2.0 pretty-format: 30.2.0 - semver: 7.7.3 + semver: 7.7.4 synckit: 0.11.12 transitivePeerDependencies: - supports-color @@ -15572,6 +15700,8 @@ snapshots: transitivePeerDependencies: - supports-color + jsdoc-type-pratt-parser@7.2.0: {} + jsdom@26.1.0: dependencies: cssstyle: 4.6.0 @@ -15805,7 +15935,7 @@ snapshots: make-dir@4.0.0: dependencies: - semver: 7.7.3 + semver: 7.7.4 make-fetch-happen@10.2.1: dependencies: @@ -16177,7 +16307,7 @@ snapshots: nopt: 5.0.0 npmlog: 6.0.2 rimraf: 3.0.2 - semver: 7.7.3 + semver: 7.7.4 tar: 6.2.1 which: 2.0.2 transitivePeerDependencies: @@ -16229,7 +16359,7 @@ snapshots: dependencies: hosted-git-info: 4.1.0 is-core-module: 2.16.1 - semver: 7.7.3 + semver: 7.7.4 validate-npm-package-license: 3.0.4 normalize-path@3.0.0: {} @@ -16253,6 +16383,8 @@ snapshots: object-assign@4.1.1: {} + object-deep-merge@2.0.0: {} + object-fit-images@3.2.4: {} object-inspect@1.13.4: {} @@ -16383,6 +16515,10 @@ snapshots: dependencies: callsites: 3.1.0 + parse-imports-exports@0.2.4: + dependencies: + parse-statements: 1.0.11 + parse-json@5.2.0: dependencies: '@babel/code-frame': 7.28.6 @@ -16390,6 +16526,8 @@ snapshots: json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 + parse-statements@1.0.11: {} + parse5@7.3.0: dependencies: entities: 6.0.1 @@ -17060,6 +17198,8 @@ snapshots: requires-port@1.0.0: {} + reserved-identifiers@1.2.0: {} + resolve-cwd@3.0.0: dependencies: resolve-from: 5.0.0 @@ -17234,6 +17374,8 @@ snapshots: semver@7.7.3: {} + semver@7.7.4: {} + send@0.19.2: dependencies: debug: 2.6.9 @@ -17437,6 +17579,11 @@ snapshots: spdx-exceptions: 2.5.0 spdx-license-ids: 3.0.22 + spdx-expression-parse@4.0.0: + dependencies: + spdx-exceptions: 2.5.0 + spdx-license-ids: 3.0.22 + spdx-license-ids@3.0.22: {} spdy-transport@3.0.0: @@ -17844,7 +17991,7 @@ snapshots: terser@5.46.0: dependencies: '@jridgewell/source-map': 0.3.11 - acorn: 8.15.0 + acorn: 8.16.0 commander: 2.20.3 source-map-support: 0.5.21 @@ -17906,6 +18053,11 @@ snapshots: dependencies: is-number: 7.0.0 + to-valid-identifier@1.0.0: + dependencies: + '@sindresorhus/base62': 1.0.0 + reserved-identifiers: 1.2.0 + toidentifier@1.0.1: {} toml@3.0.0: {} @@ -18212,7 +18364,7 @@ snapshots: espree: 9.6.1 esquery: 1.7.0 lodash: 4.17.23 - semver: 7.7.3 + semver: 7.7.4 transitivePeerDependencies: - supports-color From 79f25fad94f74f1f5216c4592848f25acc7ec8ce Mon Sep 17 00:00:00 2001 From: Richard Tibbles Date: Fri, 24 Apr 2026 18:04:07 -0700 Subject: [PATCH 02/11] Remediate JSDoc violations in packages/kolibri Restores typedef references, structured @returns shapes, and inline @type annotations that the upstream JSDoc rules required satisfying; adds typedef imports for previously-undefined types; fills missing @param descriptions, @throws, and @returns declarations. Co-Authored-By: Claude Opus 4.7 (1M context) --- packages/kolibri/apiResource.js | 183 +++++++++++------- .../kolibri/components/CoreMenu/index.vue | 4 +- packages/kolibri/components/FilterTextbox.vue | 5 +- .../AppBarPage/internal/Navbar/NavbarLink.vue | 4 +- .../pages/AppBarPage/internal/SideNav.vue | 2 +- .../kolibri/composables/__mocks__/useNav.js | 2 +- .../composables/__mocks__/useSnackbar.js | 2 +- .../composables/__mocks__/useTotalProgress.js | 2 +- .../kolibri/composables/__mocks__/useUser.js | 2 +- .../composables/useMinimumKolibriVersion.js | 15 +- packages/kolibri/internal/pluginMediator.js | 31 +-- packages/kolibri/router.js | 3 +- packages/kolibri/rtlcss.js | 15 +- packages/kolibri/uiText/commonCoreStrings.js | 3 +- packages/kolibri/utils/CatchErrors.js | 11 +- packages/kolibri/utils/appError.js | 13 +- packages/kolibri/utils/browserInfo.js | 15 +- packages/kolibri/utils/i18n.js | 23 +-- packages/kolibri/utils/scriptLoader.js | 4 +- packages/kolibri/utils/validators.js | 5 +- 20 files changed, 191 insertions(+), 153 deletions(-) diff --git a/packages/kolibri/apiResource.js b/packages/kolibri/apiResource.js index 05b10526f72..c481f04c8fa 100644 --- a/packages/kolibri/apiResource.js +++ b/packages/kolibri/apiResource.js @@ -18,6 +18,7 @@ export class Model { * fetch. * @param {Resource} resource - object of the Resource class, specifies the urls and fetching * behaviour for the model. + * @param {string} url - URL used for model-level fetch/save/delete requests */ constructor(data, getParams = {}, resource, url) { this.resource = resource; @@ -56,7 +57,7 @@ export class Model { /** * Method to fetch data from the server for this particular model. - * @param {boolean} [force=false] - fetch whether or not it's been synced already. + * @param {boolean} [force] - fetch whether or not it's been synced already. * @returns {Promise} - Promise is resolved with Model attributes when the XHR successfully * returns, otherwise reject is called with the response object. */ @@ -103,7 +104,7 @@ export class Model { /** * Method to save data to the server for this particular model. * @param {object} attrs - an object of attributes to be saved on the model. - * @param {Boolean} exists - a Boolean flag to override the default new behaviour + * @param {boolean} exists - a Boolean flag to override the default new behaviour * @returns {Promise} - Promise is resolved with Model attributes when the XHR successfully * returns, otherwise reject is called with the response object. */ @@ -179,8 +180,7 @@ export class Model { } /** - * Method to delete model. - * @param {Integer} id - target model's id. + * Method to delete the model on the server. * @returns {Promise} - Promise is resolved with target model's id * returns, otherwise reject is called with the response object. */ @@ -256,14 +256,15 @@ export class Model { } } -/** Class representing a 'view' of a single API resource. +/** + * Class representing a 'view' of a single API resource. * Contains different Model objects, depending on the parameters passed to its fetch method. */ export class Collection { /** * Create a Collection instance. - * @param {Object} getParams - Default parameters to use when fetching data from the server. - * @param {Object[]|Model[]} data - Data to prepopulate the collection with, + * @param {object} getParams - Default parameters to use when fetching data from the server. + * @param {object[] | Model[]} data - Data to prepopulate the collection with, * useful if wanting to save multiple models. * @param {Resource} resource - object of the Resource class, specifies the urls and fetching * behaviour for the collection. @@ -357,6 +358,7 @@ export class Collection { /** * Method to save data to the server for this particular collection. * Can only currently be used to save new models to the server, not do bulk updates. + * @param {Array} [data] - Array of model payloads to POST; defaults to the collection's own data * @returns {Promise} - Promise is resolved with list of collection attributes when the XHR * successfully returns, otherwise reject is called with the response object. */ @@ -485,8 +487,8 @@ export class Collection { * Make a model a member of the collection - record in the models Array, and in the mapping * from id to model. Will automatically instantiate Models for data passed in as objects, and * deduplicate within the collection. - * @param {(Object|Model|Object[]|Model[])} models - Either an Array or single instance of an - * object or Model. + * @param {(object | Model | object[] | Model[])} models - Either an Array or single instance of + * an object or Model. */ set(models) { let modelsToSet; @@ -539,7 +541,7 @@ export class Collection { /** * Set this Collection as synced or not, for true, will also set all models cached in it * as synced. - * @param {Boolean} value Is this Collection synced or not? + * @param {boolean} value - Is this Collection synced or not? */ set synced(value) { this._synced = value; @@ -559,7 +561,7 @@ export class Collection { /** * Set this Collection as new or not, for false, will also set all models cached in it * as not new. - * @param {Boolean} value Is this Collection new or not? + * @param {boolean} value - Is this Collection new or not? */ set new(value) { this._new = value; @@ -571,13 +573,19 @@ export class Collection { } } -/** Class representing a single API resource. +/** + * Class representing a single API resource. * Contains references to all Models that have been fetched from the server. * Can also be subclassed in order to create custom behaviour for particular API resources. */ export class Resource { /** * Create a resource with a Django REST API name corresponding to the name parameter. + * @param {object} options - Configuration including `name`, `idKey`, `namespace` and any + * additional properties to attach to the resource instance + * @param {string} options.name - DRF router name for the resource endpoint + * @param {string} [options.idKey] - Attribute used as the primary key (defaults to `id`) + * @param {string} [options.namespace] - URL namespace prefix (defaults to `core`) */ constructor({ name, idKey = 'id', namespace = 'core', ...options } = {}) { if (!name) { @@ -627,7 +635,10 @@ export class Resource { } /** - * @param {Object} getParams - default parameters to use for Collection fetching. + * Retrieve a cached Collection for the given params, creating one if absent. + * @param {object} [getParams] - default parameters to use for Collection fetching. + * @param {string} [endpointName] - Named detail/list endpoint to fetch against + * @param {string} [detailId] - Parent detail id for nested endpoints * @returns {Collection} - Returns an instantiated Collection object. */ getCollection(getParams = {}, endpointName, detailId) { @@ -642,9 +653,11 @@ export class Resource { /** * Optionally pass in data and instantiate a collection for saving that data or fetching * data from the resource. - * @param {Object} getParams - default parameters to use for Collection fetching. - * @param {Object[]} data - Data to instantiate the Collection - see Model constructor for + * @param {object} [getParams] - default parameters to use for Collection fetching. + * @param {object[]} [data] - Data to instantiate the Collection - see Model constructor for * details of data. + * @param {string} [endpointName] - Named detail/list endpoint used for URL resolution + * @param {string} [detailId] - Parent detail id for nested endpoints * @returns {Collection} - Returns an instantiated Collection object. */ createCollection(getParams = {}, data = [], endpointName, detailId) { @@ -663,7 +676,9 @@ export class Resource { /** * Get a model by id - * @param {String} id - The primary key of the Model instance. + * @param {string} id - The primary key of the Model instance. + * @param {object} [getParams] - Query parameters to associate with the cached model + * @param {string} [endpointName] - Named detail endpoint used for URL resolution * @returns {Model} - Returns a Model instance. */ getModel(id, getParams = {}, endpointName) { @@ -677,9 +692,9 @@ export class Resource { /** * Find a model by its attributes - will return first model found that matches - * @param {Object} attrs Hash of attributes to search by - * @param {string} endpointName name of endpoint to search model cache - * @return {Model} First matching Model + * @param {object} attrs - Hash of attributes to search by + * @param {string} endpointName - name of endpoint to search model cache + * @returns {Model} First matching Model */ findModel(attrs, endpointName) { const cache = this.__modelCache(endpointName); @@ -688,7 +703,9 @@ export class Resource { /** * Add a model to the resource for deduplication, dirty checking, and tracking purposes. - * @param {Object} data - The data for the model to add. + * @param {object} data - The data for the model to add. + * @param {object} [getParams] - Query parameters associated with the model + * @param {string} [endpointName] - Named detail endpoint used for URL resolution * @returns {Model} - Returns the instantiated Model. */ createModel(data, getParams = {}, endpointName) { @@ -703,7 +720,9 @@ export class Resource { /** * Add a model to the resource for deduplication, dirty checking, and tracking purposes. - * @param {Object|Model} model - Either the data for the model to add, or the Model itself. + * @param {object | Model} model - Either the data for the model to add, or the Model itself. + * @param {object} [getParams] - Query parameters associated with the model + * @param {string} [endpointName] - Named detail endpoint used for URL resolution * @returns {Model} - Returns the instantiated Model. */ addModel(model, getParams = {}, endpointName) { @@ -732,10 +751,12 @@ export class Resource { /** * Fetch a model from a resource - * @param {string} options.id id of the model to fetch - * @param {Object} [options.getParams={}] any getParams to use when fetching the model - * @param {Boolean} [force=false] whether to respect the cache when fetching - * @return {Promise} Promise that resolves on fetch with the model data + * @param {object} options - Bundle of `id`, `getParams` and `force` for the request + * @param {string} options.id - id of the model to fetch + * @param {object} [options.getParams] - any getParams to use when fetching the model + * @param {boolean} [options.force] - whether to respect the cache when fetching + * @returns {Promise} Promise that resolves on fetch with the model data + * @throws {TypeError} - When `id` is missing */ fetchModel({ id, getParams = {}, force = false } = {}) { if (!id) { @@ -746,11 +767,12 @@ export class Resource { /** * Save a model to a resource - * @param {string} [options.id] id of the model to save - * @param {Object} [options.getParams={}] any getParams to use when saving the model - * @param {Object} data data to save on the model - * @param {Boolean} [exists=false] flag that this model exists on the server already - * @return {Promise} Promise that resolves on save with the model data + * @param {object} options - Bundle of `id`, `getParams`, `data` and `exists` for the request + * @param {string} [options.id] - id of the model to save + * @param {object} [options.getParams] - any getParams to use when saving the model + * @param {object} [options.data] - data to save on the model + * @param {boolean} [options.exists] - flag that this model exists on the server already + * @returns {Promise} Promise that resolves on save with the model data */ saveModel({ id, getParams = {}, data = {}, exists = false } = {}) { if (!id) { @@ -761,9 +783,11 @@ export class Resource { /** * Delete a model from a resource - * @param {string} options.id id of the model to delete - * @param {Object} [options.getParams={}] any getParams to use when deleting the model - * @return {Promise} Promise that resolves on delete with the model id + * @param {object} options - Bundle of `id` and `getParams` identifying the target model + * @param {string} options.id - id of the model to delete + * @param {object} [options.getParams] - any getParams to use when deleting the model + * @returns {Promise} Promise that resolves on delete with the model id + * @throws {TypeError} - When `id` is missing */ deleteModel({ id, getParams = {} } = {}) { if (!id) { @@ -774,9 +798,10 @@ export class Resource { /** * Fetch a collection from a resource - * @param {Object} [options.getParams={}] any getParams to use when fetching the collection - * @param {Boolean} [force=false] whether to respect the cache when fetching - * @return {Promise} Promise that resolves on fetch with the collection + * @param {object} [options] - Bundle of `getParams` and `force` for the request + * @param {object} [options.getParams] - any getParams to use when fetching the collection + * @param {boolean} [options.force] - whether to respect the cache when fetching + * @returns {Promise} Promise that resolves on fetch with the collection */ fetchCollection({ getParams = {}, force = false } = {}) { return this.getCollection(getParams).fetch(force); @@ -784,10 +809,11 @@ export class Resource { /** * Do a bulk save of a collection, only works for specific resources - * @param {Object[]} options.data An array of objects representing the models to be + * @param {object} [options] - Bundle of `data` and `getParams` for the bulk save + * @param {object[]} [options.data] - An array of objects representing the models to be * saved. - * @param {Object} [options.getParams] any getParams to use when caching the collection - * @return {Promise} Promise that resolves on save with array of models + * @param {object} [options.getParams] - any getParams to use when caching the collection + * @returns {Promise} Promise that resolves on save with array of models */ saveCollection({ data = [], getParams = {} } = {}) { return this.getCollection(getParams).save(data); @@ -795,8 +821,8 @@ export class Resource { /** * Do a bulk delete of a collection, only works for specific resources, and must use getParams - * @param {Object} getParams getParams that more narrowly specify the collection to be deleted. - * @return {Promise} Promise that resolves on deletion + * @param {object} getParams - getParams that more narrowly specify the collection to be deleted. + * @returns {Promise} Promise that resolves on deletion */ deleteCollection(getParams = {}) { return this.getCollection(getParams).delete(); @@ -807,10 +833,11 @@ export class Resource { * (as opposed to an array of objects). * Mostly used as a convenience method for defining additional endpoint fetch methods on a * resource object. - * @param {string} detailName The name given to the detail endpoint - * @param {string} id The id of the model for which this is a detail - * @param {Object} getParams Any getParams needed while fetching - * @return {Promise} Promise that resolves on fetch with a single object + * @param {string} detailName - The name given to the detail endpoint + * @param {string} id - The id of the model for which this is a detail + * @param {object} [getParams] - Any getParams needed while fetching + * @returns {Promise} Promise that resolves on fetch with a single object + * @throws {TypeError} - When `id` or `detailName` is missing */ fetchDetailModel(detailName, id, getParams = {}) { if (!id) { @@ -827,10 +854,12 @@ export class Resource { * (as opposed to a single object). * Mostly used as a convenience method for defining additional endpoint fetch methods on a * resource object. - * @param {string} detailName The name given to the detail endpoint - * @param {string} id The id of the model for which this is a detail - * @param {Object} getParams Any getParams needed while fetching - * @return {Promise} Promise that resolves on fetch with an array of objects + * @param {string} detailName - The name given to the detail endpoint + * @param {string} id - The id of the model for which this is a detail + * @param {object} [getParams] - Any getParams needed while fetching + * @param {boolean} [force] - Whether to bypass the cache + * @returns {Promise} Promise that resolves on fetch with an array of objects + * @throws {TypeError} - When `id` or `detailName` is missing */ fetchDetailCollection(detailName, id, getParams = {}, force = false) { if (!id) { @@ -846,9 +875,10 @@ export class Resource { * Fetch from a custom list endpoint on a resource, that returns an array of JSON objects. * Mostly used as a convenience method for defining additional endpoint fetch methods * on a resource object. - * @param {string} listName The name given to the list endpoint - * @param {Object} getParams Any getParams needed while fetching - * @return {Promise} Promise that resolves on fetch with an array of objects + * @param {string} listName - The name given to the list endpoint + * @param {object} [getParams] - Any getParams needed while fetching + * @returns {Promise} Promise that resolves on fetch with an array of objects + * @throws {TypeError} - When `listName` is missing */ fetchListCollection(listName, getParams = {}) { if (!listName) { @@ -860,11 +890,13 @@ export class Resource { /** * This method is a convenience method for access to a resource endpoint unmediated by the * model/collection framework that facilitates caching. This method is for list endpoints. - * @param {string} method A valid HTTP method name, in all caps. - * @param {string} listName The name given to the list endpoint - * @param {Object} args The getParams or data to be passed to the endpoint, + * @param {string} method - A valid HTTP method name, in all caps. + * @param {string} listName - The name given to the list endpoint + * @param {object} [args] - The getParams or data to be passed to the endpoint, * depending on method - * @return {Promise} Promise that resolves with the request + * @param {boolean} [multipart] - Whether to encode the payload as multipart form data + * @returns {Promise} Promise that resolves with the request + * @throws {TypeError} - When `listName` is missing */ accessListEndpoint(method, listName, args = {}, multipart = false) { if (!listName) { @@ -888,12 +920,14 @@ export class Resource { /** * This method is a convenience method for access to a resource endpoint unmediated by the * model/collection framework that facilitates caching. This method is for detail endpoints. - * @param {string} method A valid HTTP method name, in all caps. - * @param {string} detailName The name given to the detail endpoint - * @param {string} id The primary key or id for the detail endpoint. - * @param {Object} args The getParams or data to be passed to the endpoint, + * @param {string} method - A valid HTTP method name, in all caps. + * @param {string} detailName - The name given to the detail endpoint + * @param {string} id - The primary key or id for the detail endpoint. + * @param {object} [args] - The getParams or data to be passed to the endpoint, * depending on method - * @return {Promise} Promise that resolves with the request + * @param {boolean} [multipart] - Whether to encode the payload as multipart form data + * @returns {Promise} Promise that resolves with the request + * @throws {TypeError} - When `detailName` or `id` is missing */ accessDetailEndpoint(method, detailName, id, args = {}, multipart = false) { if (!detailName) { @@ -933,9 +967,9 @@ export class Resource { /** * Call a GET on a custom list endpoint - * @param {string} listName The name given to the list endpoint - * @param {Object} params The getParams to be passed to the endpoint - * @return {Promise} Promise that resolves with the request + * @param {string} listName - The name given to the list endpoint + * @param {object} params - The getParams to be passed to the endpoint + * @returns {Promise} Promise that resolves with the request */ getListEndpoint(listName, params = {}) { return this.accessListEndpoint('get', listName, params); @@ -943,9 +977,9 @@ export class Resource { /** * Call a POST on a custom list endpoint - * @param {string} listName The name given to the list endpoint - * @param {Object} params The body of the request - * @return {Promise} Promise that resolves with the request + * @param {string} listName - The name given to the list endpoint + * @param {object} params - The body of the request + * @returns {Promise} Promise that resolves with the request */ postListEndpoint(listName, params = {}) { return this.accessListEndpoint('post', listName, params); @@ -954,10 +988,9 @@ export class Resource { /** * Call a POST on a custom list endpoint and use * 'multipart/form-data' as Mimetype instead of 'application/json'. - * - * @param {string} listName The name given to the list endpoint - * @param {Object} params The body of the request - * @return {Promise} Promise that resolves with the request + * @param {string} listName - The name given to the list endpoint + * @param {object} params - The body of the request + * @returns {Promise} Promise that resolves with the request */ postListEndpointMultipart(listName, params = {}) { return this.accessListEndpoint('post', listName, params, true); @@ -965,10 +998,10 @@ export class Resource { /** * Call a POST on a custom detail endpoint - * @param {string} detailName The name given to the detail endpoint - * @param {string} id The id for the detail endpoint - * @param {Object} params The body of the request - * @return {Promise} Promise that resolves with the request + * @param {string} detailName - The name given to the detail endpoint + * @param {string} id - The id for the detail endpoint + * @param {object} params - The body of the request + * @returns {Promise} Promise that resolves with the request */ postDetailEndpoint(detailName, id, params = {}) { return this.accessDetailEndpoint('post', detailName, id, params); diff --git a/packages/kolibri/components/CoreMenu/index.vue b/packages/kolibri/components/CoreMenu/index.vue index c0f6a8986f1..25df52d4185 100644 --- a/packages/kolibri/components/CoreMenu/index.vue +++ b/packages/kolibri/components/CoreMenu/index.vue @@ -103,17 +103,17 @@ }, methods: { /** - * @public * Focuses on correct last element for FocusTrap depending on content * rendered in CoreMenu. + * @public */ focusLastEl() { last(this.$el.querySelectorAll('.core-menu-option')).focus(); }, /** - * @public * Focuses on correct first element for FocusTrap depending on content * rendered in CoreMenu. + * @public */ focusFirstEl() { if (this.$el.querySelector('.core-menu-option')) { diff --git a/packages/kolibri/components/FilterTextbox.vue b/packages/kolibri/components/FilterTextbox.vue index 92ce685c74c..0b083a103f8 100644 --- a/packages/kolibri/components/FilterTextbox.vue +++ b/packages/kolibri/components/FilterTextbox.vue @@ -60,14 +60,14 @@ default: null, }, /** - * Placeholder + * Placeholder text shown when the input is empty. */ placeholder: { type: String, required: true, }, /** - * Whether to autofocus + * Whether the input should grab keyboard focus on mount. */ autofocus: { type: Boolean, @@ -135,6 +135,7 @@ this.$refs.searchinput.focus(); }, /** + * Move keyboard focus into the search input. * @public */ focus() { diff --git a/packages/kolibri/components/pages/AppBarPage/internal/Navbar/NavbarLink.vue b/packages/kolibri/components/pages/AppBarPage/internal/Navbar/NavbarLink.vue index b379a325edb..f29ae4f025b 100644 --- a/packages/kolibri/components/pages/AppBarPage/internal/Navbar/NavbarLink.vue +++ b/packages/kolibri/components/pages/AppBarPage/internal/Navbar/NavbarLink.vue @@ -32,8 +32,8 @@ import themeConfig from 'kolibri/styles/themeConfig'; /** -Links for use inside the Navbar -*/ +Links for use inside the Navbar. + */ export default { name: 'NavbarLink', setup() { diff --git a/packages/kolibri/components/pages/AppBarPage/internal/SideNav.vue b/packages/kolibri/components/pages/AppBarPage/internal/SideNav.vue index 4204578f50c..d1a0b359c66 100644 --- a/packages/kolibri/components/pages/AppBarPage/internal/SideNav.vue +++ b/packages/kolibri/components/pages/AppBarPage/internal/SideNav.vue @@ -522,8 +522,8 @@ }, /** - * @public * Focuses on correct first element for FocusTrap. + * @public */ focusFirstEl() { this.$nextTick(() => { diff --git a/packages/kolibri/composables/__mocks__/useNav.js b/packages/kolibri/composables/__mocks__/useNav.js index 7b223a8c76e..c3a8234b4ac 100644 --- a/packages/kolibri/composables/__mocks__/useNav.js +++ b/packages/kolibri/composables/__mocks__/useNav.js @@ -8,7 +8,7 @@ * If you need to override some default values from some tests, * you can import a helper function `useNavMock` that accepts * an object with values to be overriden and use it together - * with `mockImplementation` as follows: + * with `mockImplementation`, as shown in the example below. * * ``` * // eslint-disable-next-line import-x/named diff --git a/packages/kolibri/composables/__mocks__/useSnackbar.js b/packages/kolibri/composables/__mocks__/useSnackbar.js index 827ca5857c8..5bb0fbf86e3 100644 --- a/packages/kolibri/composables/__mocks__/useSnackbar.js +++ b/packages/kolibri/composables/__mocks__/useSnackbar.js @@ -9,7 +9,7 @@ * or if you need to inspect the state of the refs during tests, * you can import a helper function `useSnackbarMock` that accepts * an object with values to be overriden and use it together - * with `mockImplementation` as follows: + * with `mockImplementation`, as shown in the example below. * * ``` * // eslint-disable-next-line import-x/named diff --git a/packages/kolibri/composables/__mocks__/useTotalProgress.js b/packages/kolibri/composables/__mocks__/useTotalProgress.js index 9114b3bb49a..adb01ce5c12 100644 --- a/packages/kolibri/composables/__mocks__/useTotalProgress.js +++ b/packages/kolibri/composables/__mocks__/useTotalProgress.js @@ -9,7 +9,7 @@ * or if you need to inspect the state of the refs during tests, * you can import a helper function `useTotalProgressMock` that accepts * an object with values to be overriden and use it together - * with `mockImplementation` as follows: + * with `mockImplementation`, as shown in the example below. * * ``` * // eslint-disable-next-line import-x/named diff --git a/packages/kolibri/composables/__mocks__/useUser.js b/packages/kolibri/composables/__mocks__/useUser.js index ba013477d85..37014c57e15 100644 --- a/packages/kolibri/composables/__mocks__/useUser.js +++ b/packages/kolibri/composables/__mocks__/useUser.js @@ -8,7 +8,7 @@ * If you need to override some default values from some tests, * you can import a helper function `useUserMock` that accepts * an object with values to be overriden and use it together - * with `mockImplementation` as follows: + * with `mockImplementation`, as shown in the example below. * * ``` * // eslint-disable-next-line import-x/named diff --git a/packages/kolibri/composables/useMinimumKolibriVersion.js b/packages/kolibri/composables/useMinimumKolibriVersion.js index fcfc8a9e2f5..7a8d3152bfc 100644 --- a/packages/kolibri/composables/useMinimumKolibriVersion.js +++ b/packages/kolibri/composables/useMinimumKolibriVersion.js @@ -4,17 +4,18 @@ import logger from 'kolibri-logging'; const logging = logger.getLogger(__filename); /** - * Defaults to returning true if version is 0.15+ + * Defaults to returning true if version is 0.15+. * * If values are not provided for revisionVersion, then any values are allowed. This means that if * no value is provided for revisionVersion then alpha and beta versions will be permitted. Just * providing 0 as majorVersion and 15 as minorVersion would allow any version greater than or equal - * to 0.15 (including any alphas or betas) - * - * @param majorVersion - * @param minorVersion - * @param revisionVersion - * @return {{isMinimumKolibriVersion: (function(version: string): boolean)}} + * to 0.15 (including any alphas or betas). + * @param {number} majorVersion - Inclusive lower bound for the major version component. + * @param {number} minorVersion - Inclusive lower bound for the minor version component. + * @param {?number} revisionVersion - Inclusive lower bound for the revision component, or + * null to permit any revision (including alphas and betas). + * @returns {{isMinimumKolibriVersion: (version: string) => boolean}} An object exposing a + * memoised predicate that reports whether a given version string meets the minimum. */ export default function useMinimumKolibriVersion( majorVersion = 0, diff --git a/packages/kolibri/internal/pluginMediator.js b/packages/kolibri/internal/pluginMediator.js index 361d1479a37..318d3f52898 100644 --- a/packages/kolibri/internal/pluginMediator.js +++ b/packages/kolibri/internal/pluginMediator.js @@ -7,6 +7,10 @@ import { rtlManager } from 'kolibri/rtlcss'; import ContentViewerLoading from '../components/internal/ContentViewer/ContentViewerLoading'; import ContentViewerError from '../components/internal/ContentViewer/ContentViewerError'; +/** + * @typedef {import('kolibri-module').default} KolibriModule + */ + const logger = logging.getLogger(__filename); /** @@ -42,7 +46,7 @@ export default function pluginMediatorFactory(facade) { /** * Keep track of all registered kolibri_modules - object is of form: * kolibriModuleName: kolibri_module_object - **/ + */ _kolibriModuleRegistry: {}, // wait to call kolibri_module `ready` until dependencies are loaded @@ -59,14 +63,14 @@ export default function pluginMediatorFactory(facade) { * Map from kolibriModule name to an array of pending callbacks. Callbacks * are queued in retrieveContentViewer() when the module has not yet * registered; flushed and removed in registerKolibriModuleSync(). - * @type {Object.} + * @type {{[key: string]: Function[]}} */ _contentViewerCallbacks: {}, /** * Keep track of all registered language assets for modules. * kolibriModuleName: {object} - with keys for different languages. - **/ + */ _languageAssetRegistry: {}, /** @@ -88,18 +92,18 @@ export default function pluginMediatorFactory(facade) { /** * Mark the mediator as ready and flush all pending ready callbacks. - **/ + */ setReady() { this._ready = true; this._readyCallbacks.splice(0).forEach(cb => cb()); }, /** - * @param {KolibriModule} kolibriModule - object of KolibriModule class - * @description Registers a kolibriModule that has already been loaded into the + * Registers a kolibriModule that has already been loaded into the * frontend. Flushes any pending content viewer callbacks for the module, then * calls kolibriModule.ready() immediately if the mediator is ready, or defers it * until setReady() is called. + * @param {KolibriModule} kolibriModule - The freshly loaded module instance to register */ registerKolibriModuleSync(kolibriModule) { this._kolibriModuleRegistry[kolibriModule.name] = kolibriModule; @@ -123,9 +127,8 @@ export default function pluginMediatorFactory(facade) { * A method for directly registering language assets on the mediator. * This is used to set language assets as loaded and register them to the Vue intl * translation apparatus. - * @param {String} moduleName name of the module. - * @param {String} language language code whose messages we are registering. - * @param {Object} messageMap an object with message id to message mappings. + * @param {string} moduleName - Module whose embedded translation template should be parsed + * and registered */ registerLanguageAssets(moduleName) { const messageElement = document.querySelector(`template[data-i18n="${moduleName}"]`); @@ -163,10 +166,10 @@ export default function pluginMediatorFactory(facade) { /** * A method for registering content viewers for asynchronous loading and track * which file types we have registered viewers for. - * @param {String} kolibriModuleName name of the module. - * @param {String[]} kolibriModuleUrls the URLs of the Javascript + * @param {string} kolibriModuleName - Identifier of the module that provides the viewer + * @param {string[]} kolibriModuleUrls - the URLs of the Javascript * files that constitute the kolibriModule - * @param {String[]} contentPresets the names of presets this content viewer can render + * @param {string[]} contentPresets - the names of presets this content viewer can render */ registerContentViewer(kolibriModuleName, kolibriModuleUrls, contentPresets) { this._contentViewerUrls[kolibriModuleName] = kolibriModuleUrls; @@ -217,8 +220,8 @@ export default function pluginMediatorFactory(facade) { /** * A method to retrieve a content viewer component. - * @param {String} preset content preset - * @return {Promise} Promise that resolves with loaded content viewer Vue component + * @param {string} preset - Content preset whose viewer component should be resolved + * @returns {Promise} Promise that resolves with loaded content viewer Vue component */ retrieveContentViewer(preset) { return new Promise((resolve, reject) => { diff --git a/packages/kolibri/router.js b/packages/kolibri/router.js index 95876d005eb..544d7e782e8 100644 --- a/packages/kolibri/router.js +++ b/packages/kolibri/router.js @@ -4,7 +4,8 @@ import logger from 'kolibri-logging'; const logging = logger.getLogger(__filename); -/** Wrapper around Vue Router. +/** + * Wrapper around Vue Router. * Implements URL mapping to Vuex actions in addition to Vue components. * Otherwise intended as a mostly transparent replacement to vue-router. */ diff --git a/packages/kolibri/rtlcss.js b/packages/kolibri/rtlcss.js index 561e85dc533..2e900b50e22 100644 --- a/packages/kolibri/rtlcss.js +++ b/packages/kolibri/rtlcss.js @@ -19,9 +19,8 @@ class RTLManager { /** * Register a webpack bundle for RTL management. * Called by webpack runtime code injected into each bundle. - * - * @param {string} bundleId - The webpack bundle identifier (compilation.name) - * @returns {Object} Bundle-scoped API with miniCssF method for URL transformation + * @param {string} bundleId - The webpack bundle identifier (compilation.name). + * @returns {object} Bundle-scoped API with miniCssF method for URL transformation. */ registerBundle(bundleId) { if (!this.bundleStates.has(bundleId)) { @@ -33,9 +32,8 @@ class RTLManager { /** * Transform CSS URL based on current RTL state. * Called by webpack's miniCssF hook to transform chunk URLs. - * - * @param {string} originalPath - Original CSS file path - * @returns {string} Transformed path (.rtl.css if RTL enabled) + * @param {string} originalPath - Original CSS file path. + * @returns {string} Transformed path (.rtl.css if RTL enabled). */ miniCssF: originalPath => { if (!originalPath) return originalPath; @@ -49,7 +47,6 @@ class RTLManager { /** * Register CSS URLs for a bundle so that enableRTL/disableRTL can * create initial link elements or switch direction for them. - * * @param {string} bundleId - Bundle identifier * @param {string[]} cssUrls - All CSS URLs for the bundle (both LTR and RTL variants) */ @@ -63,7 +60,6 @@ class RTLManager { * Load CSS for a bundle using its registered URLs and the current direction. * Creates tagged link elements via reloadBundleCSS — the same code path * used by enableRTL/disableRTL for subsequent direction switches. - * * @param {string} bundleId - Bundle identifier (must have been registered via registerBundleCSS) */ loadRegisteredBundleCSS(bundleId) { @@ -76,7 +72,6 @@ class RTLManager { * Enable RTL mode for a bundle. * If CSS links already exist in the DOM, swaps them to RTL. * If no links exist but URLs are registered, creates them. - * * @param {string} bundleId - Bundle identifier * @returns {Promise} */ @@ -90,7 +85,6 @@ class RTLManager { * Disable RTL mode for a bundle. * If CSS links already exist in the DOM, swaps them to LTR. * If no links exist but URLs are registered, creates them. - * * @param {string} bundleId - Bundle identifier * @returns {Promise} */ @@ -105,7 +99,6 @@ class RTLManager { * If tagged link elements exist in the DOM, swaps their URLs. * If no tagged links exist but URLs were registered via registerBundleCSS, * creates new tagged link elements for the current direction. - * * @param {string} bundleId - Bundle identifier * @returns {Promise} * @throws {Error} If CSS file fails to load diff --git a/packages/kolibri/uiText/commonCoreStrings.js b/packages/kolibri/uiText/commonCoreStrings.js index 6ab082fd418..06da96b7c96 100644 --- a/packages/kolibri/uiText/commonCoreStrings.js +++ b/packages/kolibri/uiText/commonCoreStrings.js @@ -1706,11 +1706,11 @@ const MetadataLookup = invert( * Return translated string for key defined in the coreStrings translator. Will map * ID keys generated in the kolibri-constants library to their appropriate translations * if available. - * * @param {string} key - A key as defined in the coreStrings translator; also accepts keys * for the object MetadataLookup. * @param {object} args - An object with keys matching ICU syntax arguments for the translation * string mapping to the values to be passed for those arguments. + * @returns {string} The translated string. */ export function coreString(key, args) { const metadataKey = get(MetadataLookup, key, null); @@ -1732,7 +1732,6 @@ export default { coreString, /** * Shows a specific snackbar notification from our notificationStrings translator. - * * @param {string} key - A key as defined in the notificationsStrings translator. * @param {object} args - An object with keys matching ICU syntax arguments for the translation * string mapping to the values to be passed for those arguments. diff --git a/packages/kolibri/utils/CatchErrors.js b/packages/kolibri/utils/CatchErrors.js index fa442701d1c..66ac739db06 100644 --- a/packages/kolibri/utils/CatchErrors.js +++ b/packages/kolibri/utils/CatchErrors.js @@ -1,11 +1,10 @@ /** - * Checks if the error contains error constants that can be handled + * Checks if the error contains error constants that can be handled. * If it does, it returns the array of recognized error constants. - * If it does not, it returns false - * @export - * @param {object} errorObj Request error - * @param {Object} errorConstants Error constants to search for - * @returns {(Array|null)} An array of error constants or null. + * If it does not, it returns false. + * @param {object} errorObj - Request error. + * @param {object} errorConstants - Error constants to search for. + * @returns {(Array|boolean)} An array of recognized error constants, or false. */ export default function CatchErrors(errorObj, errorConstants) { const errors = errorObj.response.data; diff --git a/packages/kolibri/utils/appError.js b/packages/kolibri/utils/appError.js index a99dc2ad8cb..2b27ff18750 100644 --- a/packages/kolibri/utils/appError.js +++ b/packages/kolibri/utils/appError.js @@ -11,7 +11,6 @@ export const error = ref(null); /** * Set a plain error string as the global app error. - * * @param {string} errorString - The error message to display */ export function handleError(errorString) { @@ -29,13 +28,13 @@ export function clearError() { /** * Handle an API error by setting the global error state. * For disconnection errors, delegates to the heartbeat reconnection overlay instead. - * - * @param {Object} options - * @param {*} options.error - The error object or string - * @param {boolean} [options.reloadOnReconnect=false] - Whether to reload on reconnection - * @param {boolean} [options.shouldThrow=true] - Whether to re-throw the error after handling. + * @param {object} options - Options object. + * @param {Error|object|string} options.error - The error object or string. + * @param {boolean} [options.reloadOnReconnect] - Whether to reload on reconnection. + * @param {boolean} [options.shouldThrow] - Whether to re-throw the error after handling. * Defaults to true to preserve the original throw-after-set semantics. - * @throws {*} Re-throws options.error unless shouldThrow is false or the error is a disconnection + * @throws {Error|object|string} Re-throws options.error unless shouldThrow is false or the + * error is a disconnection. */ export function handleApiError({ error: err, reloadOnReconnect = false, shouldThrow = true } = {}) { let errorString = err; diff --git a/packages/kolibri/utils/browserInfo.js b/packages/kolibri/utils/browserInfo.js index ccaf8e4d999..7e78d7adebb 100644 --- a/packages/kolibri/utils/browserInfo.js +++ b/packages/kolibri/utils/browserInfo.js @@ -3,8 +3,17 @@ import UAParser from 'ua-parser-js'; import { ref } from 'vue'; /** - * A requirements specification object. - * @typedef {Object.} Requirements. + * @typedef {object} Browser + * @property {string} name - User-agent browser name. + * @property {string|number} major - Major version number. + * @property {string|number} [minor] - Minor version number, when known. + * @property {string|number} [patch] - Patch version number, when known. + */ + +/** + * A requirements specification object: a map from browser name to the minimum + * version permitted for that browser. + * @typedef {{[name: string]: Browser}} Requirements */ /** @@ -15,7 +24,7 @@ import { ref } from 'vue'; * @param {Browser} browser - a browser specification object for the browser being tested. * @param {Requirements} requirements - a requirements specification object specifying conditions * to test. - * @return {boolean} - false if failed to pass any of the requirements, true otherwise. + * @returns {boolean} - false if failed to pass any of the requirements, true otherwise. */ export function passesRequirements(browser, requirements) { if (browser.major && browser.name) { diff --git a/packages/kolibri/utils/i18n.js b/packages/kolibri/utils/i18n.js index e35450247bc..471e9e98d0a 100644 --- a/packages/kolibri/utils/i18n.js +++ b/packages/kolibri/utils/i18n.js @@ -189,9 +189,10 @@ class Translator { } /** - * Returns a Translator instance. - * @param {string} nameSpace - The nameSpace of the messages for translation. - * @param {object} defaultMessages - an object mapping message ids to default messages. + * Create a Translator instance for the given namespace and messages. + * @param {string} nameSpace - The unique namespace for this translator. + * @param {object} defaultMessages - Map of message IDs to default message strings. + * @returns {Translator} A configured Translator instance. */ export function createTranslator(nameSpace, defaultMessages) { return new Translator(nameSpace, defaultMessages); @@ -204,6 +205,7 @@ export function createTranslator(nameSpace, defaultMessages) { * - Use sparingly, e.g. to bypass string freeze * - Try to remove post-string-freeze * @param {Component} Component - An imported component. + * @returns {Translator} A Translator instance for the component. */ export function crossComponentTranslator(Component) { return new Translator(Component.name, Component.$trs); @@ -215,7 +217,7 @@ async function _setUpVueIntl() { * * Note that this _must_ be called after i18nSetup because this function sets up * the currentLanguage module variable which is referenced inside of here. - **/ + */ Vue.use(VueIntl, { defaultLocale }); Vue.prototype.isRtl = languageDirection === 'rtl'; @@ -242,7 +244,7 @@ async function _setUpVueIntl() { export async function i18nSetup(skipPolyfill = false) { /** * Load fonts, app strings, and Intl polyfills - **/ + */ // Set up typography setLanguageDensity(currentLanguage); @@ -272,18 +274,16 @@ export async function i18nSetup(skipPolyfill = false) { * fully support the locale and options parameters of String.prototype.localeCompare. * All other supported browsers (Chrome 49+, Firefox 52+, Safari 11.1+, etc.) * have full support. - * * @param {string} str1 - First string to compare * @param {string} str2 - Second string to compare * @param {string} [locale] - BCP 47 locale code (defaults to currentLanguage) * @param {object} [options] - Comparison options - * @param {string} [options.usage='sort'] - 'sort' for sorting, 'search' for case-insensitive search + * @param {string} [options.usage] - 'sort' for sorting, 'search' for case-insensitive search * @param {string} [options.sensitivity] - 'base', 'accent', 'case', or 'variant' * @param {boolean} [options.ignorePunctuation] - Whether to ignore punctuation * @param {string} [options.numeric] - Whether numeric collation should be used * @param {string} [options.caseFirst] - Whether upper or lower case should sort first * @returns {number} -1 if str1 < str2, 0 if equal, 1 if str1 > str2 - * * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare */ export function localeCompare(str1, str2, locale, options) { @@ -312,10 +312,8 @@ export function formatList(array) { * Sorts an Array of language objects by their `lang_name` property. * If currentLanguageId is truthy and is a language code that exists in * an element of availableLanguages, that element is always sorted first. - * - * @export - * @param {Array} availableLanguages Array of language objects - * @param {(String|null|undefined)} currentLanguageId Lang code for currently + * @param {Array} availableLanguages - Array of language objects + * @param {(string | null | undefined)} currentLanguageId - Lang code for currently * selected language * @returns {Array} Array of sorted language objects with the * currently selected language object first, if one exists. @@ -339,7 +337,6 @@ export function sortLanguages(availableLanguages, currentLanguageId) { /** * Compares two language objects by their lang_name property using locale-aware comparison. * Used for sorting language lists in the language switcher. - * * @param {object} a - First language object with lang_name property * @param {object} b - Second language object with lang_name property * @returns {number} -1 if a < b, 0 if equal, 1 if a > b diff --git a/packages/kolibri/utils/scriptLoader.js b/packages/kolibri/utils/scriptLoader.js index d3c2d2a41ff..f30000bd914 100644 --- a/packages/kolibri/utils/scriptLoader.js +++ b/packages/kolibri/utils/scriptLoader.js @@ -1,7 +1,7 @@ /** * Loads a Javascript file and executes it. - * @param {String} url URL for the script - * @return {Promise} Promise that resolves when the script has loaded + * @param {string} url - URL for the script. + * @returns {Promise} Promise that resolves when the script has loaded. */ export default function scriptLoader(url) { return new Promise((resolve, reject) => { diff --git a/packages/kolibri/utils/validators.js b/packages/kolibri/utils/validators.js index a6d29bd322b..0dcaaf68c07 100644 --- a/packages/kolibri/utils/validators.js +++ b/packages/kolibri/utils/validators.js @@ -5,7 +5,10 @@ import keys from 'lodash/keys'; /** * Validation for vue router "location descriptor objects". - * See e.g. https://router.vuejs.org/en/api/router-link.html + * See e.g. https://router.vuejs.org/en/api/router-link.html. + * @param {object} object - The candidate route descriptor. + * @returns {boolean} True when every key on `object` is a recognised route + * descriptor key (`name`, `path`, `params`, `query`). */ export function validateLinkObject(object) { const validKeys = ['name', 'path', 'params', 'query']; From 436c16c2393292fb3f86e06cf5b0e2aa8284016d Mon Sep 17 00:00:00 2001 From: Richard Tibbles Date: Fri, 24 Apr 2026 18:04:35 -0700 Subject: [PATCH 03/11] Remediate JSDoc violations in packages/kolibri-common Restores typedef references and inline @type annotations that the new JSDoc rules required satisfying; adds typedef imports for previously- undefined types (Vue refs, ContentNode, FetchObject, etc.); fills missing @param descriptions, @throws, and @returns declarations. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../apiResources/ChannelResource.js | 1 - .../ContentNodeGranularResource.js | 1 - .../ContentNodeProgressResource.js | 11 +- .../apiResources/ContentNodeResource.js | 125 +++++++++--------- .../apiResources/MasteryLogResource.js | 15 +-- .../apiResources/NetworkLocationResource.js | 66 ++++----- .../components/CoreFullscreen.vue | 1 + .../components/MetadataChips.vue | 14 +- .../components/MultiPaneLayout.vue | 1 + .../kolibri-common/components/SearchBox.vue | 1 + .../CategorySearchModal/index.vue | 4 +- .../components/SearchFiltersPanel/index.vue | 3 +- .../components/SidePanelModal/index.vue | 4 +- .../__tests__/current-try-overview.spec.js | 8 +- .../FacilityAdminCredentialsForm.vue | 3 +- .../SelectDeviceModalGroup/api.js | 35 +++-- .../useConnectionChecker.js | 22 +-- .../SelectDeviceModalGroup/useDevices.js | 64 +++++---- .../kolibri-common/components/useAccordion.js | 51 ++++--- .../userAccounts/FullNameTextbox.vue | 3 + .../userAccounts/PasswordTextbox.vue | 3 + .../userAccounts/UsernameTextbox.vue | 7 +- .../composables/__mocks__/useChannels.js | 4 +- .../__mocks__/useLearningActivities.js | 2 +- .../composables/__tests__/useFetch.spec.js | 12 +- .../composables/useCoachMetadataTags.js | 9 +- .../kolibri-common/composables/useFacility.js | 53 ++++++-- .../kolibri-common/composables/useFetch.js | 67 ++++------ .../composables/useLearningActivities.js | 34 ++--- .../composables/usePagination.js | 4 + .../composables/usePreviousRoute.js | 15 ++- .../machines/importLodUsersMachine.js | 9 +- packages/kolibri-common/quizzes/utils.js | 22 ++- .../utils/checkMeteredConnection.js | 5 +- .../kolibri-common/utils/picturePassword.js | 3 +- .../utils/samePageCheckGenerator.js | 5 +- 36 files changed, 389 insertions(+), 298 deletions(-) diff --git a/packages/kolibri-common/apiResources/ChannelResource.js b/packages/kolibri-common/apiResources/ChannelResource.js index bb57356da7b..8c2fcc9f89f 100644 --- a/packages/kolibri-common/apiResources/ChannelResource.js +++ b/packages/kolibri-common/apiResources/ChannelResource.js @@ -3,7 +3,6 @@ import { Resource } from 'kolibri/apiResource'; /** * @example Delete a channel * ChannelResource.deleteModel({ id: channel_id }) - * * @example Only get the channels that are "available" (i.e. with resources on device) * ChannelResource.fetchCollection({ getParams: { available: true } }) */ diff --git a/packages/kolibri-common/apiResources/ContentNodeGranularResource.js b/packages/kolibri-common/apiResources/ContentNodeGranularResource.js index 70d54ba8722..1ca82af4bf1 100644 --- a/packages/kolibri-common/apiResources/ContentNodeGranularResource.js +++ b/packages/kolibri-common/apiResources/ContentNodeGranularResource.js @@ -3,7 +3,6 @@ import { Resource } from 'kolibri/apiResource'; /** * @example Get ContentNode from a local USB drive for the purposes of importing from that drive. * ContentNodeGranular.getModel(id, { importing_from_drive_id: 'drive_1' }).fetch(); - * * @example Get ContentNode from a remote channel (whose content DB has been downloaded). * OR exporting to a USB drive. * ContentNodeGranular.getModel(id).fetch(); diff --git a/packages/kolibri-common/apiResources/ContentNodeProgressResource.js b/packages/kolibri-common/apiResources/ContentNodeProgressResource.js index 0adaa21bc7d..ffd770bd739 100644 --- a/packages/kolibri-common/apiResources/ContentNodeProgressResource.js +++ b/packages/kolibri-common/apiResources/ContentNodeProgressResource.js @@ -4,11 +4,12 @@ import urls from 'kolibri/urls'; export default new Resource({ name: 'contentnodeprogress', /** - * A method to request progress for paginated tree data from the backend - * @param {string} id - the id of the parent node for this request - * @param {Object} params - the GET parameters to return more results, - * may be both pagination and non-pagination specific parameters - * @return {Promise} Promise that resolves with the model data + * A method to request progress for paginated tree data from the backend. + * @param {object} options - Request parameters. + * @param {string} options.id - The id of the parent node for this request. + * @param {object} options.params - The GET parameters to return more results; + * may include both pagination and non-pagination specific parameters. + * @returns {Promise} Promise that resolves with the tree progress data. */ fetchTree({ id, params }) { const url = urls['kolibri:core:contentnodeprogress_tree'](id); diff --git a/packages/kolibri-common/apiResources/ContentNodeResource.js b/packages/kolibri-common/apiResources/ContentNodeResource.js index 17e7227ae52..cbec112dbf0 100644 --- a/packages/kolibri-common/apiResources/ContentNodeResource.js +++ b/packages/kolibri-common/apiResources/ContentNodeResource.js @@ -4,78 +4,80 @@ import { Resource } from 'kolibri/apiResource'; import urls from 'kolibri/urls'; /** - * Type definition for Language metadata - * @typedef {Object} Language - * @property {string} id - an IETF language tag - * @property {string} lang_code - the ISO 639‑1 language code - * @property {string} lang_subcode - the regional identifier - * @property {string} lang_name - the name of the language in that language - * @property {('ltr'|'rtl'|)} lang_direction - Direction of the language's script, - * top to bottom is not supported currently + * Type definition for Language metadata. + * @typedef {object} Language + * @property {string} id - An IETF language tag. + * @property {string} lang_code - The ISO 639‑1 language code. + * @property {string} lang_subcode - The regional identifier. + * @property {string} lang_name - The name of the language in that language. + * @property {('ltr'|'rtl')} lang_direction - Direction of the language's script; top to bottom + * is not supported currently. */ /** - * Type definition for AssessmentMetadata - * @typedef {Object} AssessmentMetadata - * @property {string[]} assessment_item_ids - an array of ids for assessment items - * @property {number} number_of_assessments - the length of assessment_item_ids - * @property {Object} mastery_model - object describing the mastery criterion for finishing practice - * @property {boolean} randomize - whether to randomize the order of assessments - * @property {boolean} is_manipulable - Whether this assessment can be programmatically updated + * Type definition for AssessmentMetadata. + * @typedef {object} AssessmentMetadata + * @property {string[]} assessment_item_ids - An array of ids for assessment items. + * @property {number} number_of_assessments - The length of assessment_item_ids. + * @property {object} mastery_model - Object describing the mastery criterion for finishing + * practice. + * @property {boolean} randomize - Whether to randomize the order of assessments. + * @property {boolean} is_manipulable - Whether this assessment can be programmatically updated. */ /** - * Type definition for File - * @typedef {Object} File - * @property {string} id - id of the file object - * @property {string} checksum - md5 checksum of the file, used to generate the file name - * @property {boolean} available - whether the file is available on the server - * @property {number} file_size - file_size in bytes - * @property {string} extension - file extension, also used to generate the file name - * @property {string} preset - preset, the role that the file plays for this content node - * @property {Language|null} lang - The language of the File - * @property {boolean} supplementary - Whether this file is optional - * @property {boolean} thumbnail - Whether this file is a thumbnail + * Type definition for File. + * @typedef {object} File + * @property {string} id - id of the file object. + * @property {string} checksum - md5 checksum of the file, used to generate the file name. + * @property {boolean} available - Whether the file is available on the server. + * @property {number} file_size - file_size in bytes. + * @property {string} extension - File extension, also used to generate the file name. + * @property {string} preset - The role that the file plays for this content node. + * @property {Language|null} lang - The language of the File. + * @property {boolean} supplementary - Whether this file is optional. + * @property {boolean} thumbnail - Whether this file is a thumbnail. */ /** - * Type definition for ContentNode metadata - * @typedef {Object} ContentNode - * @property {string} id - unique id of the ContentNode - * @property {string} channel_id - unique channel_id of the channel that the ContentNode is in - * @property {string} content_id - identifier that is common across all instances of this resource - * @property {string} title - A title that summarizes this ContentNode for the user - * @property {string} description - detailed description of the ContentNode - * @property {string} author - author of the ContentNode + * Type definition for ContentNode metadata. + * @typedef {object} ContentNode + * @property {string} id - Unique id of the ContentNode. + * @property {string} channel_id - Unique channel_id of the channel that the ContentNode is in. + * @property {string} content_id - Identifier that is common across all instances of this resource. + * @property {string} title - A title that summarizes this ContentNode for the user. + * @property {string} description - Detailed description of the ContentNode. + * @property {string} author - Author of the ContentNode. * @property {string} thumbnail_url - URL for the thumbnail for this ContentNode, - * this may be any valid URL format including base64 encoded or blob URL - * @property {boolean} available - Whether the ContentNode has all necessary files for rendering - * @property {boolean} coach_content - Whether the ContentNode is intended only for coach users - * @property {Language|null} lang - The primary language of the ContentNode - * @property {string} license_description - The description of the license, which may be localized - * @property {string} license_name - The human readable name of the license, localized - * @property {string} license_owner - The name of the person or organization that holds copyright - * @property {number} num_coach_contents - Number of coach contents that are descendants of this - * @property {string} parent - The unique id of the parent of this ContentNode + * this may be any valid URL format including base64 encoded or blob URL. + * @property {boolean} available - Whether the ContentNode has all necessary files for rendering. + * @property {boolean} coach_content - Whether the ContentNode is intended only for coach users. + * @property {Language|null} lang - The primary language of the ContentNode. + * @property {string} license_description - The description of the license, which may be localized. + * @property {string} license_name - The human readable name of the license, localized. + * @property {string} license_owner - The name of the person or organization that holds copyright. + * @property {number} num_coach_contents - Number of coach contents that are descendants of this. + * @property {string} parent - The unique id of the parent of this ContentNode. * @property {number} sort_order - The order of display for this node in its channel - * if depth recursion was not deep enough - * @property {string[]} tags - Tags that apply to this content - * @property {boolean} is_leaf - Whether is a leaf resource or not - * @property {AssessmentMetadata|null} assessmentmetadata - Additional metadata for assessments - * @property {File[]} files - array of file objects associated with this ContentNode - * @property {Object[]} ancestors - array of objects with 'id' and 'title' properties - * @property {Children} [children] - optional pagination object with children of this ContentNode + * if depth recursion was not deep enough. + * @property {string[]} tags - Tags that apply to this content. + * @property {boolean} is_leaf - Whether is a leaf resource or not. + * @property {AssessmentMetadata|null} assessmentmetadata - Additional metadata for assessments. + * @property {File[]} files - Array of file objects associated with this ContentNode. + * @property {object[]} ancestors - Array of objects with 'id' and 'title' properties. + * @property {Children} [children] - Optional pagination object with children of this ContentNode. */ /** - * Type definition for children pagination object - * @typedef {Object} Children - * @property {Object} more - parameters for requesting more objects - * @property {string} more.id - the id of the parent of these child nodes - * @property {Object} more.params - the get parameters that should be used for requesting more nodes - * @property {number} more.params.depth - 1 or 2, how deep the nesting should be returned - * @property {number} more.params.lft__gt - integer value to return a lft value greater than - * @property {ContentNode[]} results - the array of ContentNodes for this page + * Type definition for children pagination object. + * @typedef {object} Children + * @property {object} more - Parameters for requesting more objects. + * @property {string} more.id - The id of the parent of these child nodes. + * @property {object} more.params - The get parameters that should be used for requesting more + * nodes. + * @property {number} more.params.depth - 1 or 2, how deep the nesting should be returned. + * @property {number} more.params.lft__gt - Integer value to return a lft value greater than. + * @property {ContentNode[]} results - The array of ContentNodes for this page. */ export default new Resource({ @@ -154,13 +156,6 @@ export default new Resource({ return response.data; }); }, - /** - * A method to request paginated tree data from the backend - * @param {string} id - the id of the parent node for this request - * @param {Object} params - the GET parameters to return more results, - * may be both pagination and non-pagination specific parameters - * @return {Promise} Promise that resolves with the model data - */ fetchTree({ id, params }) { const url = urls['kolibri:core:contentnode_tree_detail'](id); return this.client({ url, params }).then(response => { diff --git a/packages/kolibri-common/apiResources/MasteryLogResource.js b/packages/kolibri-common/apiResources/MasteryLogResource.js index 7d361b426a2..a35303d92dd 100644 --- a/packages/kolibri-common/apiResources/MasteryLogResource.js +++ b/packages/kolibri-common/apiResources/MasteryLogResource.js @@ -8,14 +8,13 @@ export default new Resource({ * with annotations: * - `diff`: object or null, with `correct` and `time_spent` diffs * - `attemptlogs`: list of attempt logs with annotated diff with previous try's attempt - * - * @param {Object} parameters - the parameters to be used for the fetch - * @param {String} parameters.content - the content_id of the relevant assessment - * @param {String} parameters.user - the id of the user - * @param {Number} parameters.back - the integer count back to go - defaults to 0 - * @param {Boolean} parameters.complete - whether to only return completed tries - * @param {Boolean} parameters.quiz - whether this is for a quiz - * @return {Promise} + * @param {object} parameters - the parameters to be used for the fetch + * @param {string} parameters.content - the content_id of the relevant assessment + * @param {string} parameters.user - the id of the user + * @param {number} parameters.back - the integer count back to go - defaults to 0 + * @param {boolean} parameters.complete - whether to only return completed tries + * @param {boolean} parameters.quiz - whether this is for a quiz + * @returns {Promise} Resolves with the mastery log diff payload. */ fetchMostRecentDiff({ content, user, back, complete, quiz } = {}) { return this.client({ diff --git a/packages/kolibri-common/apiResources/NetworkLocationResource.js b/packages/kolibri-common/apiResources/NetworkLocationResource.js index 93ab612bfbf..3f22b3fb743 100644 --- a/packages/kolibri-common/apiResources/NetworkLocationResource.js +++ b/packages/kolibri-common/apiResources/NetworkLocationResource.js @@ -2,27 +2,28 @@ import { Resource } from 'kolibri/apiResource'; import urls from 'kolibri/urls'; /** - * @typedef {Object} NetworkLocation - * @property {string} id - * @property {string} instance_id - * @property {string} device_name - * @property {string} nickname - * @property {string} operating_system - * @property {string} application - * @property {string} kolibri_version - * @property {string} base_url - * @property {string} connection_status - * @property {bool} dynamic - * @property {bool} available - * @property {bool} subset_of_users_device - * @property {string|Date} added - * @property {string|Date} last_accessed - * @property {Number} since_last_accessed - Seconds since `last_accessed` + * @typedef {object} NetworkLocation + * @property {string} id - stable identifier for the network location record + * @property {string} instance_id - Kolibri instance UUID of the remote device + * @property {string} device_name - human-readable name reported by the device + * @property {string} nickname - user-supplied label for the device + * @property {string} operating_system - OS name reported by the device + * @property {string} application - application identifier (e.g. `kolibri`) + * @property {string} kolibri_version - Kolibri version string reported by the device + * @property {string} base_url - reachable base URL for the device + * @property {string} connection_status - current connectivity state + * @property {boolean} dynamic - true when discovered dynamically rather than statically configured + * @property {boolean} available - true when the device is currently reachable + * @property {boolean} subset_of_users_device - true when the remote device is a SoUD + * @property {string|Date} added - timestamp the location was first recorded + * @property {string|Date} last_accessed - timestamp of the last successful contact + * @property {number} since_last_accessed - Seconds since `last_accessed` */ /** - * @param {string} id - * @return {NetworkLocation} + * Refresh the cached connection status for a network location. + * @param {string} id - network location id + * @returns {NetworkLocation} the updated network location record */ function updateConnectionStatus(id) { const url = urls['kolibri:core:networklocation_update_connection_status'](id); @@ -32,8 +33,9 @@ function updateConnectionStatus(id) { } /** - * @param {string} id - * @return {{facilities: [{id: string}]}} + * Fetch the facilities advertised by a remote network location. + * @param {string} id - network location id + * @returns {{facilities: [{id: string}]}} the facilities listing from the remote device */ function fetchFacilities(id) { return this.client({ @@ -49,15 +51,15 @@ export const NetworkLocationResource = new Resource({ fetchFacilities, /** - * @method * @name fetchModel - * @return {NetworkLocation} + * @function + * @returns {NetworkLocation} */ /** - * @method * @name fetchCollection - * @return {NetworkLocation[]} + * @function + * @returns {NetworkLocation[]} */ }); @@ -67,15 +69,15 @@ export const StaticNetworkLocationResource = new Resource({ fetchFacilities, /** - * @method * @name fetchModel - * @return {NetworkLocation} + * @function + * @returns {NetworkLocation} */ /** - * @method * @name fetchCollection - * @return {NetworkLocation[]} + * @function + * @returns {NetworkLocation[]} */ }); @@ -85,14 +87,14 @@ export const DynamicNetworkLocationResource = new Resource({ fetchFacilities, /** - * @method * @name fetchModel - * @return {NetworkLocation} + * @function + * @returns {NetworkLocation} */ /** - * @method * @name fetchCollection - * @return {NetworkLocation[]} + * @function + * @returns {NetworkLocation[]} */ }); diff --git a/packages/kolibri-common/components/CoreFullscreen.vue b/packages/kolibri-common/components/CoreFullscreen.vue index ba7e331e36a..5fbb8785247 100644 --- a/packages/kolibri-common/components/CoreFullscreen.vue +++ b/packages/kolibri-common/components/CoreFullscreen.vue @@ -51,6 +51,7 @@ }, methods: { /** + * Toggle fullscreen mode; called by viewer components via $refs. * @public */ toggleFullscreen() { diff --git a/packages/kolibri-common/components/MetadataChips.vue b/packages/kolibri-common/components/MetadataChips.vue index 7cf6a5f34fe..5057a460566 100644 --- a/packages/kolibri-common/components/MetadataChips.vue +++ b/packages/kolibri-common/components/MetadataChips.vue @@ -26,15 +26,19 @@