Build reusable octopus file browser

This commit is contained in:
2026-09-23 12:39:56 -07:00
parent ff8e2e9eae
commit fae7efcd5d
17 changed files with 1406 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
dist/
node_modules/
*.log
+80
View File
@@ -0,0 +1,80 @@
octopus-file-browser
Third-Party Software Notices
This project incorporates or was informed by the following third-party works.
-------------------------------------------------------------------------------
Gitea
https://github.com/go-gitea/gitea
The file-list layout was studied as a design reference. No Gitea source code is
included in this package.
Copyright (c) 2016 The Gitea Authors
Copyright (c) 2015 The Gogs Authors
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-------------------------------------------------------------------------------
Material Icon Theme 5.38.1
https://github.com/material-extensions/vscode-material-icon-theme
Copyright (c) 2025 Material Extensions
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-------------------------------------------------------------------------------
Vite 8.3.0
https://github.com/vitejs/vite
Copyright (c) 2019-present, VoidZero Inc. and Vite contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
:root{color:#181c21;font-synthesis:none;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,system-ui,sans-serif}body{min-width:280px;margin:0}main{max-width:1220px;margin:0 auto;padding:20px}@media (width<=640px){main{padding:8px}}
+17
View File
@@ -0,0 +1,17 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="color-scheme" content="light">
<meta name="theme-color" content="#ffffff">
<title>octopus-file-browser</title>
<script type="module" crossorigin src="/assets/index-DSvdNgV5.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-rQJBLWWV.css">
</head>
<body>
<main>
<octopus-file-browser></octopus-file-browser>
</main>
</body>
</html>
+16
View File
@@ -0,0 +1,16 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="color-scheme" content="light">
<meta name="theme-color" content="#ffffff">
<title>octopus-file-browser</title>
</head>
<body>
<main>
<octopus-file-browser></octopus-file-browser>
</main>
<script type="module" src="/src/demo.js"></script>
</body>
</html>
+853
View File
@@ -0,0 +1,853 @@
{
"name": "octopus-file-browser",
"version": "0.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "octopus-file-browser",
"version": "0.1.0",
"license": "UNLICENSED",
"dependencies": {
"material-icon-theme": "5.38.1"
},
"devDependencies": {
"vite": "8.3.0"
}
},
"node_modules/@oxc-project/types": {
"version": "0.151.0",
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.151.0.tgz",
"integrity": "sha512-J1yXrIlNDZVzE3ada310xeAw7nH8yCAyLPuUIsjKatFPmfn5bS1oW+cM+QsGOtVWd5nhSpbwZWx/rue+r5Z+PA==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/oxc-project"
}
},
"node_modules/@rolldown/binding-android-arm-eabi": {
"version": "1.2.10",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.10.tgz",
"integrity": "sha512-bp9svZb+QurZeh+8H4BhrZkifEB0YBNvTVzNSJnJQkj4NrRwmQoDUCGP0vSN7PbvLeM7l1tK6GXL8mrTiH2myg==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-android-arm64": {
"version": "1.2.10",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.10.tgz",
"integrity": "sha512-wm6Dld3RXUAZ/gRWKyUy+4W1B5CB5UeFaOzsSWJWEdxZXHH8rCYiZ5dGe6oJmhsunAPWzL7FZV+VtvmN5Ye2eA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-darwin-arm64": {
"version": "1.2.10",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.10.tgz",
"integrity": "sha512-UbEfXq/AqGNgRTV3ik+X/iR6mUxu2QdYAadwRxJWquUGnW6gDqdP1FtLtFXRow7RJx0ssRwi80XAPr4r+4DtsA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-darwin-x64": {
"version": "1.2.10",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.10.tgz",
"integrity": "sha512-7f5h17q5KZVx/ji1vb8OTq31ch1O2I7K8NPIr44GkyWTApXMIsmhWqZfgpOH10xeauqghDAvGlZktasCkcF6Eg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-freebsd-x64": {
"version": "1.2.10",
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.10.tgz",
"integrity": "sha512-ynOk/eEYhC6ZB2xCGvKrEOwE58oBy9LnrAqtkrDF9Fz1VTaNdGZTsV0VarJdhPwb+sOJTGjCLwcuyRJZ1dnMcQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
"version": "1.2.10",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.10.tgz",
"integrity": "sha512-ERrAs185meZZhGan7a4l3RiiJK1ArSDlHdST++uvSxe+FDbR4TwUPahT/cbZJvaG6fIpDpF78surN+tX708Y4Q==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-linux-arm64-gnu": {
"version": "1.2.10",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.10.tgz",
"integrity": "sha512-KN7OHKD0J3jy1UzBwZWPxpwhODf9IARUIJcrH+yLYKOcmegZ8luEUM38lDP1bDVj40yP6PsSzCqOJF76vljFnQ==",
"cpu": [
"arm64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-linux-arm64-musl": {
"version": "1.2.10",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.10.tgz",
"integrity": "sha512-8l9wP8O+wa8zD6iw6egSfzVtu7oZVfH3hlUsMM4MwbLMhxleqeoXbZzjddyK3YyNlwLhqznq3tF7PkNJ8T/V2w==",
"cpu": [
"arm64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
"version": "1.2.10",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.10.tgz",
"integrity": "sha512-SeXNKeQzA5kLhz/J0CH6ZP0/HJ3v1xm/0YbiYpE0kK7emfRC2OIGGIaE14xzkISEGv2aYuUSpiLiU5Gbq+OI0A==",
"cpu": [
"ppc64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-linux-s390x-gnu": {
"version": "1.2.10",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.10.tgz",
"integrity": "sha512-mtht0nR+y8/hart4175Ll15w7lY8dg7CtQ+j2FDNTsDRspOWTK/2V3l0aj9sIj7XmvqxT8Yli/wq22e7feTTWg==",
"cpu": [
"s390x"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-linux-x64-gnu": {
"version": "1.2.10",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.10.tgz",
"integrity": "sha512-FSM94nGd55NYo48usCyM/nHfUKRnqc9+b0vJNuKV0oCCpIp/OGims7rO1Nv/DkFkt0S/s2rxsJ2kkS8J3HcpeA==",
"cpu": [
"x64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-linux-x64-musl": {
"version": "1.2.10",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.10.tgz",
"integrity": "sha512-C3YxNB16myRLs7o+B+6PnQ6jBsdIS4+AE4Ah8glVGhDpEv9AOvxhZ/1duAb4B0UGczEK/lBbccksd8VI+p6zfw==",
"cpu": [
"x64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-openharmony-arm64": {
"version": "1.2.10",
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.10.tgz",
"integrity": "sha512-571TlE/F1eeTjjdjYAMMMPs1Mfv3MtX6s3+ZKVU6HiUjZ5Njc6c/qzNy/8K3zALTZnaw3JQVYrHxvNfjm43KAg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openharmony"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-win32-arm64-msvc": {
"version": "1.2.10",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.10.tgz",
"integrity": "sha512-QXW+ZWaiqs2c7Fi++D/SsW07LTPcUrncxcskJGfGNBoaLik1IU6fJymz4HsqwEO0u5Iq11yTO0B/mc4cPk7jrQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-win32-x64-msvc": {
"version": "1.2.10",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.10.tgz",
"integrity": "sha512-5FQFGgah17YeMtG1Yd5a+rMxQpTksyNXxRtKz06FVTaQw3RKYUJQbUoKk0/5jrXBpDo+7makNP7UHA2LQyH64A==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/pluginutils": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz",
"integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==",
"dev": true,
"license": "MIT"
},
"node_modules/chroma-js": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/chroma-js/-/chroma-js-3.2.0.tgz",
"integrity": "sha512-os/OippSlX1RlWWr+QDPcGUZs0uoqr32urfxESG9U93lhUfbnlyckte84Q8P1UQY/qth983AS1JONKmLS4T0nw==",
"license": "(BSD-3-Clause AND Apache-2.0)"
},
"node_modules/detect-libc": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": ">=8"
}
},
"node_modules/fdir": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12.0.0"
},
"peerDependencies": {
"picomatch": "^3 || ^4"
},
"peerDependenciesMeta": {
"picomatch": {
"optional": true
}
}
},
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/lightningcss": {
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz",
"integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==",
"dev": true,
"license": "MPL-2.0",
"dependencies": {
"detect-libc": "^2.0.3"
},
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
},
"optionalDependencies": {
"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.33.0",
"resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz",
"integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-darwin-arm64": {
"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"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-darwin-x64": {
"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"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-freebsd-x64": {
"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"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-arm-gnueabihf": {
"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"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-arm64-gnu": {
"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"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-arm64-musl": {
"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"
],
"dev": true,
"libc": [
"musl"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-x64-gnu": {
"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"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-x64-musl": {
"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"
],
"dev": true,
"libc": [
"musl"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-win32-arm64-msvc": {
"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"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-win32-x64-msvc": {
"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"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/material-icon-theme": {
"version": "5.38.1",
"resolved": "https://registry.npmjs.org/material-icon-theme/-/material-icon-theme-5.38.1.tgz",
"integrity": "sha512-14cFM4NJGdbuo68rIZTq9TSX0f5BtA6VF+eNX3zq23Z5NoEeVtM4Zn4PpZ0ERl++50jCBrywKFWXmOjbxf0xTA==",
"license": "MIT",
"dependencies": {
"chroma-js": "^3.2.0"
},
"engines": {
"vscode": "^1.55.0"
},
"funding": {
"url": "https://github.com/sponsors/material-extensions"
}
},
"node_modules/nanoid": {
"version": "3.3.19",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.19.tgz",
"integrity": "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"bin": {
"nanoid": "bin/nanoid.cjs"
},
"engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"dev": true,
"license": "ISC"
},
"node_modules/picomatch": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
"integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/postcss": {
"version": "8.5.28",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz",
"integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==",
"dev": true,
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"dependencies": {
"nanoid": "^3.3.18",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
"engines": {
"node": "^10 || ^12 || >=14"
}
},
"node_modules/rolldown": {
"version": "1.2.10",
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.10.tgz",
"integrity": "sha512-OxkA08pSryMK7B3XiFA09B4OJ1xJMPgIYCBMY2xchzpqgBGsV1o0DetPAE+Sl3N3L4oCPiEzmHVSOj7iR04Zog==",
"dev": true,
"license": "MIT",
"dependencies": {
"@oxc-project/types": "=0.151.0",
"@rolldown/pluginutils": "^1.0.0"
},
"bin": {
"rolldown": "bin/cli.mjs"
},
"engines": {
"node": "^20.19.0 || >=22.12.0"
},
"optionalDependencies": {
"@rolldown/binding-android-arm-eabi": "1.2.10",
"@rolldown/binding-android-arm64": "1.2.10",
"@rolldown/binding-darwin-arm64": "1.2.10",
"@rolldown/binding-darwin-x64": "1.2.10",
"@rolldown/binding-freebsd-x64": "1.2.10",
"@rolldown/binding-linux-arm-gnueabihf": "1.2.10",
"@rolldown/binding-linux-arm64-gnu": "1.2.10",
"@rolldown/binding-linux-arm64-musl": "1.2.10",
"@rolldown/binding-linux-ppc64-gnu": "1.2.10",
"@rolldown/binding-linux-s390x-gnu": "1.2.10",
"@rolldown/binding-linux-x64-gnu": "1.2.10",
"@rolldown/binding-linux-x64-musl": "1.2.10",
"@rolldown/binding-openharmony-arm64": "1.2.10",
"@rolldown/binding-win32-arm64-msvc": "1.2.10",
"@rolldown/binding-win32-x64-msvc": "1.2.10"
}
},
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/tinyglobby": {
"version": "0.2.17",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
"integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
"dev": true,
"license": "MIT",
"dependencies": {
"fdir": "^6.5.0",
"picomatch": "^4.0.4"
},
"engines": {
"node": ">=12.0.0"
},
"funding": {
"url": "https://github.com/sponsors/SuperchupuDev"
}
},
"node_modules/vite": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/vite/-/vite-8.3.0.tgz",
"integrity": "sha512-lhZBVvEHefgE+HQZC9O7EBJgCU/nVzFNl7vkS4RE0APtWLP02/8QVIkQtzBxPquh7lq5/78NHipTj7ODQ6XuyQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"lightningcss": "^1.33.0",
"picomatch": "^4.0.7",
"postcss": "^8.5.28",
"rolldown": "~1.2.6",
"tinyglobby": "^0.2.17"
},
"bin": {
"vite": "bin/vite.js"
},
"engines": {
"node": "^20.19.0 || >=22.12.0"
},
"funding": {
"url": "https://github.com/vitejs/vite?sponsor=1"
},
"optionalDependencies": {
"fsevents": "~2.3.3"
},
"peerDependencies": {
"@types/node": "^20.19.0 || >=22.12.0",
"@vitejs/devtools": "^0.7.1",
"esbuild": "^0.27.0 || ^0.28.0",
"jiti": ">=1.21.0",
"less": "^4.0.0",
"sass": "^1.70.0",
"sass-embedded": "^1.70.0",
"stylus": ">=0.54.8",
"sugarss": "^5.0.0",
"terser": "^5.16.0",
"tsx": "^4.8.1",
"yaml": "^2.4.2"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
},
"@vitejs/devtools": {
"optional": true
},
"esbuild": {
"optional": true
},
"jiti": {
"optional": true
},
"less": {
"optional": true
},
"sass": {
"optional": true
},
"sass-embedded": {
"optional": true
},
"stylus": {
"optional": true
},
"sugarss": {
"optional": true
},
"terser": {
"optional": true
},
"tsx": {
"optional": true
},
"yaml": {
"optional": true
}
}
}
}
}
+40
View File
@@ -0,0 +1,40 @@
{
"name": "octopus-file-browser",
"version": "0.1.0",
"description": "A compact, framework-free file browser inspired by Gitea's repository view.",
"type": "module",
"main": "./dist/octopus-file-browser.umd.cjs",
"module": "./dist/octopus-file-browser.js",
"exports": {
".": {
"import": "./dist/octopus-file-browser.js",
"require": "./dist/octopus-file-browser.umd.cjs"
}
},
"files": [
"dist",
"THIRD_PARTY_NOTICES"
],
"scripts": {
"dev": "vite",
"test": "node --test",
"demo": "vite build",
"build:demo": "npm run demo",
"build:library": "vite build --config vite.lib.config.js",
"build": "npm run build:demo && npm run build:library",
"check": "npm test && npm run build"
},
"keywords": [
"file-browser",
"web-component",
"javascript",
"gitea"
],
"license": "UNLICENSED",
"dependencies": {
"material-icon-theme": "5.38.1"
},
"devDependencies": {
"vite": "8.3.0"
}
}
+17
View File
@@ -0,0 +1,17 @@
:root {
color: #181c21;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
font-synthesis: none;
}
body { margin: 0; min-width: 280px; }
main {
margin: 0 auto;
max-width: 1220px;
padding: 20px;
}
@media (max-width: 640px) {
main { padding: 8px; }
}
+6
View File
@@ -0,0 +1,6 @@
import './demo.css';
import './octopus-file-browser.js';
import entries from 'virtual:octopus-repo-tree';
const browser = document.querySelector('octopus-file-browser');
browser.entries = entries;
+54
View File
@@ -0,0 +1,54 @@
const UNITS = ['B', 'KB', 'MB', 'GB', 'TB'];
export function sortEntries(entries = []) {
return [...entries].sort((a, b) =>
Number(b.type === 'directory') - Number(a.type === 'directory') ||
a.name.localeCompare(b.name, undefined, { numeric: true, sensitivity: 'base' }),
);
}
export function formatFileSize(bytes) {
if (!Number.isFinite(bytes) || bytes < 0) return '—';
if (!bytes) return '0 B';
const unit = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), UNITS.length - 1);
const value = bytes / 1024 ** unit;
return `${value.toFixed(unit && value < 10 ? 1 : 0)} ${UNITS[unit]}`;
}
export function formatRelativeDate(value, now = Date.now()) {
const date = new Date(value);
if (Number.isNaN(date.getTime())) return '—';
const delta = date.getTime() - now;
const abs = Math.abs(delta);
const units = abs < 60 * 60e3
? ['minute', 60e3]
: abs < 24 * 60 * 60e3
? ['hour', 60 * 60e3]
: abs < 7 * 24 * 60 * 60e3
? ['day', 24 * 60 * 60e3]
: abs < 30 * 24 * 60 * 60e3
? ['week', 7 * 24 * 60 * 60e3]
: abs < 365 * 24 * 60 * 60e3
? ['month', 30 * 24 * 60 * 60e3]
: ['year', 365 * 24 * 60 * 60e3];
return new Intl.RelativeTimeFormat(undefined, { numeric: 'auto' })
.format(Math.round(delta / units[1]), units[0]);
}
export function isRecent(value, now = Date.now()) {
const time = new Date(value).getTime();
const age = now - time;
return Number.isFinite(time) && age >= 0 && age < 30 * 24 * 60 * 60e3;
}
export function fileKindLabel(name = '') {
const lower = name.toLowerCase();
if (lower === 'license' || lower.startsWith('license.')) return 'License';
if (lower === 'third_party_notices') return 'Notices';
if (lower.startsWith('.git')) return 'Git';
const extension = lower.includes('.') ? lower.split('.').pop() : '';
return ({
css: 'CSS', html: 'HTML', js: 'JavaScript', json: 'JSON', md: 'Markdown',
png: 'Image', svg: 'SVG', yml: 'YAML', yaml: 'YAML', ts: 'TypeScript',
})[extension] || (extension ? extension.toUpperCase() : 'File');
}
+29
View File
@@ -0,0 +1,29 @@
import css from 'material-icon-theme/icons/css.svg';
import file from 'material-icon-theme/icons/file.svg';
import folder from 'material-icon-theme/icons/folder.svg';
import git from 'material-icon-theme/icons/git.svg';
import html from 'material-icon-theme/icons/html.svg';
import image from 'material-icon-theme/icons/image.svg';
import javascript from 'material-icon-theme/icons/javascript.svg';
import json from 'material-icon-theme/icons/json.svg';
import license from 'material-icon-theme/icons/license.svg';
import markdown from 'material-icon-theme/icons/markdown.svg';
import npm from 'material-icon-theme/icons/npm.svg';
import svg from 'material-icon-theme/icons/svg.svg';
import typescript from 'material-icon-theme/icons/typescript.svg';
import vite from 'material-icon-theme/icons/vite.svg';
import yaml from 'material-icon-theme/icons/yaml.svg';
const EXTENSIONS = { css, htm: html, html, jpeg: image, jpg: image, js: javascript,
json, md: markdown, mjs: javascript, png: image, svg, ts: typescript, yaml, yml: yaml };
export function resolveIcon(entry) {
if (entry.icon) return entry.icon;
if (entry.type === 'directory') return folder;
const name = entry.name.toLowerCase();
if (name === 'license' || name.startsWith('license.')) return license;
if (name === 'package.json' || name === 'package-lock.json') return npm;
if (name.startsWith('vite.config.')) return vite;
if (name.startsWith('.git')) return git;
return EXTENSIONS[name.split('.').pop()] || file;
}
+91
View File
@@ -0,0 +1,91 @@
:host {
--ofb-border: #d0d7de;
--ofb-text: #181c21;
--ofb-muted: #57606a;
--ofb-link: #1f2328;
--ofb-folder: #54aeff;
--ofb-hover: #f6f8fa;
--ofb-focus: #0969da;
--ofb-recent: #d15700;
color: var(--ofb-text);
display: block;
font: 14px/20px -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, Roboto,
"Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif;
min-width: 0;
}
* { box-sizing: border-box; }
.list {
border: 1px solid var(--ofb-border);
border-radius: 5px;
list-style: none;
margin: 0;
overflow: hidden;
padding: 0;
}
.entry {
appearance: none;
background: #fff;
border: 0;
border-top: 1px solid var(--ofb-border);
color: inherit;
cursor: pointer;
display: grid;
font: inherit;
grid-template-columns: minmax(0, 1fr) 120px;
margin: 0;
min-height: 37px;
padding: 0;
text-align: left;
width: 100%;
}
li:first-child .entry { border-top: 0; }
.entry:hover { background: var(--ofb-hover); }
.entry:active { background: #eef1f4; }
.entry:focus-visible { box-shadow: inset 0 0 0 2px var(--ofb-focus); outline: 0; }
.cell {
align-items: center;
display: flex;
min-width: 0;
padding: 8px 10px;
}
.name { gap: 8px; }
.icon { flex: 0 0 auto; height: 18px; object-fit: contain; width: 18px; }
.label, .modified { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.label { color: var(--ofb-link); min-width: 0; }
.size {
color: var(--ofb-muted);
flex: 0 0 auto;
font-size: 12px;
margin-left: 8px;
white-space: nowrap;
}
.modified { color: var(--ofb-muted); }
.modified.recent { color: var(--ofb-recent); }
.modified { justify-content: flex-end; }
.empty {
color: var(--ofb-muted);
margin: 0;
padding: 24px;
text-align: center;
}
@media (max-width: 640px) {
.entry {
grid-template-columns: minmax(0, 1fr) 104px;
min-height: 44px;
}
.cell { padding: 10px 8px; }
.name { gap: 7px; }
.icon { height: 17px; width: 17px; }
}
@media (prefers-reduced-motion: no-preference) {
.entry { transition: background-color 80ms ease; }
}
+118
View File
@@ -0,0 +1,118 @@
import styles from './octopus-file-browser.css?inline';
import { fileKindLabel, formatFileSize, formatRelativeDate, isRecent, sortEntries } from './file-browser-utils.js';
import { resolveIcon } from './icon-resolver.js';
const HTMLElementBase = globalThis.HTMLElement || class {};
function element(tag, className, text) {
const node = document.createElement(tag);
if (className) node.className = className;
if (text !== undefined) node.textContent = text;
return node;
}
export class OctopusFileBrowser extends HTMLElementBase {
#entries = [];
#path = [];
static get observedAttributes() { return ['empty-label']; }
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
connectedCallback() { this.#render(); }
attributeChangedCallback() { if (this.isConnected) this.#render(); }
set entries(value) {
this.#entries = Array.isArray(value) ? value : [];
this.#path = [];
this.#render();
}
get entries() { return this.#entries; }
get path() { return [...this.#path]; }
navigate(path = []) {
let entries = this.#entries;
const valid = [];
for (const part of path) {
const directory = entries.find((entry) => entry.type === 'directory' && entry.name === part);
if (!directory) break;
valid.push(part);
entries = directory.children || [];
}
this.#path = valid;
this.#render();
this.#emit('octopus:navigate', { path: this.path, entries: this.#currentEntries() });
}
#currentEntries() {
return this.#path.reduce((entries, part) =>
entries.find((entry) => entry.type === 'directory' && entry.name === part)?.children || [],
this.#entries);
}
#emit(name, detail) {
this.dispatchEvent(new CustomEvent(name, { bubbles: true, composed: true, detail }));
}
#open(entry) {
if (entry.parent) return this.navigate(this.#path.slice(0, -1));
if (entry.type === 'directory') return this.navigate([...this.#path, entry.name]);
this.#emit('octopus:open', { entry, path: [...this.#path, entry.name] });
}
#renderEntry(entry) {
const item = element('li');
const row = element('button', 'entry');
row.type = 'button';
row.title = entry.parent ? 'Parent folder' : entry.name;
row.setAttribute('aria-label', entry.parent
? 'Parent folder'
: `${entry.name}, ${entry.type === 'directory' ? 'folder' : fileKindLabel(entry.name)}`);
row.addEventListener('click', () => this.#open(entry));
const name = element('span', 'cell name');
const icon = element('img', 'icon');
icon.alt = '';
icon.src = resolveIcon(entry);
name.append(icon, element('span', 'label', entry.name));
if (entry.type !== 'directory') name.append(element('span', 'size', formatFileSize(entry.size)));
const modified = element('span', 'cell modified', entry.modified ? formatRelativeDate(entry.modified) : '');
if (entry.modified) {
modified.title = new Date(entry.modified).toLocaleString();
if (isRecent(entry.modified)) modified.classList.add('recent');
}
row.append(name, modified);
item.append(row);
return item;
}
#render() {
if (!this.shadowRoot || typeof document === 'undefined') return;
const style = element('style');
style.textContent = styles;
const list = element('ul', 'list');
list.setAttribute('aria-label', 'Files');
const entries = sortEntries(this.#currentEntries());
if (this.#path.length) list.append(this.#renderEntry({ name: '..', type: 'directory', parent: true }));
if (entries.length) entries.forEach((entry) => list.append(this.#renderEntry(entry)));
else list.append(element('li', 'empty', this.getAttribute('empty-label') || 'This folder is empty.'));
this.shadowRoot.replaceChildren(style, list);
}
}
if (globalThis.customElements && !customElements.get('octopus-file-browser'))
customElements.define('octopus-file-browser', OctopusFileBrowser);
export function createOctopusFileBrowser(target, options = {}) {
const browser = document.createElement('octopus-file-browser');
if (options.emptyLabel) browser.setAttribute('empty-label', options.emptyLabel);
browser.entries = options.entries || [];
target.append(browser);
return browser;
}
export { fileKindLabel, formatFileSize, formatRelativeDate, isRecent, sortEntries };
+38
View File
@@ -0,0 +1,38 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { fileKindLabel, formatFileSize, formatRelativeDate, isRecent, sortEntries } from '../src/file-browser-utils.js';
test('sortEntries keeps folders first and names natural', () => {
const sorted = sortEntries([
{ name: 'file10.js', type: 'file' },
{ name: 'zeta', type: 'directory' },
{ name: 'file2.js', type: 'file' },
{ name: 'alpha', type: 'directory' },
]);
assert.deepEqual(sorted.map(({ name }) => name), ['alpha', 'zeta', 'file2.js', 'file10.js']);
});
test('formatFileSize uses compact binary units', () => {
assert.equal(formatFileSize(0), '0 B');
assert.equal(formatFileSize(1536), '1.5 KB');
assert.equal(formatFileSize(undefined), '—');
});
test('formatRelativeDate reports useful relative values', () => {
const now = Date.parse('2026-09-23T12:00:00Z');
assert.equal(formatRelativeDate('2026-09-22T12:00:00Z', now), 'yesterday');
assert.equal(formatRelativeDate('2026-09-09T12:00:00Z', now), '2 weeks ago');
});
test('isRecent marks dates younger than 30 days', () => {
const now = Date.parse('2026-09-23T12:00:00Z');
assert.equal(isRecent('2026-09-01T12:00:00Z', now), true);
assert.equal(isRecent('2026-08-01T12:00:00Z', now), false);
assert.equal(isRecent('invalid', now), false);
});
test('fileKindLabel identifies special and common files', () => {
assert.equal(fileKindLabel('LICENSE'), 'License');
assert.equal(fileKindLabel('app.js'), 'JavaScript');
assert.equal(fileKindLabel('Dockerfile'), 'File');
});
+28
View File
@@ -0,0 +1,28 @@
import { readdirSync, statSync } from 'node:fs';
import { resolve } from 'node:path';
import { defineConfig } from 'vite';
const VIRTUAL_ID = 'virtual:octopus-repo-tree';
const RESOLVED_ID = `\0${VIRTUAL_ID}`;
const ignored = new Set(['.git', 'demo', 'dist', 'node_modules']);
function scan(directory) {
return readdirSync(directory, { withFileTypes: true })
.filter((entry) => !ignored.has(entry.name))
.map((entry) => {
const path = resolve(directory, entry.name);
const stats = statSync(path);
return entry.isDirectory()
? { name: entry.name, type: 'directory', modified: stats.mtime.toISOString(), children: scan(path) }
: { name: entry.name, type: 'file', modified: stats.mtime.toISOString(), size: stats.size };
});
}
export default defineConfig({
build: { outDir: 'demo' },
plugins: [{
name: 'octopus-demo-tree',
resolveId(id) { return id === VIRTUAL_ID ? RESOLVED_ID : null; },
load(id) { return id === RESOLVED_ID ? `export default ${JSON.stringify(scan(process.cwd()))}` : null; },
}],
});
+14
View File
@@ -0,0 +1,14 @@
import { resolve } from 'node:path';
import { defineConfig } from 'vite';
export default defineConfig({
build: {
lib: {
entry: resolve(import.meta.dirname, 'src/octopus-file-browser.js'),
fileName: (format) => format === 'es' ? 'octopus-file-browser.js' : 'octopus-file-browser.umd.cjs',
formats: ['es', 'umd'],
name: 'OctopusFileBrowser',
},
outDir: 'dist',
},
});