Superpowered to do lists. No signup required.

✨ implement default local list, update current list

+441 -28
+5 -13
README.md
··· 1 1 # easytodo.link 2 2 3 - Local first, simple to do list. Made by [@zeu_dev](https://twitter.com/zeu_dev). 3 + Local first, shareable to do list. Made by [@zeu_dev](https://twitter.com/zeu_dev). 4 4 5 5 ## Roadmap 6 6 7 - ### Features 8 - 9 - - [x] ✨ Sync data with local storage 10 - - [x] 💄 Set color theme 11 - - [ ] 💄 Change background with image 12 - - [X] 🏗️ Multiple lists with titles 13 - - [ ] ✨ Add stopwatch (optional time tracking) 14 - - [ ] 🚀 Publish lists as template (ready for copy) 15 - - [ ] 🛂 Online account with cloud sync 16 - - [ ] 🔨 Paid third party integrations (???) 17 - - [ ] 🚀 Publish lists as public (attached to user's current data) 18 - - [ ] 🚀 Publish lists as private (password protected) 7 + - [ ] Local first tasks and list management 8 + - [ ] Cloud sync with Accounts 9 + - [ ] Share and explore lists 10 + - [ ] AI task suggestions 19 11 20 12 ## Made with 21 13
+2 -1
package.json
··· 25 25 "type": "module", 26 26 "dependencies": { 27 27 "@vercel/analytics": "^1.2.2", 28 - "@vercel/speed-insights": "^1.0.10" 28 + "@vercel/speed-insights": "^1.0.10", 29 + "oslo": "^1.1.3" 29 30 } 30 31 }
+357 -1
pnpm-lock.yaml
··· 11 11 '@vercel/speed-insights': 12 12 specifier: ^1.0.10 13 13 version: 1.0.10(@sveltejs/kit@2.5.2)(svelte@5.0.0-next.69) 14 + oslo: 15 + specifier: ^1.1.3 16 + version: 1.1.3 14 17 15 18 devDependencies: 16 19 '@sveltejs/adapter-auto': ··· 60 63 dependencies: 61 64 '@jridgewell/gen-mapping': 0.3.5 62 65 '@jridgewell/trace-mapping': 0.3.25 66 + 67 + /@emnapi/core@0.45.0: 68 + resolution: {integrity: sha512-DPWjcUDQkCeEM4VnljEOEcXdAD7pp8zSZsgOujk/LGIwCXWbXJngin+MO4zbH429lzeC3WbYLGjE2MaUOwzpyw==} 69 + requiresBuild: true 70 + dependencies: 71 + tslib: 2.6.2 72 + dev: false 73 + optional: true 74 + 75 + /@emnapi/runtime@0.45.0: 76 + resolution: {integrity: sha512-Txumi3td7J4A/xTTwlssKieHKTGl3j4A1tglBx72auZ49YK7ePY6XZricgIg9mnZT4xPfA+UPCUdnhRuEFDL+w==} 77 + requiresBuild: true 78 + dependencies: 79 + tslib: 2.6.2 80 + dev: false 81 + optional: true 63 82 64 83 /@esbuild/aix-ppc64@0.19.12: 65 84 resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==} ··· 282 301 '@jridgewell/resolve-uri': 3.1.2 283 302 '@jridgewell/sourcemap-codec': 1.4.15 284 303 304 + /@node-rs/argon2-android-arm-eabi@1.7.0: 305 + resolution: {integrity: sha512-udDqkr5P9E+wYX1SZwAVPdyfYvaF4ry9Tm+R9LkfSHbzWH0uhU6zjIwNRp7m+n4gx691rk+lqqDAIP8RLKwbhg==} 306 + engines: {node: '>= 10'} 307 + cpu: [arm] 308 + os: [android] 309 + requiresBuild: true 310 + dev: false 311 + optional: true 312 + 313 + /@node-rs/argon2-android-arm64@1.7.0: 314 + resolution: {integrity: sha512-s9j/G30xKUx8WU50WIhF0fIl1EdhBGq0RQ06lEhZ0Gi0ap8lhqbE2Bn5h3/G2D1k0Dx+yjeVVNmt/xOQIRG38A==} 315 + engines: {node: '>= 10'} 316 + cpu: [arm64] 317 + os: [android] 318 + requiresBuild: true 319 + dev: false 320 + optional: true 321 + 322 + /@node-rs/argon2-darwin-arm64@1.7.0: 323 + resolution: {integrity: sha512-ZIz4L6HGOB9U1kW23g+m7anGNuTZ0RuTw0vNp3o+2DWpb8u8rODq6A8tH4JRL79S+Co/Nq608m9uackN2pe0Rw==} 324 + engines: {node: '>= 10'} 325 + cpu: [arm64] 326 + os: [darwin] 327 + requiresBuild: true 328 + dev: false 329 + optional: true 330 + 331 + /@node-rs/argon2-darwin-x64@1.7.0: 332 + resolution: {integrity: sha512-5oi/pxqVhODW/pj1+3zElMTn/YukQeywPHHYDbcAW3KsojFjKySfhcJMd1DjKTc+CHQI+4lOxZzSUzK7mI14Hw==} 333 + engines: {node: '>= 10'} 334 + cpu: [x64] 335 + os: [darwin] 336 + requiresBuild: true 337 + dev: false 338 + optional: true 339 + 340 + /@node-rs/argon2-freebsd-x64@1.7.0: 341 + resolution: {integrity: sha512-Ify08683hA4QVXYoIm5SUWOY5DPIT/CMB0CQT+IdxQAg/F+qp342+lUkeAtD5bvStQuCx/dFO3bnnzoe2clMhA==} 342 + engines: {node: '>= 10'} 343 + cpu: [x64] 344 + os: [freebsd] 345 + requiresBuild: true 346 + dev: false 347 + optional: true 348 + 349 + /@node-rs/argon2-linux-arm-gnueabihf@1.7.0: 350 + resolution: {integrity: sha512-7DjDZ1h5AUHAtRNjD19RnQatbhL+uuxBASuuXIBu4/w6Dx8n7YPxwTP4MXfsvuRgKuMWiOb/Ub/HJ3kXVCXRkg==} 351 + engines: {node: '>= 10'} 352 + cpu: [arm] 353 + os: [linux] 354 + requiresBuild: true 355 + dev: false 356 + optional: true 357 + 358 + /@node-rs/argon2-linux-arm64-gnu@1.7.0: 359 + resolution: {integrity: sha512-nJDoMP4Y3YcqGswE4DvP080w6O24RmnFEDnL0emdI8Nou17kNYBzP2546Nasx9GCyLzRcYQwZOUjrtUuQ+od2g==} 360 + engines: {node: '>= 10'} 361 + cpu: [arm64] 362 + os: [linux] 363 + requiresBuild: true 364 + dev: false 365 + optional: true 366 + 367 + /@node-rs/argon2-linux-arm64-musl@1.7.0: 368 + resolution: {integrity: sha512-BKWS8iVconhE3jrb9mj6t1J9vwUqQPpzCbUKxfTGJfc+kNL58F1SXHBoe2cDYGnHrFEHTY0YochzXoAfm4Dm/A==} 369 + engines: {node: '>= 10'} 370 + cpu: [arm64] 371 + os: [linux] 372 + requiresBuild: true 373 + dev: false 374 + optional: true 375 + 376 + /@node-rs/argon2-linux-x64-gnu@1.7.0: 377 + resolution: {integrity: sha512-EmgqZOlf4Jurk/szW1iTsVISx25bKksVC5uttJDUloTgsAgIGReCpUUO1R24pBhu9ESJa47iv8NSf3yAfGv6jQ==} 378 + engines: {node: '>= 10'} 379 + cpu: [x64] 380 + os: [linux] 381 + requiresBuild: true 382 + dev: false 383 + optional: true 384 + 385 + /@node-rs/argon2-linux-x64-musl@1.7.0: 386 + resolution: {integrity: sha512-/o1efYCYIxjfuoRYyBTi2Iy+1iFfhqHCvvVsnjNSgO1xWiWrX0Rrt/xXW5Zsl7vS2Y+yu8PL8KFWRzZhaVxfKA==} 387 + engines: {node: '>= 10'} 388 + cpu: [x64] 389 + os: [linux] 390 + requiresBuild: true 391 + dev: false 392 + optional: true 393 + 394 + /@node-rs/argon2-wasm32-wasi@1.7.0: 395 + resolution: {integrity: sha512-Evmk9VcxqnuwQftfAfYEr6YZYSPLzmKUsbFIMep5nTt9PT4XYRFAERj7wNYp+rOcBenF3X4xoB+LhwcOMTNE5w==} 396 + engines: {node: '>=14.0.0'} 397 + cpu: [wasm32] 398 + requiresBuild: true 399 + dependencies: 400 + '@emnapi/core': 0.45.0 401 + '@emnapi/runtime': 0.45.0 402 + '@tybys/wasm-util': 0.8.1 403 + memfs-browser: 3.5.10302 404 + dev: false 405 + optional: true 406 + 407 + /@node-rs/argon2-win32-arm64-msvc@1.7.0: 408 + resolution: {integrity: sha512-qgsU7T004COWWpSA0tppDqDxbPLgg8FaU09krIJ7FBl71Sz8SFO40h7fDIjfbTT5w7u6mcaINMQ5bSHu75PCaA==} 409 + engines: {node: '>= 10'} 410 + cpu: [arm64] 411 + os: [win32] 412 + requiresBuild: true 413 + dev: false 414 + optional: true 415 + 416 + /@node-rs/argon2-win32-ia32-msvc@1.7.0: 417 + resolution: {integrity: sha512-JGafwWYQ/HpZ3XSwP4adQ6W41pRvhcdXvpzIWtKvX+17+xEXAe2nmGWM6s27pVkg1iV2ZtoYLRDkOUoGqZkCcg==} 418 + engines: {node: '>= 10'} 419 + cpu: [ia32] 420 + os: [win32] 421 + requiresBuild: true 422 + dev: false 423 + optional: true 424 + 425 + /@node-rs/argon2-win32-x64-msvc@1.7.0: 426 + resolution: {integrity: sha512-9oq4ShyFakw8AG3mRls0AoCpxBFcimYx7+jvXeAf2OqKNO+mSA6eZ9z7KQeVCi0+SOEUYxMGf5UiGiDb9R6+9Q==} 427 + engines: {node: '>= 10'} 428 + cpu: [x64] 429 + os: [win32] 430 + requiresBuild: true 431 + dev: false 432 + optional: true 433 + 434 + /@node-rs/argon2@1.7.0: 435 + resolution: {integrity: sha512-zfULc+/tmcWcxn+nHkbyY8vP3+MpEqKORbszt4UkpqZgBgDAAIYvuDN/zukfTgdmo6tmJKKVfzigZOPk4LlIog==} 436 + engines: {node: '>= 10'} 437 + optionalDependencies: 438 + '@node-rs/argon2-android-arm-eabi': 1.7.0 439 + '@node-rs/argon2-android-arm64': 1.7.0 440 + '@node-rs/argon2-darwin-arm64': 1.7.0 441 + '@node-rs/argon2-darwin-x64': 1.7.0 442 + '@node-rs/argon2-freebsd-x64': 1.7.0 443 + '@node-rs/argon2-linux-arm-gnueabihf': 1.7.0 444 + '@node-rs/argon2-linux-arm64-gnu': 1.7.0 445 + '@node-rs/argon2-linux-arm64-musl': 1.7.0 446 + '@node-rs/argon2-linux-x64-gnu': 1.7.0 447 + '@node-rs/argon2-linux-x64-musl': 1.7.0 448 + '@node-rs/argon2-wasm32-wasi': 1.7.0 449 + '@node-rs/argon2-win32-arm64-msvc': 1.7.0 450 + '@node-rs/argon2-win32-ia32-msvc': 1.7.0 451 + '@node-rs/argon2-win32-x64-msvc': 1.7.0 452 + dev: false 453 + 454 + /@node-rs/bcrypt-android-arm-eabi@1.9.0: 455 + resolution: {integrity: sha512-nOCFISGtnodGHNiLrG0WYLWr81qQzZKYfmwHc7muUeq+KY0sQXyHOwZk9OuNQAWv/lnntmtbwkwT0QNEmOyLvA==} 456 + engines: {node: '>= 10'} 457 + cpu: [arm] 458 + os: [android] 459 + requiresBuild: true 460 + dev: false 461 + optional: true 462 + 463 + /@node-rs/bcrypt-android-arm64@1.9.0: 464 + resolution: {integrity: sha512-+ZrIAtigVmjYkqZQTThHVlz0+TG6D+GDHWhVKvR2DifjtqJ0i+mb9gjo++hN+fWEQdWNGxKCiBBjwgT4EcXd6A==} 465 + engines: {node: '>= 10'} 466 + cpu: [arm64] 467 + os: [android] 468 + requiresBuild: true 469 + dev: false 470 + optional: true 471 + 472 + /@node-rs/bcrypt-darwin-arm64@1.9.0: 473 + resolution: {integrity: sha512-CQiS+F9Pa0XozvkXR1g7uXE9QvBOPOplDg0iCCPRYTN9PqA5qYxhwe48G3o+v2UeQceNRrbnEtWuANm7JRqIhw==} 474 + engines: {node: '>= 10'} 475 + cpu: [arm64] 476 + os: [darwin] 477 + requiresBuild: true 478 + dev: false 479 + optional: true 480 + 481 + /@node-rs/bcrypt-darwin-x64@1.9.0: 482 + resolution: {integrity: sha512-4pTKGawYd7sNEjdJ7R/R67uwQH1VvwPZ0SSUMmeNHbxD5QlwAPXdDH11q22uzVXsvNFZ6nGQBg8No5OUGpx6Ug==} 483 + engines: {node: '>= 10'} 484 + cpu: [x64] 485 + os: [darwin] 486 + requiresBuild: true 487 + dev: false 488 + optional: true 489 + 490 + /@node-rs/bcrypt-freebsd-x64@1.9.0: 491 + resolution: {integrity: sha512-UmWzySX4BJhT/B8xmTru6iFif3h0Rpx3TqxRLCcbgmH43r7k5/9QuhpiyzpvKGpKHJCFNm4F3rC2wghvw5FCIg==} 492 + engines: {node: '>= 10'} 493 + cpu: [x64] 494 + os: [freebsd] 495 + requiresBuild: true 496 + dev: false 497 + optional: true 498 + 499 + /@node-rs/bcrypt-linux-arm-gnueabihf@1.9.0: 500 + resolution: {integrity: sha512-8qoX4PgBND2cVwsbajoAWo3NwdfJPEXgpCsZQZURz42oMjbGyhhSYbovBCskGU3EBLoC8RA2B1jFWooeYVn5BA==} 501 + engines: {node: '>= 10'} 502 + cpu: [arm] 503 + os: [linux] 504 + requiresBuild: true 505 + dev: false 506 + optional: true 507 + 508 + /@node-rs/bcrypt-linux-arm64-gnu@1.9.0: 509 + resolution: {integrity: sha512-TuAC6kx0SbcIA4mSEWPi+OCcDjTQUMl213v5gMNlttF+D4ieIZx6pPDGTaMO6M2PDHTeCG0CBzZl0Lu+9b0c7Q==} 510 + engines: {node: '>= 10'} 511 + cpu: [arm64] 512 + os: [linux] 513 + requiresBuild: true 514 + dev: false 515 + optional: true 516 + 517 + /@node-rs/bcrypt-linux-arm64-musl@1.9.0: 518 + resolution: {integrity: sha512-/sIvKDABOI8QOEnLD7hIj02BVaNOuCIWBKvxcJOt8+TuwJ6zmY1UI5kSv9d99WbiHjTp97wtAUbZQwauU4b9ew==} 519 + engines: {node: '>= 10'} 520 + cpu: [arm64] 521 + os: [linux] 522 + requiresBuild: true 523 + dev: false 524 + optional: true 525 + 526 + /@node-rs/bcrypt-linux-x64-gnu@1.9.0: 527 + resolution: {integrity: sha512-DyyhDHDsLBsCKz1tZ1hLvUZSc1DK0FU0v52jK6IBQxrj24WscSU9zZe7ie/V9kdmA4Ep57BfpWX8Dsa2JxGdgQ==} 528 + engines: {node: '>= 10'} 529 + cpu: [x64] 530 + os: [linux] 531 + requiresBuild: true 532 + dev: false 533 + optional: true 534 + 535 + /@node-rs/bcrypt-linux-x64-musl@1.9.0: 536 + resolution: {integrity: sha512-duIiuqQ+Lew8ASSAYm6ZRqcmfBGWwsi81XLUwz86a2HR7Qv6V4yc3ZAUQovAikhjCsIqe8C11JlAZSK6+PlXYg==} 537 + engines: {node: '>= 10'} 538 + cpu: [x64] 539 + os: [linux] 540 + requiresBuild: true 541 + dev: false 542 + optional: true 543 + 544 + /@node-rs/bcrypt-wasm32-wasi@1.9.0: 545 + resolution: {integrity: sha512-ylaGmn9Wjwv/D5lxtawttx3H6Uu2WTTR7lWlRHGT6Ga/MB1Vj4OjSGUW8G8zIVnKuXpGbZ92pgHlt4HUpSLctw==} 546 + engines: {node: '>=14.0.0'} 547 + cpu: [wasm32] 548 + requiresBuild: true 549 + dependencies: 550 + '@emnapi/core': 0.45.0 551 + '@emnapi/runtime': 0.45.0 552 + '@tybys/wasm-util': 0.8.1 553 + memfs-browser: 3.5.10302 554 + dev: false 555 + optional: true 556 + 557 + /@node-rs/bcrypt-win32-arm64-msvc@1.9.0: 558 + resolution: {integrity: sha512-2h86gF7QFyEzODuDFml/Dp1MSJoZjxJ4yyT2Erf4NkwsiA5MqowUhUsorRwZhX6+2CtlGa7orbwi13AKMsYndw==} 559 + engines: {node: '>= 10'} 560 + cpu: [arm64] 561 + os: [win32] 562 + requiresBuild: true 563 + dev: false 564 + optional: true 565 + 566 + /@node-rs/bcrypt-win32-ia32-msvc@1.9.0: 567 + resolution: {integrity: sha512-kqxalCvhs4FkN0+gWWfa4Bdy2NQAkfiqq/CEf6mNXC13RSV673Ev9V8sRlQyNpCHCNkeXfOT9pgoBdJmMs9muA==} 568 + engines: {node: '>= 10'} 569 + cpu: [ia32] 570 + os: [win32] 571 + requiresBuild: true 572 + dev: false 573 + optional: true 574 + 575 + /@node-rs/bcrypt-win32-x64-msvc@1.9.0: 576 + resolution: {integrity: sha512-2y0Tuo6ZAT2Cz8V7DHulSlv1Bip3zbzeXyeur+uR25IRNYXKvI/P99Zl85Fbuu/zzYAZRLLlGTRe6/9IHofe/w==} 577 + engines: {node: '>= 10'} 578 + cpu: [x64] 579 + os: [win32] 580 + requiresBuild: true 581 + dev: false 582 + optional: true 583 + 584 + /@node-rs/bcrypt@1.9.0: 585 + resolution: {integrity: sha512-u2OlIxW264bFUfvbFqDz9HZKFjwe8FHFtn7T/U8mYjPZ7DWYpbUB+/dkW/QgYfMSfR0ejkyuWaBBe0coW7/7ig==} 586 + engines: {node: '>= 10'} 587 + optionalDependencies: 588 + '@node-rs/bcrypt-android-arm-eabi': 1.9.0 589 + '@node-rs/bcrypt-android-arm64': 1.9.0 590 + '@node-rs/bcrypt-darwin-arm64': 1.9.0 591 + '@node-rs/bcrypt-darwin-x64': 1.9.0 592 + '@node-rs/bcrypt-freebsd-x64': 1.9.0 593 + '@node-rs/bcrypt-linux-arm-gnueabihf': 1.9.0 594 + '@node-rs/bcrypt-linux-arm64-gnu': 1.9.0 595 + '@node-rs/bcrypt-linux-arm64-musl': 1.9.0 596 + '@node-rs/bcrypt-linux-x64-gnu': 1.9.0 597 + '@node-rs/bcrypt-linux-x64-musl': 1.9.0 598 + '@node-rs/bcrypt-wasm32-wasi': 1.9.0 599 + '@node-rs/bcrypt-win32-arm64-msvc': 1.9.0 600 + '@node-rs/bcrypt-win32-ia32-msvc': 1.9.0 601 + '@node-rs/bcrypt-win32-x64-msvc': 1.9.0 602 + dev: false 603 + 285 604 /@nodelib/fs.scandir@2.1.5: 286 605 resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} 287 606 engines: {node: '>= 8'} ··· 473 792 transitivePeerDependencies: 474 793 - supports-color 475 794 795 + /@tybys/wasm-util@0.8.1: 796 + resolution: {integrity: sha512-GSsTwyBl4pIzsxAY5wroZdyQKyhXk0d8PCRZtrSZ2WEB1cBdrp2EgGBwHOGCZtIIPun/DL3+AykCv+J6fyRH4Q==} 797 + requiresBuild: true 798 + dependencies: 799 + tslib: 2.6.2 800 + dev: false 801 + optional: true 802 + 476 803 /@types/cookie@0.6.0: 477 804 resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} 478 805 ··· 848 1175 resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} 849 1176 dev: true 850 1177 1178 + /fs-monkey@1.0.5: 1179 + resolution: {integrity: sha512-8uMbBjrhzW76TYgEV27Y5E//W2f/lTFmx78P2w19FZSxarhI/798APGQyuGCwmkNxgwGRhrLfvWyLBvNtuOmew==} 1180 + requiresBuild: true 1181 + dev: false 1182 + optional: true 1183 + 851 1184 /fs.realpath@1.0.0: 852 1185 resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} 853 1186 dev: true ··· 1029 1362 dependencies: 1030 1363 '@jridgewell/sourcemap-codec': 1.4.15 1031 1364 1365 + /memfs-browser@3.5.10302: 1366 + resolution: {integrity: sha512-JJTc/nh3ig05O0gBBGZjTCPOyydaTxNF0uHYBrcc1gHNnO+KIHIvo0Y1FKCJsaei6FCl8C6xfQomXqu+cuzkIw==} 1367 + requiresBuild: true 1368 + dependencies: 1369 + memfs: 3.5.3 1370 + dev: false 1371 + optional: true 1372 + 1373 + /memfs@3.5.3: 1374 + resolution: {integrity: sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==} 1375 + engines: {node: '>= 4.0.0'} 1376 + requiresBuild: true 1377 + dependencies: 1378 + fs-monkey: 1.0.5 1379 + dev: false 1380 + optional: true 1381 + 1032 1382 /merge2@1.4.1: 1033 1383 resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} 1034 1384 engines: {node: '>= 8'} ··· 1129 1479 dependencies: 1130 1480 wrappy: 1.0.2 1131 1481 dev: true 1482 + 1483 + /oslo@1.1.3: 1484 + resolution: {integrity: sha512-hCz528UlNTiegplcyBg6AvG0HLNrnq06EJMp88Ze308GX1hszkb8u3puhNC4aqLMbYQ0hXpl+wQGnwxMtt5+5w==} 1485 + dependencies: 1486 + '@node-rs/argon2': 1.7.0 1487 + '@node-rs/bcrypt': 1.9.0 1488 + dev: false 1132 1489 1133 1490 /parent-module@1.0.1: 1134 1491 resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} ··· 1604 1961 1605 1962 /tslib@2.6.2: 1606 1963 resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} 1607 - dev: true 1608 1964 1609 1965 /typescript@5.4.2: 1610 1966 resolution: {integrity: sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==}
+32 -3
src/lib/stores.svelte.ts
··· 1 + import { alphabet, generateRandomString } from "oslo/crypto"; 2 + 1 3 // Browser + Local Storage 2 4 const browser_exists = (typeof window !== "undefined") && (typeof (document) !== "undefined"); 3 5 const storage = browser_exists ? localStorage : null; 4 6 5 7 // Generalized Local Storage 6 8 export function persisted<T>(key: string, default_value: T) { 7 - let value : T = $state(default_value); 9 + let value : T | undefined = $state(); 8 10 9 11 const initial_local = storage?.getItem(key); 10 12 if (initial_local) { ··· 12 14 if (!value) { update(); } 13 15 } 14 16 else { 17 + value = default_value; 15 18 update(); 16 19 } 17 20 ··· 28 31 } 29 32 } 30 33 31 - // Dark/Light Mode 32 - export const theme = persisted<string>("theme", "dark"); 34 + export type Task = { 35 + id: string; 36 + description: string; 37 + is_completed: boolean; 38 + } 39 + 40 + export type List = { 41 + id: string; 42 + title: string; 43 + tasks: Task[]; 44 + } 45 + 46 + export const local_lists = persisted<List[]>("local_lists", [ 47 + { 48 + id: generateId(), 49 + title: "Take a Break", 50 + tasks: [ 51 + { id: generateId(), description: "Drink water", is_completed: false }, 52 + { id: generateId(), description: "Stand up and stretch", is_completed: false }, 53 + { id: generateId(), description: "Go outside for 10 seconds", is_completed: false }, ] 54 + } 55 + ]); 56 + 57 + export const pinned_list = persisted<string>("pinned_list", local_lists.value![0].id); 58 + 59 + export function generateId() { 60 + return generateRandomString(10, alphabet("a-z", "0-9")); 61 + }
+16 -9
src/routes/+layout.svelte
··· 1 1 <script lang="ts"> 2 2 import "../app.css"; 3 + import { page } from "$app/stores"; 4 + import { onMount } from "svelte"; 3 5 import { fade } from "svelte/transition"; 4 - import { theme } from "$lib/stores.svelte"; 6 + import { persisted, pinned_list } from "$lib/stores.svelte"; 7 + import { goto } from "$app/navigation"; 5 8 9 + const theme = persisted<string>("theme", "light"); 6 10 let is_menu_open = $state(false); 7 11 let theme_style = $derived(theme.value === "light" 8 12 ? "text-black absolute inset-0 -z-10 h-full w-full bg-white bg-[radial-gradient(#e5e7eb_1px,transparent_1px)] [background-size:16px_16px]" 9 13 : "text-white absolute top-0 z-[-2] h-screen w-screen bg-[#000000] bg-[radial-gradient(#ffffff33_1px,#00091d_1px)] bg-[size:20px_20px]" 10 14 ); 11 15 12 - function getTheme(if_light: any, if_dark: any) { 13 - return theme.value === "light" ? if_light : if_dark; 14 - } 16 + onMount(() => { 17 + if ($page.url.pathname === "/") { 18 + goto(`/${pinned_list.value}`); 19 + } 20 + }); 15 21 </script> 16 22 17 23 <div class={`${theme_style} flex flex-col w-full h-full min-w-screen min-h-screen p-8`}> ··· 24 30 {#if is_menu_open} 25 31 <menu 26 32 transition:fade={{ duration: 150 }} 27 - class={`${getTheme("border-black", "border-[#00091d]")} w-fit border z-50 flex flex-col items-start gap-2 h-fit p-2 rounded-xl bg-white`} 33 + class={`${theme.value === "light" ? "border-black" : "border-[#00091d]"} w-fit border z-50 flex flex-col items-start gap-2 h-fit p-2 rounded-xl bg-white`} 28 34 > 29 35 <button class="line-through flex gap-2 text-start w-full h-full rounded-xl pl-2 pr-5 py-2 hover:bg-slate-500/10 transition-all duration-150 items-center"> 30 36 <img src="/shooting-star.svg" alt="Item 1" class="w-8 h-8" /> ··· 36 42 </button> 37 43 </menu> 38 44 {/if} 39 - <nav class={`${getTheme("border-black", "border-[#00091d]")} border z-50 flex self-center items-center gap-4 mx-auto w-fit h-fit p-2 rounded-xl bg-white`}> 45 + 46 + <nav class={`${theme.value === "light" ? "border-black" : "border-[#00091d]"} border z-50 flex self-center items-center gap-4 mx-auto w-fit h-fit p-2 rounded-xl bg-white`}> 40 47 <button 41 48 onclick={() => is_menu_open = !is_menu_open} 42 49 class="w-full h-fit hover:bg-slate-500/10 rounded-full" ··· 56 63 57 64 58 65 <button 59 - onclick={() => { theme.value = getTheme("dark", "light") }} 60 - class={`${getTheme("border-black", "border-[#00091d]")} border w-fit h-fit p-2 bg-white rounded-xl`} 66 + onclick={() => { theme.value = theme.value === "light" ? "dark" : "light" }} 67 + class={`${theme.value === "light" ? "border-black" : "border-[#00091d]"} border w-fit h-fit p-2 bg-white rounded-xl`} 61 68 > 62 69 <img 63 - src={getTheme("/moon.svg", "/light-bulb.svg")} 70 + src="/light-bulb.svg" 64 71 alt="Theme toggle button" 65 72 class="w-12 h-12 hover:bg-slate-500/10 rounded-full" 66 73 />
-1
src/routes/+page.svelte
··· 1 - <p>Test</p>
+29
src/routes/[id]/+page.svelte
··· 1 + <script lang="ts"> 2 + import { onMount } from "svelte"; 3 + import { page } from "$app/stores"; 4 + import { local_lists, pinned_list, type List } from "$lib/stores.svelte"; 5 + 6 + let list : List | undefined = $state(); 7 + 8 + onMount(() => { 9 + list = local_lists.value!.find((l) => l.id === $page.params.id); 10 + }); 11 + 12 + $effect(() => local_lists.update()); 13 + </script> 14 + 15 + <main class="flex flex-col p-4 gap-8"> 16 + {#if list} 17 + <p>{list.title}</p> 18 + <ul> 19 + {#each list.tasks as task (task.id)} 20 + <li class="flex gap-4"> 21 + <input type="checkbox" bind:checked={task.is_completed} /> 22 + <input type="text" bind:value={task.description} /> 23 + </li> 24 + {/each} 25 + </ul> 26 + {:else} 27 + <p>Loading...</p> 28 + {/if} 29 + </main>