From 414dd50a5ccbf41bf2940587dfac00dd5f5cb71a Mon Sep 17 00:00:00 2001 From: Ren Amamiya <123083837+reyamir@users.noreply.github.com> Date: Thu, 17 Aug 2023 15:11:40 +0700 Subject: [PATCH] wip: refactor --- package.json | 21 +- pnpm-lock.yaml | 551 ++++++++++-------- src-tauri/Cargo.lock | 20 +- src-tauri/Cargo.toml | 5 +- .../20230814083543_add_events_table.sql | 13 +- ...7014932_add_last_login_time_to_account.sql | 3 + src-tauri/src/main.rs | 6 + src-tauri/tauri.conf.json | 13 +- src/app/auth/create/step-1.tsx | 25 +- src/app/auth/create/step-3.tsx | 4 - src/app/auth/import/step-1.tsx | 26 +- src/app/auth/import/step-3.tsx | 6 - src/app/auth/onboarding/step-1.tsx | 14 +- src/app/space/components/widgets/feed.tsx | 152 +++-- src/app/space/components/widgets/hashtag.tsx | 84 ++- src/app/space/components/widgets/network.tsx | 170 +++--- src/app/space/components/widgets/user.tsx | 102 ++-- src/app/space/index.tsx | 22 +- src/app/splash.tsx | 9 +- src/libs/ndk/cache.ts | 44 +- src/libs/ndk/instance.ts | 9 +- src/libs/storage/instance.ts | 122 +++- src/shared/notes/kinds/kind1.tsx | 4 +- src/shared/notes/kinds/kind1063.tsx | 6 +- src/shared/notes/kinds/repost.tsx | 25 +- src/shared/notes/kinds/thread.tsx | 4 +- src/shared/notes/kinds/unsupport.tsx | 6 +- src/shared/notes/mentions/note.tsx | 16 +- src/utils/hooks/useEvent.tsx | 28 +- src/utils/hooks/useNostr.tsx | 75 ++- src/utils/parser.tsx | 5 +- src/utils/transform.tsx | 4 +- src/utils/types.d.ts | 12 +- 33 files changed, 958 insertions(+), 648 deletions(-) create mode 100644 src-tauri/migrations/20230817014932_add_last_login_time_to_account.sql diff --git a/package.json b/package.json index 1564f7cd..aceba6ea 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "@ctrl/magnet-link": "^3.1.2", "@headlessui/react": "^1.7.16", "@nostr-dev-kit/ndk": "^0.8.17", + "@nostr-fetch/adapter-ndk": "^0.12.2", "@radix-ui/react-alert-dialog": "^1.0.4", "@radix-ui/react-collapsible": "^1.0.3", "@radix-ui/react-dialog": "^1.0.4", @@ -44,21 +45,21 @@ "@tauri-apps/plugin-stronghold": "github:tauri-apps/tauri-plugin-stronghold#v2", "@tauri-apps/plugin-upload": "github:tauri-apps/tauri-plugin-upload#v2", "@tauri-apps/plugin-window": "github:tauri-apps/tauri-plugin-window#v2", - "@tiptap/extension-image": "^2.0.4", - "@tiptap/extension-mention": "^2.0.4", - "@tiptap/extension-placeholder": "^2.0.4", - "@tiptap/pm": "^2.0.4", - "@tiptap/react": "^2.0.4", - "@tiptap/starter-kit": "^2.0.4", - "@tiptap/suggestion": "^2.0.4", + "@tiptap/extension-image": "^2.1.1", + "@tiptap/extension-mention": "^2.1.1", + "@tiptap/extension-placeholder": "^2.1.1", + "@tiptap/pm": "^2.1.1", + "@tiptap/react": "^2.1.1", + "@tiptap/starter-kit": "^2.1.1", + "@tiptap/suggestion": "^2.1.1", "@void-cat/api": "^1.0.7", "dayjs": "^1.11.9", "destr": "^2.0.1", "get-urls": "^12.1.0", "html-to-text": "^9.0.5", - "immer": "^10.0.2", "light-bolt11-decoder": "^3.0.0", "lru-cache": "^10.0.1", + "nostr-fetch": "^0.12.2", "nostr-tools": "^1.14.0", "qrcode.react": "^3.1.0", "react": "^18.2.0", @@ -68,7 +69,6 @@ "react-markdown": "^8.0.7", "react-player": "^2.12.0", "react-router-dom": "^6.15.0", - "react-string-replace": "^1.1.1", "react-virtuoso": "^4.5.0", "remark-gfm": "^3.0.1", "tippy.js": "^6.3.7", @@ -93,7 +93,7 @@ "eslint": "^8.47.0", "eslint-config-prettier": "^9.0.0", "eslint-plugin-jsx-a11y": "^6.7.1", - "eslint-plugin-react": "^7.33.1", + "eslint-plugin-react": "^7.33.2", "eslint-plugin-simple-import-sort": "^10.0.0", "husky": "^8.0.3", "lint-staged": "^14.0.0", @@ -105,7 +105,6 @@ "tailwindcss": "^3.3.3", "typescript": "^5.1.6", "vite": "^4.4.9", - "vite-plugin-top-level-await": "^1.3.1", "vite-tsconfig-paths": "^4.2.0" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 02c3dbdf..1cd617e4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,6 +10,9 @@ dependencies: '@nostr-dev-kit/ndk': specifier: ^0.8.17 version: 0.8.17(typescript@5.1.6) + '@nostr-fetch/adapter-ndk': + specifier: ^0.12.2 + version: 0.12.2(@nostr-dev-kit/ndk@0.8.17)(nostr-fetch@0.12.2) '@radix-ui/react-alert-dialog': specifier: ^1.0.4 version: 1.0.4(@types/react-dom@18.2.7)(@types/react@18.2.20)(react-dom@18.2.0)(react@18.2.0) @@ -83,26 +86,26 @@ dependencies: specifier: github:tauri-apps/tauri-plugin-window#v2 version: github.com/tauri-apps/tauri-plugin-window/09c9732d0c98c13ee8aeb51de61f7eef3eb33a4b '@tiptap/extension-image': - specifier: ^2.0.4 - version: 2.0.4(@tiptap/core@2.0.4) + specifier: ^2.1.1 + version: 2.1.1(@tiptap/core@2.1.1) '@tiptap/extension-mention': - specifier: ^2.0.4 - version: 2.0.4(@tiptap/core@2.0.4)(@tiptap/pm@2.0.4)(@tiptap/suggestion@2.0.4) + specifier: ^2.1.1 + version: 2.1.1(@tiptap/core@2.1.1)(@tiptap/pm@2.1.1)(@tiptap/suggestion@2.1.1) '@tiptap/extension-placeholder': - specifier: ^2.0.4 - version: 2.0.4(@tiptap/core@2.0.4)(@tiptap/pm@2.0.4) + specifier: ^2.1.1 + version: 2.1.1(@tiptap/core@2.1.1)(@tiptap/pm@2.1.1) '@tiptap/pm': - specifier: ^2.0.4 - version: 2.0.4(@tiptap/core@2.0.4) + specifier: ^2.1.1 + version: 2.1.1 '@tiptap/react': - specifier: ^2.0.4 - version: 2.0.4(@tiptap/core@2.0.4)(@tiptap/pm@2.0.4)(react-dom@18.2.0)(react@18.2.0) + specifier: ^2.1.1 + version: 2.1.1(@tiptap/core@2.1.1)(@tiptap/pm@2.1.1)(react-dom@18.2.0)(react@18.2.0) '@tiptap/starter-kit': - specifier: ^2.0.4 - version: 2.0.4(@tiptap/pm@2.0.4) + specifier: ^2.1.1 + version: 2.1.1(@tiptap/pm@2.1.1) '@tiptap/suggestion': - specifier: ^2.0.4 - version: 2.0.4(@tiptap/core@2.0.4)(@tiptap/pm@2.0.4) + specifier: ^2.1.1 + version: 2.1.1(@tiptap/core@2.1.1)(@tiptap/pm@2.1.1) '@void-cat/api': specifier: ^1.0.7 version: 1.0.7 @@ -118,15 +121,15 @@ dependencies: html-to-text: specifier: ^9.0.5 version: 9.0.5 - immer: - specifier: ^10.0.2 - version: 10.0.2 light-bolt11-decoder: specifier: ^3.0.0 version: 3.0.0 lru-cache: specifier: ^10.0.1 version: 10.0.1 + nostr-fetch: + specifier: ^0.12.2 + version: 0.12.2 nostr-tools: specifier: ^1.14.0 version: 1.14.0 @@ -154,9 +157,6 @@ dependencies: react-router-dom: specifier: ^6.15.0 version: 6.15.0(react-dom@18.2.0)(react@18.2.0) - react-string-replace: - specifier: ^1.1.1 - version: 1.1.1 react-virtuoso: specifier: ^4.5.0 version: 4.5.0(react-dom@18.2.0)(react@18.2.0) @@ -168,7 +168,7 @@ dependencies: version: 6.3.7 zustand: specifier: ^4.4.1 - version: 4.4.1(@types/react@18.2.20)(immer@10.0.2)(react@18.2.0) + version: 4.4.1(@types/react@18.2.20)(react@18.2.0) devDependencies: '@tailwindcss/typography': @@ -226,8 +226,8 @@ devDependencies: specifier: ^6.7.1 version: 6.7.1(eslint@8.47.0) eslint-plugin-react: - specifier: ^7.33.1 - version: 7.33.1(eslint@8.47.0) + specifier: ^7.33.2 + version: 7.33.2(eslint@8.47.0) eslint-plugin-simple-import-sort: specifier: ^10.0.0 version: 10.0.0(eslint@8.47.0) @@ -261,9 +261,6 @@ devDependencies: vite: specifier: ^4.4.9 version: 4.4.9(@types/node@20.5.0) - vite-plugin-top-level-await: - specifier: ^1.3.1 - version: 1.3.1(vite@4.4.9) vite-tsconfig-paths: specifier: ^4.2.0 version: 4.2.0(typescript@5.1.6)(vite@4.4.9) @@ -994,6 +991,24 @@ packages: - typescript dev: false + /@nostr-fetch/adapter-ndk@0.12.2(@nostr-dev-kit/ndk@0.8.17)(nostr-fetch@0.12.2): + resolution: {integrity: sha512-+7EVuxS5DDZvNo6qbfFp7xRHwIyjyi36hYkiQFDjbQ4gX5LKo9RIPB1P+1XGkOSDFshypTbovZCaFunscJ/zhQ==} + peerDependencies: + '@nostr-dev-kit/ndk': ^0.7.5 + nostr-fetch: ^0.12.2 + dependencies: + '@nostr-dev-kit/ndk': 0.8.17(typescript@5.1.6) + '@nostr-fetch/kernel': 0.12.2 + nostr-fetch: 0.12.2 + dev: false + + /@nostr-fetch/kernel@0.12.2: + resolution: {integrity: sha512-ja7StOV33NmdtAMGfQIS0/R0dAkLRm3QxN6u/YAQdp5mXER4BYxiQKxUS/dCoTCSX986MH2zp9Fm0f76u4VaNQ==} + dependencies: + '@noble/curves': 1.1.0 + '@noble/hashes': 1.3.1 + dev: false + /@popperjs/core@2.11.8: resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} dev: false @@ -1542,16 +1557,6 @@ packages: engines: {node: '>=14.0.0'} dev: false - /@rollup/plugin-virtual@3.0.1: - resolution: {integrity: sha512-fK8O0IL5+q+GrsMLuACVNk2x21g3yaw+sG2qn16SnUd3IlBsQyvWxLMGHmCmXRMecPjGRSZ/1LmZB4rjQm68og==} - engines: {node: '>=14.0.0'} - peerDependencies: - rollup: ^1.20.0||^2.0.0||^3.0.0 - peerDependenciesMeta: - rollup: - optional: true - dev: true - /@scure/base@1.1.1: resolution: {integrity: sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==} dev: false @@ -1582,8 +1587,8 @@ packages: resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} dev: false - /@swc/core-darwin-arm64@1.3.76: - resolution: {integrity: sha512-ovviEhZ/1E81Z9OGrO0ivLWk4VCa3I3ZzM+cd3gugglRRwVwtlIaoIYqY5S3KiCAupDd1+UCl5X7Vbio7a/V8g==} + /@swc/core-darwin-arm64@1.3.77: + resolution: {integrity: sha512-l4KGQAGB4Ih1Al2tWoUBrtVJCF/xZRjH3jCMCRD52KZDRAnRVDq42JKek7+aHjjH8juzTISaqzsI8Ipv6zvKhA==} engines: {node: '>=10'} cpu: [arm64] os: [darwin] @@ -1591,8 +1596,8 @@ packages: dev: true optional: true - /@swc/core-darwin-x64@1.3.76: - resolution: {integrity: sha512-tcySTDqs0SHCebtW35sCdcLWsmTEo7bEwx0gNL/spetqVT9fpFi6qU8qcnt7i2KaZHbeNl9g1aadu+Yrni+GzA==} + /@swc/core-darwin-x64@1.3.77: + resolution: {integrity: sha512-eFCkZg/BzObOn5IWn7t/Ywz+jlZKff/1XBymT7Arh/UkO39Agh+rYdBqjbylp4JQMl0qGRBfxD3wPgDRoViNVQ==} engines: {node: '>=10'} cpu: [x64] os: [darwin] @@ -1600,8 +1605,8 @@ packages: dev: true optional: true - /@swc/core-linux-arm-gnueabihf@1.3.76: - resolution: {integrity: sha512-apgzpGWy1AwoMF4urAAASsAjE7rEzZFIF+p6utuxhS7cNHzE0AyEVDYJbo+pzBdlZ8orBdzzsHtFwoEgKOjebA==} + /@swc/core-linux-arm-gnueabihf@1.3.77: + resolution: {integrity: sha512-+1BueyGcCQAtxSORJml0CU8aKQNssQ5E3ABMFJwCbcec+lUCiGYK1fBfqj4FmWQMbXuQ+mn1SMeXSZAtaXoQ3w==} engines: {node: '>=10'} cpu: [arm] os: [linux] @@ -1609,8 +1614,8 @@ packages: dev: true optional: true - /@swc/core-linux-arm64-gnu@1.3.76: - resolution: {integrity: sha512-c3c0zz6S0eludqidDpuqbadE0WT3OZczyQxe9Vw8lFFXES85mvNGtwYzyGK2o7TICpsuHrndwDIoYpmpWk879g==} + /@swc/core-linux-arm64-gnu@1.3.77: + resolution: {integrity: sha512-3smbzVcuuCiWWPFeUIp1c0aAXd+fGsc8x8rUcYvoJAWBgLJ45JymOI5WSUjIybl3rk0prdkbFylZuR0t1Rue3A==} engines: {node: '>=10'} cpu: [arm64] os: [linux] @@ -1618,8 +1623,8 @@ packages: dev: true optional: true - /@swc/core-linux-arm64-musl@1.3.76: - resolution: {integrity: sha512-Is3bpq7F2qtlnkzEeOD6HIZJPpOmu3q6c82lKww90Q0NnrlSluVMozTHJgwVoFZyizH7uLnk0LuNcEAWLnmJIw==} + /@swc/core-linux-arm64-musl@1.3.77: + resolution: {integrity: sha512-e81+i4ef5vDeu9AkMY2AamPcmtPVPUqeqq3aNWM1tcHCaUej1DwY4xhRxrd1OvEoYyVBLtiMb5nenF3V9OzXIQ==} engines: {node: '>=10'} cpu: [arm64] os: [linux] @@ -1627,8 +1632,8 @@ packages: dev: true optional: true - /@swc/core-linux-x64-gnu@1.3.76: - resolution: {integrity: sha512-iwCeRzd9oSvUzqt7nU6p/ztceAWfnO9XVxBn502R5gs6QCBbE1HCKrWHDO77aKPK7ss+0NcIGHvXTd9L8/wRzw==} + /@swc/core-linux-x64-gnu@1.3.77: + resolution: {integrity: sha512-gl3+9VESckZ/GYCmGClGgXqB2tAA2MivEV/51Wde+2alo2lPSSujEhxE6Q3TNYkXOLAHSupYyDZ0ou9RfXufOw==} engines: {node: '>=10'} cpu: [x64] os: [linux] @@ -1636,8 +1641,8 @@ packages: dev: true optional: true - /@swc/core-linux-x64-musl@1.3.76: - resolution: {integrity: sha512-a671g4tW8kyFeuICsgq4uB9ukQfiIyXJT4V6YSnmqhCTz5mazWuDxZ5wKnx/1g5nXTl+U5cWH2TZaCJatp4GKA==} + /@swc/core-linux-x64-musl@1.3.77: + resolution: {integrity: sha512-AqQLZAMYTaNrA4i/Nv/GhXdildDZyRv6xsK8u2actevv5PPjD/69yYB3Z4uaptwh/4ys4W/Y2vnt+OPCNH4OQg==} engines: {node: '>=10'} cpu: [x64] os: [linux] @@ -1645,8 +1650,8 @@ packages: dev: true optional: true - /@swc/core-win32-arm64-msvc@1.3.76: - resolution: {integrity: sha512-+swEFtjdMezS0vKUhJC3psdSDtOJGY5pEOt4e8XOPvn7aQpKQ9LfF49XVtIwDSk5SGuWtVoLFzkSY3reWUJCyg==} + /@swc/core-win32-arm64-msvc@1.3.77: + resolution: {integrity: sha512-Wdw++6w7WyavxZ3WruElCrRJ6EO0iHS0Mts4qHnbKgD08GJqIMTZPtZ5qhRe9zCf6sj2rQqhAMf/HKhYrHoF+w==} engines: {node: '>=10'} cpu: [arm64] os: [win32] @@ -1654,8 +1659,8 @@ packages: dev: true optional: true - /@swc/core-win32-ia32-msvc@1.3.76: - resolution: {integrity: sha512-5CqwAykpGBJ3PqGLOlWGLGIPpBAG1IwWVDUfro3hhjQ7XJxV5Z1aQf5V5OJ90HJVtrEAVx2xx59UV/Dh081LOg==} + /@swc/core-win32-ia32-msvc@1.3.77: + resolution: {integrity: sha512-ObNVpdtLdXDpmVKuMZh87yBYL4ti64WX95o2j5Oq3r0e0RqwIGqGvPDxvJVEiyCnaXHfl8eSNKWuiOxPHPkMNQ==} engines: {node: '>=10'} cpu: [ia32] os: [win32] @@ -1663,8 +1668,8 @@ packages: dev: true optional: true - /@swc/core-win32-x64-msvc@1.3.76: - resolution: {integrity: sha512-CiMpWLLlR3Cew9067E7XxaLBwYYJ90r9EhGSO6V1pvYSWj7ET/Ppmtj1ZhzPJMqRXAP6xflfl5R5o4ee1m4WLA==} + /@swc/core-win32-x64-msvc@1.3.77: + resolution: {integrity: sha512-Ew6jg/qr0v/2ixeJXvIUBuAPMKTz8HRoDBO/nHkvlnDFmkhsyH7h5YwJS1rLBwAEhWuJaVYjYi7cibZTI/QRYQ==} engines: {node: '>=10'} cpu: [x64] os: [win32] @@ -1672,8 +1677,8 @@ packages: dev: true optional: true - /@swc/core@1.3.76: - resolution: {integrity: sha512-aYYTA2aVYkwJAZepQXtPnkUthhOfn8qd6rsh+lrJxonFrjmpI7RHt2tMDVTXP6XDX7fvnvrVtT1bwZfmBFPh0Q==} + /@swc/core@1.3.77: + resolution: {integrity: sha512-CiLD2NGTdhE8JnWFHeRAglaCAcvwOxvpeWNtCIT261GrxTKCXHPAn4eqIWiBzXnwWDmZ6XdyrCL4/GmPESNnrg==} engines: {node: '>=10'} requiresBuild: true peerDependencies: @@ -1682,16 +1687,16 @@ packages: '@swc/helpers': optional: true optionalDependencies: - '@swc/core-darwin-arm64': 1.3.76 - '@swc/core-darwin-x64': 1.3.76 - '@swc/core-linux-arm-gnueabihf': 1.3.76 - '@swc/core-linux-arm64-gnu': 1.3.76 - '@swc/core-linux-arm64-musl': 1.3.76 - '@swc/core-linux-x64-gnu': 1.3.76 - '@swc/core-linux-x64-musl': 1.3.76 - '@swc/core-win32-arm64-msvc': 1.3.76 - '@swc/core-win32-ia32-msvc': 1.3.76 - '@swc/core-win32-x64-msvc': 1.3.76 + '@swc/core-darwin-arm64': 1.3.77 + '@swc/core-darwin-x64': 1.3.77 + '@swc/core-linux-arm-gnueabihf': 1.3.77 + '@swc/core-linux-arm64-gnu': 1.3.77 + '@swc/core-linux-arm64-musl': 1.3.77 + '@swc/core-linux-x64-gnu': 1.3.77 + '@swc/core-linux-x64-musl': 1.3.77 + '@swc/core-win32-arm64-msvc': 1.3.77 + '@swc/core-win32-ia32-msvc': 1.3.77 + '@swc/core-win32-x64-msvc': 1.3.77 dev: true /@tailwindcss/typography@0.5.9(tailwindcss@3.3.3): @@ -1853,226 +1858,223 @@ packages: '@tauri-apps/cli-win32-x64-msvc': 2.0.0-alpha.11 dev: false - /@tiptap/core@2.0.4(@tiptap/pm@2.0.4): - resolution: {integrity: sha512-2YOMjRqoBGEP4YGgYpuPuBBJHMeqKOhLnS0WVwjVP84zOmMgZ7A8M6ILC9Xr7Q/qHZCvyBGWOSsI7+3HsEzzYQ==} + /@tiptap/core@2.1.1(@tiptap/pm@2.1.1): + resolution: {integrity: sha512-lzutUAbS2MA0aAqFVpsVo3fvbf4t+1d4xrlMpQVs/A3FxSrRbnykpZy4FRTI36e82Z32VCyrhSno/z+GYqypjw==} peerDependencies: '@tiptap/pm': ^2.0.0 dependencies: - '@tiptap/pm': 2.0.4(@tiptap/core@2.0.4) + '@tiptap/pm': 2.1.1 dev: false - /@tiptap/extension-blockquote@2.0.4(@tiptap/core@2.0.4): - resolution: {integrity: sha512-z5qfuLi04OgCBI6/odzB2vhulT/wpjymYOnON65vLXGZZbUw4cbPloykhqgWvQp+LzKH+HBhl4fz53d5CgnbOA==} + /@tiptap/extension-blockquote@2.1.1(@tiptap/core@2.1.1): + resolution: {integrity: sha512-EK9mld4gYFhpNdabhvc7EMbhhFVrhaTZtTW17cCB3ONArMZx7ps8g+aKSdV5Ftdn/TxI3n4lQXazlIz1HQj+Zg==} peerDependencies: '@tiptap/core': ^2.0.0 dependencies: - '@tiptap/core': 2.0.4(@tiptap/pm@2.0.4) + '@tiptap/core': 2.1.1(@tiptap/pm@2.1.1) dev: false - /@tiptap/extension-bold@2.0.4(@tiptap/core@2.0.4): - resolution: {integrity: sha512-CWSQy1uWkVsen8HUsqhm+oEIxJrCiCENABUbhaVcJL/MqhnP4Trrh1B6O00Yfoc0XToPRRibDaHMFs4A3MSO0g==} + /@tiptap/extension-bold@2.1.1(@tiptap/core@2.1.1): + resolution: {integrity: sha512-a65KDTkdEc+NUSm9YW+i568svRMwN4vOZ5L0lgEWXtENJok5vw25egrzW8bVVnJ9VE/CS2YOn2niBZMwdKTD7A==} peerDependencies: '@tiptap/core': ^2.0.0 dependencies: - '@tiptap/core': 2.0.4(@tiptap/pm@2.0.4) + '@tiptap/core': 2.1.1(@tiptap/pm@2.1.1) dev: false - /@tiptap/extension-bubble-menu@2.0.4(@tiptap/core@2.0.4)(@tiptap/pm@2.0.4): - resolution: {integrity: sha512-+cRZwj0YINNNDElSAiX1pvY2K98S2j9MQW2dXV5oLqsJhqGPZsKxVo8I1u7ZtqUla3QE1V18RYPAzVgTiMRkBg==} + /@tiptap/extension-bubble-menu@2.1.1(@tiptap/core@2.1.1)(@tiptap/pm@2.1.1): + resolution: {integrity: sha512-v41R0xtBeAG72yL9p70gwxE1SW+16+Z71MzcjyEIrifNFCA27DeyXqS+n/N8m0W/a8WPVgVO3yqEGqlOwTS+JA==} peerDependencies: '@tiptap/core': ^2.0.0 '@tiptap/pm': ^2.0.0 dependencies: - '@tiptap/core': 2.0.4(@tiptap/pm@2.0.4) - '@tiptap/pm': 2.0.4(@tiptap/core@2.0.4) + '@tiptap/core': 2.1.1(@tiptap/pm@2.1.1) + '@tiptap/pm': 2.1.1 tippy.js: 6.3.7 dev: false - /@tiptap/extension-bullet-list@2.0.4(@tiptap/core@2.0.4): - resolution: {integrity: sha512-JSZKBVTaKSuLl5fR4EKE4dOINOrgeRHYA25Vj6cWjgdvpTw5ef7vcUdn9yP4JwTmLRI+VnnMlYL3rqigU3iZNg==} + /@tiptap/extension-bullet-list@2.1.1(@tiptap/core@2.1.1): + resolution: {integrity: sha512-ji7PbjNneZ7Jk+g2cMSwd8J1eJlTHYsrMUeRDxz4ZVxykDn97ur73wtx1FsEMKxxxNKeOlUayU6F6UE1Bwyv5g==} peerDependencies: '@tiptap/core': ^2.0.0 dependencies: - '@tiptap/core': 2.0.4(@tiptap/pm@2.0.4) + '@tiptap/core': 2.1.1(@tiptap/pm@2.1.1) dev: false - /@tiptap/extension-code-block@2.0.4(@tiptap/core@2.0.4)(@tiptap/pm@2.0.4): - resolution: {integrity: sha512-In2tV3rgm/MznVF0N7qYsYugPWSzhZHaCRCWcFKNvllMExpo91bUWvk+hXaIhhPxvuqGIVezjybwrYuU3bJW0g==} + /@tiptap/extension-code-block@2.1.1(@tiptap/core@2.1.1)(@tiptap/pm@2.1.1): + resolution: {integrity: sha512-Zt+la0WWAI4bz+Zv41w1ZY2rURS9etMIIsdnbDKgt3GHaNUhrc23hZAsTGKr9HjHv5I4vB2+593zRxBVdbs1qA==} peerDependencies: '@tiptap/core': ^2.0.0 '@tiptap/pm': ^2.0.0 dependencies: - '@tiptap/core': 2.0.4(@tiptap/pm@2.0.4) - '@tiptap/pm': 2.0.4(@tiptap/core@2.0.4) + '@tiptap/core': 2.1.1(@tiptap/pm@2.1.1) + '@tiptap/pm': 2.1.1 dev: false - /@tiptap/extension-code@2.0.4(@tiptap/core@2.0.4): - resolution: {integrity: sha512-HuwJSJkipZf4hkns9witv1CABNIPiB9C8lgAQXK4xJKcoUQChcnljEL+PQ2NqeEeMTEeV3nG3A/0QafH0pgTgg==} + /@tiptap/extension-code@2.1.1(@tiptap/core@2.1.1): + resolution: {integrity: sha512-zHu98cyrWau8a9DPaypFOKEnYRI6TLD8OC3odlo+3BC+RhmcFi+azM2bG9/ba+DUIlaxImZwK5BmpfXM7QvZ5A==} peerDependencies: '@tiptap/core': ^2.0.0 dependencies: - '@tiptap/core': 2.0.4(@tiptap/pm@2.0.4) + '@tiptap/core': 2.1.1(@tiptap/pm@2.1.1) dev: false - /@tiptap/extension-document@2.0.4(@tiptap/core@2.0.4): - resolution: {integrity: sha512-mCj2fAhnNhIHttPSqfTPSSTGwClGaPYvhT56Ij/Pi4iCrWjPXzC4XnIkIHSS34qS2tJN4XJzr/z7lm3NeLkF1w==} + /@tiptap/extension-document@2.1.1(@tiptap/core@2.1.1): + resolution: {integrity: sha512-t2OCJNFM9aU+z1AGQiZmWEWRvoFo/NyOCWHOlT5O9cqR/H+h/twlX76wfAJEAmtcP/HYYffkdlmVBjWFu8DVsQ==} peerDependencies: '@tiptap/core': ^2.0.0 dependencies: - '@tiptap/core': 2.0.4(@tiptap/pm@2.0.4) + '@tiptap/core': 2.1.1(@tiptap/pm@2.1.1) dev: false - /@tiptap/extension-dropcursor@2.0.4(@tiptap/core@2.0.4)(@tiptap/pm@2.0.4): - resolution: {integrity: sha512-1OmKBv/E+nJo2vsosvu8KwFiBB+gZM1pY61qc7JbwEKHSYAxUFHfvLkIA0IQ53Z0DHMrFSKgWmHEcbnqtGevCA==} + /@tiptap/extension-dropcursor@2.1.1(@tiptap/core@2.1.1)(@tiptap/pm@2.1.1): + resolution: {integrity: sha512-gAaRrKA3LVIHAgoazmfZbFbLtmYPCk/1/C1N+ULmBbzWj7bCshGjhOlWgrDD/1uyc7HRMO8iRz6u1f6EqbHjmg==} peerDependencies: '@tiptap/core': ^2.0.0 '@tiptap/pm': ^2.0.0 dependencies: - '@tiptap/core': 2.0.4(@tiptap/pm@2.0.4) - '@tiptap/pm': 2.0.4(@tiptap/core@2.0.4) + '@tiptap/core': 2.1.1(@tiptap/pm@2.1.1) + '@tiptap/pm': 2.1.1 dev: false - /@tiptap/extension-floating-menu@2.0.4(@tiptap/core@2.0.4)(@tiptap/pm@2.0.4): - resolution: {integrity: sha512-0YRE738k+kNKuSHhAb3jj9ZQ7Kda78RYRr+cX2jrQVueIMKebPIY07eBt6JcKmob9V9vcNn9qLtBfmygfcPUQg==} + /@tiptap/extension-floating-menu@2.1.1(@tiptap/core@2.1.1)(@tiptap/pm@2.1.1): + resolution: {integrity: sha512-LGVOhuTTFOK+RReaXSkfdzFcPHkB6M/mL6X2Hps/ieib4e36bnB7+1/yfB690fU4yxfI5EFLL8E3+i5Igm5XlA==} peerDependencies: '@tiptap/core': ^2.0.0 '@tiptap/pm': ^2.0.0 dependencies: - '@tiptap/core': 2.0.4(@tiptap/pm@2.0.4) - '@tiptap/pm': 2.0.4(@tiptap/core@2.0.4) + '@tiptap/core': 2.1.1(@tiptap/pm@2.1.1) + '@tiptap/pm': 2.1.1 tippy.js: 6.3.7 dev: false - /@tiptap/extension-gapcursor@2.0.4(@tiptap/core@2.0.4)(@tiptap/pm@2.0.4): - resolution: {integrity: sha512-VxmKfBQjSSu1mNvHlydA4dJW/zawGKyqmnryiFNcUV9s+/HWLR5i9SiUl4wJM/B8sG8cQxClne5/LrCAeGNYuA==} + /@tiptap/extension-gapcursor@2.1.1(@tiptap/core@2.1.1)(@tiptap/pm@2.1.1): + resolution: {integrity: sha512-ObKdsvtCcGej+4XPoxd7gWFJJCWcVDN1E1zFluoRKaRLCsc8nNIj4C92FflVbhYzrwHJY6YUzhV3ZYF+AJejMQ==} peerDependencies: '@tiptap/core': ^2.0.0 '@tiptap/pm': ^2.0.0 dependencies: - '@tiptap/core': 2.0.4(@tiptap/pm@2.0.4) - '@tiptap/pm': 2.0.4(@tiptap/core@2.0.4) + '@tiptap/core': 2.1.1(@tiptap/pm@2.1.1) + '@tiptap/pm': 2.1.1 dev: false - /@tiptap/extension-hard-break@2.0.4(@tiptap/core@2.0.4): - resolution: {integrity: sha512-4j8BZa6diuoRytWoIc7j25EYWWut5TZDLbb+OVURdkHnsF8B8zeNTo55W40CdwSaSyTtXtxbTIldV80ShQarGQ==} + /@tiptap/extension-hard-break@2.1.1(@tiptap/core@2.1.1): + resolution: {integrity: sha512-mfShnSb74U2nryl2Of3D7ej7+IzKHf5qD72kf3vljNlDf29Z2Vq+Jb5emNlj18/vnhBQIevTEeO1Plyfl0X+KQ==} peerDependencies: '@tiptap/core': ^2.0.0 dependencies: - '@tiptap/core': 2.0.4(@tiptap/pm@2.0.4) + '@tiptap/core': 2.1.1(@tiptap/pm@2.1.1) dev: false - /@tiptap/extension-heading@2.0.4(@tiptap/core@2.0.4): - resolution: {integrity: sha512-EfitUbew5ljH3xVlBXAxqqcJ4rjv15b8379LYOV6KQCf+Y1wY0gy9Q8wXSnrsAagqrvqipja4Ihn3OZeyIM+CA==} + /@tiptap/extension-heading@2.1.1(@tiptap/core@2.1.1): + resolution: {integrity: sha512-hITv6yWEm8MiYX9zNwwDxdPLsvWYz4Pp5jp/Owy4SmZpUcBNL1hJQ1tzHgJApv/odygGtjDrz0Al15ERRWOGnQ==} peerDependencies: '@tiptap/core': ^2.0.0 dependencies: - '@tiptap/core': 2.0.4(@tiptap/pm@2.0.4) + '@tiptap/core': 2.1.1(@tiptap/pm@2.1.1) dev: false - /@tiptap/extension-history@2.0.4(@tiptap/core@2.0.4)(@tiptap/pm@2.0.4): - resolution: {integrity: sha512-3GAUszn1xZx3vniHMiX9BSKmfvb5QOb0oSLXInN+hx80CgJDIHqIFuhx2dyV9I/HWpa0cTxaLWj64kfDzb1JVg==} + /@tiptap/extension-history@2.1.1(@tiptap/core@2.1.1)(@tiptap/pm@2.1.1): + resolution: {integrity: sha512-YDGrqWvnWcXPNIqpFklc8Xm/bG451RLJEebYS1gZbaQ0QJ2MCGxyDFJvimE3Qf5FP08Ugjxn3q5xHDoVlX+9/g==} peerDependencies: '@tiptap/core': ^2.0.0 '@tiptap/pm': ^2.0.0 dependencies: - '@tiptap/core': 2.0.4(@tiptap/pm@2.0.4) - '@tiptap/pm': 2.0.4(@tiptap/core@2.0.4) + '@tiptap/core': 2.1.1(@tiptap/pm@2.1.1) + '@tiptap/pm': 2.1.1 dev: false - /@tiptap/extension-horizontal-rule@2.0.4(@tiptap/core@2.0.4)(@tiptap/pm@2.0.4): - resolution: {integrity: sha512-OMx2ImQseKbSUjPbbRCuYGOJshxYedh9giWAqwgWWokhYkH4nGxXn5m7+Laj+1wLre4bnWgHWVY4wMGniEj3aw==} + /@tiptap/extension-horizontal-rule@2.1.1(@tiptap/core@2.1.1)(@tiptap/pm@2.1.1): + resolution: {integrity: sha512-iYV1J3FQHYU/v2dVf/GvXWdIns/wCZq+CHB2WIWXEuTaTt7GJEbNS3zzH2pAwuVUUG7xDyLaS9mON5LsnonPdQ==} peerDependencies: '@tiptap/core': ^2.0.0 '@tiptap/pm': ^2.0.0 dependencies: - '@tiptap/core': 2.0.4(@tiptap/pm@2.0.4) - '@tiptap/pm': 2.0.4(@tiptap/core@2.0.4) + '@tiptap/core': 2.1.1(@tiptap/pm@2.1.1) + '@tiptap/pm': 2.1.1 dev: false - /@tiptap/extension-image@2.0.4(@tiptap/core@2.0.4): - resolution: {integrity: sha512-5iQ96pt9xppM8sWzwhGgc99PPoYPQuokTaCXAQKDI0Y1CFCjZ+/duUG3al1VUMpBXsjJw3/RVO1+7CEhRTd3mA==} + /@tiptap/extension-image@2.1.1(@tiptap/core@2.1.1): + resolution: {integrity: sha512-gKHqk30OLvSzSsTlUlF3QGIg2MpDkggvPoLt2+7idNT2XbxhBw88kt/eskiSSUGlqFuziTXLWtXPazaH9whK3w==} peerDependencies: '@tiptap/core': ^2.0.0 dependencies: - '@tiptap/core': 2.0.4(@tiptap/pm@2.0.4) + '@tiptap/core': 2.1.1(@tiptap/pm@2.1.1) dev: false - /@tiptap/extension-italic@2.0.4(@tiptap/core@2.0.4): - resolution: {integrity: sha512-C/6+qs4Jh8xERRP0wcOopA1+emK8MOkBE4RQx5NbPnT2iCpERP0GlmHBFQIjaYPctZgKFHxsCfRnneS5Xe76+A==} + /@tiptap/extension-italic@2.1.1(@tiptap/core@2.1.1): + resolution: {integrity: sha512-4DzBx4nmnVqhLKvhbq2UZl80mAa3tHSuZ6lYJ4MCh3WIANc6e9XIGPZ2mmT8M51Tx8xwWBWUQukMN+uQFzMsVw==} peerDependencies: '@tiptap/core': ^2.0.0 dependencies: - '@tiptap/core': 2.0.4(@tiptap/pm@2.0.4) + '@tiptap/core': 2.1.1(@tiptap/pm@2.1.1) dev: false - /@tiptap/extension-list-item@2.0.4(@tiptap/core@2.0.4): - resolution: {integrity: sha512-tSkbLgRo1QMNDJttWs9FeRywkuy5T2HdLKKfUcUNzT3s0q5AqIJl7VyimsBL4A6MUfN1qQMZCMHB4pM9Mkluww==} + /@tiptap/extension-list-item@2.1.1(@tiptap/core@2.1.1): + resolution: {integrity: sha512-UikZsgcdgno5UWXn2TPGZeHi0mHxujw5gtaA6+x9j/oVjVPqevqg2uDilMkig+HrsTWwLVzntvnVsXdu2TneWw==} peerDependencies: '@tiptap/core': ^2.0.0 dependencies: - '@tiptap/core': 2.0.4(@tiptap/pm@2.0.4) + '@tiptap/core': 2.1.1(@tiptap/pm@2.1.1) dev: false - /@tiptap/extension-mention@2.0.4(@tiptap/core@2.0.4)(@tiptap/pm@2.0.4)(@tiptap/suggestion@2.0.4): - resolution: {integrity: sha512-6IpG4BoylcfZhooKRx12PMSwLN9Nt5Baxh/4HcH0wknNZHINlAqslL8kIZ09vzeS9REP77T4oRTZFjYBINV1Ig==} + /@tiptap/extension-mention@2.1.1(@tiptap/core@2.1.1)(@tiptap/pm@2.1.1)(@tiptap/suggestion@2.1.1): + resolution: {integrity: sha512-gksHDhzGIdXe8MnYxDA2mcS5Ex0z24/mF8Ihh13UUalKWeeO7GvJ7gWWDvPN3+9z8tFtrm9fMXhiLG1hwgklAg==} peerDependencies: '@tiptap/core': ^2.0.0 '@tiptap/pm': ^2.0.0 '@tiptap/suggestion': ^2.0.0 dependencies: - '@tiptap/core': 2.0.4(@tiptap/pm@2.0.4) - '@tiptap/pm': 2.0.4(@tiptap/core@2.0.4) - '@tiptap/suggestion': 2.0.4(@tiptap/core@2.0.4)(@tiptap/pm@2.0.4) + '@tiptap/core': 2.1.1(@tiptap/pm@2.1.1) + '@tiptap/pm': 2.1.1 + '@tiptap/suggestion': 2.1.1(@tiptap/core@2.1.1)(@tiptap/pm@2.1.1) dev: false - /@tiptap/extension-ordered-list@2.0.4(@tiptap/core@2.0.4): - resolution: {integrity: sha512-Kfg+8k9p4iJCUKP/yIa18LfUpl9trURSMP/HX3/yQTz9Ul1vDrjxeFjSE5uWNvupcXRAM24js+aYrCmV7zpU+Q==} + /@tiptap/extension-ordered-list@2.1.1(@tiptap/core@2.1.1): + resolution: {integrity: sha512-GxOnvAReBza9+r/tVBne/0b0Dp3/4vHb24TpKYFG1yLZzsEren6qqtForqEhzA/O74ar1WLl6bpcET0/wBAn0w==} peerDependencies: '@tiptap/core': ^2.0.0 dependencies: - '@tiptap/core': 2.0.4(@tiptap/pm@2.0.4) + '@tiptap/core': 2.1.1(@tiptap/pm@2.1.1) dev: false - /@tiptap/extension-paragraph@2.0.4(@tiptap/core@2.0.4): - resolution: {integrity: sha512-nDxpopi9WigVqpfi8nU3B0fWYB14EMvKIkutNZo8wJvKGTZufNI8hw66wupIx/jZH1gFxEa5dHerw6aSYuWjgQ==} + /@tiptap/extension-paragraph@2.1.1(@tiptap/core@2.1.1): + resolution: {integrity: sha512-5H1nIBW6oKr90JKZI2BTCIlQShIMtO0dCUj/n0CI1pBhJOuGPz8HIVrcZlvYY42KRJQu6pWW3HfdvCiWFVqAzA==} peerDependencies: '@tiptap/core': ^2.0.0 dependencies: - '@tiptap/core': 2.0.4(@tiptap/pm@2.0.4) + '@tiptap/core': 2.1.1(@tiptap/pm@2.1.1) dev: false - /@tiptap/extension-placeholder@2.0.4(@tiptap/core@2.0.4)(@tiptap/pm@2.0.4): - resolution: {integrity: sha512-Y8hjUYBGTbytgrsplSZdHGciqbuVHQX+h0JcuvVaIlAy1kR7hmbxJLqL8tNa7qLtTqo2MfS2942OtSv85JOCzA==} + /@tiptap/extension-placeholder@2.1.1(@tiptap/core@2.1.1)(@tiptap/pm@2.1.1): + resolution: {integrity: sha512-9/rTe2KvKm0sugWbkTo4bXwT4+u5coksJI8t64v9WrjB6j1N3dwDiOAV2OfL6g1S5Jr7b3XzMIDDr1YP54lO/w==} peerDependencies: '@tiptap/core': ^2.0.0 '@tiptap/pm': ^2.0.0 dependencies: - '@tiptap/core': 2.0.4(@tiptap/pm@2.0.4) - '@tiptap/pm': 2.0.4(@tiptap/core@2.0.4) + '@tiptap/core': 2.1.1(@tiptap/pm@2.1.1) + '@tiptap/pm': 2.1.1 dev: false - /@tiptap/extension-strike@2.0.4(@tiptap/core@2.0.4): - resolution: {integrity: sha512-Men7LK6N/Dh3/G4/z2Z9WkDHM2Gxx1XyxYix2ZMf5CnqY37SeDNUnGDqit65pdIN3Y/TQnOZTkKSBilSAtXfJA==} + /@tiptap/extension-strike@2.1.1(@tiptap/core@2.1.1): + resolution: {integrity: sha512-Ah1LAXn9ST0aynyxmbB3sd6ndnB0ltFRqFE9AoB4c7akwOHIlS3ovp90u4z2OUe2Bf0pJlMffYXur7TtqyM8hQ==} peerDependencies: '@tiptap/core': ^2.0.0 dependencies: - '@tiptap/core': 2.0.4(@tiptap/pm@2.0.4) + '@tiptap/core': 2.1.1(@tiptap/pm@2.1.1) dev: false - /@tiptap/extension-text@2.0.4(@tiptap/core@2.0.4): - resolution: {integrity: sha512-i8/VFlVZh7TkAI49KKX5JmC0tM8RGwyg5zUpozxYbLdCOv07AkJt+E1fLJty9mqH4Y5HJMNnyNxsuZ9Ol/ySRA==} + /@tiptap/extension-text@2.1.1(@tiptap/core@2.1.1): + resolution: {integrity: sha512-cOjbaUo5IFRu7VxbncuIPNjekZbrgqOPE/HCuUVohNRwLbdCYqrvO76yk8N3D3j+RL6mSVISsOgqz6DNg/+mig==} peerDependencies: '@tiptap/core': ^2.0.0 dependencies: - '@tiptap/core': 2.0.4(@tiptap/pm@2.0.4) + '@tiptap/core': 2.1.1(@tiptap/pm@2.1.1) dev: false - /@tiptap/pm@2.0.4(@tiptap/core@2.0.4): - resolution: {integrity: sha512-DNgxntpEaiW7ciW0BTNTL0TFqAreZTrAROWakI4XaYRAyi5H9NfZW8jmwGwMBkoZ1KB3pfy+jT/Bisy4okEQGQ==} - peerDependencies: - '@tiptap/core': ^2.0.0 + /@tiptap/pm@2.1.1: + resolution: {integrity: sha512-WhHUDI+PdoGQZK6hVPW5oyoz1nlMtgOTJQ1/nwLHz3sqyOz8LOQZn8HUkktRMilm3ybX18TYTpR1vCUqBqtbOA==} dependencies: - '@tiptap/core': 2.0.4(@tiptap/pm@2.0.4) prosemirror-changeset: 2.2.1 prosemirror-collab: 1.3.1 prosemirror-commands: 1.5.2 @@ -2082,7 +2084,7 @@ packages: prosemirror-inputrules: 1.2.1 prosemirror-keymap: 1.2.2 prosemirror-markdown: 1.11.2 - prosemirror-menu: 1.2.2 + prosemirror-menu: 1.2.3 prosemirror-model: 1.19.3 prosemirror-schema-basic: 1.2.2 prosemirror-schema-list: 1.3.0 @@ -2093,56 +2095,56 @@ packages: prosemirror-view: 1.31.7 dev: false - /@tiptap/react@2.0.4(@tiptap/core@2.0.4)(@tiptap/pm@2.0.4)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-NcrZL4Tu3+1Xfj/us5AOD7+kJhwYo2XViOB2iRRnfwS80PUtiLWDis6o3ngMGot/jBWzaMn4gofXnMWHtFdIAw==} + /@tiptap/react@2.1.1(@tiptap/core@2.1.1)(@tiptap/pm@2.1.1)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-/xtBtJ+F/vIuk0OAf362WpZyOnk6wTZVsm6Q1oSm4vdKZlBPhcd75N9Z8UqsYb0dGSBQW28x9qQUi7qXFjnG1A==} peerDependencies: '@tiptap/core': ^2.0.0 '@tiptap/pm': ^2.0.0 react: ^17.0.0 || ^18.0.0 react-dom: ^17.0.0 || ^18.0.0 dependencies: - '@tiptap/core': 2.0.4(@tiptap/pm@2.0.4) - '@tiptap/extension-bubble-menu': 2.0.4(@tiptap/core@2.0.4)(@tiptap/pm@2.0.4) - '@tiptap/extension-floating-menu': 2.0.4(@tiptap/core@2.0.4)(@tiptap/pm@2.0.4) - '@tiptap/pm': 2.0.4(@tiptap/core@2.0.4) + '@tiptap/core': 2.1.1(@tiptap/pm@2.1.1) + '@tiptap/extension-bubble-menu': 2.1.1(@tiptap/core@2.1.1)(@tiptap/pm@2.1.1) + '@tiptap/extension-floating-menu': 2.1.1(@tiptap/core@2.1.1)(@tiptap/pm@2.1.1) + '@tiptap/pm': 2.1.1 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) dev: false - /@tiptap/starter-kit@2.0.4(@tiptap/pm@2.0.4): - resolution: {integrity: sha512-9WtVXhujyp5cOlE7qlcQMFr0FEx3Cvo1isvfQGzhKKPzXa3rR7FT8bnOFsten31/Ia/uwvGXAvRDQy24YfHdNA==} + /@tiptap/starter-kit@2.1.1(@tiptap/pm@2.1.1): + resolution: {integrity: sha512-8QC/cRSsoa0X3IJBlG0GFjNAi/fBZoVa4k/xm0xRrAqJN0dU0Vq9wFLxQC2Vdo0so55tZJbLgdwoAwEhJdCgcg==} dependencies: - '@tiptap/core': 2.0.4(@tiptap/pm@2.0.4) - '@tiptap/extension-blockquote': 2.0.4(@tiptap/core@2.0.4) - '@tiptap/extension-bold': 2.0.4(@tiptap/core@2.0.4) - '@tiptap/extension-bullet-list': 2.0.4(@tiptap/core@2.0.4) - '@tiptap/extension-code': 2.0.4(@tiptap/core@2.0.4) - '@tiptap/extension-code-block': 2.0.4(@tiptap/core@2.0.4)(@tiptap/pm@2.0.4) - '@tiptap/extension-document': 2.0.4(@tiptap/core@2.0.4) - '@tiptap/extension-dropcursor': 2.0.4(@tiptap/core@2.0.4)(@tiptap/pm@2.0.4) - '@tiptap/extension-gapcursor': 2.0.4(@tiptap/core@2.0.4)(@tiptap/pm@2.0.4) - '@tiptap/extension-hard-break': 2.0.4(@tiptap/core@2.0.4) - '@tiptap/extension-heading': 2.0.4(@tiptap/core@2.0.4) - '@tiptap/extension-history': 2.0.4(@tiptap/core@2.0.4)(@tiptap/pm@2.0.4) - '@tiptap/extension-horizontal-rule': 2.0.4(@tiptap/core@2.0.4)(@tiptap/pm@2.0.4) - '@tiptap/extension-italic': 2.0.4(@tiptap/core@2.0.4) - '@tiptap/extension-list-item': 2.0.4(@tiptap/core@2.0.4) - '@tiptap/extension-ordered-list': 2.0.4(@tiptap/core@2.0.4) - '@tiptap/extension-paragraph': 2.0.4(@tiptap/core@2.0.4) - '@tiptap/extension-strike': 2.0.4(@tiptap/core@2.0.4) - '@tiptap/extension-text': 2.0.4(@tiptap/core@2.0.4) + '@tiptap/core': 2.1.1(@tiptap/pm@2.1.1) + '@tiptap/extension-blockquote': 2.1.1(@tiptap/core@2.1.1) + '@tiptap/extension-bold': 2.1.1(@tiptap/core@2.1.1) + '@tiptap/extension-bullet-list': 2.1.1(@tiptap/core@2.1.1) + '@tiptap/extension-code': 2.1.1(@tiptap/core@2.1.1) + '@tiptap/extension-code-block': 2.1.1(@tiptap/core@2.1.1)(@tiptap/pm@2.1.1) + '@tiptap/extension-document': 2.1.1(@tiptap/core@2.1.1) + '@tiptap/extension-dropcursor': 2.1.1(@tiptap/core@2.1.1)(@tiptap/pm@2.1.1) + '@tiptap/extension-gapcursor': 2.1.1(@tiptap/core@2.1.1)(@tiptap/pm@2.1.1) + '@tiptap/extension-hard-break': 2.1.1(@tiptap/core@2.1.1) + '@tiptap/extension-heading': 2.1.1(@tiptap/core@2.1.1) + '@tiptap/extension-history': 2.1.1(@tiptap/core@2.1.1)(@tiptap/pm@2.1.1) + '@tiptap/extension-horizontal-rule': 2.1.1(@tiptap/core@2.1.1)(@tiptap/pm@2.1.1) + '@tiptap/extension-italic': 2.1.1(@tiptap/core@2.1.1) + '@tiptap/extension-list-item': 2.1.1(@tiptap/core@2.1.1) + '@tiptap/extension-ordered-list': 2.1.1(@tiptap/core@2.1.1) + '@tiptap/extension-paragraph': 2.1.1(@tiptap/core@2.1.1) + '@tiptap/extension-strike': 2.1.1(@tiptap/core@2.1.1) + '@tiptap/extension-text': 2.1.1(@tiptap/core@2.1.1) transitivePeerDependencies: - '@tiptap/pm' dev: false - /@tiptap/suggestion@2.0.4(@tiptap/core@2.0.4)(@tiptap/pm@2.0.4): - resolution: {integrity: sha512-C5LGGjH8VFET34V7vKkqlwpSzrPl+7oAcj9h+P3jvJQ076iYpmpnMtz6dNLSFGKpHp5mtyl4RoJzh7lTvlfyiA==} + /@tiptap/suggestion@2.1.1(@tiptap/core@2.1.1)(@tiptap/pm@2.1.1): + resolution: {integrity: sha512-bEgDI97nk1Qj6Zdz2jg0euxdKriqWfSDb17KHMd/T+DRhATJuVmuaTMt4vZckvqj7/JNLDeZRTILQkC4Nov/OQ==} peerDependencies: '@tiptap/core': ^2.0.0 '@tiptap/pm': ^2.0.0 dependencies: - '@tiptap/core': 2.0.4(@tiptap/pm@2.0.4) - '@tiptap/pm': 2.0.4(@tiptap/core@2.0.4) + '@tiptap/core': 2.1.1(@tiptap/pm@2.1.1) + '@tiptap/pm': 2.1.1 dev: false /@trivago/prettier-plugin-sort-imports@4.2.0(prettier@3.0.2): @@ -2532,7 +2534,7 @@ packages: peerDependencies: vite: ^4 dependencies: - '@swc/core': 1.3.76 + '@swc/core': 1.3.77 vite: 4.4.9(@types/node@20.5.0) transitivePeerDependencies: - '@swc/helpers' @@ -2720,6 +2722,12 @@ packages: resolution: {integrity: sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==} dev: true + /asynciterator.prototype@1.0.0: + resolution: {integrity: sha512-wwHYEIS0Q80f5mosx3L/dfG5t5rjEa9Ft51GTaNt862EnpyGHpgz2RkZvLPp1oF5TnAiTohkEKVEu8pQPJI7Vg==} + dependencies: + has-symbols: 1.0.3 + dev: true + /autoprefixer@10.4.15(postcss@8.4.28): resolution: {integrity: sha512-KCuPB8ZCIqFdA4HwKXsvz7j6gvSDNhDP7WnUjBleRkKjPdvCmHFuQ77ocavI8FT6NdvlBnE2UFr2H4Mycn8Vew==} engines: {node: ^10 || ^12 || >=14} @@ -2728,7 +2736,7 @@ packages: postcss: ^8.1.0 dependencies: browserslist: 4.21.10 - caniuse-lite: 1.0.30001520 + caniuse-lite: 1.0.30001521 fraction.js: 4.2.0 normalize-range: 0.1.2 picocolors: 1.0.0 @@ -2780,8 +2788,8 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true dependencies: - caniuse-lite: 1.0.30001520 - electron-to-chromium: 1.4.492 + caniuse-lite: 1.0.30001521 + electron-to-chromium: 1.4.493 node-releases: 2.0.13 update-browserslist-db: 1.0.11(browserslist@4.21.10) dev: true @@ -2823,8 +2831,8 @@ packages: engines: {node: '>=6'} dev: false - /caniuse-lite@1.0.30001520: - resolution: {integrity: sha512-tahF5O9EiiTzwTUqAeFjIZbn4Dnqxzz7ktrgGlMYNLH43Ul26IgTMH/zvL3DG0lZxBYnlT04axvInszUsZULdA==} + /caniuse-lite@1.0.30001521: + resolution: {integrity: sha512-fnx1grfpEOvDGH+V17eccmNjucGUnCbP6KL+l5KqBIerp26WK/+RQ7CIDE37KGJjaPyqWXXlFUyKiWmvdNNKmQ==} dev: true /case-anything@2.1.13: @@ -3166,8 +3174,8 @@ packages: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} dev: true - /electron-to-chromium@1.4.492: - resolution: {integrity: sha512-36K9b/6skMVwAIEsC7GiQ8I8N3soCALVSHqWHzNDtGemAcI9Xu8hP02cywWM0A794rTHm0b0zHPeLJHtgFVamQ==} + /electron-to-chromium@1.4.493: + resolution: {integrity: sha512-T1k9mhYPdjnmS4VAz4J1oKVn6/M6LxoqQEVtYRL0swJVj73bA2NzqF0HjwxGsW3zL3ir0oPmLfd7lyi/RYzreg==} dev: true /emoji-regex@8.0.0: @@ -3244,6 +3252,25 @@ packages: unbox-primitive: 1.0.2 which-typed-array: 1.1.11 + /es-iterator-helpers@1.0.13: + resolution: {integrity: sha512-LK3VGwzvaPWobO8xzXXGRUOGw8Dcjyfk62CsY/wfHN75CwsJPbuypOYJxK6g5RyEL8YDjIWcl6jgd8foO6mmrA==} + dependencies: + asynciterator.prototype: 1.0.0 + call-bind: 1.0.2 + define-properties: 1.2.0 + es-abstract: 1.22.1 + es-set-tostringtag: 2.0.1 + function-bind: 1.1.1 + get-intrinsic: 1.2.1 + globalthis: 1.0.3 + has-property-descriptors: 1.0.0 + has-proto: 1.0.1 + has-symbols: 1.0.3 + internal-slot: 1.0.5 + iterator.prototype: 1.1.0 + safe-array-concat: 1.0.0 + dev: true + /es-set-tostringtag@2.0.1: resolution: {integrity: sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==} engines: {node: '>= 0.4'} @@ -3504,8 +3531,8 @@ packages: semver: 6.3.1 dev: true - /eslint-plugin-react@7.33.1(eslint@8.47.0): - resolution: {integrity: sha512-L093k0WAMvr6VhNwReB8VgOq5s2LesZmrpPdKz/kZElQDzqS7G7+DnKoqT+w4JwuiGeAhAvHO0fvy0Eyk4ejDA==} + /eslint-plugin-react@7.33.2(eslint@8.47.0): + resolution: {integrity: sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw==} engines: {node: '>=4'} peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 @@ -3514,6 +3541,7 @@ packages: array.prototype.flatmap: 1.3.1 array.prototype.tosorted: 1.1.1 doctrine: 2.1.0 + es-iterator-helpers: 1.0.13 eslint: 8.47.0 estraverse: 5.3.0 jsx-ast-utils: 3.3.5 @@ -4001,10 +4029,6 @@ packages: resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} engines: {node: '>= 4'} - /immer@10.0.2: - resolution: {integrity: sha512-Rx3CqeqQ19sxUtYV9CU911Vhy8/721wRFnJv3REVGWUmoAcIwzifTsdmJte/MV+0/XpM35LZdQMBGkRIoLPwQA==} - dev: false - /import-fresh@3.3.0: resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} engines: {node: '>=6'} @@ -4069,6 +4093,13 @@ packages: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} dev: false + /is-async-function@2.0.0: + resolution: {integrity: sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + dev: true + /is-bigint@1.0.4: resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} dependencies: @@ -4119,6 +4150,12 @@ packages: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} + /is-finalizationregistry@1.0.2: + resolution: {integrity: sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==} + dependencies: + call-bind: 1.0.2 + dev: true + /is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} @@ -4129,12 +4166,23 @@ packages: engines: {node: '>=12'} dev: true + /is-generator-function@1.0.10: + resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + dev: true + /is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} dependencies: is-extglob: 2.1.1 + /is-map@2.0.2: + resolution: {integrity: sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==} + dev: true + /is-negative-zero@2.0.2: resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==} engines: {node: '>= 0.4'} @@ -4182,6 +4230,10 @@ packages: engines: {node: '>=12'} dev: false + /is-set@2.0.2: + resolution: {integrity: sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==} + dev: true + /is-shared-array-buffer@1.0.2: resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==} dependencies: @@ -4219,11 +4271,22 @@ packages: engines: {node: '>=10'} dev: false + /is-weakmap@2.0.1: + resolution: {integrity: sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==} + dev: true + /is-weakref@1.0.2: resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} dependencies: call-bind: 1.0.2 + /is-weakset@2.0.2: + resolution: {integrity: sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.2.1 + dev: true + /isarray@2.0.5: resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} @@ -4235,6 +4298,16 @@ packages: engines: {node: '>=0.10.0'} dev: false + /iterator.prototype@1.1.0: + resolution: {integrity: sha512-rjuhAk1AJ1fssphHD0IFV6TWL40CwRZ53FrztKx43yk2v6rguBYsY4Bj1VU4HmoMmKwZUlx7mfnhDf9cOp4YTw==} + dependencies: + define-properties: 1.2.0 + get-intrinsic: 1.2.1 + has-symbols: 1.0.3 + has-tostringtag: 1.0.0 + reflect.getprototypeof: 1.0.3 + dev: true + /javascript-natural-sort@0.7.1: resolution: {integrity: sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==} dev: true @@ -5063,6 +5136,12 @@ packages: engines: {node: '>=14.16'} dev: false + /nostr-fetch@0.12.2: + resolution: {integrity: sha512-0WH0LlaPcIvG5gOIwrGtzRwHpaZ+JQxH0XG7EjQcKpviePVmVKWK7UAGuzuWJj/V0iSqnDGOLSQ+HSEBjGVCEQ==} + dependencies: + '@nostr-fetch/kernel': 0.12.2 + dev: false + /nostr-tools@1.14.0: resolution: {integrity: sha512-hwq2i1z5/DneXRE5Zu/TzQuKzVLcB+gOdfT9CeoiScvNw/2dWRGJvyTXIdF92d7NQ7nMcEwqVJPDytLpEpiiKw==} dependencies: @@ -5585,8 +5664,8 @@ packages: prosemirror-model: 1.19.3 dev: false - /prosemirror-menu@1.2.2: - resolution: {integrity: sha512-437HIWTq4F9cTX+kPfqZWWm+luJm95Aut/mLUy+9OMrOml0bmWDS26ceC6SNfb2/S94et1sZ186vLO7pDHzxSw==} + /prosemirror-menu@1.2.3: + resolution: {integrity: sha512-13H9+XvdJiUt2vQVMqCveFbc7YfEKR3g70pUwuQdQLwuvNfVGTzMHr1y5dwdY5vOBQbzhmjgnWUnclKzMdnlJA==} dependencies: crelt: 1.0.6 prosemirror-commands: 1.5.2 @@ -5819,11 +5898,6 @@ packages: react: 18.2.0 dev: false - /react-string-replace@1.1.1: - resolution: {integrity: sha512-26TUbLzLfHQ5jO5N7y3Mx88eeKo0Ml0UjCQuX4BMfOd/JX+enQqlKpL1CZnmjeBRvQE8TR+ds9j1rqx9CxhKHQ==} - engines: {node: '>=0.12.0'} - dev: false - /react-style-singleton@2.2.1(@types/react@18.2.20)(react@18.2.0): resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==} engines: {node: '>=10'} @@ -5908,6 +5982,18 @@ packages: strip-indent: 3.0.0 dev: false + /reflect.getprototypeof@1.0.3: + resolution: {integrity: sha512-TTAOZpkJ2YLxl7mVHWrNo3iDMEkYlva/kgFcXndqMgbo/AZUmmavEkdXV+hXtE4P8xdyEKRzalaFqZVuwIk/Nw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.0 + es-abstract: 1.22.1 + get-intrinsic: 1.2.1 + globalthis: 1.0.3 + which-builtin-type: 1.1.3 + dev: true + /regenerator-runtime@0.14.0: resolution: {integrity: sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==} @@ -6715,11 +6801,6 @@ packages: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} dev: true - /uuid@9.0.0: - resolution: {integrity: sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==} - hasBin: true - dev: true - /uvu@0.5.6: resolution: {integrity: sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==} engines: {node: '>=8'} @@ -6754,20 +6835,6 @@ packages: vfile-message: 3.1.4 dev: false - /vite-plugin-top-level-await@1.3.1(vite@4.4.9): - resolution: {integrity: sha512-55M1h4NAwkrpxPNOJIBzKZFihqLUzIgnElLSmPNPMR2Fn9+JHKaNg3sVX1Fq+VgvuBksQYxiD3OnwQAUu7kaPQ==} - peerDependencies: - vite: '>=2.8' - dependencies: - '@rollup/plugin-virtual': 3.0.1 - '@swc/core': 1.3.76 - uuid: 9.0.0 - vite: 4.4.9(@types/node@20.5.0) - transitivePeerDependencies: - - '@swc/helpers' - - rollup - dev: true - /vite-tsconfig-paths@4.2.0(typescript@5.1.6)(vite@4.4.9): resolution: {integrity: sha512-jGpus0eUy5qbbMVGiTxCL1iB9ZGN6Bd37VGLJU39kTDD6ZfULTTb1bcc5IeTWqWJKiWV5YihCaibeASPiGi8kw==} peerDependencies: @@ -6862,6 +6929,33 @@ packages: is-string: 1.0.7 is-symbol: 1.0.4 + /which-builtin-type@1.1.3: + resolution: {integrity: sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw==} + engines: {node: '>= 0.4'} + dependencies: + function.prototype.name: 1.1.5 + has-tostringtag: 1.0.0 + is-async-function: 2.0.0 + is-date-object: 1.0.5 + is-finalizationregistry: 1.0.2 + is-generator-function: 1.0.10 + is-regex: 1.1.4 + is-weakref: 1.0.2 + isarray: 2.0.5 + which-boxed-primitive: 1.0.2 + which-collection: 1.0.1 + which-typed-array: 1.1.11 + dev: true + + /which-collection@1.0.1: + resolution: {integrity: sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==} + dependencies: + is-map: 2.0.2 + is-set: 2.0.2 + is-weakmap: 2.0.1 + is-weakset: 2.0.2 + dev: true + /which-typed-array@1.1.11: resolution: {integrity: sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==} engines: {node: '>= 0.4'} @@ -6920,7 +7014,7 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} - /zustand@4.4.1(@types/react@18.2.20)(immer@10.0.2)(react@18.2.0): + /zustand@4.4.1(@types/react@18.2.20)(react@18.2.0): resolution: {integrity: sha512-QCPfstAS4EBiTQzlaGP1gmorkh/UL1Leaj2tdj+zZCZ/9bm0WS7sI2wnfD5lpOszFqWJ1DcPnGoY8RDL61uokw==} engines: {node: '>=12.7.0'} peerDependencies: @@ -6936,7 +7030,6 @@ packages: optional: true dependencies: '@types/react': 18.2.20 - immer: 10.0.2 react: 18.2.0 use-sync-external-store: 1.2.0(react@18.2.0) dev: false diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 16ade8fc..6b7609c9 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -2912,9 +2912,9 @@ dependencies = [ [[package]] name = "muda" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5381209b232fae437a2dd8054c9fe4953fb7a4b05e6e437f64aacdd01f97859" +checksum = "a7de0cd2fdb9ef32781658513eab9080a22b3a9775955019daaf1f54bd37753a" dependencies = [ "cocoa 0.25.0", "crossbeam-channel", @@ -5589,9 +5589,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.31.0" +version = "1.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40de3a2ba249dcb097e01be5e67a5ff53cf250397715a071a81543e8a832a920" +checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" dependencies = [ "backtrace", "bytes", @@ -5745,9 +5745,9 @@ dependencies = [ [[package]] name = "tray-icon" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "672bc5a19944bfd51e5a874e9fcb7f129b76e5dd448fe295fa25411f3f800637" +checksum = "6b164327e17101c78ba3dfdf879b977027ef1bd7855668ac30063de21fc02447" dependencies = [ "cocoa 0.25.0", "core-graphics 0.23.1", @@ -6492,9 +6492,9 @@ checksum = "d419259aba16b663966e29e6d7c6ecfa0bb8425818bb96f6f1f3c3eb71a6e7b9" [[package]] name = "winnow" -version = "0.5.11" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e461589e194280efaa97236b73623445efa195aa633fd7004f39805707a9d53" +checksum = "83817bbecf72c73bad717ee86820ebf286203d2e04c3951f3cd538869c897364" dependencies = [ "memchr", ] @@ -6520,9 +6520,9 @@ dependencies = [ [[package]] name = "wry" -version = "0.31.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6289018fa3cbc051c13f4ae1a102d80c3f35a527456c75567eb2cad6989020" +checksum = "07bf838a5430184dfe0b1f568af7998a455c0df75a1df300a3894e0f181e7408" dependencies = [ "base64 0.21.2", "block", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 5a403bcf..a25d60f9 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -16,7 +16,10 @@ tauri-build = { version = "2.0.0-alpha.8", features = [] } [dependencies] serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } -tauri = { version = "2.0.0-alpha.11", features = ["macos-private-api"] } +tauri = { version = "2.0.0-alpha.11", features = [ + "protocol-asset", + "macos-private-api", +] } tauri-plugin-single-instance = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" } tauri-plugin-autostart = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" } tauri-plugin-stronghold = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" } diff --git a/src-tauri/migrations/20230814083543_add_events_table.sql b/src-tauri/migrations/20230814083543_add_events_table.sql index deb8ec52..e7eb66ff 100644 --- a/src-tauri/migrations/20230814083543_add_events_table.sql +++ b/src-tauri/migrations/20230814083543_add_events_table.sql @@ -1,9 +1,12 @@ -- Add migration script here CREATE TABLE events ( - id INTEGER NOT NULL PRIMARY KEY, - cache_key TEXT NOT NULL, - event_id TEXT NOT NULL UNIQUE, - event_kind INTEGER NOT NULL DEFAULT 1, - event TEXT NOT NULL + id TEXT NOT NULL PRIMARY KEY, + account_id INTEGER NOT NULL, + event TEXT NOT NULL, + author TEXT NOT NULL, + root_id TEXT, + reply_id TEXT, + created_at INTEGER NOT NULL, + FOREIGN KEY (account_id) REFERENCES accounts (id) ); \ No newline at end of file diff --git a/src-tauri/migrations/20230817014932_add_last_login_time_to_account.sql b/src-tauri/migrations/20230817014932_add_last_login_time_to_account.sql new file mode 100644 index 00000000..a1b25281 --- /dev/null +++ b/src-tauri/migrations/20230817014932_add_last_login_time_to_account.sql @@ -0,0 +1,3 @@ +-- Add migration script here +ALTER TABLE accounts +ADD COLUMN last_login_at NUMBER NOT NULL DEFAULT 0; \ No newline at end of file diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 2bb51659..5306397a 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -129,6 +129,12 @@ fn main() { sql: include_str!("../migrations/20230816090508_clean_up_tables.sql"), kind: MigrationKind::Up, }, + Migration { + version: 20230817014932, + description: "add last login to account", + sql: include_str!("../migrations/20230817014932_add_last_login_time_to_account.sql"), + kind: MigrationKind::Up, + }, ], ) .build(), diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 4c9b7057..719b3cbf 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -80,7 +80,18 @@ } }, "security": { - "csp": "upgrade-insecure-requests" + "csp": { + "connect-src": "ipc: https://ipc.localhost", + "content-security-policy": "upgrade-insecure-requests" + }, + "freezePrototype": false, + "assetProtocol": { + "enable": true, + "scope": { + "allow": ["$APPCONFIG/*.db", "$RESOURCE/**"], + "deny": ["$APPCONFIG/*.stronghold"] + } + } }, "windows": [ { diff --git a/src/app/auth/create/step-1.tsx b/src/app/auth/create/step-1.tsx index bc6dce7c..fc6868f4 100644 --- a/src/app/auth/create/step-1.tsx +++ b/src/app/auth/create/step-1.tsx @@ -1,4 +1,3 @@ -import { useMutation, useQueryClient } from '@tanstack/react-query'; import { BaseDirectory, writeTextFile } from '@tauri-apps/plugin-fs'; import { generatePrivateKey, getPublicKey, nip19 } from 'nostr-tools'; import { useEffect, useMemo, useState } from 'react'; @@ -16,7 +15,6 @@ import { useStronghold } from '@stores/stronghold'; export function CreateStep1Screen() { const { db } = useStorage(); - const queryClient = useQueryClient(); const navigate = useNavigate(); const setPrivkey = useStronghold((state) => state.setPrivkey); const setTempPrivkey = useOnboarding((state) => state.setTempPrivkey); @@ -52,33 +50,16 @@ export function CreateStep1Screen() { setDownloaded(true); }; - const account = useMutation({ - mutationFn: (data: { - npub: string; - pubkey: string; - follows: null | string[][]; - is_active: number; - }) => { - return db.createAccount(data.npub, data.pubkey); - }, - onSuccess: (data) => { - queryClient.setQueryData(['account'], data); - }, - }); - const submit = () => { setLoading(true); + // update state setPrivkey(privkey); setTempPrivkey(privkey); // only use if user close app and reopen it setPubkey(pubkey); - account.mutate({ - npub, - pubkey, - follows: null, - is_active: 1, - }); + // save to database + db.createAccount(npub, pubkey); // redirect to next step navigate('/auth/create/step-2', { replace: true }); diff --git a/src/app/auth/create/step-3.tsx b/src/app/auth/create/step-3.tsx index dc85bfcd..d8dd848a 100644 --- a/src/app/auth/create/step-3.tsx +++ b/src/app/auth/create/step-3.tsx @@ -1,4 +1,3 @@ -import { useQueryClient } from '@tanstack/react-query'; import { useEffect, useState } from 'react'; import { useForm } from 'react-hook-form'; import { useNavigate } from 'react-router-dom'; @@ -17,7 +16,6 @@ import { useNostr } from '@utils/hooks/useNostr'; export function CreateStep3Screen() { const navigate = useNavigate(); const setStep = useOnboarding((state) => state.setStep); - const queryClient = useQueryClient(); const [loading, setLoading] = useState(false); const [picture, setPicture] = useState(DEFAULT_AVATAR); @@ -47,8 +45,6 @@ export function CreateStep3Screen() { tags: [], }); - queryClient.invalidateQueries(['account']); - if (event) { navigate('/auth/onboarding', { replace: true }); } diff --git a/src/app/auth/import/step-1.tsx b/src/app/auth/import/step-1.tsx index 94282b1c..ac8c12c5 100644 --- a/src/app/auth/import/step-1.tsx +++ b/src/app/auth/import/step-1.tsx @@ -1,4 +1,3 @@ -import { useMutation, useQueryClient } from '@tanstack/react-query'; import { getPublicKey, nip19 } from 'nostr-tools'; import { useEffect, useState } from 'react'; import { Resolver, useForm } from 'react-hook-form'; @@ -31,9 +30,6 @@ const resolver: Resolver = async (values) => { }; export function ImportStep1Screen() { - const { db } = useStorage(); - - const queryClient = useQueryClient(); const navigate = useNavigate(); const setPrivkey = useStronghold((state) => state.setPrivkey); const setTempPubkey = useOnboarding((state) => state.setTempPrivkey); @@ -42,20 +38,7 @@ export function ImportStep1Screen() { const [loading, setLoading] = useState(false); - const account = useMutation({ - mutationFn: (data: { - npub: string; - pubkey: string; - follows: null | string[]; - is_active: number | boolean; - }) => { - return db.createAccount(data.npub, data.pubkey); - }, - onSuccess: (data) => { - queryClient.setQueryData(['account'], data); - }, - }); - + const { db } = useStorage(); const { register, setError, @@ -81,12 +64,7 @@ export function ImportStep1Screen() { setPubkey(pubkey); // add account to local database - account.mutate({ - npub, - pubkey, - follows: null, - is_active: 1, - }); + db.createAccount(npub, pubkey); // redirect to step 2 navigate('/auth/import/step-2', { replace: true }); diff --git a/src/app/auth/import/step-3.tsx b/src/app/auth/import/step-3.tsx index a32f8e38..392c1981 100644 --- a/src/app/auth/import/step-3.tsx +++ b/src/app/auth/import/step-3.tsx @@ -1,4 +1,3 @@ -import { useQueryClient } from '@tanstack/react-query'; import { useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; @@ -13,7 +12,6 @@ import { useOnboarding } from '@stores/onboarding'; import { useNostr } from '@utils/hooks/useNostr'; export function ImportStep3Screen() { - const queryClient = useQueryClient(); const navigate = useNavigate(); const setStep = useOnboarding((state) => state.setStep); @@ -30,10 +28,6 @@ export function ImportStep3Screen() { const data = await fetchUserData(); if (data.status === 'ok') { - // update last login - await db.updateLastLogin(Math.floor(Date.now() / 1000)); - - queryClient.invalidateQueries(['account']); navigate('/auth/onboarding/step-2', { replace: true }); } else { console.log('error: ', data.message); diff --git a/src/app/auth/onboarding/step-1.tsx b/src/app/auth/onboarding/step-1.tsx index 6e6f92a8..53e87369 100644 --- a/src/app/auth/onboarding/step-1.tsx +++ b/src/app/auth/onboarding/step-1.tsx @@ -1,4 +1,4 @@ -import { useQuery, useQueryClient } from '@tanstack/react-query'; +import { useQuery } from '@tanstack/react-query'; import { useEffect, useState } from 'react'; import { Link, useNavigate } from 'react-router-dom'; @@ -14,12 +14,11 @@ import { useNostr } from '@utils/hooks/useNostr'; import { arrayToNIP02 } from '@utils/transform'; export function OnboardStep1Screen() { - const queryClient = useQueryClient(); const navigate = useNavigate(); const setStep = useOnboarding((state) => state.setStep); + const { publish, fetchUserData, prefetchEvents } = useNostr(); const { db } = useStorage(); - const { publish, fetchUserData } = useNostr(); const { status, data } = useQuery(['trending-profiles'], async () => { const res = await fetch('https://api.nostr.band/v0/trending/profiles'); if (!res.ok) { @@ -45,14 +44,13 @@ export function OnboardStep1Screen() { const tags = arrayToNIP02([...follows, db.account.pubkey]); const event = await publish({ content: '', kind: 3, tags: tags }); - await db.updateAccount('follows', follows); - // prefetch notes with current follows - const data = await fetchUserData(follows); + // prefetch data + const user = await fetchUserData(follows); + const data = await prefetchEvents(); // redirect to next step - if (event && data.status === 'ok') { - queryClient.invalidateQueries(['account']); + if (event && user.status === 'ok' && data.status === 'ok') { navigate('/auth/onboarding/step-2', { replace: true }); } else { setLoading(false); diff --git a/src/app/space/components/widgets/feed.tsx b/src/app/space/components/widgets/feed.tsx index df17179a..e071615f 100644 --- a/src/app/space/components/widgets/feed.tsx +++ b/src/app/space/components/widgets/feed.tsx @@ -1,92 +1,112 @@ +import { NDKEvent } from '@nostr-dev-kit/ndk'; import { useInfiniteQuery } from '@tanstack/react-query'; import { useVirtualizer } from '@tanstack/react-virtual'; -import { useCallback, useEffect, useRef } from 'react'; +import { useCallback, useMemo, useRef } from 'react'; +import { useStorage } from '@libs/storage/provider'; + +import { ArrowRightCircleIcon, LoaderIcon } from '@shared/icons'; import { NoteKind_1, NoteKind_1063, NoteThread, Repost } from '@shared/notes'; import { NoteKindUnsupport } from '@shared/notes/kinds/unsupport'; import { NoteSkeleton } from '@shared/notes/skeleton'; import { TitleBar } from '@shared/titleBar'; -import { LumeEvent, Widget } from '@utils/types'; +import { DBEvent, Widget } from '@utils/types'; -export function FeedBlock({ params }: { params: Widget }) { - const { status, data, fetchNextPage, hasNextPage, isFetchingNextPage } = +export function FeedWidget({ params }: { params: Widget }) { + const { db } = useStorage(); + const { status, data, hasNextPage, isFetchingNextPage, fetchNextPage } = useInfiniteQuery({ queryKey: ['newsfeed', params.content], - queryFn: async () => { - return { data: [], nextCursor: 0 }; + queryFn: async ({ pageParam = 0 }) => { + const authors = JSON.parse(params.content); + return await db.getAllEventsByAuthors(authors, 20, pageParam); }, getNextPageParam: (lastPage) => lastPage.nextCursor, }); - const notes = data ? data.pages.flatMap((d: { data: LumeEvent[] }) => d.data) : []; - - const parentRef = useRef(); - const rowVirtualizer = useVirtualizer({ - count: hasNextPage ? notes.length + 1 : notes.length, + const dbEvents = useMemo( + () => (data ? data.pages.flatMap((d: { data: DBEvent[] }) => d.data) : []), + [data] + ); + const parentRef = useRef(); + const virtualizer = useVirtualizer({ + count: hasNextPage ? dbEvents.length : dbEvents.length, getScrollElement: () => parentRef.current, - estimateSize: () => 500, - overscan: 2, + estimateSize: () => 650, + overscan: 4, }); + const items = virtualizer.getVirtualItems(); - const itemsVirtualizer = rowVirtualizer.getVirtualItems(); - const totalSize = rowVirtualizer.getTotalSize(); - - useEffect(() => { - const [lastItem] = [...rowVirtualizer.getVirtualItems()].reverse(); - - if (!lastItem) { - return; - } - - if (lastItem.index >= notes.length - 1 && hasNextPage && !isFetchingNextPage) { - fetchNextPage(); - } - }, [notes.length, fetchNextPage, rowVirtualizer.getVirtualItems()]); - + // render event match event kind const renderItem = useCallback( (index: string | number) => { - const note: LumeEvent = notes[index]; - if (!note) return; - switch (note.kind) { + const dbEvent: DBEvent = dbEvents[index]; + if (!dbEvent) return; + + const event: NDKEvent = JSON.parse(dbEvent.event as string); + + switch (event.kind) { case 1: { - const root = note.tags.find((el) => el[3] === 'root')?.[1]; - const reply = note.tags.find((el) => el[3] === 'reply')?.[1]; - if (root || reply) { + if (dbEvent.root_id || dbEvent.reply_id) { return ( -
- +
+
); } else { return ( -
- +
+
); } } case 6: return ( -
- +
+
); case 1063: return ( -
- +
+
); default: return ( -
- +
+
); } }, - [notes] + [dbEvents] ); return ( @@ -99,32 +119,31 @@ export function FeedBlock({ params }: { params: Widget }) {
- ) : itemsVirtualizer.length === 0 ? ( + ) : items.length === 0 ? (

- Not found any posts from last 48 hours + Not found any postrs from last 48 hours

) : (
- {itemsVirtualizer.map((virtualRow) => renderItem(virtualRow.index))} + {items.map((item) => renderItem(item.index))}
)} @@ -135,6 +154,33 @@ export function FeedBlock({ params }: { params: Widget }) {
)} +
+ +
); diff --git a/src/app/space/components/widgets/hashtag.tsx b/src/app/space/components/widgets/hashtag.tsx index 20504c95..5dd1e017 100644 --- a/src/app/space/components/widgets/hashtag.tsx +++ b/src/app/space/components/widgets/hashtag.tsx @@ -1,35 +1,74 @@ +import { NDKEvent } from '@nostr-dev-kit/ndk'; import { useQuery } from '@tanstack/react-query'; import { useVirtualizer } from '@tanstack/react-virtual'; -import { useRef } from 'react'; +import { useCallback, useRef } from 'react'; import { useNDK } from '@libs/ndk/provider'; -import { NoteKind_1, NoteSkeleton } from '@shared/notes'; +import { NoteKind_1, NoteSkeleton, Repost } from '@shared/notes'; +import { NoteKindUnsupport } from '@shared/notes/kinds/unsupport'; import { TitleBar } from '@shared/titleBar'; import { nHoursAgo } from '@utils/date'; -import { LumeEvent, Widget } from '@utils/types'; +import { Widget } from '@utils/types'; -export function HashtagBlock({ params }: { params: Widget }) { +export function HashtagWidget({ params }: { params: Widget }) { const { ndk } = useNDK(); - const { status, data } = useQuery(['hashtag', params.content], async () => { + const { status, data } = useQuery(['hashtag-widget', params.content], async () => { const events = await ndk.fetchEvents({ kinds: [1], '#t': [params.content], since: nHoursAgo(24), }); - return [...events] as unknown as LumeEvent[]; + return [...events] as unknown as NDKEvent[]; }); - const parentRef = useRef(); - const rowVirtualizer = useVirtualizer({ + const parentRef = useRef(null); + const virtualizer = useVirtualizer({ count: data ? data.length : 0, getScrollElement: () => parentRef.current, - estimateSize: () => 400, + estimateSize: () => 650, + overscan: 4, }); + const items = virtualizer.getVirtualItems(); - const itemsVirtualizer = rowVirtualizer.getVirtualItems(); - const totalSize = rowVirtualizer.getTotalSize(); + // render event match event kind + const renderItem = useCallback( + (index: string | number) => { + const event: NDKEvent = data[index]; + if (!event) return; + + switch (event.kind) { + case 1: + return ( +
+ +
+ ); + case 6: + return ( +
+ +
+ ); + default: + return ( +
+ +
+ ); + } + }, + [data] + ); return (
@@ -41,40 +80,31 @@ export function HashtagBlock({ params }: { params: Widget }) {
- ) : itemsVirtualizer.length === 0 ? ( + ) : items.length === 0 ? (

- No new posts about this hashtag in 24 hours ago + No new postrs about this hashtag in 24 hours ago

) : (
- {itemsVirtualizer.map((virtualRow) => ( -
- -
- ))} + {items.map((item) => renderItem(item.index))}
)} diff --git a/src/app/space/components/widgets/network.tsx b/src/app/space/components/widgets/network.tsx index a67a1118..1e0303c4 100644 --- a/src/app/space/components/widgets/network.tsx +++ b/src/app/space/components/widgets/network.tsx @@ -1,4 +1,4 @@ -import { NDKFilter } from '@nostr-dev-kit/ndk'; +import { NDKEvent, NDKFilter } from '@nostr-dev-kit/ndk'; import { useInfiniteQuery } from '@tanstack/react-query'; import { useVirtualizer } from '@tanstack/react-virtual'; import { useCallback, useEffect, useMemo, useRef } from 'react'; @@ -6,85 +6,75 @@ import { Link } from 'react-router-dom'; import { useStorage } from '@libs/storage/provider'; +import { ArrowRightCircleIcon, LoaderIcon } from '@shared/icons'; import { NoteKind_1, NoteKind_1063, NoteThread, Repost } from '@shared/notes'; import { NoteKindUnsupport } from '@shared/notes/kinds/unsupport'; import { NoteSkeleton } from '@shared/notes/skeleton'; import { TitleBar } from '@shared/titleBar'; import { useNostr } from '@utils/hooks/useNostr'; -import { LumeEvent } from '@utils/types'; +import { DBEvent } from '@utils/types'; -export function NetworkBlock() { +export function NetworkWidget() { + const { sub } = useNostr(); const { db } = useStorage(); - const { sub, fetchNotes } = useNostr(); - const { status, data, hasNextPage, isFetchingNextPage } = useInfiniteQuery({ - queryKey: ['network-widget'], - queryFn: async ({ pageParam = 24 }) => { - return { data: [], nextCursor: 0 }; - }, - getNextPageParam: (lastPage) => lastPage.nextCursor, - refetchOnWindowFocus: false, - refetchOnReconnect: false, - }); + const { status, data, hasNextPage, isFetchingNextPage, fetchNextPage } = + useInfiniteQuery({ + queryKey: ['network-widget'], + queryFn: async ({ pageParam = 0 }) => { + return await db.getAllEvents(20, pageParam); + }, + getNextPageParam: (lastPage) => lastPage.nextCursor, + refetchOnWindowFocus: false, + refetchOnReconnect: false, + refetchOnMount: false, + }); - const parentRef = useRef(); - const notes = useMemo( - () => (data ? data.pages.flatMap((d: { data: LumeEvent[] }) => d.data) : []), + const dbEvents = useMemo( + () => (data ? data.pages.flatMap((d: { data: DBEvent[] }) => d.data) : []), [data] ); - - const rowVirtualizer = useVirtualizer({ - count: hasNextPage ? notes.length + 1 : notes.length, + const parentRef = useRef(); + const virtualizer = useVirtualizer({ + count: hasNextPage ? dbEvents.length : dbEvents.length, getScrollElement: () => parentRef.current, - estimateSize: () => 500, - overscan: 2, + estimateSize: () => 650, + overscan: 4, }); - const itemsVirtualizer = rowVirtualizer.getVirtualItems(); - const totalSize = rowVirtualizer.getTotalSize(); - - useEffect(() => { - const since = Math.floor(Date.now() / 1000); - const filter: NDKFilter = { - kinds: [1, 6], - authors: db.account.network, - since: since, - }; - - sub(filter, (event) => console.log('[network] event received: ', event)); - }, []); + const items = virtualizer.getVirtualItems(); + // render event match event kind const renderItem = useCallback( (index: string | number) => { - const note: LumeEvent = notes[index]; - if (!note) return; - switch (note.kind) { + const dbEvent: DBEvent = dbEvents[index]; + if (!dbEvent) return; + + const event: NDKEvent = JSON.parse(dbEvent.event as string); + + switch (event.kind) { case 1: { - let root: string; - let reply: string; - if (note.tags?.[0]?.[0] === 'e' && !note.tags?.[0]?.[3]) { - root = note.tags[0][1]; - } else { - root = note.tags.find((el) => el[3] === 'root')?.[1]; - reply = note.tags.find((el) => el[3] === 'reply')?.[1]; - } - if (root || reply) { + if (dbEvent.root_id || dbEvent.reply_id) { return (
- +
); } else { return (
- +
); } @@ -92,38 +82,52 @@ export function NetworkBlock() { case 6: return (
- +
); case 1063: return (
- +
); default: return (
- +
); } }, - [notes] + [dbEvents] ); + // subscribe for new event + // sub will be managed by lru-cache + useEffect(() => { + if (db.account && db.account.network) { + const filter: NDKFilter = { + kinds: [1, 6], + authors: db.account.network, + since: Math.floor(Date.now() / 1000), + }; + + sub(filter, (event) => console.log('[network] event received: ', event.content)); + } + }, []); + return (
@@ -134,7 +138,7 @@ export function NetworkBlock() {
- ) : itemsVirtualizer.length === 0 ? ( + ) : dbEvents.length === 0 ? (
@@ -154,30 +158,56 @@ export function NetworkBlock() {
) : (
- {itemsVirtualizer.map((virtualRow) => renderItem(virtualRow.index))} + {items.map((item) => renderItem(item.index))}
)} {isFetchingNextPage && ( -
+
)} +
+ +
); diff --git a/src/app/space/components/widgets/user.tsx b/src/app/space/components/widgets/user.tsx index bc09dfb1..1b9affbd 100644 --- a/src/app/space/components/widgets/user.tsx +++ b/src/app/space/components/widgets/user.tsx @@ -1,37 +1,79 @@ +import { NDKEvent } from '@nostr-dev-kit/ndk'; import { useQuery } from '@tanstack/react-query'; import { useVirtualizer } from '@tanstack/react-virtual'; -import { useRef } from 'react'; +import { useCallback, useRef } from 'react'; import { useNDK } from '@libs/ndk/provider'; -import { NoteKind_1, NoteSkeleton } from '@shared/notes'; +import { NoteKind_1, NoteSkeleton, Repost } from '@shared/notes'; +import { NoteKindUnsupport } from '@shared/notes/kinds/unsupport'; import { TitleBar } from '@shared/titleBar'; import { UserProfile } from '@shared/userProfile'; import { nHoursAgo } from '@utils/date'; -import { LumeEvent, Widget } from '@utils/types'; - -export function UserBlock({ params }: { params: Widget }) { - const parentRef = useRef(null); +import { DBEvent, Widget } from '@utils/types'; +export function UserWidget({ params }: { params: Widget }) { const { ndk } = useNDK(); - const { status, data } = useQuery(['user-feed', params.content], async () => { - const events = await ndk.fetchEvents({ - kinds: [1], - authors: [params.content], - since: nHoursAgo(48), - }); - return [...events] as unknown as LumeEvent[]; - }); + const { status, data } = useQuery( + ['user-widget', params.content], + async () => { + const events = await ndk.fetchEvents({ + kinds: [1], + authors: [params.content], + since: nHoursAgo(24), + }); + return [...events] as unknown as DBEvent[]; + }, + { refetchOnMount: false, refetchOnReconnect: false, refetchOnWindowFocus: false } + ); - const rowVirtualizer = useVirtualizer({ + const parentRef = useRef(null); + const virtualizer = useVirtualizer({ count: data ? data.length : 0, getScrollElement: () => parentRef.current, - estimateSize: () => 400, - overscan: 2, + estimateSize: () => 650, + overscan: 4, }); + const items = virtualizer.getVirtualItems(); - const itemsVirtualizer = rowVirtualizer.getVirtualItems(); + // render event match event kind + const renderItem = useCallback( + (index: string | number) => { + const event: NDKEvent = data[index]; + if (!event) return; + + switch (event.kind) { + case 1: + return ( +
+ +
+ ); + case 6: + return ( +
+ +
+ ); + default: + return ( +
+ +
+ ); + } + }, + [data] + ); return (
@@ -49,41 +91,31 @@ export function UserBlock({ params }: { params: Widget }) {
- ) : itemsVirtualizer.length === 0 ? ( + ) : items.length === 0 ? (

- No new posts from this user in 48 hours ago + No new postr from user in 24 hours ago

) : (
- {itemsVirtualizer.map((virtualRow) => ( -
- -
- ))} -
+ {items.map((item) => renderItem(item.index))}
)} diff --git a/src/app/space/index.tsx b/src/app/space/index.tsx index b05824a1..a8fb23b7 100644 --- a/src/app/space/index.tsx +++ b/src/app/space/index.tsx @@ -3,11 +3,11 @@ import { useCallback, useEffect } from 'react'; import { FeedModal } from '@app/space/components/modals/feed'; import { HashtagModal } from '@app/space/components/modals/hashtag'; import { ImageModal } from '@app/space/components/modals/image'; -import { FeedBlock } from '@app/space/components/widgets/feed'; -import { HashtagBlock } from '@app/space/components/widgets/hashtag'; -import { NetworkBlock } from '@app/space/components/widgets/network'; +import { FeedWidget } from '@app/space/components/widgets/feed'; +import { HashtagWidget } from '@app/space/components/widgets/hashtag'; +import { NetworkWidget } from '@app/space/components/widgets/network'; import { ThreadBlock } from '@app/space/components/widgets/thread'; -import { UserBlock } from '@app/space/components/widgets/user'; +import { UserWidget } from '@app/space/components/widgets/user'; import { useStorage } from '@libs/storage/provider'; @@ -29,15 +29,15 @@ export function SpaceScreen() { (widget: Widget) => { switch (widget.kind) { case 1: - return ; + return ; case 2: return ; case 3: - return ; + return ; case 5: - return ; + return ; case 9999: - return ; + return ; default: break; } @@ -52,7 +52,7 @@ export function SpaceScreen() { return (
{!widgets ? ( -
+
@@ -60,14 +60,14 @@ export function SpaceScreen() { ) : ( widgets.map((widget) => renderItem(widget)) )} -
+
-
+
); } diff --git a/src/app/splash.tsx b/src/app/splash.tsx index 7be1558d..c0844d98 100644 --- a/src/app/splash.tsx +++ b/src/app/splash.tsx @@ -11,7 +11,7 @@ import { useNostr } from '@utils/hooks/useNostr'; export function SplashScreen() { const { db } = useStorage(); const { ndk, relayUrls } = useNDK(); - const { fetchUserData } = useNostr(); + const { fetchUserData, prefetchEvents } = useNostr(); const [isLoading, setIsLoading] = useState(true); const [errorMessage, setErrorMessage] = useState(null); @@ -27,9 +27,10 @@ export function SplashScreen() { try { const user = await fetchUserData(); - if (user.status === 'ok') { - const now = Math.floor(Date.now() / 1000); - await db.updateLastLogin(now); + const data = await prefetchEvents(); + + if (user.status === 'ok' && data.status === 'ok') { + await db.updateLastLogin(); await invoke('close_splashscreen'); } else { setIsLoading(false); diff --git a/src/libs/ndk/cache.ts b/src/libs/ndk/cache.ts index 55406a21..c10b09e6 100644 --- a/src/libs/ndk/cache.ts +++ b/src/libs/ndk/cache.ts @@ -1,14 +1,13 @@ import { NDKCacheAdapter } from '@nostr-dev-kit/ndk'; import { NDKEvent, NDKSubscription } from '@nostr-dev-kit/ndk'; - -import { LumeStorage } from '@libs/storage/instance'; +import { Store } from '@tauri-apps/plugin-store'; export default class TauriAdapter implements NDKCacheAdapter { - public store: LumeStorage; + public store: Store; readonly locking: boolean; - constructor(db: LumeStorage) { - this.store = db; + constructor() { + this.store = new Store('.ndk_cache.dat'); this.locking = true; } @@ -21,34 +20,15 @@ export default class TauriAdapter implements NDKCacheAdapter { for (const author of filter.authors) { for (const kind of filter.kinds) { const key = `${author}:${kind}`; - promises.concat(this.store.getALlEventByKey(key)); + promises.push(this.store.get(key)); } } const results = await Promise.all(promises); for (const result of results) { - if (result && result.event) { - console.log('cache hit: ', result.event); - const ndkEvent = new NDKEvent( - subscription.ndk, - JSON.parse(result.event as string) - ); - subscription.eventReceived(ndkEvent, undefined, true); - } - } - } - - if (filter.ids) { - for (const id of filter.ids) { - const cacheEvent = await this.store.getEventByID(id); - - if (cacheEvent) { - console.log('cache hit: ', id); - const ndkEvent = new NDKEvent( - subscription.ndk, - JSON.parse(cacheEvent.event as string) - ); + if (result) { + const ndkEvent = new NDKEvent(subscription.ndk, JSON.parse(result as string)); subscription.eventReceived(ndkEvent, undefined, true); } } @@ -60,9 +40,13 @@ export default class TauriAdapter implements NDKCacheAdapter { const key = `${nostrEvent.pubkey}:${nostrEvent.kind}`; return new Promise((resolve) => { - Promise.all([ - this.store.createEvent(key, event.id, event.kind, JSON.stringify(nostrEvent)), - ]).then(() => resolve()); + Promise.all([this.store.set(key, JSON.stringify(nostrEvent))]).then(() => + resolve() + ); }); } + + public async saveCache(): Promise { + return await this.store.save(); + } } diff --git a/src/libs/ndk/instance.ts b/src/libs/ndk/instance.ts index 5e7f04b9..c9e0791d 100644 --- a/src/libs/ndk/instance.ts +++ b/src/libs/ndk/instance.ts @@ -1,6 +1,6 @@ // inspire by: https://github.com/nostr-dev-kit/ndk-react/ import NDK from '@nostr-dev-kit/ndk'; -import { useEffect, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import TauriAdapter from '@libs/ndk/cache'; import { useStorage } from '@libs/storage/provider'; @@ -13,6 +13,8 @@ export const NDKInstance = () => { const [ndk, setNDK] = useState(undefined); const [relayUrls, setRelayUrls] = useState([]); + const cacheAdapter = useMemo(() => new TauriAdapter(), [ndk]); + // TODO: fully support NIP-11 async function verifyRelays(relays: string[]) { const verifiedRelays: string[] = []; @@ -64,7 +66,6 @@ export const NDKInstance = () => { explicitRelayUrls = await verifyRelays(FULL_RELAYS); } - const cacheAdapter = new TauriAdapter(db); console.log('ndk cache adapter: ', cacheAdapter); const instance = new NDK({ explicitRelayUrls, cacheAdapter }); @@ -80,6 +81,10 @@ export const NDKInstance = () => { useEffect(() => { if (!ndk) initNDK(); + + return () => { + cacheAdapter.saveCache(); + }; }, []); return { diff --git a/src/libs/storage/instance.ts b/src/libs/storage/instance.ts index 960aa2d2..7da35e3d 100644 --- a/src/libs/storage/instance.ts +++ b/src/libs/storage/instance.ts @@ -2,7 +2,7 @@ import { BaseDirectory, removeFile } from '@tauri-apps/plugin-fs'; import Database from '@tauri-apps/plugin-sql'; import { Stronghold } from '@tauri-apps/plugin-stronghold'; -import { Account, LumeEvent, Relays, Widget } from '@utils/types'; +import { Account, DBEvent, Relays, Widget } from '@utils/types'; export class LumeStorage { public db: Database; @@ -93,6 +93,13 @@ export class LumeStorage { } } + public async updateLastLogin() { + return await this.db.execute( + 'UPDATE accounts SET last_login_at = $1 WHERE id = $2;', + [Math.floor(Date.now() / 1000), this.account.id] + ); + } + public async getWidgets() { const result: Array = await this.db.select( `SELECT * FROM widgets WHERE account_id = "${this.account.id}" ORDER BY created_at DESC;` @@ -122,35 +129,99 @@ export class LumeStorage { } public async createEvent( - cacheKey: string, - event_id: string, - event_kind: number, - event: string + id: string, + event: string, + author: string, + root_id: string, + reply_id: string, + created_at: number ) { return await this.db.execute( - 'INSERT OR IGNORE INTO events (cache_key, event_id, event_kind, event) VALUES ($1, $2, $3, $4);', - [cacheKey, event_id, event_kind, event] + 'INSERT OR IGNORE INTO events (id, account_id, event, author, root_id, reply_id, created_at) VALUES ($1, $2, $3, $4, $5, $6, $7);', + [id, this.account.id, event, author, root_id, reply_id, created_at] ); } - public async getALlEventByKey(cacheKey: string) { - const events: LumeEvent[] = await this.db.select( - 'SELECT * FROM events WHERE cache_key = $1 ORDER BY id DESC;', - [cacheKey] - ); - - if (events.length < 1) return null; - return events; - } - public async getEventByID(id: string) { - const event = await this.db.select( - 'SELECT * FROM events WHERE event_id = $1 ORDER BY id DESC LIMIT 1;', + const results: DBEvent[] = await this.db.select( + 'SELECT * FROM events WHERE id = $1 ORDER BY id DESC LIMIT 1;', [id] - )?.[0]; + ); - if (!event) return null; - return event; + if (results.length < 1) return null; + return results[0]; + } + + public async countTotalEvents() { + const result: Array<{ total: string }> = await this.db.select( + 'SELECT COUNT(*) AS "total" FROM events;' + ); + return parseInt(result[0].total); + } + + public async getAllEvents(limit: number, offset: number) { + const totalEvents = await this.countTotalEvents(); + const nextCursor = offset + limit; + + const events: { data: DBEvent[] | null; nextCursor: number } = { + data: null, + nextCursor: 0, + }; + + const query: DBEvent[] = await this.db.select( + 'SELECT * FROM events ORDER BY created_at DESC LIMIT $1 OFFSET $2;', + [limit, offset] + ); + + if (query && query.length > 0) { + events['data'] = query; + events['nextCursor'] = + Math.round(totalEvents / nextCursor) > 1 ? nextCursor : undefined; + + return events; + } + + return { + data: [], + nextCursor: 0, + }; + } + + public async getAllEventsByAuthors(authors: string[], limit: number, offset: number) { + const totalEvents = await this.countTotalEvents(); + const nextCursor = offset + limit; + const authorsArr = `'${authors.join("','")}'`; + + const events: { data: DBEvent[] | null; nextCursor: number } = { + data: null, + nextCursor: 0, + }; + + const query: DBEvent[] = await this.db.select( + 'SELECT * FROM events WHERE author IN ($1) ORDER BY created_at DESC LIMIT $2 OFFSET $3;', + [authorsArr, limit, offset] + ); + + if (query && query.length > 0) { + events['data'] = query; + events['nextCursor'] = + Math.round(totalEvents / nextCursor) > 1 ? nextCursor : undefined; + + return events; + } + + return { + data: [], + nextCursor: 0, + }; + } + + public async isEventsEmpty() { + const results: DBEvent[] = await this.db.select( + 'SELECT * FROM events ORDER BY id DESC LIMIT 1;' + ); + + return results.length < 1; } public async getExplicitRelayUrls() { @@ -175,13 +246,6 @@ export class LumeStorage { return await this.db.execute(`DELETE FROM relays WHERE relay = "${relay}";`); } - public async updateLastLogin(time: number) { - return await this.db.execute( - 'UPDATE settings SET value = $1 WHERE key = "last_login";', - [time] - ); - } - public async removePrivkey() { return await this.db.execute( `UPDATE accounts SET privkey = "privkey is stored in secure storage" WHERE id = "${this.account.id}";` diff --git a/src/shared/notes/kinds/kind1.tsx b/src/shared/notes/kinds/kind1.tsx index dd9eca6c..365f600e 100644 --- a/src/shared/notes/kinds/kind1.tsx +++ b/src/shared/notes/kinds/kind1.tsx @@ -1,16 +1,16 @@ +import { NDKEvent } from '@nostr-dev-kit/ndk'; import { useMemo } from 'react'; import { NoteActions, NoteContent, NoteMetadata } from '@shared/notes'; import { User } from '@shared/user'; import { parser } from '@utils/parser'; -import { LumeEvent } from '@utils/types'; export function NoteKind_1({ event, skipMetadata = false, }: { - event: LumeEvent; + event: NDKEvent; skipMetadata?: boolean; }) { const content = useMemo(() => parser(event), [event.id]); diff --git a/src/shared/notes/kinds/kind1063.tsx b/src/shared/notes/kinds/kind1063.tsx index ca8dd6ca..9d373ee5 100644 --- a/src/shared/notes/kinds/kind1063.tsx +++ b/src/shared/notes/kinds/kind1063.tsx @@ -1,14 +1,14 @@ +import { NDKEvent } from '@nostr-dev-kit/ndk'; + import { Image } from '@shared/image'; import { NoteActions, NoteMetadata } from '@shared/notes'; import { User } from '@shared/user'; -import { LumeEvent } from '@utils/types'; - function isImage(url: string) { return /\.(jpg|jpeg|gif|png|webp|avif)$/.test(url); } -export function NoteKind_1063({ event }: { event: LumeEvent }) { +export function NoteKind_1063({ event }: { event: NDKEvent }) { const url = event.tags[0][1]; return ( diff --git a/src/shared/notes/kinds/repost.tsx b/src/shared/notes/kinds/repost.tsx index 16f4d026..bae6df2d 100644 --- a/src/shared/notes/kinds/repost.tsx +++ b/src/shared/notes/kinds/repost.tsx @@ -1,3 +1,5 @@ +import { NDKEvent } from '@nostr-dev-kit/ndk'; + import { NoteActions, NoteContent, @@ -8,25 +10,32 @@ import { import { User } from '@shared/user'; import { useEvent } from '@utils/hooks/useEvent'; -import { getRepostID } from '@utils/transform'; -import { LumeEvent } from '@utils/types'; -export function Repost({ event }: { event: LumeEvent }) { - const repostID = getRepostID(event.tags); +export function Repost({ event }: { event: NDKEvent }) { + const repostID = event.tags.find((el) => el[0] === 'e')?.[1]; const { status, data } = useEvent(repostID, event.content as unknown as string); if (status === 'loading') { return ( -
- +
+
+ +
); } if (status === 'error') { return ( -
-

Failed to fetch event: {repostID}

+
+
+

+ Failed to get repostr with ID +

+
+

{repostID}

+
+
); } diff --git a/src/shared/notes/kinds/thread.tsx b/src/shared/notes/kinds/thread.tsx index 3961bffe..5a0493e2 100644 --- a/src/shared/notes/kinds/thread.tsx +++ b/src/shared/notes/kinds/thread.tsx @@ -1,17 +1,17 @@ +import { NDKEvent } from '@nostr-dev-kit/ndk'; import { useMemo } from 'react'; import { NoteActions, NoteContent, NoteMetadata, SubNote } from '@shared/notes'; import { User } from '@shared/user'; import { parser } from '@utils/parser'; -import { LumeEvent } from '@utils/types'; export function NoteThread({ event, root, reply, }: { - event: LumeEvent; + event: NDKEvent; root: string; reply: string; }) { diff --git a/src/shared/notes/kinds/unsupport.tsx b/src/shared/notes/kinds/unsupport.tsx index 6f604b8d..81f98964 100644 --- a/src/shared/notes/kinds/unsupport.tsx +++ b/src/shared/notes/kinds/unsupport.tsx @@ -1,9 +1,9 @@ +import { NDKEvent } from '@nostr-dev-kit/ndk'; + import { NoteActions, NoteMetadata } from '@shared/notes'; import { User } from '@shared/user'; -import { LumeEvent } from '@utils/types'; - -export function NoteKindUnsupport({ event }: { event: LumeEvent }) { +export function NoteKindUnsupport({ event }: { event: NDKEvent }) { return (
diff --git a/src/shared/notes/mentions/note.tsx b/src/shared/notes/mentions/note.tsx index c18cc30f..7a381faf 100644 --- a/src/shared/notes/mentions/note.tsx +++ b/src/shared/notes/mentions/note.tsx @@ -2,6 +2,8 @@ import { memo } from 'react'; import ReactMarkdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; +import { useStorage } from '@libs/storage/provider'; + import { MentionUser, NoteSkeleton } from '@shared/notes'; import { User } from '@shared/user'; @@ -11,13 +13,15 @@ import { useWidgets } from '@stores/widgets'; import { useEvent } from '@utils/hooks/useEvent'; export const MentionNote = memo(function MentionNote({ id }: { id: string }) { + const { db } = useStorage(); const { status, data } = useEvent(id); + const setWidget = useWidgets((state) => state.setWidget); const openThread = (event, thread: string) => { const selection = window.getSelection(); if (selection.toString().length === 0) { - setWidget({ kind: widgetKinds.thread, title: 'Thread', content: thread }); + setWidget(db, { kind: widgetKinds.thread, title: 'Thread', content: thread }); } else { event.stopPropagation(); } @@ -26,7 +30,7 @@ export const MentionNote = memo(function MentionNote({ id }: { id: string }) { if (!id) { return (
-

Failed to fetch event: {id}

+

Failed to get event with id: {id}

); } @@ -64,14 +68,14 @@ export const MentionNote = memo(function MentionNote({ id }: { id: string }) { }, }} > - {data?.content?.original?.length > 160 - ? data.content.original.substring(0, 160) + '...' - : data.content.original} + {data?.content.length > 160 + ? data.content.substring(0, 160) + '...' + : data.content}
) : ( -

Failed to fetch event: {id}

+

Failed to get event with id: {id}

)}
); diff --git a/src/utils/hooks/useEvent.tsx b/src/utils/hooks/useEvent.tsx index f67348a2..1a22b671 100644 --- a/src/utils/hooks/useEvent.tsx +++ b/src/utils/hooks/useEvent.tsx @@ -1,25 +1,29 @@ +import { NDKEvent } from '@nostr-dev-kit/ndk'; import { useQuery } from '@tanstack/react-query'; import { useNDK } from '@libs/ndk/provider'; import { parser } from '@utils/parser'; -import { LumeEvent } from '@utils/types'; export function useEvent(id: string, embed?: string) { const { ndk } = useNDK(); - const { status, data, error, isFetching } = useQuery( - ['note', id], + const { status, data, error } = useQuery( + ['event', id], async () => { if (embed) { - const event: LumeEvent = JSON.parse(embed); - if (event.kind === 1) embed['content'] = parser(event); - return embed as unknown as LumeEvent; - } else { - const event = (await ndk.fetchEvent(id)) as LumeEvent; - if (!event) throw new Error('event not found'); - if (event.kind === 1) event['content'] = parser(event) as unknown as string; - return event as LumeEvent; + const event: NDKEvent = JSON.parse(embed); + // @ts-expect-error, #TODO: convert NDKEvent to ExNDKEvent + if (event.kind === 1) event.content = parser(event); + + return event as unknown as NDKEvent; } + + const event = (await ndk.fetchEvent(id)) as NDKEvent; + if (!event) throw new Error('event not found'); + // @ts-expect-error, #TODO: convert NDKEvent to ExNDKEvent + if (event.kind === 1) event.content = parser(event); + + return event as NDKEvent; }, { staleTime: Infinity, @@ -29,5 +33,5 @@ export function useEvent(id: string, embed?: string) { } ); - return { status, data, error, isFetching }; + return { status, data, error }; } diff --git a/src/utils/hooks/useNostr.tsx b/src/utils/hooks/useNostr.tsx index 20c21ea2..fc08116b 100644 --- a/src/utils/hooks/useNostr.tsx +++ b/src/utils/hooks/useNostr.tsx @@ -6,7 +6,9 @@ import { NDKSubscription, NDKUser, } from '@nostr-dev-kit/ndk'; +import { ndkAdapter } from '@nostr-fetch/adapter-ndk'; import { LRUCache } from 'lru-cache'; +import { NostrFetcher } from 'nostr-fetch'; import { nip19 } from 'nostr-tools'; import { useMemo } from 'react'; @@ -16,17 +18,9 @@ import { useStorage } from '@libs/storage/provider'; import { useStronghold } from '@stores/stronghold'; import { nHoursAgo } from '@utils/date'; -import { LumeEvent } from '@utils/types'; - -interface NotesResponse { - status: string; - data: LumeEvent[]; - nextCursor?: number; - message?: string; -} export function useNostr() { - const { ndk } = useNDK(); + const { ndk, relayUrls } = useNDK(); const { db } = useStorage(); const privkey = useStronghold((state) => state.privkey); @@ -81,30 +75,65 @@ export function useNostr() { await db.updateAccount('follows', [...follows]); await db.updateAccount('network', [...new Set([...follows, ...network])]); - return { status: 'ok' }; + // clear lru caches + lruNetwork.clear(); + + return { status: 'ok', message: 'User data fetched' }; } catch (e) { return { status: 'failed', message: e }; } }; - const fetchNotes = async (since: number): Promise => { + const prefetchEvents = async () => { try { if (!ndk) return { status: 'failed', data: [], message: 'NDK instance not found' }; - console.log('fetch all events since: ', since); - const events = await ndk.fetchEvents({ - kinds: [1], - authors: db.account.network ?? db.account.follows, - since: nHoursAgo(since), - }); + // setup nostr-fetch + const fetcher = NostrFetcher.withCustomPool(ndkAdapter(ndk)); + const dbEventsEmpty = await db.isEventsEmpty(); - const sorted = [...events].sort( - (a, b) => b.created_at - a.created_at - ) as unknown as LumeEvent[]; + let since: number; + if (dbEventsEmpty) { + since = nHoursAgo(24); + } else { + since = db.account.last_login_at ?? nHoursAgo(24); + } - return { status: 'ok', data: sorted, nextCursor: since * 2 }; + console.log("prefetching events with user's network: ", db.account.network.length); + console.log('prefetching events since: ', since); + + const events = fetcher.allEventsIterator( + relayUrls, + { + kinds: [1, 6], + authors: db.account.network, + }, + { since: since } + ); + + // save all events to database + for await (const event of events) { + let root: string; + let reply: string; + if (event.tags?.[0]?.[0] === 'e' && !event.tags?.[0]?.[3]) { + root = event.tags[0][1]; + } else { + root = event.tags.find((el) => el[3] === 'root')?.[1]; + reply = event.tags.find((el) => el[3] === 'reply')?.[1]; + } + db.createEvent( + event.id, + JSON.stringify(event), + event.pubkey, + root, + reply, + event.created_at + ); + } + + return { status: 'ok', data: [], message: 'prefetch completed' }; } catch (e) { - console.error('failed get notes, error: ', e); + console.error('prefetch events failed, error: ', e); return { status: 'failed', data: [], message: e }; } }; @@ -150,5 +179,5 @@ export function useNostr() { return res; }; - return { sub, fetchUserData, fetchNotes, publish, createZap }; + return { sub, fetchUserData, prefetchEvents, publish, createZap }; } diff --git a/src/utils/parser.tsx b/src/utils/parser.tsx index d8383a5b..e2edd727 100644 --- a/src/utils/parser.tsx +++ b/src/utils/parser.tsx @@ -1,10 +1,11 @@ +import { NDKEvent } from '@nostr-dev-kit/ndk'; import getUrls from 'get-urls'; import { Event, parseReferences } from 'nostr-tools'; import ReactPlayer from 'react-player'; -import { LumeEvent, RichContent } from '@utils/types'; +import { RichContent } from '@utils/types'; -export function parser(event: LumeEvent) { +export function parser(event: NDKEvent) { if (event.kind !== 1) return; const references = parseReferences(event as unknown as Event); diff --git a/src/utils/transform.tsx b/src/utils/transform.tsx index fb67a921..74441dd9 100644 --- a/src/utils/transform.tsx +++ b/src/utils/transform.tsx @@ -1,5 +1,4 @@ import { NDKTag } from '@nostr-dev-kit/ndk'; -import { destr } from 'destr'; // convert array to NIP-02 tag list export function arrayToNIP02(arr: string[]) { @@ -12,8 +11,7 @@ export function arrayToNIP02(arr: string[]) { } // get repost id from event tags -export function getRepostID(arr: NDKTag[]) { - const tags = destr(arr) as string[]; +export function getRepostID(tags: NDKTag[]) { let quoteID = null; if (tags.length > 0) { diff --git a/src/utils/types.d.ts b/src/utils/types.d.ts index fbe7a849..9ea268d9 100644 --- a/src/utils/types.d.ts +++ b/src/utils/types.d.ts @@ -8,8 +8,15 @@ export interface RichContent { links: string[]; } -export interface LumeEvent extends NDKEvent { - richContent: RichContent; +export interface DBEvent { + id: string; + account_id: number; + event: string | NDKEvent; + author: string; + root_id: string; + reply_id: string; + created_at: number; + richContent?: RichContent; } export interface Account extends NDKUserProfile { @@ -20,6 +27,7 @@ export interface Account extends NDKUserProfile { network: null | string[]; is_active: number; privkey?: string; // deprecated + last_login_at: number; } export interface Profile extends NDKUserProfile {