diff --git a/README.md b/README.md index 95e591be..a86143c1 100644 --- a/README.md +++ b/README.md @@ -91,34 +91,4 @@ We have added comments in the code to make it explicit which parts are 100% writ * Raptor mini: Update the code to use the new solid-ui-header component. Keep in mind the log in and sign up are wired in specific ways. -* Auto: change the menu to fill up the menu items like in the code: async function getMenuItems (outliner: any) { -const items = await outliner.getDashboardItems() -return items.map((element) => { -return { -label: element.label, -onclick: () => openDashboardPane(outliner, element.tabName || element.paneName) -} -}) -} - -* Auto: each #sym:MenuItem has an icon which i want displayed on the left side of each menu item when rendered - -* Auto: don't add each menu item in a button looking border. Simply list them. -Upon hover apply background color e6dcff and selected or active to be background color: cbb9ff - -* Raptor mini: the menu dissapears when on mobile. That is great. -I want the menu to have a tiny button on the bottom margin left with an arrow to the left or right for expanding the menu or for making it small. This is only for web. When we make it small it folds and only displays the icons of teh menu items, when it is expanded it should also add the labels to the menu items. - -* Raptor mini: i want to imporve the left side menu on mobile. When the menu is visible it should be higher, the top part should be on top of the header, folding out and in with the rest of the menu. It should have an x close button and it should say menu. The locor of that top line of the header should be the color of the header 332746 - -* Raptor mini: the menu, on desktop, has a button to fold in or out. I want the same behavior to occur also when i simply click the folded in menu: it should expend. Not just when i click the dedicated button - -* Raprot mini: instead of this code (index.ts of footer), I want to make use of a new footer web component with the readme: # solid-ui-footer component - -* Raptor mini: The footer created should actually be part of the left side menu only. Should be displayed inside it and should collaps and expand as the menu. - -* Raptor mini: the footer should completely dissapear when menu folded up. - -* Raptor mini: Please always keep the footer at the bottom of the menu - * GPT-5.4 Model: Add a compatibility shim in the form pane for mixed `ui:Group` plus field typing. diff --git a/package-lock.json b/package-lock.json index fbf7fb79..5afb7f79 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,11 +21,11 @@ "meeting-pane": "3.0.3-1", "mime-types": "^3.0.2", "pane-registry": "3.1.2-2", - "profile-pane": "3.2.3-2", + "profile-pane": "3.2.3-3", "rdflib": "2.4.0", "solid-logic": "4.0.8-1", "solid-namespace": "^0.5.4", - "solid-ui": "3.1.3-12", + "solid-ui": "3.1.3-13", "source-pane": "3.1.1-4" }, "devDependencies": { @@ -3640,17 +3640,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.64.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.64.0.tgz", - "integrity": "sha512-CGvQPBxN3wZLu6Rz2kFUpZeoCm78xUic92ck39KPePkO1NPOwjCqdQnm5Q87tpWw9vcBvW8XLrDXjH9PWYtJ3Q==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.65.0.tgz", + "integrity": "sha512-IEgob78X12rHpUmtcwFsXhZdVGJtwTVP8FiCLZkR6GlYVrl2PcuB+KhCE5BlVC/eQpQnu8WXRtkHZuPar+gCRA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.64.0", - "@typescript-eslint/type-utils": "8.64.0", - "@typescript-eslint/utils": "8.64.0", - "@typescript-eslint/visitor-keys": "8.64.0", + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/type-utils": "8.65.0", + "@typescript-eslint/utils": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" @@ -3663,7 +3663,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.64.0", + "@typescript-eslint/parser": "^8.65.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } @@ -3679,15 +3679,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.64.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.64.0.tgz", - "integrity": "sha512-KA0OshtlcCCXmbfqyZkM5pV3/WNraJf7DkJRLpyrmwPtud57H5BDX7C3k0LPSPxpprfRL+cJDGabF10mvNCoCw==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.65.0.tgz", + "integrity": "sha512-CZ4nMxWwgu1HEEFNkeaCptra9QCtkmKdgf3sWh1rl1trIhmxLilgTV4cwcbQ4wemnT4sWQN8CaKOmdYx+g2gMA==", "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.64.0", - "@typescript-eslint/types": "8.64.0", - "@typescript-eslint/typescript-estree": "8.64.0", - "@typescript-eslint/visitor-keys": "8.64.0", + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", "debug": "^4.4.3" }, "engines": { @@ -3703,13 +3703,13 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.64.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.64.0.tgz", - "integrity": "sha512-tk4WpOJ6IEbGrVHaNmM0YRrwAD3exZlIK3iadQNAxh4YKk6jvUQ4ecq18n+v7+meh+cJ3j+D8nbk8sRKhlwLQg==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.65.0.tgz", + "integrity": "sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q==", "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.64.0", - "@typescript-eslint/types": "^8.64.0", + "@typescript-eslint/tsconfig-utils": "^8.65.0", + "@typescript-eslint/types": "^8.65.0", "debug": "^4.4.3" }, "engines": { @@ -3724,13 +3724,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.64.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.64.0.tgz", - "integrity": "sha512-CXEaFdYXjSTgKhisNkwCcJwTP8Pl+fmRrEQrri4nm3vU743bALrxzLmq7fHG/7e6a5xO0lDYeURpZmBuhHk54w==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.65.0.tgz", + "integrity": "sha512-Esbl8OSYiVxBokYgWPf7VVWg/BE798wXhimnn9ML9Pt5qoDf8bfQlgjlKXR/k98+AcNzlLKYrpCcrcuZ9DZLgg==", "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.64.0", - "@typescript-eslint/visitor-keys": "8.64.0" + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3741,9 +3741,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.64.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.64.0.tgz", - "integrity": "sha512-2yo8rRNKuzbVWQp5kslhANqZ2uDAeROQHBRZNPu8JDsHmeFNj/XJJhX/FhNUWmkHHvoNsKa6+tHJiig87EzsQw==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.65.0.tgz", + "integrity": "sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg==", "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3757,15 +3757,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.64.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.64.0.tgz", - "integrity": "sha512-XWG4Fmmv/6SvyS9nH8jWrKs6terwJvE8cyRt1CzYYqzp9OrPhCT4cMc/f7C6RZCwG+qMmiffJS1/qJP8G1URtg==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.65.0.tgz", + "integrity": "sha512-YjaZ7PRI5qY7ax2L3PbvX0rRyGtipAReCWs0mhhDBHjH/vl0g0BonaGXrKdKpMbIIsMIwDgbk/xzkBTyAltS5g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.64.0", - "@typescript-eslint/typescript-estree": "8.64.0", - "@typescript-eslint/utils": "8.64.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0", + "@typescript-eslint/utils": "8.65.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, @@ -3782,9 +3782,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.64.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.64.0.tgz", - "integrity": "sha512-qjhfuTfLXjA4IOzXvz0rTjT01BqEiIgPoUeMwiEjnaHKJMTNo8rH5pYW1a2L/0Dnux2fPC85AeyJoWaGa8WxTA==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.65.0.tgz", + "integrity": "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==", "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3795,15 +3795,15 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.64.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.64.0.tgz", - "integrity": "sha512-Pztpsn1aCE1oWDvDEfUk31nngvvF7vUB5SwHFEaZIFpvw7WJtqUHHL4plBZDA9HfWJJjL13BdG0YrJInTUvoVA==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.65.0.tgz", + "integrity": "sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg==", "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.64.0", - "@typescript-eslint/tsconfig-utils": "8.64.0", - "@typescript-eslint/types": "8.64.0", - "@typescript-eslint/visitor-keys": "8.64.0", + "@typescript-eslint/project-service": "8.65.0", + "@typescript-eslint/tsconfig-utils": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -3822,16 +3822,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.64.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.64.0.tgz", - "integrity": "sha512-aJUGVB3+U0htrrCjoA8qukw8cm8fNCGAxK/tVoS70k8aeb7DETKeFozRiVFIwEeN9WJLsjaP3ph8I60tY2XZoQ==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.65.0.tgz", + "integrity": "sha512-gXiwIHsYreboxeJucHKPvgwl7dXt50mF8s1/c00cP/WoVTyWKFdtfhRWwZiXYFU5H2O8vVoSLNrexFZjYS/SGA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.64.0", - "@typescript-eslint/types": "8.64.0", - "@typescript-eslint/typescript-estree": "8.64.0" + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3846,12 +3846,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.64.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.64.0.tgz", - "integrity": "sha512-mrtuL8Nsn6gi2H4mo5KMTp823M+3Q19Ew/i+Zlikq20tIMm99C3Ez0dCmkWWnxut20esQvTg8aUSEhMcAOXhEw==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.65.0.tgz", + "integrity": "sha512-8C71BQkGjiMmXtop7pHVJu1l2NNShFdkCyD6a2ezzs5vU/L3LRtb69EtcteFwz0mYMPzIgOw0n6OV4VBUWZd7A==", "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.64.0", + "@typescript-eslint/types": "8.65.0", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -4490,9 +4490,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.10.43", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.43.tgz", - "integrity": "sha512-AjYpR78kDWAY3Efj+cDTFH9t9SCoL7OoTp1BOb0mQV7S+6CiLwnWM3FyxhJtdPufDFKzmCSFoUncKjWgJEZTCQ==", + "version": "2.10.44", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.44.tgz", + "integrity": "sha512-T3ghW+sl/ZJ8w1v/yQx3qvJ9040DWoLBz8JT/CILbAKcFyG9b2MRe75v6W5uXjv6uH1lumK2Kv46y2zSkcej0Q==", "dev": true, "license": "Apache-2.0", "bin": { @@ -5248,9 +5248,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.393", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.393.tgz", - "integrity": "sha512-kiDJdIUawuEIcp9XoICKp1iTYDEbgguIPq526N1Q7jIQDeQ3CqoMx71025PI/7E48Ddtw2HuWsVjY7afEgNxmg==", + "version": "1.5.394", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.394.tgz", + "integrity": "sha512-Wmt2Gm0o8JWBuGgmc4XZ0u9s1RaCRqhxP47phplmfg04+qypTUurpeJGP45A7Fhv7jdrrVH44PLlR9qXo37cVQ==", "dev": true, "license": "ISC" }, @@ -5261,9 +5261,9 @@ "license": "MIT" }, "node_modules/enhanced-resolve": { - "version": "5.24.2", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.2.tgz", - "integrity": "sha512-rpsZEGT1jFuve6QlpyRp9ckQ+kN61hvF9BzCPyMdaKTm8UJce96KBn3sorXOFXlzjPrs3Vc4T1NsSroZ3PxlFw==", + "version": "5.24.3", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.3.tgz", + "integrity": "sha512-PwKooW9JUzh5chmYfHM3IQl5OkK2u2Nm011MgeZrss3JmFraUx/fqrf78kk8GUMYoibx/14MdwTl/1WKkG7TpQ==", "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", @@ -7786,9 +7786,9 @@ } }, "node_modules/lightningcss": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", - "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", "license": "MPL-2.0", "dependencies": { "detect-libc": "^2.0.3" @@ -7801,23 +7801,23 @@ "url": "https://opencollective.com/parcel" }, "optionalDependencies": { - "lightningcss-android-arm64": "1.32.0", - "lightningcss-darwin-arm64": "1.32.0", - "lightningcss-darwin-x64": "1.32.0", - "lightningcss-freebsd-x64": "1.32.0", - "lightningcss-linux-arm-gnueabihf": "1.32.0", - "lightningcss-linux-arm64-gnu": "1.32.0", - "lightningcss-linux-arm64-musl": "1.32.0", - "lightningcss-linux-x64-gnu": "1.32.0", - "lightningcss-linux-x64-musl": "1.32.0", - "lightningcss-win32-arm64-msvc": "1.32.0", - "lightningcss-win32-x64-msvc": "1.32.0" + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" } }, "node_modules/lightningcss-android-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", - "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", "cpu": [ "arm64" ], @@ -7835,9 +7835,9 @@ } }, "node_modules/lightningcss-darwin-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", - "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", "cpu": [ "arm64" ], @@ -7855,9 +7855,9 @@ } }, "node_modules/lightningcss-darwin-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", - "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", "cpu": [ "x64" ], @@ -7875,9 +7875,9 @@ } }, "node_modules/lightningcss-freebsd-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", - "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", "cpu": [ "x64" ], @@ -7895,9 +7895,9 @@ } }, "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", - "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", "cpu": [ "arm" ], @@ -7915,9 +7915,9 @@ } }, "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", - "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", "cpu": [ "arm64" ], @@ -7935,9 +7935,9 @@ } }, "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", - "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", "cpu": [ "arm64" ], @@ -7955,9 +7955,9 @@ } }, "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", - "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", "cpu": [ "x64" ], @@ -7975,9 +7975,9 @@ } }, "node_modules/lightningcss-linux-x64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", - "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", "cpu": [ "x64" ], @@ -7995,9 +7995,9 @@ } }, "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", - "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", "cpu": [ "arm64" ], @@ -8015,9 +8015,9 @@ } }, "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", - "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", "cpu": [ "x64" ], @@ -8205,9 +8205,9 @@ } }, "node_modules/marked": { - "version": "18.0.6", - "resolved": "https://registry.npmjs.org/marked/-/marked-18.0.6.tgz", - "integrity": "sha512-MrV5puXBfuiy6wl6DLaq3BtIJQAJToAd5zt/ZKhRfGRAuFPALE7/4Y7jnxRQoEgK/pBgurGqLyAuRgZ2xOjr6w==", + "version": "18.0.7", + "resolved": "https://registry.npmjs.org/marked/-/marked-18.0.7.tgz", + "integrity": "sha512-iDVQ5ldaiKXn6b2JroX5kgRfmwgqolW7NpaEzTl1k/2Zh1njIEN9yniyLV/mOvWwtsE8OGgkjsCYvijuPk1dtA==", "license": "MIT", "bin": { "marked": "bin/marked.js" @@ -9177,9 +9177,9 @@ } }, "node_modules/postcss": { - "version": "8.5.19", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.19.tgz", - "integrity": "sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ==", + "version": "8.5.21", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.21.tgz", + "integrity": "sha512-v4sDNP3fdNiWMfabO7OwOQdOX8TiQSztKyT1Wj0w+j7LDallJThJRBBBmzVGyYj0crMh7jlV4zepPkiNu9UwDQ==", "funding": [ { "type": "opencollective", @@ -9196,7 +9196,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.12", + "nanoid": "^3.3.16", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -9313,17 +9313,24 @@ "license": "MIT" }, "node_modules/profile-pane": { - "version": "3.2.3-2", - "resolved": "https://registry.npmjs.org/profile-pane/-/profile-pane-3.2.3-2.tgz", - "integrity": "sha512-SbDODI0Z9rxJROS4wNtXktPUXWR+e7ZDtCxFBW+OWjQCYB6JRJe2ZRnAIwXH8dvoRewC+9hKWmUVO2wvS+PK2g==", + "version": "3.2.3-3", + "resolved": "https://registry.npmjs.org/profile-pane/-/profile-pane-3.2.3-3.tgz", + "integrity": "sha512-sT/gcJInMlLTtLWrbmeoWFjIPKbfL6OWSVqASVYuBe6Nyb0x6GmVLkBNsBmDrdrJNqVyzVfE9KMAukXc4KEIaQ==", "license": "MIT", "dependencies": { "@solid-data-modules/contacts-rdflib": "^0.7.1", + "chat-pane": "3.0.4-2", "lit-html": "^3.3.3", - "pane-registry": "3.1.2-1", + "pane-registry": "3.1.2-2", "patch-package": "^8.0.1", "qrcode": "^1.5.4", - "validate-color": "^2.2.4" + "rdflib": "2.4.0", + "solid-logic": "4.0.8-1", + "solid-ui": "3.1.3-13", + "validate-color": "^2.2.4", + "vite": "^8.0.16", + "vitest": "^4.0.18", + "vitest-axe": "^0.1.0" } }, "node_modules/prop-types": { @@ -10147,9 +10154,9 @@ "license": "MIT" }, "node_modules/solid-ui": { - "version": "3.1.3-12", - "resolved": "https://registry.npmjs.org/solid-ui/-/solid-ui-3.1.3-12.tgz", - "integrity": "sha512-V3ovEE0JQpDmTji/Cac+SMuIJU8R1Y6/c6puIeb/ExCIJTak8voXxsp0s+1JOgBsmzH27srApNe9lHJF0+09OQ==", + "version": "3.1.3-13", + "resolved": "https://registry.npmjs.org/solid-ui/-/solid-ui-3.1.3-13.tgz", + "integrity": "sha512-0meJycoMXoelTD06W9A0S9Q4vg0I8BDSsui4BfwocfwFIpEZ02FBFZZf1LcanP7NNpAOs0So6HOQp4QlwSJE3w==", "license": "MIT", "dependencies": { "@awesome.me/webawesome": "^3.9.0", @@ -10826,16 +10833,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.64.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.64.0.tgz", - "integrity": "sha512-0qg+pDNMnqYzqH9AnNK+39tejHvsShUOUUoRUgtnTGE7QuMZhiFDnozq8nHJVq+Wae6NMLKNWLg5WmkcC/ndyQ==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.65.0.tgz", + "integrity": "sha512-/ggrHAwyjENDusvyxbuqxAC2dTnZg/Z8F+fgQtYIz+L6n/9HfSlEZcFGV/NsMNa6CkGk0xUjUAFwC0vHOflvIA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.64.0", - "@typescript-eslint/parser": "8.64.0", - "@typescript-eslint/typescript-estree": "8.64.0", - "@typescript-eslint/utils": "8.64.0" + "@typescript-eslint/eslint-plugin": "8.65.0", + "@typescript-eslint/parser": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0", + "@typescript-eslint/utils": "8.65.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" diff --git a/package.json b/package.json index f539e379..0488fe62 100644 --- a/package.json +++ b/package.json @@ -68,11 +68,11 @@ "meeting-pane": "3.0.3-1", "mime-types": "^3.0.2", "pane-registry": "3.1.2-2", - "profile-pane": "3.2.3-2", + "profile-pane": "3.2.3-3", "rdflib": "2.4.0", "solid-logic": "4.0.8-1", "solid-namespace": "^0.5.4", - "solid-ui": "3.1.3-12", + "solid-ui": "3.1.3-13", "source-pane": "3.1.1-4" }, "overrides": { diff --git a/src/mainPage/footer.ts b/src/mainPage/footer.ts deleted file mode 100644 index 57511c0a..00000000 --- a/src/mainPage/footer.ts +++ /dev/null @@ -1,39 +0,0 @@ -import 'solid-ui/components/footer' -import type { LiveStore } from 'rdflib' - -declare module 'solid-ui/components/footer' -import 'solid-ui/components/footer' - -type FooterElement = HTMLElement & { - store?: LiveStore | null - position?: 'static' | 'absolute' | 'relative' | 'fixed' | 'sticky' - top?: string - right?: string - bottom?: string - left?: string -} - -export function createFooter (store: LiveStore) { - let footer = document.querySelector('solid-ui-footer') as FooterElement | null - - if (!footer) { - footer = document.createElement('solid-ui-footer') as FooterElement - footer.className = 'menu-footer' - footer.style.position = 'static' - footer.style.width = '100%' - const navMenu = document.getElementById('NavMenu') - if (navMenu) { - navMenu.appendChild(footer) - } else { - document.body.appendChild(footer) - } - } else { - const navMenu = document.getElementById('NavMenu') - if (navMenu && footer.parentElement !== navMenu) { - navMenu.appendChild(footer) - } - } - - footer.store = store - return footer -} diff --git a/src/mainPage/header.ts b/src/mainPage/header.ts index 3a661f0b..c2ac2ec8 100644 --- a/src/mainPage/header.ts +++ b/src/mainPage/header.ts @@ -1,11 +1,13 @@ import { authn } from 'solid-logic' import { html, render } from 'lit-html' import type { AccountMenuItem } from 'solid-ui/components/account' - -import { setActiveMenuPane } from './menu' import type { OutlineManager } from '../outline/manager' import '~icons/lucide/user' +import '~icons/lucide/users' +import '~icons/lucide/folder-open' +import '~icons/lucide/layout-dashboard' +import '~icons/lucide/settings-2' import '../components/header' export async function createHeader (outliner: OutlineManager) { @@ -25,10 +27,49 @@ export async function createHeader (outliner: OutlineManager) { if (currentUser) { outliner.showDashboard(currentUser, { pane: 'profile' }) - setActiveMenuPane('profile') } } - } + }, + { + label: html` Friends`, + onSelected () { + const currentUser = authn.currentUser() + + if (currentUser) { + outliner.showDashboard(currentUser, { pane: 'social' }) + } + } + }, + { + label: html` Storage`, + onSelected () { + const currentUser = authn.currentUser() + + if (currentUser) { + outliner.showDashboard(currentUser, { pane: 'folder' }) + } + } + }, + { + label: html` Dashboard`, + onSelected () { + const currentUser = authn.currentUser() + + if (currentUser) { + outliner.showDashboard(currentUser, { pane: 'home' }) + } + } + }, + { + label: html` Preferences`, + onSelected () { + const currentUser = authn.currentUser() + + if (currentUser) { + outliner.showDashboard(currentUser, { pane: 'basicPreferences' }) + } + } + }, ] render( diff --git a/src/mainPage/index.ts b/src/mainPage/index.ts index 7af05d2b..e19cd646 100644 --- a/src/mainPage/index.ts +++ b/src/mainPage/index.ts @@ -1,14 +1,12 @@ /* Main Page ** - ** This code is called in mashlib and renders the header and footer of the Databrowser. + ** This code is called in mashlib and renders the header and left side menu of the Databrowser. */ import { LiveStore, NamedNode } from 'rdflib' import type { RenderEnvironment } from 'pane-registry' import { getOutliner, OutlineManager } from '../index' import { createHeader } from './header' -import { createFooter } from './footer' -import { createLeftSideMenu, refreshMenu } from './menu' // Symbol used to stash the last render-relevant env snapshot on the outliner // so refreshUI can skip a full GotoSubject re-render when nothing changed. @@ -19,8 +17,6 @@ function renderEnvSignature (env?: RenderEnvironment): string { return [env.layout, env.theme, env.inputMode].join('|') } -export { refreshMenu as updateMenuLayout } from './menu' - function ensureMainContent () { let main = document.getElementById('MainContent') as HTMLElement | null if (!main) { @@ -47,9 +43,7 @@ export async function initMainPage ( outliner.GotoSubject(subject, true, undefined, true, undefined) const header = await createHeader(outliner) - const menu = createLeftSideMenu(subject, outliner) - const footer = menu.then(() => createFooter(store)) - return Promise.all([header, menu, footer]) + return Promise.all([header]) } export async function refreshUI (outliner: OutlineManager) { @@ -69,6 +63,4 @@ export async function refreshUI (outliner: OutlineManager) { outliner.GotoSubject(store.sym(subjectUri), true, pane, true, undefined) ;(outliner as any)[LAST_RENDER_ENV_KEY] = currentSignature } - - refreshMenu(outliner.context.environment?.layout === 'mobile' ? 'mobile' : 'desktop') } diff --git a/src/mainPage/menu.css b/src/mainPage/menu.css deleted file mode 100644 index 4c3b8df2..00000000 --- a/src/mainPage/menu.css +++ /dev/null @@ -1,243 +0,0 @@ -.menu-content { - display: flex; - flex-direction: column; - gap: 0.5rem; - min-height: 0; - overflow-y: auto; -} - -.app-nav.mobile-hidden .menu-content, -.app-nav.mobile-visible .menu-content { - padding-top: 0.75rem; -} - -.menu-footer { - width: 100%; - padding: 1rem; - border-top: 1px solid var(--color-border, #d1d1d1); - font-size: 0.875rem; - color: var(--color-text-muted, #555); - line-height: 1.4; - background: inherit; -} - -.app-nav.collapsed .menu-footer { - display: none; - text-align: center; - padding: 0.75rem 0; -} - -.menu-item { - display: flex; - align-items: center; - gap: var(--spacing-2xs, 0.625rem); - border: 0; - border-radius: var(--border-radius-sm, 0.2rem); - padding: 0.5rem var(--spacing-xs, 0.75rem); - background: transparent; - color: var(--color-text-heading, #000000); - text-decoration: none; - width: 100%; - text-align: left; - cursor: pointer; -} - -.menu-toggle-icon { - width: 30px; - height: 30px; -} - -.menu-item-icon { - width: var(--icon-xxs, 1rem); - height: var(--icon-xxs, 1rem); - flex: 0 0 var(--icon-xxs, 1rem); - object-fit: contain; -} - -.menu-item-label { - min-width: 0; -} - -.menu-item:hover, -.menu-item:focus { - background: var(--color-header-menu-item-hover, #e6dcff); -} - -.menu-item-active, -.menu-item-active:hover, -.menu-item-active:focus { - background: var(--color-header-menu-item-selected, #cbb9ff); - color: var(--color-header-menu-item-text-selected, #7c4cff); -} - -.menu-toggle { - position: fixed; - top: 0.5rem; - left: 1rem; - z-index: 300; - display: inline-flex; - align-items: center; - justify-content: center; - gap: 0.375rem; - min-width: var(--icon-md, 2.5rem); - height: var(--icon-md, 2.5rem); - padding: 0 0.625rem; - border: 1px solid var(--color-border); - background: var(--color-menu-bg, #ffffff); - color: var(--color-text); - border-radius: var(--border-radius-sm, 0.2rem); - margin: 0; -} - -.menu-toggle-label { - font-size: 1.2rem; - font-weight: 600; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - max-width: 10rem; -} - -.menu-overlay { - position: fixed; - inset: 0; - background: rgba(0, 0, 0, 0.35); - z-index: 100; -} - -.app-nav { - position: fixed; - left: 0; - top: 0; - bottom: 0; - width: min(18rem, 320px); - display: grid; - grid-template-rows: 1fr auto; - overflow: hidden; - background: var(--color-menu-bg, #ffffff); - border-right: 1px solid var(--color-border); - z-index: 10; -} - -.app-nav.mobile-hidden, -.app-nav.mobile-visible { - position: fixed; - left: 0; - top: 0; - bottom: 0; - width: min(80vw, 320px); - height: 100vh; - min-height: 100vh; - transform: translateX(-100%); - transition: transform 0.25s ease-in-out; - z-index: 999; - display: flex; - flex-direction: column; - background: var(--color-menu-bg, #ffffff); - border-right: none; - box-shadow: none; - overflow: hidden; - padding-top: 3.75rem; -} - -.app-nav.mobile-hidden .menu-content, -.app-nav.mobile-visible .menu-content { - flex: 1; - min-height: 0; -} - -.app-nav.mobile-visible { - transform: translateX(0); - visibility: visible; - background: var(--color-menu-bg, #ffffff); -} - -.menu-header { - display: none; - position: absolute; - top: 0; - left: 0; - right: 0; - align-items: center; - justify-content: flex-start; - background-color: #42345a !important; - color: #ffffff; - font-weight: 600; - min-height: 3.75rem; - height: 3.75rem; - box-sizing: border-box; - width: 100%; - z-index: 1000; - border-right: none; - box-shadow: none; -} - -.app-nav.mobile-hidden .menu-header, -.app-nav.mobile-visible .menu-header { - display: flex; -} - -.menu-header-title { - font-size: 1.25rem; - color: #ffffff; -} - -.menu-close { - border: none; - background: transparent; - color: #ffffff; - font-size: 1.25rem; - cursor: pointer; - padding-left: 1.25rem; - line-height: 1; -} - -.menu-close:hover, -.menu-close:focus { - opacity: 0.85; -} - -.app-nav.collapsed { - width: 4.5rem; - min-width: 4.5rem; -} - -.app-nav.collapsed .menu-content { - gap: 0.25rem; - align-items: center; -} - -.app-nav.collapsed .menu-item { - justify-content: center; - padding-left: 0.5rem; - padding-right: 0.5rem; -} - -.app-nav.collapsed .menu-item-label { - display: none; -} - -.menu-collapse { - position: fixed; - left: 3rem; - bottom: 8rem; - z-index: 320; - width: 1.25rem; - height: 1.25rem; - display: inline-flex; - align-items: center; - justify-content: center; - border: 1px solid var(--color-border); - border-radius: 999px; - color: var(--color-text); - font-size: 1.3rem; - line-height: 1; - cursor: pointer; - box-shadow: 0 1px 4px rgba(124,77,255,0.12); -} - -@media (max-width: 768px) { - .menu-collapse { - display: none; - } -} \ No newline at end of file diff --git a/src/mainPage/menu.ts b/src/mainPage/menu.ts deleted file mode 100644 index c5d8989b..00000000 --- a/src/mainPage/menu.ts +++ /dev/null @@ -1,513 +0,0 @@ -import './menu.css' -import { OutlineManager } from '../outline/manager' -import { authSession, authn } from 'solid-logic' -import { NamedNode, sym } from 'rdflib' -import { loadProfileFromURI } from '../profileUtils/ownerProfile' -import menuIcon from '../icons/menu.svg?raw' -import { createUiIcon } from '../icons/iconHelper' - -type MenuItem = { - id?: string - icon?: string - paneName?: string - subject?: NamedNode - label: string - onclick: () => void | Promise -} - -const MENU_ICON = createUiIcon(menuIcon, 'Menu Icon', '#ffffff') -const MENU_COLLAPSED_KEY = 'solid-panes-menu-collapsed' -let menuCollapsed = false - -const loadMenuCollapsedState = (): boolean => { - try { - return localStorage.getItem(MENU_COLLAPSED_KEY) === 'true' - } catch (error) { - return false - } -} - -const saveMenuCollapsedState = (collapsed: boolean): void => { - try { - localStorage.setItem(MENU_COLLAPSED_KEY, String(collapsed)) - } catch (error) { - // ignore storage errors - } -} - -const updateMenuCollapseButton = (): void => { - const collapseBtn = document.getElementById('MenuCollapseBtn') as HTMLButtonElement | null - if (!collapseBtn) return - collapseBtn.textContent = menuCollapsed ? '\u203A' : '\u2039' - collapseBtn.setAttribute('aria-label', menuCollapsed ? 'Expand navigation menu' : 'Collapse navigation menu') -} - -const updateCollapseButtonPosition = (navMenu: HTMLElement | null, collapseBtn: HTMLButtonElement | null): void => { - if (!navMenu || !collapseBtn) return - const navRect = navMenu.getBoundingClientRect() - const rootFontSize = parseFloat(getComputedStyle(document.documentElement).fontSize) || 16 - const buttonWidth = parseFloat(getComputedStyle(collapseBtn).width) || 1.25 * rootFontSize - const offsetRem = (navRect.width - buttonWidth / 2) / rootFontSize - collapseBtn.style.setProperty('left', `${offsetRem.toFixed(3)}rem`, 'important') -} - -const applyMenuCollapsedState = (navMenu: HTMLElement | null): void => { - if (!navMenu) return - navMenu.classList.toggle('collapsed', menuCollapsed) - updateMenuCollapseButton() - const collapseBtn = document.getElementById('MenuCollapseBtn') as HTMLButtonElement | null - updateCollapseButtonPosition(navMenu, collapseBtn) -} - -const refreshAuthStateFromSession = async (): Promise => { - try { - const webId = await authn.checkUser() - return Boolean(webId || authn.currentUser()) - } catch { - // Keep the menu responsive even if auth refresh is transiently unavailable. - return Boolean(authn.currentUser()) - } -} - -const isLoggedIn = (): boolean => Boolean(authn.currentUser()) - -const setFooterVisibility = (loggedIn: boolean): void => { - const footer = document.querySelector('solid-ui-footer') as HTMLElement | null - if (!footer) return - footer.style.display = loggedIn ? 'none' : '' -} - -const isViewingOwnProfile = (subject: NamedNode): boolean => { - const currentUser = authn.currentUser() - return Boolean(currentUser && subject && currentUser.sameTerm(subject)) -} - -const ensureMenuSkeleton = () => { - menuCollapsed = loadMenuCollapsedState() - const root = document.querySelector('[role="main"]') || document.body - - let navMenu = document.getElementById('NavMenu') as HTMLElement | null - if (!navMenu) { - navMenu = document.createElement('nav') - navMenu.id = 'NavMenu' - navMenu.className = 'app-nav' - navMenu.setAttribute('aria-label', 'App navigation') - navMenu.hidden = true - - const headerEl = document.createElement('div') - headerEl.className = 'menu-header' - - const closeBtn = document.createElement('button') - closeBtn.id = 'MenuCloseBtn' - closeBtn.className = 'menu-close' - closeBtn.type = 'button' - closeBtn.setAttribute('aria-label', 'Close menu') - closeBtn.textContent = '✕' - headerEl.appendChild(closeBtn) - - const title = document.createElement('span') - title.className = 'menu-header-title' - title.textContent = 'Menu' - headerEl.appendChild(title) - - navMenu.appendChild(headerEl) - - const authStateEl = document.createElement('div') - authStateEl.id = 'AuthState' - authStateEl.className = 'menu-auth-state' - navMenu.appendChild(authStateEl) - - const contentEl = document.createElement('div') - contentEl.id = 'NavMenuContent' - contentEl.className = 'menu-content' - navMenu.appendChild(contentEl) - - root.insertBefore(navMenu, root.firstChild) - } else if (!navMenu.querySelector('.menu-header')) { - const headerEl = document.createElement('div') - headerEl.className = 'menu-header' - - const closeBtn = document.createElement('button') - closeBtn.id = 'MenuCloseBtn' - closeBtn.className = 'menu-close' - closeBtn.type = 'button' - closeBtn.setAttribute('aria-label', 'Close menu') - closeBtn.textContent = '✕' - headerEl.appendChild(closeBtn) - - const title = document.createElement('span') - title.className = 'menu-header-title' - title.textContent = 'Menu' - headerEl.appendChild(title) - - navMenu.insertBefore(headerEl, navMenu.firstChild) - } - - let toggle = document.getElementById('MenuToggleBtn') as HTMLButtonElement | null - if (!toggle) { - toggle = document.createElement('button') - toggle.id = 'MenuToggleBtn' - toggle.className = 'menu-toggle' - toggle.type = 'button' - toggle.setAttribute('aria-label', 'Toggle navigation menu') - toggle.hidden = true - const toggleImg = document.createElement('img') - toggleImg.src = MENU_ICON - toggleImg.alt = '' - toggleImg.className = 'menu-toggle-icon' - toggleImg.setAttribute('aria-hidden', 'true') - toggle.appendChild(toggleImg) - const toggleLabel = document.createElement('span') - toggleLabel.id = 'MenuToggleLabel' - toggleLabel.className = 'menu-toggle-label' - toggle.appendChild(toggleLabel) - root.insertBefore(toggle, root.firstChild) - } - - let overlay = document.getElementById('MenuOverlay') as HTMLElement | null - if (!overlay) { - overlay = document.createElement('div') - overlay.id = 'MenuOverlay' - overlay.className = 'menu-overlay' - overlay.hidden = true - document.body.appendChild(overlay) - } - - let collapseBtn = document.getElementById('MenuCollapseBtn') as HTMLButtonElement | null - if (!collapseBtn) { - collapseBtn = document.createElement('button') - collapseBtn.id = 'MenuCollapseBtn' - collapseBtn.className = 'menu-collapse' - collapseBtn.type = 'button' - collapseBtn.setAttribute('aria-label', 'Collapse navigation menu') - collapseBtn.textContent = '\u2039' - collapseBtn.hidden = true - document.body.appendChild(collapseBtn) - } -} - -const getMenuItems = async (subject: NamedNode, outliner: any): Promise => { - try { - const items = await outliner.getDashboardItems(subject) - return items.map((element) => { - const targetSubject = element.subject || (authn.currentUser() || subject) - return { - icon: element.icon, - subject: targetSubject, - paneName: element.tabName || element.paneName, - label: element.label, - onclick: () => openDashboardPane(targetSubject, outliner, element.tabName || element.paneName) - } - }) - } catch (error) { - console.error('Unable to load navigation menu items', error) - return [] - } -} - -const createMenuButton = (item: MenuItem) => { - const button = document.createElement('button') - button.className = 'menu-item' - button.type = 'button' - if (item.paneName) { - button.dataset.paneName = item.paneName - } - - if (item.icon) { - const icon = document.createElement('img') - icon.className = 'menu-item-icon' - icon.src = item.icon - icon.alt = '' - icon.setAttribute('aria-hidden', 'true') - button.appendChild(icon) - } - - const label = document.createElement('span') - label.className = 'menu-item-label' - label.textContent = item.label - button.appendChild(label) - - if (item.id) { - button.id = item.id - } - - button.addEventListener('click', async () => { - await item.onclick() - }) - - return button -} - -const setActiveMenuItem = (container: HTMLElement, paneName?: string) => { - const menuItems = Array.from(container.querySelectorAll('.menu-item')) - let activeItem: HTMLButtonElement | undefined - - menuItems.forEach((item) => { - const isActive = Boolean(paneName) && item.dataset.paneName === paneName - item.classList.toggle('menu-item-active', isActive) - if (isActive) { - item.setAttribute('aria-current', 'page') - activeItem = item - } else { - item.removeAttribute('aria-current') - } - }) - - if (paneName) { - if (!activeItem && menuItems[0]) { - // If an explicit pane name is provided but does not match any menu - // item, fall back to clearing selection rather than selecting the first item. - container.dataset.activePaneName = '' - } else { - container.dataset.activePaneName = paneName - } - } else { - // If there is no active pane, do not auto-select the first menu item. - container.dataset.activePaneName = '' - } - - updateToggleLabel(activeItem) -} - -export const setActiveMenuPane = (paneName?: string): void => { - const navMenuContent = document.getElementById('NavMenuContent') as HTMLElement | null - if (!navMenuContent) return - setActiveMenuItem(navMenuContent, paneName) -} - -const updateToggleLabel = (activeItem?: HTMLButtonElement): void => { - const toggleLabel = document.getElementById('MenuToggleLabel') - if (!toggleLabel) return - toggleLabel.textContent = activeItem?.querySelector('.menu-item-label')?.textContent || '' -} - -const renderMenuItems = async (subject: NamedNode, outliner: OutlineManager, container: HTMLElement) => { - const menuItems = await getMenuItems(subject, outliner) - - container.replaceChildren(...menuItems.map(createMenuButton)) - // If the user is logged in and viewing their own profile, select "Your profile" - // by default. This also surfaces "Your profile" at the top of the mobile view - // via the menu toggle label, which mirrors the active menu item. - const activePane = isViewingOwnProfile(subject) - ? 'profile' - : container.dataset.activePaneName - setActiveMenuItem(container, activePane) -} - -export const refreshMenu = (layout: 'mobile' | 'desktop') => { - const navMenu = document.getElementById('NavMenu') as HTMLElement | null - const toggle = document.getElementById('MenuToggleBtn') as HTMLButtonElement | null - const collapseBtn = document.getElementById('MenuCollapseBtn') as HTMLButtonElement | null - const overlay = document.getElementById('MenuOverlay') as HTMLElement | null - - if (!navMenu || !toggle || !overlay || !collapseBtn) return - - const loggedIn = isLoggedIn() - if (!loggedIn) { - navMenu.hidden = true - navMenu.style.display = 'none' - toggle.hidden = true - toggle.style.display = 'none' - collapseBtn.hidden = true - collapseBtn.style.display = 'none' - overlay.hidden = true - overlay.style.display = 'none' - setFooterVisibility(false) - return - } - - setFooterVisibility(true) - - if (layout === 'mobile') { - navMenu.classList.add('mobile-hidden') - navMenu.classList.remove('mobile-visible') - toggle.hidden = false - toggle.style.display = '' - collapseBtn.hidden = true - collapseBtn.style.display = 'none' - overlay.hidden = true - overlay.style.display = 'none' - navMenu.hidden = false - navMenu.style.display = '' - navMenu.classList.remove('collapsed') - toggle.setAttribute('aria-expanded', 'false') - } else { - navMenu.classList.remove('mobile-hidden', 'mobile-visible') - toggle.hidden = true - toggle.style.display = 'none' - collapseBtn.hidden = false - collapseBtn.style.display = '' - overlay.hidden = true - overlay.style.display = 'none' - navMenu.hidden = false - navMenu.style.display = '' - applyMenuCollapsedState(navMenu) - updateCollapseButtonPosition(navMenu, collapseBtn) - toggle.setAttribute('aria-expanded', 'false') - } -} - -export const createLeftSideMenu = async (subject: NamedNode, outliner: OutlineManager) => { - ensureMenuSkeleton() - const navMenu = document.getElementById('NavMenu') as HTMLElement | null - const menuToggle = document.getElementById('MenuToggleBtn') as HTMLElement | null - const menuOverlay = document.getElementById('MenuOverlay') as HTMLElement | null - const navMenuContent = document.getElementById('NavMenuContent') as HTMLElement | null - await refreshAuthStateFromSession() - - const closeMobileMenu = () => { - if (!navMenu || !menuToggle || !menuOverlay) return - navMenu.classList.remove('mobile-visible') - navMenu.classList.add('mobile-hidden') - navMenu.style.display = '' - menuToggle.setAttribute('aria-expanded', 'false') - menuOverlay.hidden = true - menuOverlay.style.display = 'none' - } - - const openMobileMenu = () => { - if (!navMenu || !menuToggle || !menuOverlay) return - navMenu.classList.remove('mobile-hidden') - navMenu.classList.add('mobile-visible') - navMenu.style.display = '' - menuToggle.setAttribute('aria-expanded', 'true') - menuOverlay.hidden = false - menuOverlay.style.display = '' - } - - const collapseBtn = document.getElementById('MenuCollapseBtn') as HTMLButtonElement | null - - const expandDesktopMenu = () => { - if (!navMenu || !collapseBtn) return - if (!menuCollapsed) return - menuCollapsed = false - saveMenuCollapsedState(menuCollapsed) - applyMenuCollapsedState(navMenu) - } - - if (navMenu) { - navMenu.addEventListener('click', (event) => { - const isMobile = outliner.context?.environment?.layout === 'mobile' - const clickedCollapseButton = (event.target as HTMLElement).closest('#MenuCollapseBtn') - if (!isMobile && menuCollapsed && !clickedCollapseButton) { - expandDesktopMenu() - } - }) - } - - if (menuToggle) { - menuToggle.addEventListener('click', () => { - if (navMenu?.classList.contains('mobile-visible')) { - closeMobileMenu() - } else { - openMobileMenu() - } - }) - } - if (collapseBtn) { - collapseBtn.addEventListener('click', () => { - menuCollapsed = !menuCollapsed - saveMenuCollapsedState(menuCollapsed) - applyMenuCollapsedState(navMenu) - }) - } - - const updateMenuVisibility = () => { - const loggedIn = isLoggedIn() - if (navMenu) { - navMenu.hidden = !loggedIn - navMenu.style.display = loggedIn ? '' : 'none' - } - if (menuToggle) { - menuToggle.hidden = !loggedIn - menuToggle.style.display = loggedIn ? '' : 'none' - } - if (collapseBtn) { - collapseBtn.hidden = !loggedIn - collapseBtn.style.display = loggedIn ? '' : 'none' - } - if (menuOverlay) { - menuOverlay.hidden = !loggedIn - menuOverlay.style.display = loggedIn ? '' : 'none' - } - setFooterVisibility(loggedIn) - } - - updateMenuVisibility() - - if (menuOverlay) { - menuOverlay.addEventListener('click', closeMobileMenu) - } - - const closeBtn = document.getElementById('MenuCloseBtn') as HTMLButtonElement | null - if (closeBtn) { - closeBtn.addEventListener('click', closeMobileMenu) - } - - if (navMenuContent) { - updateMenuVisibility() - const loggedIn = isLoggedIn() - if (loggedIn) { - await renderMenuItems(subject, outliner, navMenuContent) - } - - if (!navMenuContent.dataset.authEventsBound) { - const refreshMenuItems = async () => { - await renderMenuItems(subject, outliner, navMenuContent) - } - - authSession.events.on('login', async () => { - await refreshAuthStateFromSession() - updateMenuVisibility() - refreshMenu(outliner.context?.environment?.layout === 'mobile' ? 'mobile' : 'desktop') - await refreshMenuItems() - }) - authSession.events.on('logout', async () => { - await refreshAuthStateFromSession() - updateMenuVisibility() - refreshMenu(outliner.context?.environment?.layout === 'mobile' ? 'mobile' : 'desktop') - await refreshMenuItems() - }) - authSession.events.on('sessionRestore', async () => { - await refreshAuthStateFromSession() - updateMenuVisibility() - refreshMenu(outliner.context?.environment?.layout === 'mobile' ? 'mobile' : 'desktop') - await refreshMenuItems() - }) - navMenuContent.dataset.authEventsBound = 'true' - } - - navMenuContent.addEventListener('click', (event) => { - const item = (event.target as HTMLElement).closest('.menu-item') - if (item instanceof HTMLButtonElement) { - setActiveMenuItem(navMenuContent, item.dataset.paneName) - } - const isMobile = outliner.context?.environment?.layout === 'mobile' - if (item && isMobile) { - closeMobileMenu() - } - }) - } - - refreshMenu(outliner.context?.environment?.layout === 'mobile' ? 'mobile' : 'desktop') -} - -async function openDashboardPane (subject, outliner: any, pane: string): Promise { - const me = authn.currentUser() - if (!subject) { - if (me) { - subject = me - } else { - const store = outliner?.context?.store || outliner?.context?.session?.store || outliner?.kb - const fetcher = outliner?.context?.fetcher || store?.fetcher - if (!store || !fetcher) { - throw new Error('Unable to load profile: missing RDF store or fetcher') - } - const fallbackUri = sym(window.location.href) - subject = await loadProfileFromURI(subject || fallbackUri, store, fetcher) - } - } - outliner.showDashboard(subject, { - pane - }) -}