Compare commits

...

568 Commits

Author SHA1 Message Date
a0820a9187 feat: simple login process 2025-12-19 10:09:29 +07:00
d6504a8170 wip: onboarding 2025-12-18 09:00:29 +07:00
4dffc4de20 chore: update deps 2025-12-17 07:53:33 +07:00
0be73e8e82 feat: simple poc 2025-12-16 09:18:39 +07:00
15cefbd84f wip: handle events 2025-12-15 11:30:24 +07:00
dd2f940e33 wip: handle events 2025-12-14 08:58:33 +07:00
af740f462c feat: simple dock layout 2025-12-13 09:20:36 +07:00
703fe988bd feat: add simple account state 2025-12-12 09:09:30 +07:00
187e53b3a2 feat: add simple workspace 2025-12-11 10:44:45 +07:00
b9093f49a0 chore: setup project structure 2025-12-11 08:55:27 +07:00
aac0b7510a chore: update deps 2025-12-11 07:32:05 +07:00
98eda38377 reboot 2025-07-26 20:18:49 +07:00
fab34de1ee chore: update readme 2025-04-01 17:52:13 +07:00
9730837e00 chore: update deps 2025-01-11 20:18:30 +07:00
ec34255df1 chore: update deps 2024-11-30 07:39:23 +07:00
a262217ab2 chore: release 2024-11-11 10:02:44 +07:00
c5d06a2492 feat: add negentropy sync to settings 2024-11-11 10:02:19 +07:00
c93edde7d2 chore: use upstream rust nostr 2024-11-11 07:56:31 +07:00
5103126001 fix: build on windows 2024-11-10 14:28:14 +07:00
b0c49c5141 chore: release 2024-11-10 13:23:57 +07:00
2ed2cb3afd chore: update dependencies 2024-11-10 10:43:38 +07:00
2bcda1f2ef feat: support NIP22 for comment 2024-11-10 08:51:07 +07:00
ca20bbd298 chore: release 2024-11-08 09:19:51 +07:00
c6da06cd4d chore: clean up 2024-11-08 09:02:57 +07:00
50bf6c04c1 chore: use upstream nostr-sdk instead of fork 2024-11-08 07:51:48 +07:00
490417771c chore: release 2024-11-07 15:02:05 +07:00
0cb491eaf9 feat: auto open browser if receive auth_url 2024-11-07 14:26:13 +07:00
ece6bcc125 feat: add DVM feeds 2024-11-07 09:26:28 +07:00
4b79e559d2 chore: clean up 2024-11-06 09:43:31 +07:00
322e510db2 fix: settings 2024-11-06 09:16:22 +07:00
4e279f127d chore: release 2024-11-06 07:45:00 +07:00
5655a8136d feat: improve dark mode and some copy 2024-11-05 19:33:47 +07:00
d80534c51f feat: allow user enter custom relay for relayfeeds 2024-11-05 15:04:39 +07:00
0b97248fb8 chore: release 2024-11-04 10:42:46 +07:00
f54f448ecb feat: add tray 2024-11-04 10:40:50 +07:00
bd1f2b899d refactor: sync based on interval not window event 2024-11-04 09:40:33 +07:00
efd3c83193 fix: missing check for update 2024-11-03 13:50:52 +07:00
85fa1e2359 fix: missing check for update 2024-11-03 13:50:31 +07:00
cd6ba5884f chore: release 2024-11-03 13:18:36 +07:00
bfed56ba13 fix: crash on windows 2024-11-03 13:05:24 +07:00
d1018ba8d1 feat: remove mica effect and support windows 10 2024-11-02 13:43:13 +07:00
a42542c16e fix: performance 2024-11-02 13:33:48 +07:00
26a6ec954c fix: thread not reload after reply 2024-11-02 08:26:46 +07:00
f346282c3c fix: calver 2024-11-01 18:33:03 +07:00
f3e875eeea fix: some issues with publish new note 2024-11-01 17:57:01 +07:00
6ad8ffddf0 chore: clean up 2024-11-01 13:14:47 +07:00
d37e2a3c80 chore: update nostr-sdk and nostr-connect 2024-11-01 09:57:46 +07:00
18e1ac0e6c feat: add relay feeds 2024-11-01 09:25:12 +07:00
aa4f21a869 fix: dark mode 2024-10-31 16:25:16 +07:00
bbc052eebc chore: switch to calver 2024-10-31 13:46:41 +07:00
c201b5816c feat: add discover newsfeeds and interests 2024-10-31 13:34:25 +07:00
043cabfd4e fix: overload 2024-10-31 10:25:00 +07:00
9b02ab5842 feat: re-enable gossip and optimize 2024-10-30 16:02:09 +07:00
618a45d349 refactor: app settings 2024-10-30 10:57:43 +07:00
11dcef4e87 feat: group metadata query 2024-10-29 15:05:03 +07:00
d87371aec4 chore: use latest nostr sdk 2024-10-28 16:19:50 +07:00
cfb017f70b chore: update deps 2024-10-28 09:00:28 +07:00
9ba95301db update 2024-10-28 08:04:19 +07:00
0518389f50 update 2024-10-27 15:57:32 +07:00
eb6e3e52df update 2024-10-27 09:57:56 +07:00
1e95a2fd95 update 2024-10-27 08:14:15 +07:00
83d24351cd update 2024-10-26 17:24:39 +07:00
470dc1c759 fix: command 2024-10-26 13:11:07 +07:00
42b780ce6a update 2024-10-26 09:08:50 +07:00
5ab2b1ae31 feat: negentropy progress 2024-10-25 14:57:12 +07:00
055d73c829 feat: rework multi account 2024-10-24 15:50:45 +07:00
469296790e wip: rework multi account 2024-10-24 07:59:41 +07:00
c032dbea1a chore: clean up 2024-10-23 10:43:39 +07:00
172566028b feat: use latest nostr sdk 2024-10-23 10:16:56 +07:00
雨宮蓮
cc7de41bfd feat: Multi Accounts (#237)
* wip: new sync

* wip: restructure routes

* update

* feat: improve sync

* feat: repost with multi-account

* feat: improve sync

* feat: publish with multi account

* fix: settings screen

* feat: add zap for multi accounts
2024-10-22 16:00:06 +07:00
ba9c81a10a feat: use upstream rust nostr 2024-10-15 10:37:07 +07:00
e158f2e4d7 feat: improve column carousel 2024-10-15 09:59:26 +07:00
62bd689031 fix: async mutex lock forever 2024-10-14 14:59:42 +07:00
cb6006f596 feat: handle error when publish note 2024-10-11 14:09:15 +07:00
adad048873 feat: improve negentropy sync 2024-10-11 08:56:43 +07:00
790fee6c05 chore: bump version 2024-10-09 14:51:20 +07:00
9f5b14956a feat: save state when close app 2024-10-09 14:02:13 +07:00
e04497d841 chore: update README 2024-10-09 09:42:49 +07:00
106c627ec4 feat: only query from local database and other improvements 2024-10-09 09:15:49 +07:00
c40762cc04 feat: add support for NIP-09 2024-10-08 16:30:26 +07:00
d2b5ae0507 feat: use negentropy as much as possible 2024-10-08 10:36:31 +07:00
8c6aea8050 chore: follow-up #236 2024-10-07 15:12:30 +07:00
雨宮蓮
090a815f99 feat: Add support for NIP-51 (#236)
* feat: and follow and interest sets

* feat: improve query

* feat: improve
2024-10-07 14:33:20 +07:00
d841163ba7 revamp 2024-10-05 08:49:09 +07:00
8398ae80d3 chore: bump version 2024-10-03 10:43:16 +07:00
fe60f75e96 chore: upgrade to tauri v2 stable 2024-10-03 09:00:48 +07:00
雨宮蓮
e098743d43 feat: new flow (#235)
* feat: redesign initial screen

* feat: improve login process
2024-10-02 14:56:26 +07:00
0c19ada1ab chore: bump version 2024-09-30 05:57:42 +07:00
09db39fce1 chore: add some small improvements 2024-09-30 04:49:17 +07:00
雨宮蓮
f0fc89724d feat: improve performance (#234)
* feat: use negentropy as much as possible

* update

* update
2024-09-29 16:53:39 +07:00
afa9327bb7 feat: add content parser to reply 2024-09-27 09:58:02 +07:00
5c3644f977 feat: update general settings 2024-09-27 09:12:38 +07:00
0a8eed9a46 feat: new post editor 2024-09-26 10:57:58 +07:00
bacfaed48a wip: local relay 2024-09-25 09:59:54 +07:00
3d5085785b feat: pow by default 2024-09-23 17:18:41 +07:00
9152c3e122 feat: add basic web of trust 2024-09-23 13:24:33 +07:00
a5574bef6c feat: add notification for nip42 2024-09-22 09:40:07 +07:00
2c7f3685b6 fix: build on macos 2024-09-20 09:26:48 +07:00
be0abc4075 chore: update deps 2024-09-20 08:42:08 +07:00
dafe35cd1f feat: add client tag 2024-09-20 08:05:32 +07:00
b23903240b fix: wrong menu item in repost button 2024-09-20 08:05:22 +07:00
872a6cee36 fix: missing context menu in user's avatar 2024-09-20 07:25:07 +07:00
雨宮蓮
ac7ce726c5 feat: Add gossip model (#232)
* feat: enable gossip

* chore: remove deprecated functions

* chore: use upstream rust nostr

* fix
2024-09-11 11:10:11 +07:00
Andrew
e5e290c0c3 fix: Improved text legibility on login/create account screens (#230)
* style: standardized placeholder text to be color neutral-400

* fix: changed dark:text-neutral-600 to dark:text-neutral-200 as in dark mode text was not visible
2024-08-30 20:04:44 +07:00
2eab6f04c7 chore: bump version 2024-08-30 13:30:29 +07:00
reya
e06b0334a5 chore: bump version 2024-08-29 08:00:58 +07:00
reya
74d8bf2ead fix: cannot import ncryptsec 2024-08-29 07:48:26 +07:00
reya
d128af1db8 chore: clean up 2024-08-28 08:48:17 +07:00
reya
f6eb5eea44 chore: update ci 2024-08-27 19:43:51 +07:00
reya
bca2e0b7b7 chore: bump version 2024-08-27 19:42:35 +07:00
雨宮蓮
61ad96ca63 Release v4.1 (#229)
* refactor: remove custom icon packs

* fix: command not work on windows

* fix: make open_window command async

* feat: improve commands

* feat: improve

* refactor: column

* feat: improve thread column

* feat: improve

* feat: add stories column

* feat: improve

* feat: add search column

* feat: add reset password

* feat: add subscription

* refactor: settings

* chore: improve commands

* fix: crash on production

* feat: use tauri store plugin for cache

* feat: new icon

* chore: update icon for windows

* chore: improve some columns

* chore: polish code
2024-08-27 19:37:30 +07:00
reya
26ae473521 fix: disable some default webview behaviors 2024-08-19 13:46:22 +07:00
reya
bcc5e18082 fix: adjust window controls position 2024-08-19 10:48:29 +07:00
reya
307fff7a53 fix: titlebar on windows 2024-08-19 10:28:38 +07:00
reya
ce7828310b chore: add some improvements and remove linux support 2024-08-19 09:58:29 +07:00
reya
beac1a189e chore: update deps 2024-08-18 15:51:34 +07:00
reya
4cb49d44c7 feat: improve account management 2024-08-13 10:33:21 +07:00
reya
be16d5c21d chore: upgrade to react 19 rc 2024-08-12 10:32:20 +07:00
reya
da8162069b refactor: remove turborepo 2024-08-12 09:07:11 +07:00
reya
e2103ae23a feat: upgrade to tauri v2 rc 2024-08-10 16:45:56 +07:00
XIAO YU
4c6d1c768a Handle errors when adding and connecting relays in init_nip65 (#227) 2024-08-03 08:56:38 +07:00
reya
9b75a04f91 chore: update deps 2024-07-31 13:03:27 +07:00
reya
a5255fa503 chore: bump version 2024-07-31 12:51:41 +07:00
reya
954a17b541 fix: build on linux 2024-07-31 12:51:17 +07:00
reya
a55b31b0e6 feat: add keyring support for linux and windows 2024-07-31 10:59:54 +07:00
reya
bdf3ffd7bf fix: tray panel is missing 2024-07-19 13:58:32 +07:00
reya
07ce253f5b fix: child webview is not reposition after scroll 2024-07-19 13:10:29 +07:00
reya
f3db010c74 chore: update tauri config 2024-07-19 10:01:20 +07:00
reya
dcf2791fe5 fix: build 2024-07-19 09:28:03 +07:00
reya
8fcf3551d8 chore: bump version 2024-07-19 08:33:46 +07:00
reya
2d987849d8 feat: use native border in macos 2024-07-19 08:33:16 +07:00
reya
3b99926f3b feat: adapt latest changes in tauri v2 2024-07-19 08:25:36 +07:00
reya
113d69a4df chore: update deps 2024-07-18 18:57:28 +07:00
reya
5d12ba7216 fix: some screen too large 2024-07-03 14:36:54 +07:00
reya
72b59020b4 fix: build on linux and windows 2024-07-03 14:09:07 +07:00
XIAO YU
4c323b9daa chore: Refactor code to use HashSet for account search results (#222) 2024-07-03 07:33:16 +07:00
reya
72da83d648 feat: add force quit command 2024-07-02 17:24:55 +07:00
reya
783a4538a4 Revert "chore: update ci"
This reverts commit 04706a6d7c.
2024-07-02 15:54:00 +07:00
reya
15e62cad11 fix: temporary include devtools in release build 2024-07-02 14:58:00 +07:00
reya
c52b20ca80 chore: bump version 2024-07-02 14:36:16 +07:00
reya
04706a6d7c chore: update ci 2024-07-02 14:07:12 +07:00
reya
0755cbeb6c chore: bump version 2024-07-02 13:03:45 +07:00
雨宮蓮
8eb01c8bbf Improve column management (#221)
* wip: redesign store

* feat: update trending column

* feat: add more functions
2024-07-02 12:51:50 +07:00
reya
ed4f89ff66 feat: add option to toggle window transparent 2024-07-02 08:49:52 +07:00
reya
d9fe647f8e feat: few improvements 2024-07-01 14:41:33 +07:00
reya
843c2d52e7 refactor: tray panel 2024-07-01 13:04:32 +07:00
reya
017a3676a4 feat: optimize spawn thread 2024-06-30 21:06:04 +07:00
reya
fcb70c0e9a chore: update deps 2024-06-30 14:37:26 +07:00
雨宮蓮
0fec21b9ce Some improments and Negentropy (#219)
* feat: adjust default window size

* feat: save window state

* feat: add window state plugin

* feat: add search

* feat: use negentropy for newsfeed

* feat: live feeds

* feat: add search user
2024-06-30 14:26:02 +07:00
XIAO YU
968b1ada94 refactor: improve relay management code structure (#220) 2024-06-29 07:41:16 +07:00
reya
5c9b599b1e feat: use memo for some components 2024-06-26 17:49:36 +07:00
雨宮蓮
717c3e17df Event Subscriptions (#218)
* feat: improve create column command

* refactor: thread

* feat: add window virtualized to event screen

* chore: update deps

* fix: window decoration

* feat: improve mention ntoe

* feat: add subscription to event screen
2024-06-26 14:51:50 +07:00
XIAO YU
a4540a0802 refactor: improve error handling in event.rs (#217) 2024-06-26 07:57:53 +07:00
reya
31bacc2646 chore: remove unused code 2024-06-24 11:09:32 +07:00
reya
6e5d0f0e76 chore: bump version 2024-06-24 10:06:12 +07:00
XIAO YU
f0712e5961 refactor: improve error handling (#216) 2024-06-24 07:19:36 +07:00
雨宮蓮
3fbd66dece Add bitcoin connect (#215)
* feat: add bitcoin connect

* feat: improve zap screen
2024-06-21 14:56:10 +07:00
reya
1283432632 feat: use native feature instead of react 2024-06-21 10:24:09 +07:00
reya
59eaaec903 feat: support content warning 2024-06-21 08:57:49 +07:00
雨宮蓮
4f0f210076 Add lazy carousel (#214)
* refactor: carousel

* feat: improve image carousel
2024-06-21 07:51:11 +07:00
reya
e4a317f038 chore: bump version 2024-06-20 13:23:05 +07:00
reya
9779d020c7 feat: improve list virtualization 2024-06-20 13:22:28 +07:00
雨宮蓮
f8280ec8ee fix: get replies function (#213) 2024-06-19 21:02:33 +07:00
XIAO YU
6c26f8967b chore: Refactor code for better performance and reliability (#212) 2024-06-19 14:36:59 +07:00
reya
e1424b851c fix: routes 2024-06-19 14:22:45 +07:00
reya
d14e609579 chore: bump version 2024-06-19 14:14:34 +07:00
reya
8c0627ff27 fix: crash on startup 2024-06-19 14:14:03 +07:00
雨宮蓮
18c133d096 Settings Manager (#211)
* refactor: landing screen

* fix: code debt

* feat: add settings screen

* chore: clean up

* feat: settings

* feat: small updates
2024-06-19 14:00:58 +07:00
reya
0061ecea78 feat: use native context menu in tray panel 2024-06-18 09:07:58 +07:00
reya
d01cf8319d feat: native context menu 2024-06-17 15:31:59 +07:00
雨宮蓮
843895d876 Make Lume Faster (#208)
* chore: fix some lint issues

* feat: refactor contact list

* feat: refactor relay hint

* feat: add missing commands

* feat: use new cache layer for react query

* feat: refactor column

* feat: improve relay hint

* fix: replace break with continue in parser

* refactor: publish function

* feat: add reply command

* feat: improve editor

* fix: quote

* chore: update deps

* refactor: note component

* feat: improve repost

* feat: improve cache

* fix: backup screen

* refactor: column manager
2024-06-17 13:52:06 +07:00
XIAO YU
7c99ed39e4 chore: handle unwrap err (#210) 2024-06-16 08:11:48 +07:00
雨宮蓮
71be59b2e9 Move the event parser and dedup functions to Rust (#206)
* feat: improve js parser

* feat: move parser and dedup to rust

* fix: parser

* fix: get event function

* feat: improve parser performance (#207)

* feat: improve parser performance

* feat: add test for video parsing

* feat: finish new parser

---------

Co-authored-by: XIAO YU <xyzmhx@gmail.com>
2024-06-12 08:27:53 +07:00
reya
1c20512ecc chore: update deps 2024-06-10 13:15:57 +07:00
雨宮蓮
90342c552f Customize Bootstrap Relays (#205)
* feat: add bootstrap relays file

* feat: add save bootstrap relays command

* feat: add customize bootstrap relays screen
2024-06-10 10:48:39 +07:00
reya
b396c8a695 feat: upgrade to rust-nostr 0.32 2024-06-08 08:00:02 +07:00
reya
6996e30889 chore: update github ci 2024-06-07 11:26:20 +07:00
reya
7ba793fad8 chore: bump version 2024-06-07 10:03:04 +07:00
reya
f11f836518 chore: update tray icon 2024-06-07 09:56:57 +07:00
reya
04fe0fcec8 feat: respect the relay hint 2024-06-07 09:07:33 +07:00
雨宮蓮
799835a629 Notification Panel (#200)
* feat: add tauri nspanel

* feat: add notification panel

* feat: move notification service to backend

* feat: add sync notification job

* feat: enable panel to join all spaces including fullscreen (#203)

* feat: fetch notification

* feat: listen for new notification

* feat: finish panel

---------

Co-authored-by: Victor Aremu <me@victorare.mu>
2024-06-06 14:32:30 +07:00
XIAO YU
4e7da4108b feat: Add get user following function (#202)
* feat: Add get user following function

* refactor: Refactor get_following function to use state and string public key

* feat: Update get_following function to use timeout duration

* feat: Fix connect_remote_account function to return remote_npub without conversion

* feat: Refactor get_following function to handle public key parsing errors

* Refactor get_followers function to handle public key parsing errors and use timeout duration
2024-06-05 13:24:32 +07:00
reya
7c7b082b3a fix: memory leak in image component 2024-06-03 07:32:34 +07:00
reya
38d6c51921 feat: use nostrdb for unix and rocksdb for windows 2024-06-02 08:16:59 +07:00
reya
1738cbdd97 chore: upgrade tauri 2024-06-01 14:54:35 +07:00
reya
2e885b76a1 feat: improve text wrap 2024-06-01 08:27:22 +07:00
reya
f94680e487 fix: column overlapped after change account 2024-05-31 15:25:47 +07:00
XIAO YU
c682a58842 chore: Remove unused modules and update metadata.rs (#199) 2024-05-31 12:53:35 +07:00
reya
921cf871ee chore: bump version 2024-05-31 08:54:53 +07:00
reya
d5b1593aca feat: improve nostr connect flow 2024-05-31 08:54:17 +07:00
reya
6676b4e2a4 Revert "chore: Update dependencies and add thiserror crate (#196)"
This reverts commit e254ee3203.
2024-05-30 15:40:44 +07:00
reya
5f30ddcfca Merge branch 'main' of github.com:lumehq/lume 2024-05-30 15:23:11 +07:00
reya
41d0de539d feat: revamp nostr connect flow 2024-05-30 15:21:33 +07:00
XIAO YU
e254ee3203 chore: Update dependencies and add thiserror crate (#196) 2024-05-30 07:12:46 +07:00
reya
6d42360549 chore: bump version 2024-05-29 14:47:22 +07:00
reya
70c5143445 feat: redirect to index after add account 2024-05-29 14:41:33 +07:00
reya
41b66b18f5 feat: lazy load onboarding videos 2024-05-29 14:11:30 +07:00
雨宮蓮
dda0720ed4 chore: update deps (#195) 2024-05-29 12:54:58 +07:00
reya
4b60b39119 chore: bump version 2024-05-29 10:34:32 +07:00
reya
d2e5122d5a feat: improve splash screen and notification service 2024-05-29 10:33:38 +07:00
reya
32f3315344 fix: editor crash on open 2024-05-28 14:43:34 +07:00
reya
5ca9444358 feat: add theme switcher 2024-05-26 16:11:27 +07:00
reya
4dc13385a5 feat: add option for relay hint 2024-05-26 08:23:29 +07:00
XIAO YU
b90ad1421f chore: update keys.rs and relay.rs (#193) 2024-05-26 07:28:16 +07:00
雨宮蓮
bba324ea53 refactor: use specta for commands (#192)
* feat: add tauri-specta

* refactor: system library

* chore: format
2024-05-25 15:21:40 +07:00
reya
7449000f5f chore: bump version 2024-05-23 08:01:04 +07:00
reya
dc7762ca11 fix: column lose state when navigate back 2024-05-23 08:00:33 +07:00
XIAO YU
3a3f960dde refactor: use borrowed references for adding and connecting relays (#191) 2024-05-23 07:08:39 +07:00
reya
12e066ff2e fix: web 2024-05-22 15:36:41 +07:00
reya
fe4f965ed5 chore: update website 2024-05-22 15:35:41 +07:00
reya
5d3f2264e9 chore: bump version 2024-05-22 13:26:08 +07:00
雨宮蓮
407fe40b67 refactor: account managements (#190)
* feat: add keyring-search

* feat: improve nostr connect
2024-05-22 13:24:58 +07:00
雨宮蓮
1f38eba2cc feat: immersive onboarding (#189)
* feat: change the default onboarding column

* feat: add newsfeed onboarding

* feat: add topic onboarding

* feat: add group onboarding

* chore: polish and format

* feat: rename foryou to topic

* fix: array
2024-05-22 10:44:19 +07:00
reya
9b5867f80c feat: improve tauri commands 2024-05-20 07:05:30 +07:00
reya
cac774a0c1 feat: small ui improve 2024-05-19 08:48:29 +07:00
reya
82689bf3c3 feat: add windows support for v4 2024-05-18 21:02:04 +07:00
reya
f60e438a64 feat: add custom titlebar 2024-05-18 19:07:34 +07:00
reya
ca06f2b6ed chore: clean up tauri commands 2024-05-18 14:59:34 +07:00
reya
99d9c70826 feat: improve event discovery 2024-05-18 08:16:05 +07:00
reya
60afbf090b chore: update to rust-nostr 0.31 2024-05-18 07:52:28 +07:00
reya
10ca4e6ff4 chore: bump version 2024-05-17 15:32:14 +07:00
reya
b0f387d029 fix: wrong permissions for dialog 2024-05-17 15:31:35 +07:00
reya
1a8f750640 fix: profile is not show on edit screen 2024-05-17 14:45:17 +07:00
reya
25523229a2 fix: copy to clipboard is not working properly 2024-05-16 14:24:55 +07:00
雨宮蓮
47835ed857 feat: improve ui for multi-account (#188) 2024-05-16 14:13:07 +07:00
reya
d84647bc6b chore: bump version 2024-05-15 13:58:39 +07:00
reya
7724eccd72 feat: improve tauri commands 2024-05-15 13:58:03 +07:00
reya
8ea2335225 chore: update deps 2024-05-15 10:50:35 +07:00
reya
b60d4db0df feat: improve multi-account switching 2024-05-15 10:14:21 +07:00
reya
f1e17ff3c4 fix: allow app auto restart after update 2024-05-13 15:36:47 +07:00
reya
32954f17b6 chore: bump version and update readme 2024-05-13 15:21:05 +07:00
雨宮蓮
cf70b0f882 fix: context issue in production (#187) 2024-05-13 15:18:23 +07:00
reya
135d0918b3 feat: add check for updates 2024-05-12 15:07:53 +07:00
reya
e1fbcf0460 chore: fix ci again 2024-05-12 08:57:57 +07:00
reya
99aaf3da82 fix: misconfigure in github actions 2024-05-12 08:54:24 +07:00
reya
3ef13e43f1 chore: update github ci 2024-05-12 08:38:23 +07:00
reya
8939196ae4 chore: bump version to 4.0 2024-05-12 08:20:52 +07:00
reya
571d4b4004 feat: improve search 2024-05-12 08:18:25 +07:00
reya
73f80f27fb feat: add basic relay management in rust 2024-05-11 12:28:07 +07:00
雨宮蓮
b46a5cf68f Merge pull request #186 from kasugamirai/chore
feat: Refactor code to improve error handling and readability
2024-05-10 07:13:32 +07:00
xy
8c0d03aed0 feat: Refactor code to improve error handling and readability 2024-05-09 19:09:55 +09:00
reya
777eb15b4f feat: onboarding 2024-05-09 15:06:42 +07:00
reya
c8e1b8b8bd feat: add unread notification badge to dock icon 2024-05-07 14:38:00 +07:00
reya
437cd71f7e feat: improve editor 2024-05-07 14:14:21 +07:00
reya
afb7c87fa3 feat: add bell 2024-05-07 08:29:58 +07:00
reya
c843626bca feat: add notification screen 2024-05-06 15:17:34 +07:00
reya
28337e5915 feat: improve dedup events 2024-05-04 08:29:52 +07:00
雨宮蓮
a4aef25adb final design (#184)
* feat: redesign

* feat: update other columns to new design

* chore: small fixes

* fix: better manage external webview

* feat: redesign note

* feat: update ui

* chore: update

* chore: update

* chore: polish ui

* chore: update auth ui

* feat: finalize note design

* chore: small fixes

* feat: add window management in rust

* chore: format

* feat: update ui for event screen

* feat: update event screen

* feat: final
2024-05-03 15:15:48 +07:00
reya
61d1f095d4 chore: clean up 2024-04-24 15:21:13 +07:00
雨宮蓮
f027eae52d feat: use negentropy (#182)
* feat: use negentropy

* chore: polish
2024-04-24 10:18:51 +07:00
雨宮蓮
174a3cc74e feat: add search window (NIP-50) (#181)
* feat: add search window
* chore: improve search ui
2024-04-23 15:34:08 +07:00
reya
c00a7749b4 feat: use nip44 to encrypt key-value store 2024-04-23 08:04:42 +07:00
Ren Amamiya
c755b8d137 feat: add ability change column name on the fly (#180)
Co-authored-by: reya <reya@lume.nu>
2024-04-22 14:33:14 +07:00
17766d29d6 chore: update deps 2024-04-22 13:18:34 +07:00
3b13dfeed8 chore: update github action 2024-04-20 18:13:36 +07:00
17ba79e01b chore: update app icon 2024-04-20 08:58:00 +07:00
bafad544e9 chore: update deps and bump version 2024-04-19 14:21:12 +07:00
89c36423ae feat: add nip46 2024-04-18 15:09:33 +07:00
cd31b99559 feat: improve official columns 2024-04-18 07:50:46 +07:00
f3c52237fa fix: privacy setting is not working 2024-04-17 13:50:50 +07:00
413d8d82df feat: finish settings screen 2024-04-16 15:28:38 +07:00
2eb2010d43 chore: upgrade deps 2024-04-16 08:55:36 +07:00
94d400cab2 feat: support nip-36 2024-04-16 07:49:44 +07:00
09b143cb08 feat: auto resize mini webview when main webview resized 2024-04-15 13:30:55 +07:00
e3ede34108 feat: revert to sqlite 2024-04-14 09:05:46 +07:00
ed6aca41ea feat: add new spinner component 2024-04-13 15:01:27 +07:00
89f577fbef feat: upgrade to rust nostr 0.30.0 and migrate to nostrdb 2024-04-13 08:53:31 +07:00
a14aeaeb55 feat: add empty state and polish trending column 2024-04-13 08:30:58 +07:00
reya
35cf0abda4 fix: support windows 2024-04-12 18:35:19 +07:00
8a7b246315 chore: update deps 2024-04-11 07:47:37 +07:00
Ren Amamiya
d98c6d0709 Merge pull request #175 from lumehq/v4/beta
Prepare for v4 - beta
2024-04-11 07:45:43 +07:00
bda20e8fe8 chore: small updates 2024-04-11 07:44:41 +07:00
c342daecc8 feat: improve 2024-04-10 14:11:05 +07:00
5e6692cd6d feat: re add group column 2024-04-09 14:05:50 +07:00
420be77b5c feat: add nstore 2024-04-07 15:11:20 +07:00
999073f84c feat: readd for you column 2024-04-04 13:47:15 +07:00
174b28f1a7 chore: update deps 2024-04-03 08:01:40 +07:00
763cb10e85 chore: clean up 2024-04-03 07:29:46 +07:00
89bb8d88f6 feat: settings screens 2024-04-02 13:19:26 +07:00
09aa2ecafc feat: re-add trending column 2024-03-30 10:50:45 +07:00
7271e9ea87 feat: add custom traffic light inset for macos 2024-03-29 13:26:35 +07:00
a02e496b29 chore: update tauri 2024-03-29 07:45:37 +07:00
cbbf5eaf50 feat: add create account flow 2024-03-28 15:12:43 +07:00
d3fa59d2b1 chore: polish 2024-03-27 08:36:34 +07:00
aa23e39334 feat: update new icons 2024-03-26 14:58:06 +07:00
c8e2018fd0 feat: update tray icon 2024-03-26 13:08:03 +07:00
6e489f1c49 wip: onboarding 2024-03-25 14:22:06 +07:00
a49b88ab35 Merge branch 'main' into v4/beta 2024-03-23 07:24:58 +07:00
31839531ea feat: improve open store column 2024-03-22 15:25:53 +07:00
ec0f3fabc0 feat: improve column management 2024-03-22 11:28:54 +07:00
dd7155a3a6 feat: update default column informations 2024-03-21 14:53:08 +07:00
cb565ff35b feat: readd default columns 2024-03-20 15:12:54 +07:00
5d59040224 chore: follow up 2024-03-19 15:36:20 +07:00
7fabf949c6 feat: move column manager to rust 2024-03-19 14:56:24 +07:00
ea5120e2f0 chore: upgrade tauri and bump version 2024-03-19 12:46:19 +07:00
14f07dfea8 feat: add lume store screen 2024-03-19 08:21:16 +07:00
Ren Amamiya
1de48cc640 Merge pull request #174 from fernandolguevara/minors/styling-scrollbar
feature: make scrollbar nicer
2024-03-19 07:10:28 +07:00
Fernando López Guevara
f72eb456e8 feature: make scrollbar nicer 2024-03-18 09:42:25 -03:00
05b52564e0 feat: column manager 2024-03-18 10:50:08 +07:00
c8e014f33e chore: clean up 2024-03-16 18:45:54 +07:00
46cc01e0ee feat: child webview 2024-03-16 15:05:06 +07:00
16e6d234e5 feat: space 2024-03-14 14:22:41 +07:00
3005d27403 feat: update fetch opg function 2024-03-13 10:24:45 +07:00
1be84f3139 chore: release v4.0.0-alpha+2 2024-03-13 09:40:30 +07:00
Ren Amamiya
04bde7dd43 Merge pull request #172 from lumehq/v4/functions
Some polish for v4
2024-03-13 09:39:57 +07:00
f1504d99ac feat: add mock tray menu 2024-03-13 09:19:25 +07:00
e928f2ee37 feat: polish 2024-03-13 08:40:18 +07:00
Ren Amamiya
ccab78ca11 Merge pull request #171 from lumehq/v4/zap
Add zap to v4
2024-03-11 15:39:35 +07:00
5b13c3e45c feat: auto close window if zapp successfully 2024-03-11 15:38:54 +07:00
e6b97ab9ae feat: add zap 2024-03-11 15:36:17 +07:00
bb6badfed6 feat: add zap setup screen 2024-03-10 16:39:23 +07:00
Ren Amamiya
0c4b309a11 Merge pull request #170 from lumehq/v4/home-screen
v4/home-screen
2024-03-09 10:09:29 +07:00
f4400d711f feat: improve home screen 2024-03-09 10:08:50 +07:00
a3e46aa96b fix: crash when database is not exist 2024-03-08 14:47:36 +07:00
a4fdcfdf0b feat: add local and global newsfeeds 2024-03-07 15:39:43 +07:00
25d07303a3 fix: build error in windows and linux 2024-03-06 15:32:51 +07:00
0e1e7524c9 chore: bump version 2024-03-06 15:05:17 +07:00
Ren Amamiya
32d770eb91 Merge pull request #169 from lumehq/v4/onboarding
Add basic onboarding dialog to v4
2024-03-06 15:00:07 +07:00
e93cbf78d5 feat: add npub to file associations 2024-03-06 14:40:55 +07:00
71a2290b8f feat: add backup dialog 2024-03-06 14:40:00 +07:00
95294a80cb feat: simplify account management 2024-03-06 11:11:24 +07:00
8eaf47f6d2 feat: add login dialog 2024-03-06 09:42:44 +07:00
86183d799a fix: package version in windows 2024-03-02 17:59:36 +07:00
2dbfff3c17 chore: release v4.0.0-alpha.0 2024-03-02 16:08:01 +07:00
Ren Amamiya
d1f5c372ae Merge pull request #166 from lumehq/feat/rust-nostr
Rust Nostr
2024-03-02 15:32:13 +07:00
13281455ed chore: refer old branch in readme 2024-03-02 15:31:41 +07:00
9cfb32be4d feat: update readme 2024-03-02 15:29:45 +07:00
cb0672c22a Merge branch 'main' into feat/rust-nostr 2024-03-02 15:22:11 +07:00
ca0e041731 feat: improve 2024-03-02 15:21:28 +07:00
cfcb9bc6ed feat: improve ui 2024-02-29 13:02:16 +07:00
09df8672d0 feat: add zap command 2024-02-27 15:37:49 +07:00
2403231ac4 feat: add user screen 2024-02-27 13:42:59 +07:00
98ef1927f2 feat: editor 2024-02-26 15:10:42 +07:00
63db8b1423 chore: clean up 2024-02-25 15:52:47 +07:00
2c8dd71792 feat: add simple tray icon 2024-02-25 09:06:23 +07:00
reya
fe73e1428b feat: upgrade tauri 2024-02-24 18:26:20 +07:00
88a6c3c81f feat: account switcher 2024-02-24 14:41:57 +07:00
84584a4d1f feat: add editor screen 2024-02-23 14:56:24 +07:00
64286aa354 wip 2024-02-22 14:01:29 +07:00
e9ce932646 chore: remove storage layer 2024-02-22 08:58:45 +07:00
Ren Amamiya
4c28b4879c chore: update readme 2024-02-21 15:04:30 +07:00
9127d5c5ea feat: add more metadata commands 2024-02-21 14:29:54 +07:00
090e54ec5a feat: add contact list commands 2024-02-20 14:46:21 +07:00
d0c9f93ebb wip: small updates 2024-02-20 10:26:19 +07:00
e2cdc5b576 feat: account selection screen 2024-02-19 14:13:33 +07:00
bfe35ad885 wip: multi accounts 2024-02-19 09:58:27 +07:00
0f06522c28 wip: refactor 2024-02-17 16:01:53 +07:00
f47eba5af7 feat: upgrade to rust nostr 0.28 2024-02-17 09:29:37 +07:00
f28a7ae82f wip: update design 2024-02-16 14:11:49 +07:00
296b11b7b8 wip: basic support multi windows 2024-02-16 09:58:07 +07:00
a0d9a729dd feat: add tauri devtools 2024-02-15 14:41:32 +07:00
1de8c7240d wip: desktop2 2024-02-15 14:32:40 +07:00
70126ef1b3 Merge branch 'main' into feat/rust-nostr 2024-02-15 12:48:20 +07:00
cdf29f8a54 wip: update 2024-02-15 12:47:15 +07:00
6171b9bed1 wip: tired... 2024-02-14 15:51:06 +07:00
60fd09000b wip: new design 2024-02-14 08:29:24 +07:00
4292def206 feat: add more nostr commands 2024-02-13 09:36:11 +07:00
90f149b09c chore: clean up 2024-02-13 08:57:24 +07:00
ed52105c02 wip: migrate to desktop2 2024-02-12 20:04:45 +07:00
1950cb59a2 wip: migrate to desktop2 2024-02-12 09:08:35 +07:00
Ren Amamiya
9753e6d6b4 Merge pull request #167 from anthony-robin/french-translations
Add french translations 🇫🇷
2024-02-12 07:55:46 +07:00
Anthony Robin
3488f05960 Add french translations 🇫🇷
This PR adds support for french translations into Lume.
2024-02-11 17:09:59 +01:00
c809ab6b4e feat: add desktop2 2024-02-11 09:30:18 +07:00
Ren Amamiya
c6674c4a2d Merge pull request #165 from higedamc/main
Update ja.json
2024-02-11 08:10:36 +07:00
35c5b5fb78 chore: fix build 2024-02-10 11:19:18 +07:00
739ba63e6c wip: migrate frontend to new backend 2024-02-09 15:33:27 +07:00
ec78cf8bf7 feat: migrate frontend to new backend 2024-02-08 21:24:08 +07:00
17052aeeaa feat: add new account management 2024-02-08 17:17:45 +07:00
d7bbda6e7b fix: remove native db 2024-02-07 14:26:58 +07:00
6a08c1de10 feat: add all nostr events command 2024-02-07 14:04:57 +07:00
3c4bd39384 feat: update rust nostr 2024-02-06 19:28:46 +07:00
a4069dae99 chore: clean up 2024-02-06 09:15:20 +07:00
HigeMan
55cd556cd6 Update ja.json 2024-02-06 08:45:17 +09:00
a21da11a91 feat: add desktop2 2024-02-05 14:18:27 +07:00
08fa7de01d feat: add check active account before init client 2024-02-05 13:36:10 +07:00
2a58326cd1 chore: update dependencies 2024-02-05 08:30:06 +07:00
8a17c36a5b feat: upgrade to tauri beta 2024-02-05 08:14:58 +07:00
7bd6f6a8db fix: remove surrealdb and clean up 2024-02-05 07:46:41 +07:00
3ba870be4b feat: add verify signer 2024-02-04 10:46:50 +07:00
bd2b6a3759 feat: add surreal db 2024-02-03 11:24:08 +07:00
a3a8f57bfc feat: add more nostr commands 2024-02-03 09:13:17 +07:00
fd393a4d30 feat: add basic nostr connection 2024-02-02 09:09:14 +07:00
e8bd48e51b feat: add rust nostr 2024-02-01 12:54:25 +07:00
0191180f31 chore: rename 2024-02-01 09:15:25 +07:00
60ed56b1b9 chore: bump version 2024-02-01 08:40:27 +07:00
Ren Amamiya
da722afed3 Merge pull request #154 from luminous-devs/next
v3.0.1
2024-02-01 08:36:21 +07:00
d8eb51e49c Revert "chore: update dependencies"
This reverts commit c700a45ab6.
2024-02-01 08:18:51 +07:00
c700a45ab6 chore: update dependencies 2024-02-01 07:59:25 +07:00
b806a34edb chore: remove submodule and clean up flatpak 2024-01-31 14:31:16 +07:00
21989e6fa5 fix: minor improves 2024-01-31 14:20:03 +07:00
0539c5649d feat: add waifu column and fix wrong package 2024-01-31 11:04:47 +07:00
ad488ff72d feat: add global column 2024-01-31 09:12:26 +07:00
02e0309a41 feat: refactor rust commands 2024-01-31 08:20:39 +07:00
b7f4af7883 fix: respect NIP spec 2024-01-31 07:29:47 +07:00
cc48a4f36b chore: update dependencies 2024-01-30 15:33:24 +07:00
46ed3330fc feat: add hover card to user 2024-01-30 14:45:46 +07:00
1fa1872ca6 feat: add trending notes column 2024-01-30 14:22:56 +07:00
Ren Amamiya
c389a23365 Merge pull request #153 from luminous-devs/feat/improve-updater
feat: Add new update bubble to navigation
2024-01-30 13:18:52 +07:00
eaf9bda077 feat: add new update notify to navigation 2024-01-30 13:16:39 +07:00
84a248a5a9 chore: fix typos 2024-01-30 10:01:45 +07:00
Ren Amamiya
711c1d561a Merge pull request #151 from luminous-devs/feat/multi-lang
Add support for multi-languages
2024-01-30 09:12:06 +07:00
21210b4336 feat: migrate activity screen to i18n 2024-01-30 09:10:26 +07:00
3bd480b75e feat: optimize locale loader 2024-01-30 08:55:49 +07:00
2b19650e46 feat: migrate onboarding to i18n 2024-01-30 08:13:12 +07:00
23482531c5 feat: migrate settings screen to i18n 2024-01-29 14:57:00 +07:00
cfda9ba899 feat: migrate ui components to i18n 2024-01-29 13:38:22 +07:00
698bd78684 feat: migrate note component to i18n 2024-01-29 10:22:55 +07:00
b97676dd3e feat: add translation to relay screen 2024-01-29 09:30:33 +07:00
25ae4f2201 feat: add multi-lang 2024-01-29 09:12:44 +07:00
Ren Amamiya
59435ccd13 Merge pull request #150 from jurraca/add-libappindicator
add libappindicator to runtime libs in nix shell
2024-01-28 17:35:40 +07:00
jurraca
e81912c5e9 add libappindicator to runtime libs in nix shell 2024-01-28 08:24:25 +00:00
af1b4e60d3 web: update download link 2024-01-28 08:45:04 +07:00
648cbf6f80 chore: fix ci 2024-01-28 08:05:36 +07:00
7b06a82ee7 chore: bump version and fix gh action 2024-01-28 07:40:08 +07:00
Ren Amamiya
d18de93c60 Merge pull request #148 from luminous-devs/feat/improve-perf
Improve overall performance
2024-01-27 20:01:55 +07:00
df15eb7a03 Merge branch 'main' into feat/improve-perf 2024-01-27 17:56:50 +07:00
Ren Amamiya
06674df6cc Merge pull request #137 from kogeletey/feat/package-flatpak
Package lume in flatpak
2024-01-27 17:34:47 +07:00
reya
8295625a44 feat: polish on windows 2024-01-27 15:53:19 +07:00
b11e2a4291 fix: update mention popup style 2024-01-27 11:16:17 +07:00
353c18bb76 feat: update ark provider 2024-01-27 09:08:41 +07:00
kogeletey
02b0c9e48a merge remote-tracking branch 'origin/main' into feat/package-flatpak 2024-01-26 19:49:27 +03:00
kogeletey
ff73c8ac88 feat: supporting hash of github actions cache 2024-01-26 19:37:12 +03:00
Ren Amamiya
bc48391a1a Merge pull request #147 from luminous-devs/feat/improve-design
Improve overal design
2024-01-26 14:21:27 +07:00
b0a443c002 feat: polish 2024-01-26 14:15:25 +07:00
bef1f136ad chore: bump version 2024-01-26 12:43:19 +07:00
9ba584bf14 feat: improve onboarding 2024-01-26 10:17:23 +07:00
kogeletey
43509fc943 feat: supported flatpak version v3 2024-01-25 15:00:08 +03:00
kogeletey
4a99eb94e2 feat: supported flatpak 2024-01-25 14:59:48 +03:00
74426e13c8 wip: improve onboarding 2024-01-25 15:25:40 +07:00
bd45c36072 feat: fix typos and other stuffs 2024-01-25 09:49:04 +07:00
c13aefcd15 feat: update dependencies 2024-01-25 08:35:17 +07:00
167caee8bc feat: improve ui contrast 2024-01-25 08:14:25 +07:00
d527078d5c feat: redesign column header 2024-01-24 15:08:55 +07:00
Ren Amamiya
763ace5ddf Merge pull request #145 from luminous-devs/feat/search
feat: add search based on NIP-50
2024-01-24 13:36:17 +07:00
057c57b70f feat: add empty state to search dialog 2024-01-24 13:34:27 +07:00
cb71786ac1 feat: add basic search dialog 2024-01-23 13:07:24 +07:00
Ren Amamiya
67afeac198 Merge pull request #143 from luminous-devs/apps/web
Add landing page
2024-01-22 14:41:24 +07:00
f4ee25de8e feat: add landing page 2024-01-22 14:40:39 +07:00
445a218a9e Merge branch 'main' into next 2024-01-21 09:54:47 +07:00
f09139ffbe final 2024-01-21 09:43:46 +07:00
446721729b feat: polish 2024-01-20 20:14:26 +07:00
reya
e0250d7f5c chore: fix some issues on windows 2024-01-20 10:15:27 +07:00
9fcdac4edb feat: add suggest screen 2024-01-20 14:51:13 +07:00
b726ae3c7c feat: add for you column 2024-01-20 09:06:00 +07:00
a3460418f6 feat: add interest screen to onboarding 2024-01-19 14:53:26 +07:00
f65175f11e feat: polish 2024-01-19 08:24:13 +07:00
16efd495a0 chore: clean up 2024-01-19 07:45:28 +07:00
ed6423e4aa feat: polish 2024-01-18 15:09:16 +07:00
0e9418949b feat: add instant zap 2024-01-18 13:56:35 +07:00
240fe8bc7c feat: fix relay manager 2024-01-18 09:41:53 +07:00
c3482cddd8 feat: improve zap 2024-01-18 08:22:35 +07:00
d13e7b3ef6 feat: polish 2024-01-18 07:37:40 +07:00
47800bd2ff chore: upgrade tauri 2024-01-17 13:07:01 +07:00
c0305db5fc chore: update and fix dependencies 2024-01-17 12:39:04 +07:00
0b745cb40e feat: add reply form 2024-01-17 12:24:04 +07:00
a20f5ca15d feat: polish 2024-01-17 09:30:32 +07:00
c29b4e173e feat: polish 2024-01-17 08:50:43 +07:00
33dd8b1d8a feat: better error handler 2024-01-16 19:56:07 +07:00
1503d90bd5 fix: tauri cache 2024-01-16 14:51:06 +07:00
6581ffb92b feat: polish 2024-01-16 14:49:00 +07:00
939dfd9cc1 feat: fix errros 2024-01-16 08:37:42 +07:00
7744a5e17c feat: update translation 2024-01-15 20:01:06 +07:00
3301af5cbb feat: move nwc to settings 2024-01-15 15:33:05 +07:00
3f1218e7bc feat: add tutorial 2024-01-15 14:06:11 +07:00
fbcb3ae6dc feat: add register translate api 2024-01-15 09:51:49 +07:00
e93aedb703 feat: update default column list 2024-01-15 08:36:23 +07:00
dae4b1d52b feat: redesign relay screen 2024-01-14 18:05:36 +07:00
f908c46a19 feat: polish 2024-01-14 09:39:56 +07:00
ab27bd5f44 feat: polish 2024-01-13 20:24:52 +07:00
72870bb131 feat: add activity screen 2024-01-13 17:12:44 +07:00
1822eac488 feat(ark): add user component 2024-01-13 08:21:49 +07:00
0487b8a801 feat: refactor 2024-01-12 20:32:45 +07:00
67c6177291 chore: update 2024-01-12 15:14:49 +07:00
ad6ae6745d chore: fixed circular import 2024-01-12 14:46:50 +07:00
a9d10ff93b feat: polish note component 2024-01-12 13:56:20 +07:00
e0d4c53098 feat: update onboarding flow 2024-01-12 10:12:06 +07:00
2c8571ecc7 wip: new activity sidebar 2024-01-11 21:00:42 +07:00
a8cd34d998 chore: small fixes 2024-01-11 07:56:28 +07:00
a5ad4fe05c feat: redesign navigation bar 2024-01-10 13:57:44 +07:00
f2504071cd feat: update login flow 2024-01-10 09:22:13 +07:00
73f90ebaf9 chore: minor fixes and updates 2024-01-09 08:35:30 +07:00
c172c0f80f feat: add onboarding modal 2024-01-08 20:18:07 +07:00
aa80301778 refactor: add event and user routes to default ui 2024-01-08 09:30:04 +07:00
c04ca3a1ab fix: migarate to virtua 0.2.0 2024-01-08 08:03:19 +07:00
3eae38e1cb chore: update dependencies 2024-01-07 20:47:48 +07:00
87099c6388 feat: update create account flow 2024-01-07 16:39:05 +07:00
7554a35c31 chore: update config 2024-01-07 07:44:37 +07:00
70707f69c8 feat: update create account screen 2024-01-07 07:42:08 +07:00
Ren Amamiya
a98ffd4887 Merge pull request #135 from fernandolguevara/feat-open-notes-edit-rail-title
feat(rail): edit title & open user notes
2024-01-07 07:39:18 +07:00
Fernando López Guevara
2e23b3ae06 feat(rail): edit title & open user notes 2024-01-06 11:38:34 -03:00
8e8e6fe244 chore: restructure 2024-01-05 07:31:08 +07:00
2726bfd595 feat: support nip 89 2024-01-04 15:10:52 +07:00
542b6033c2 refactor(note): only support kind 1 2024-01-04 12:35:21 +07:00
fcde669685 feat(editor): add hot key and update function 2024-01-04 10:44:00 +07:00
f4cbcee8b4 feat: add editor 2024-01-04 08:52:45 +07:00
ba13ac7535 chore: restructure packages 2024-01-03 11:12:36 +07:00
9f27d68533 chore: clean up 2024-01-03 11:03:56 +07:00
698f5a5d6d feat(columns): add default column 2024-01-02 12:28:48 +07:00
7856d6d49d feat(columns): add antenas column 2024-01-02 09:02:11 +07:00
a52fb3c437 feat(columns): add group column 2024-01-01 17:32:57 +07:00
499765c10a chore: update dependencies 2024-01-01 08:19:43 +07:00
56fab1dda6 feat: the last commit of year 2023-12-31 20:53:51 +07:00
b1d2496f8e feat(column): add hashtag column 2023-12-30 17:33:04 +07:00
ddbbcf41b5 chore: polish some components 2023-12-30 09:02:39 +07:00
55d6318614 refactor(ark): update note component 2023-12-29 14:14:39 +07:00
be333260f2 refactor(column): use context for manage column 2023-12-29 13:12:37 +07:00
e1edba8a78 chore: update dependencies 2023-12-29 08:26:13 +07:00
4fc3cc8a80 feat(column): add thread and user columns 2023-12-28 11:31:47 +07:00
893f3f7181 feat(icon): update navigation icons 2023-12-28 09:38:59 +07:00
4103b509d4 feat(parser): improve media parser 2023-12-28 08:44:55 +07:00
ed538c91c6 feat(columns): update timeline column 2023-12-27 15:01:40 +07:00
b4dac2d477 refactor(ark): add note provider 2023-12-27 10:52:13 +07:00
3956ed622d chore: fix build 2023-12-26 15:21:51 +07:00
e1db873bd5 refactor(ark): rename widget to column 2023-12-26 13:44:38 +07:00
227c2ddefa chore: monorepo 2023-12-25 14:28:39 +07:00
a6da07cd3f refactor: everything 2023-12-24 19:14:46 +07:00
9591d8626d chore: update dependencies 2023-12-23 09:05:52 +07:00
ee4e6b1ee6 chore: remove signal 2023-12-22 14:49:00 +07:00
a882ead649 chore: update icon 2023-12-22 14:10:23 +07:00
0522611669 feat(icon): update app and tray icons 2023-12-22 10:13:21 +07:00
2536630ff7 update dependencies 2023-12-22 07:52:24 +07:00
4670778181 feat(depot): update screens 2023-12-21 15:29:07 +07:00
a6ca2589ab feat(depot): update onboarding screen 2023-12-19 15:43:32 +07:00
d9e8d05db7 feat(ui): update ui consistent 2023-12-19 10:13:52 +07:00
ec2ac2dce3 feat(ark): add note component to ark 2023-12-19 08:06:10 +07:00
55298515af refactor(widget): migrate widget component to ark lib 2023-12-18 13:39:03 +07:00
344bdc0c66 feat(depot): add setting run depot at launch 2023-12-17 08:07:44 +07:00
ba88a4e0f2 feat(ark): update screen 2023-12-16 10:53:16 +07:00
17c64ee357 feat(ark): refactor 2023-12-16 07:47:00 +07:00
ba93bdbb91 feat(depot): initial work for depot 2023-12-15 09:15:30 +07:00
591373fd52 fix(layout): fix flickering on home layout 2023-12-14 14:30:49 +07:00
2fcc4dead1 feat(router): restructure 2023-12-14 13:22:03 +07:00
d9ab7893e0 feat: update format 2023-12-14 13:11:21 +07:00
Ren Amamiya
a93ebd3861 Merge pull request #132 from luminous-devs/feat/universal-titlebar
feat: add window titlebar
2023-12-11 08:57:31 +07:00
7c4ec71089 feat: add window titlebar 2023-12-11 08:56:00 +07:00
Ren Amamiya
e9d845cf25 Merge pull request #131 from luminous-devs/wip/cleanup
Clean up
2023-12-11 07:34:24 +07:00
8883be7ed6 upgrade to vite 5 2023-12-10 17:00:04 +07:00
132ea7f887 clean up 2023-12-10 16:53:07 +07:00
Ren Amamiya
f9402f5c4f Merge pull request #130 from luminous-devs/feat/ark
Introduction Ark
2023-12-10 11:19:04 +07:00
72a38e3aa7 polish 2023-12-10 08:39:40 +07:00
38e82a4feb prefetch data 2023-12-09 18:11:02 +07:00
6440680898 fix ark 2023-12-09 09:34:20 +07:00
e507187044 wip: fully migrate to ark 2023-12-08 12:39:15 +07:00
Ren Amamiya
feeb92b6ef Merge pull request #129 from vivganes/patch-2
Grammar touch-up + 1 more tip 🚀
2023-12-08 11:20:04 +07:00
Vivek Ganesan
2d4a77e8ed Grammar touch-up + 1 more tip 🚀 2023-12-08 08:47:54 +05:30
6f5ea1229d Merge branch 'main' into feat/ark 2023-12-08 09:43:35 +07:00
68886ad584 wip: migrate to ark 2023-12-08 09:32:48 +07:00
5f90bd0d22 update dependencies 2023-12-08 08:18:47 +07:00
8b434d577f fix nip-05 verification 2023-12-08 08:01:06 +07:00
f2b1458bd2 bump version & fix using nsecbunker with token 2023-12-07 18:49:55 +07:00
7507cd9ba1 wip: migrate to ark 2023-12-07 18:09:00 +07:00
0d43c13928 bump version 2023-12-07 11:55:49 +07:00
95124e5ded wip: ark 2023-12-07 11:50:25 +07:00
a42a2788ea fix nip-05 2023-12-06 10:15:41 +07:00
e30274dab3 fix typo 2023-12-06 09:07:32 +07:00
Ren Amamiya
740b7588bc Merge pull request #127 from luminous-devs/hotfix/2.2.1
v2.2.1
2023-12-06 08:09:44 +07:00
24c2ed4eb2 update 2023-12-06 08:08:11 +07:00
4006c0010e polish 2023-12-05 15:31:45 +07:00
7decf264d7 polish nsecbunker 2023-12-05 14:25:44 +07:00
482b218f74 improve error handling for useevent hook 2023-12-05 09:42:08 +07:00
e06b760e41 bump version 2023-12-04 14:14:50 +07:00
7efc35f622 replace media chrome with vidstack 2023-12-04 14:02:07 +07:00
8795923443 update 2023-12-04 13:36:16 +07:00
4093821fd0 clean up 2023-12-04 12:47:29 +07:00
b19637bdb7 remove misconfigure in react query 2023-12-04 12:17:16 +07:00
21e758ec13 update user component 2023-12-04 11:49:52 +07:00
48ab123850 improve relay screen 2023-12-04 09:26:19 +07:00
a401070031 update dependencies 2023-12-04 08:46:42 +07:00
396 changed files with 12227 additions and 31041 deletions

1
.envrc
View File

@@ -1 +0,0 @@
use flake

View File

@@ -1,3 +0,0 @@
/**/node_modules/*
node_modules/
dist/

View File

@@ -1,49 +0,0 @@
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 2020,
sourceType: 'module',
ecmaFeatures: {
jsx: true,
},
},
settings: {
react: {
version: 'detect',
},
'import/resolver': {
node: {
paths: ['src'],
extensions: ['.js', '.jsx', '.ts', '.tsx'],
},
},
},
env: {
browser: true,
amd: true,
node: true,
},
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react/recommended',
'plugin:jsx-a11y/recommended',
'prettier'
],
plugins: [],
rules: {
'react/react-in-jsx-scope': 'off',
'jsx-a11y/accessible-emoji': 'off',
'react/prop-types': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'jsx-a11y/anchor-is-valid': [
'error',
{
components: ['Link'],
specialLink: ['hrefLeft', 'hrefRight'],
aspects: ['invalidHref', 'preferButton'],
},
],
},
};

View File

@@ -1,70 +0,0 @@
name: 'Publish'
on: workflow_dispatch
env:
CARGO_INCREMENTAL: 0
RUST_BACKTRACE: short
RUSTFLAGS: '-W unreachable-pub -W rust-2021-compatibility'
jobs:
publish-tauri:
strategy:
fail-fast: false
matrix:
settings:
- platform: 'macos-latest'
args: '--target universal-apple-darwin'
- platform: 'ubuntu-22.04'
args: ''
- platform: 'windows-latest'
args: '--target x86_64-pc-windows-msvc'
runs-on: ${{ matrix.settings.platform }}
steps:
- uses: actions/checkout@v3
- name: setup node
uses: actions/setup-node@v3
with:
node-version: 18
- uses: dtolnay/rust-toolchain@stable
with:
targets: aarch64-apple-darwin
- name: install dependencies (ubuntu only)
if: matrix.settings.platform == 'ubuntu-22.04'
run: |
sudo apt-get update
sudo apt-get install -y build-essential libssl-dev javascriptcoregtk-4.1 libayatana-appindicator3-dev libsoup-3.0-dev libgtk-3-dev libwebkit2gtk-4.1-dev webkit2gtk-4.1 librsvg2-dev patchelf
- name: Install pnpm
uses: pnpm/action-setup@v2
with:
version: 8.x.x
run_install: false
- name: Setup node and cache for package data
uses: actions/setup-node@v3
with:
node-version: 'lts/*'
cache: 'pnpm'
cache-dependency-path: pnpm-lock.yaml
- uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true
- run: pnpm install
- uses: tauri-apps/tauri-action@dev
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
ENABLE_CODE_SIGNING: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
with:
tagName: v__VERSION__
releaseName: 'v__VERSION__'
releaseBody: 'See the assets to download this version and install.'
releaseDraft: true
prerelease: false
args: ${{ matrix.settings.args }}
includeDebug: true

48
.gitignore vendored
View File

@@ -1,33 +1,23 @@
# Logs # Generated by Cargo
logs # will have compiled files and executables
*.log debug/
npm-debug.log* target/
yarn-debug.log* dist/
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules # These are backup files generated by rustfmt
dist **/*.rs.bk
dist-ssr
out
*.local
.next
.vscode
*.db
*.db-journal
bun.lockb
.direnv # MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
# Editor directories and files # RustRover
.vscode/* # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
!.vscode/extensions.json # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
.idea # and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
.idea/
# Useless stuffs
.DS_Store .DS_Store
*.suo # Added by goreleaser init:
*.ntvs* .intentionally-empty-file.o
*.njsproj
*.sln
*.sw?
/.gtm/

View File

@@ -1,4 +0,0 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
pnpm exec lint-staged

View File

@@ -1,9 +0,0 @@
.tmp
.cache/
coverage/
.nyc_output/
**/.yarn/**
**/.pnp.*
/dist*/
node_modules/
src-tauri/

View File

@@ -1,22 +0,0 @@
{
"semi": true,
"trailingComma": "es5",
"singleQuote": true,
"tabWidth": 2,
"printWidth": 90,
"useTabs": false,
"bracketSpacing": true,
"bracketSameLine": false,
"importOrder": [
"^@app/(.*)$",
"^@libs/(.*)$",
"^@shared/(.*)$",
"^@stores/(.*)$",
"^@utils/(.*)$",
"^[./]"
],
"importOrderSeparation": true,
"importOrderSortSpecifiers": true,
"plugins": ["@trivago/prettier-plugin-sort-imports", "prettier-plugin-tailwindcss"],
"pluginSearchDirs": false
}

8846
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

45
Cargo.toml Normal file
View File

@@ -0,0 +1,45 @@
[workspace]
resolver = "2"
members = ["crates/*"]
default-members = ["crates/lume"]
[workspace.package]
version = "0.0.1"
edition = "2021"
publish = false
[workspace.dependencies]
# GPUI
gpui = { git = "https://github.com/zed-industries/zed" }
gpui-component = { git = "https://github.com/longbridge/gpui-component" }
gpui_tokio = { git = "https://github.com/zed-industries/zed" }
reqwest_client = { git = "https://github.com/zed-industries/zed" }
# Nostr
nostr-lmdb = { git = "https://github.com/rust-nostr/nostr" }
nostr-connect = { git = "https://github.com/rust-nostr/nostr" }
nostr-gossip-memory = { git = "https://github.com/rust-nostr/nostr" }
nostr-sdk = { git = "https://github.com/rust-nostr/nostr", features = [ "all-nips", "pow-multi-thread" ] }
# Others
anyhow = "1.0.44"
chrono = "0.4.38"
dirs = "5.0"
futures = "0.3"
itertools = "0.13.0"
log = "0.4"
reqwest = { version = "0.12", features = ["multipart", "stream", "json"] }
flume = { version = "0.12", default-features = false, features = ["async", "select"] }
rust-embed = "8.5.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
smallvec = "1.14.0"
smol = "2"
tracing = "0.1.40"
[profile.release]
strip = true
opt-level = "z"
lto = true
codegen-units = 1
panic = "abort"

328
LICENSE
View File

@@ -1,190 +1,190 @@
GNU GENERAL PUBLIC LICENSE GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007 Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/> Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed. of this license document, but changing it is not allowed.
Preamble Preamble
The GNU General Public License is a free, copyleft license for The GNU General Public License is a free, copyleft license for
software and other kinds of works. software and other kinds of works.
The licenses for most software and other practical works are designed The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast, to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to any other work released this way by its authors. You can apply it to
your programs, too. your programs, too.
When we speak of free software, we are referring to freedom, not When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things. free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others. you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they or can get the source code. And you must show them these terms so they
know their rights. know their rights.
Developers that use the GNU GPL protect your rights with two steps: Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License (1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it. giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to changed, so that their problems will not be attributed erroneously to
authors of previous versions. authors of previous versions.
Some devices are designed to deny users access to install or run Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users. of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents. Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free. patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and The precise terms and conditions for copying, distribution and
modification follow. modification follow.
TERMS AND CONDITIONS TERMS AND CONDITIONS
0. Definitions. 0. Definitions.
"This License" refers to version 3 of the GNU General Public License. "This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of "Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks. works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this "The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations. "recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work. earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based A "covered work" means either the unmodified Program or a work based
on the Program. on the Program.
To "propagate" a work means to do anything with it that, without To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying, computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the distribution (with or without modification), making available to the
public, and in some countries other activities as well. public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying. a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices" An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2) feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion. menu, a prominent item in the list meets this criterion.
1. Source Code. 1. Source Code.
The "source code" for a work means the preferred form of the work The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source for making modifications to it. "Object code" means any non-source
form of a work. form of a work.
A "Standard Interface" means an interface that either is an official A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that interfaces specified for a particular programming language, one that
is widely used among developers working in that language. is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component "Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system (kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to (if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it. produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require, linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those such as by intimate data communication or control flow between those
subprograms and other parts of the work. subprograms and other parts of the work.
The Corresponding Source need not include anything that users The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding can regenerate automatically from other parts of the Corresponding
Source. Source.
The Corresponding Source for a work in source code form is that The Corresponding Source for a work in source code form is that
same work. same work.
2. Basic Permissions. 2. Basic Permissions.
All rights granted under this License are granted for the term of All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law. rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you. your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10 the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary. makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law. 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or 11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such similar laws prohibiting or restricting circumvention of such
measures. measures.
When you convey a covered work, you waive any legal power to forbid When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or the covered work, and you disclaim any intention to limit operation or
@@ -192,9 +192,9 @@ modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of users, your or third parties' legal rights to forbid circumvention of
technological measures. technological measures.
4. Conveying Verbatim Copies. 4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice; appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any keep intact all notices stating that this License and any
@@ -202,12 +202,12 @@ non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program. recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey, You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee. and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions. 5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions: terms of section 4, provided that you also meet all of these conditions:
@@ -232,19 +232,19 @@ terms of section 4, provided that you also meet all of these conditions:
interfaces that do not display Appropriate Legal Notices, your interfaces that do not display Appropriate Legal Notices, your
work need not make them do so. work need not make them do so.
A compilation of a covered work with other separate and independent A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work, works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program, and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not "aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other in an aggregate does not cause this License to apply to the other
parts of the aggregate. parts of the aggregate.
6. Conveying Non-Source Forms. 6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License, machine-readable Corresponding Source under the terms of this License,
in one of these ways: in one of these ways:
@@ -290,75 +290,75 @@ in one of these ways:
Source of the work are being offered to the general public at no Source of the work are being offered to the general public at no
charge under subsection 6d. charge under subsection 6d.
A separable portion of the object code, whose source code is excluded A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be from the Corresponding Source as a System Library, need not be
included in conveying the object code work. included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family, tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product, into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product. the only significant mode of use of the product.
"Installation Information" for a User Product means any methods, "Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because code is in no case prevented or interfered with solely because
modification has been made. modification has been made.
If you convey an object code work under this section in, or with, or If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has modified object code on the User Product (for example, the work has
been installed in ROM). been installed in ROM).
The requirement to provide Installation Information does not include a The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and adversely affects the operation of the network or violates the rules and
protocols for communication across the network. protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided, Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in documented (and with an implementation available to the public in
source code form), and must require no special password or key for source code form), and must require no special password or key for
unpacking, reading or copying. unpacking, reading or copying.
7. Additional Terms. 7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this "Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions. License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions. this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work, additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission. for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms: that material) supplement the terms of this License with terms:
@@ -385,74 +385,74 @@ that material) supplement the terms of this License with terms:
any liability that these contractual assumptions directly impose on any liability that these contractual assumptions directly impose on
those licensors and authors. those licensors and authors.
All other non-permissive additional terms are considered "further All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does of that license document, provided that the further restriction does
not survive such relicensing or conveying. not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating additional terms that apply to those files, or a notice indicating
where to find the applicable terms. where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions; form of a separately written license, or stated as exceptions;
the above requirements apply either way. the above requirements apply either way.
8. Termination. 8. Termination.
You may not propagate or modify a covered work except as expressly You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third this License (including any patent licenses granted under the third
paragraph of section 11). paragraph of section 11).
However, if you cease all violation of this License, then your However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a) license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation. prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice. your receipt of the notice.
Termination of your rights under this section does not terminate the Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same reinstated, you do not qualify to receive new licenses for the same
material under section 10. material under section 10.
9. Acceptance Not Required for Having Copies. 9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However, to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so. covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients. 10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License. for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could licenses to the work the party's predecessor in interest had or could
@@ -460,43 +460,43 @@ give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts. the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that (including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it. sale, or importing the Program or any portion of it.
11. Patents. 11. Patents.
A "contributor" is a copyright holder who authorizes use under this A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version". work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version, by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of patent sublicenses in a manner consistent with the requirements of
this License. this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version. propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to (such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a party means to make such an agreement or commitment not to enforce a
patent against the party. patent against the party.
If you convey a covered work, knowingly relying on a patent license, If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means, publicly available network server or other readily accessible means,
@@ -504,13 +504,13 @@ then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid. country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify receiving the covered work authorizing them to use, propagate, modify
@@ -518,10 +518,10 @@ or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered you grant is automatically extended to all recipients of the covered
work and works based on it. work and works based on it.
A patent license is "discriminatory" if it does not include within A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying to the third party based on the extent of your activity of conveying
@@ -533,73 +533,73 @@ for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement, contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007. or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law. otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom. 12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program. License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License. 13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work, License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License, but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the section 13, concerning interaction through a network will apply to the
combination as such. combination as such.
14. Revised Versions of this License. 14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to be similar in spirit to the present version, but may differ in detail to
address new problems or concerns. address new problems or concerns.
Each version is given a distinguishing version number. If the Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published GNU General Public License, you may choose any version ever published
by the Free Software Foundation. by the Free Software Foundation.
If the Program specifies that a proxy can decide which future If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you public statement of acceptance of a version permanently authorizes you
to choose that version for the Program. to choose that version for the Program.
Later license versions may give you additional or different Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a author or copyright holder as a result of your choosing to follow a
later version. later version.
15. Disclaimer of Warranty. 15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION. ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability. 16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
@@ -609,9 +609,9 @@ PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES. SUCH DAMAGES.
17. Interpretation of Sections 15 and 16. 17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms, above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the an absolute waiver of all civil liability in connection with the
@@ -622,11 +622,11 @@ copy of the Program in return for a fee.
How to Apply These Terms to Your New Programs How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms. free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found. the "copyright" line and a pointer to where the full notice is found.
@@ -649,7 +649,7 @@ the "copyright" line and a pointer to where the full notice is found.
Also add information on how to contact you by electronic and paper mail. Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode: notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author> <program> Copyright (C) <year> <name of author>
@@ -658,17 +658,17 @@ notice like this when it starts in an interactive mode:
under certain conditions; type `show c' for details. under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box". might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school, You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary. if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>. <https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>. <https://www.gnu.org/licenses/why-not-lgpl.html>.

View File

@@ -1,50 +1 @@
### Introduction ### Rebooting...
Lume is a nostr client
### Usage
Download Lume for your platform here: [https://github.com/luminous-devs/lume/releases](https://github.com/luminous-devs/lume/releases)
Supported platform: macOS, Windows and Linux
### Prerequisites
- PNPM or Bun (experiment)
- Tauri: https://tauri.app/v1/guides/getting-started/prerequisites#setting-up-macos
### Develop
Clone project
```
git clone https://github.com/luminous-devs/lume.git && cd lume
```
Install packages
```
pnpm install
```
Run dev build
```
pnpm tauri dev
```
Generate production build
```
pnpm tauri build
```
#### Nix
Requirements:
1. [Install Nix](https://zero-to-flakes.com/install)
1. [Setup `direnv`](https://zero-to-flakes.com/direnv)
`cd` into the root folder of the project to enter `nix develop` shell. Run `direnv allow` (only once). Then run `pnpm` or `bun` (experimental) commands as described above.

0
assets/.keep Normal file
View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,92 @@
Copyright © 2017 IBM Corp. with Reserved Font Name "Plex"
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,92 @@
Copyright © 2017 IBM Corp. with Reserved Font Name "Plex"
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

View File

@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg width="24px" height="24px" viewBox="0 0 24 24" stroke-width="1.5" fill="none" xmlns="http://www.w3.org/2000/svg" color="#000000"><path d="M3 12L21 12M21 12L12.5 3.5M21 12L12.5 20.5" stroke="#000000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg>

After

Width:  |  Height:  |  Size: 321 B

1
assets/icons/check.svg Normal file
View File

@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg width="24px" height="24px" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" color="#000000"><path d="M5 13L9 17L19 7" stroke="#000000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg>

After

Width:  |  Height:  |  Size: 294 B

View File

@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg width="24px" height="24px" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" color="#000000"><path d="M9 6L15 12L9 18" stroke="#000000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg>

After

Width:  |  Height:  |  Size: 294 B

View File

@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg width="24px" height="24px" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" color="#000000"><path d="M9.17218 14.8284L12.0006 12M14.829 9.17157L12.0006 12M12.0006 12L9.17218 9.17157M12.0006 12L14.829 14.8284" stroke="#000000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><path d="M12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22Z" stroke="#000000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg>

After

Width:  |  Height:  |  Size: 599 B

1
assets/icons/close.svg Normal file
View File

@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg width="24px" height="24px" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" color="#000000"><path d="M6.75827 17.2426L12.0009 12M17.2435 6.75736L12.0009 12M12.0009 12L6.75827 6.75736M12.0009 12L17.2435 17.2426" stroke="#000000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg>

After

Width:  |  Height:  |  Size: 387 B

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
<path fill="currentColor" d="M12 13a1 1 0 1 0 0-2 1 1 0 0 0 0 2Zm8.25 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2Zm-16.5 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z"/>
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M12 13a1 1 0 1 0 0-2 1 1 0 0 0 0 2Zm8.25 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2Zm-16.5 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z"/>
</svg>

After

Width:  |  Height:  |  Size: 457 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M3.75 5.75a2 2 0 0 1 2-2h12.5a2 2 0 0 1 2 2v12.5a2 2 0 0 1-2 2H5.75a2 2 0 0 1-2-2V5.75Zm5-1.75v16"/>
</svg>

After

Width:  |  Height:  |  Size: 303 B

19
crates/account/Cargo.toml Normal file
View File

@@ -0,0 +1,19 @@
[package]
name = "account"
version.workspace = true
edition.workspace = true
publish.workspace = true
[dependencies]
common = { path = "../common" }
state = { path = "../state" }
gpui.workspace = true
nostr-sdk.workspace = true
anyhow.workspace = true
smallvec.workspace = true
smol.workspace = true
log.workspace = true
serde.workspace = true
serde_json.workspace = true

131
crates/account/src/lib.rs Normal file
View File

@@ -0,0 +1,131 @@
use anyhow::Error;
use gpui::{App, AppContext, Context, Entity, Global, Subscription, Task};
use nostr_sdk::prelude::*;
use smallvec::{smallvec, SmallVec};
use state::client;
pub fn init(cx: &mut App) {
Account::set_global(cx.new(Account::new), cx);
}
struct GlobalAccount(Entity<Account>);
impl Global for GlobalAccount {}
pub struct Account {
/// Public Key of the account
public_key: Option<PublicKey>,
/// Event subscriptions
_subscriptions: SmallVec<[Subscription; 1]>,
/// Tasks for asynchronous operations
_tasks: SmallVec<[Task<Result<(), Error>>; 1]>,
}
impl Account {
/// Retrieve the global account state
pub fn global(cx: &App) -> Entity<Self> {
cx.global::<GlobalAccount>().0.clone()
}
/// Check if the global account state exists
pub fn has_global(cx: &App) -> bool {
cx.has_global::<GlobalAccount>()
}
/// Remove the global account state
pub fn remove_global(cx: &mut App) {
cx.remove_global::<GlobalAccount>();
}
/// Set the global account instance
fn set_global(state: Entity<Self>, cx: &mut App) {
cx.set_global(GlobalAccount(state));
}
/// Create a new account instance
fn new(cx: &mut Context<Self>) -> Self {
let mut subscriptions = smallvec![];
subscriptions.push(
// Listen for public key set
cx.observe_self(move |this, cx| {
if let Some(public_key) = this.public_key {
this.init(public_key, cx);
}
}),
);
Self {
public_key: None,
_subscriptions: subscriptions,
_tasks: smallvec![],
}
}
fn init(&mut self, public_key: PublicKey, cx: &mut Context<Self>) {
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
let client = client();
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
// Construct a filter to get the user's metadata
let filter = Filter::new()
.kind(Kind::Metadata)
.author(public_key)
.limit(1);
// Subscribe to the user metadata
client.subscribe(filter, Some(opts)).await?;
// Construct a filter to get the user's contact list
let filter = Filter::new()
.kind(Kind::ContactList)
.author(public_key)
.limit(1);
// Subscribe to the user's contact list
client.subscribe(filter, Some(opts)).await?;
// Construct a filter to get the user's other metadata
let filter = Filter::new()
.kinds(vec![
Kind::MuteList,
Kind::Bookmarks,
Kind::BookmarkSet,
Kind::SearchRelays,
Kind::BlockedRelays,
Kind::RelaySet,
Kind::Custom(10012),
])
.author(public_key)
.limit(24);
// Subscribe to the user's other metadata
client.subscribe(filter, Some(opts)).await?;
log::info!("Subscribed to user metadata");
Ok(())
});
self._tasks.push(task);
}
/// Set the public key of the account
pub fn set_public_key(&mut self, public_key: PublicKey, cx: &mut Context<Self>) {
self.public_key = Some(public_key);
cx.notify();
}
/// Check if the account entity has a public key
pub fn has_account(&self) -> bool {
self.public_key.is_some()
}
/// Get the public key of the account
pub fn public_key(&self) -> PublicKey {
// This method is only called when user is logged in, so unwrap safely
self.public_key.unwrap()
}
}

11
crates/assets/Cargo.toml Normal file
View File

@@ -0,0 +1,11 @@
[package]
name = "assets"
version.workspace = true
edition.workspace = true
publish.workspace = true
[dependencies]
gpui.workspace = true
anyhow.workspace = true
log.workspace = true
rust-embed.workspace = true

59
crates/assets/src/lib.rs Normal file
View File

@@ -0,0 +1,59 @@
use anyhow::Context;
use gpui::{App, AssetSource, Result, SharedString};
use rust_embed::RustEmbed;
#[derive(RustEmbed)]
#[folder = "../../assets"]
#[include = "fonts/**/*"]
#[include = "brand/**/*"]
#[include = "icons/**/*"]
#[exclude = "*.DS_Store"]
pub struct Assets;
impl AssetSource for Assets {
fn load(&self, path: &str) -> Result<Option<std::borrow::Cow<'static, [u8]>>> {
Self::get(path)
.map(|f| Some(f.data))
.with_context(|| format!("loading asset at path {path:?}"))
}
fn list(&self, path: &str) -> Result<Vec<SharedString>> {
Ok(Self::iter()
.filter_map(|p| {
if p.starts_with(path) {
Some(p.into())
} else {
None
}
})
.collect())
}
}
impl Assets {
/// Populate the [`TextSystem`] of the given [`AppContext`] with all `.ttf` fonts in the `fonts` directory.
pub fn load_fonts(&self, cx: &App) -> anyhow::Result<()> {
let font_paths = self.list("fonts")?;
let mut embedded_fonts = Vec::new();
for font_path in font_paths {
if font_path.ends_with(".ttf") {
let font_bytes = cx
.asset_source()
.load(&font_path)?
.expect("Assets should never return None");
embedded_fonts.push(font_bytes);
}
}
cx.text_system().add_fonts(embedded_fonts)
}
pub fn load_test_fonts(&self, cx: &App) {
cx.text_system()
.add_fonts(vec![self
.load("fonts/plex-mono/ZedPlexMono-Regular.ttf")
.unwrap()
.unwrap()])
.unwrap()
}
}

21
crates/common/Cargo.toml Normal file
View File

@@ -0,0 +1,21 @@
[package]
name = "common"
version.workspace = true
edition.workspace = true
publish.workspace = true
[dependencies]
gpui.workspace = true
nostr-sdk.workspace = true
anyhow.workspace = true
itertools.workspace = true
chrono.workspace = true
smallvec.workspace = true
smol.workspace = true
futures.workspace = true
reqwest.workspace = true
log.workspace = true
dirs = "5.0"
nostr = { git = "https://github.com/rust-nostr/nostr" }

View File

@@ -0,0 +1,23 @@
/// Client (or application) name.
pub const CLIENT_NAME: &str = "Lume";
/// Application ID.
pub const APP_ID: &str = "su.reya.lume";
/// Bootstrap Relays.
pub const BOOTSTRAP_RELAYS: [&str; 5] = [
"wss://relay.damus.io",
"wss://relay.primal.net",
"wss://relay.nos.social",
"wss://user.kindpag.es",
"wss://purplepag.es",
];
/// Default relay for Nostr Connect
pub const NOSTR_CONNECT_RELAY: &str = "wss://relay.nsec.app";
/// Default timeout for Nostr Connect (seconds)
pub const NOSTR_CONNECT_TIMEOUT: u64 = 30;
/// Default width of the sidebar.
pub const DEFAULT_SIDEBAR_WIDTH: f32 = 240.;

7
crates/common/src/lib.rs Normal file
View File

@@ -0,0 +1,7 @@
pub use constants::*;
pub use paths::*;
pub use utils::*;
mod constants;
mod paths;
mod utils;

View File

@@ -0,0 +1,64 @@
use std::path::PathBuf;
use std::sync::OnceLock;
/// Returns the path to the user's home directory.
pub fn home_dir() -> &'static PathBuf {
static HOME_DIR: OnceLock<PathBuf> = OnceLock::new();
HOME_DIR.get_or_init(|| dirs::home_dir().expect("failed to determine home directory"))
}
/// Returns the path to the configuration directory used by Lume.
pub fn config_dir() -> &'static PathBuf {
static CONFIG_DIR: OnceLock<PathBuf> = OnceLock::new();
CONFIG_DIR.get_or_init(|| {
if cfg!(target_os = "windows") {
return dirs::config_dir()
.expect("failed to determine RoamingAppData directory")
.join("Lume");
}
if cfg!(any(target_os = "linux", target_os = "freebsd")) {
return if let Ok(flatpak_xdg_config) = std::env::var("FLATPAK_XDG_CONFIG_HOME") {
flatpak_xdg_config.into()
} else {
dirs::config_dir().expect("failed to determine XDG_CONFIG_HOME directory")
}
.join("lume");
}
home_dir().join(".config").join("lume")
})
}
/// Returns the path to the support directory used by Lume.
pub fn support_dir() -> &'static PathBuf {
static SUPPORT_DIR: OnceLock<PathBuf> = OnceLock::new();
SUPPORT_DIR.get_or_init(|| {
if cfg!(target_os = "macos") {
return home_dir().join("Library/Application Support/Lume");
}
if cfg!(any(target_os = "linux", target_os = "freebsd")) {
return if let Ok(flatpak_xdg_data) = std::env::var("FLATPAK_XDG_DATA_HOME") {
flatpak_xdg_data.into()
} else {
dirs::data_local_dir().expect("failed to determine XDG_DATA_HOME directory")
}
.join("lume");
}
if cfg!(target_os = "windows") {
return dirs::data_local_dir()
.expect("failed to determine LocalAppData directory")
.join("lume");
}
config_dir().clone()
})
}
/// Returns the path to the `nostr` file.
pub fn nostr_file() -> &'static PathBuf {
static NOSTR_FILE: OnceLock<PathBuf> = OnceLock::new();
NOSTR_FILE.get_or_init(|| support_dir().join("nostr-db"))
}

View File

@@ -0,0 +1,11 @@
use nostr_sdk::prelude::*;
pub fn shorten_pubkey(public_key: PublicKey, len: usize) -> String {
let Ok(pubkey) = public_key.to_bech32();
format!(
"{}:{}",
&pubkey[0..(len + 1)],
&pubkey[pubkey.len() - len..]
)
}

38
crates/lume/Cargo.toml Normal file
View File

@@ -0,0 +1,38 @@
[package]
name = "lume"
version.workspace = true
edition.workspace = true
publish.workspace = true
[[bin]]
name = "lume"
path = "src/main.rs"
[dependencies]
common = { path = "../common" }
assets = { path = "../assets" }
state = { path = "../state" }
account = { path = "../account" }
person = { path = "../person" }
gpui.workspace = true
gpui-component.workspace = true
gpui_tokio.workspace = true
reqwest_client.workspace = true
nostr-connect.workspace = true
nostr-sdk.workspace = true
anyhow.workspace = true
serde.workspace = true
serde_json.workspace = true
itertools.workspace = true
log.workspace = true
smallvec.workspace = true
smol.workspace = true
futures.workspace = true
flume.workspace = true
tracing-subscriber = { version = "0.3.18", features = ["fmt"] }
qrcode = "0.14.1"
webbrowser = "1.0.6"

View File

@@ -0,0 +1,44 @@
use std::sync::Mutex;
use gpui::{actions, App};
use nostr_connect::prelude::*;
actions!(lume, [Quit, About, Open]);
#[derive(Debug, Clone)]
pub struct LumeAuthUrlHandler;
impl AuthUrlHandler for LumeAuthUrlHandler {
#[allow(mismatched_lifetime_syntaxes)]
fn on_auth_url(&self, auth_url: Url) -> BoxedFuture<Result<()>> {
Box::pin(async move {
log::info!("Received Auth URL: {auth_url}");
webbrowser::open(auth_url.as_str())?;
Ok(())
})
}
}
pub fn load_embedded_fonts(cx: &App) {
let asset_source = cx.asset_source();
let font_paths = asset_source.list("fonts").unwrap();
let embedded_fonts = Mutex::new(Vec::new());
let executor = cx.background_executor();
executor.block(executor.scoped(|scope| {
for font_path in &font_paths {
if !font_path.ends_with(".ttf") {
continue;
}
scope.spawn(async {
let font_bytes = asset_source.load(font_path).unwrap().unwrap();
embedded_fonts.lock().unwrap().push(font_bytes);
});
}
}));
cx.text_system()
.add_fonts(embedded_fonts.into_inner().unwrap())
.unwrap();
}

96
crates/lume/src/main.rs Normal file
View File

@@ -0,0 +1,96 @@
use std::sync::Arc;
use assets::Assets;
use common::{APP_ID, BOOTSTRAP_RELAYS, CLIENT_NAME};
use gpui::{
point, px, size, AppContext, Application, Bounds, SharedString, TitlebarOptions,
WindowBackgroundAppearance, WindowBounds, WindowDecorations, WindowKind, WindowOptions,
};
use gpui_component::Root;
use state::client;
use crate::actions::load_embedded_fonts;
use crate::workspace::Workspace;
mod actions;
mod menus;
mod panels;
mod sidebar;
mod themes;
mod title_bar;
mod workspace;
fn main() {
// Initialize logging
tracing_subscriber::fmt::init();
// Initialize the Application
let app = Application::new()
.with_assets(Assets)
.with_http_client(Arc::new(reqwest_client::ReqwestClient::new()));
// Initialize the nostr client
let client = client();
// Establish connection to all bootstrap relays
app.background_executor()
.spawn_with_priority(gpui::Priority::High, async move {
// Add bootstrap relays to the relay pool
for url in BOOTSTRAP_RELAYS {
client.add_relay(url).await.ok();
}
// Connect to all added relays
client.connect().await;
})
.detach();
// Run application
app.run(move |cx| {
// Load embedded fonts in assets/fonts
load_embedded_fonts(cx);
// Set up the window bounds
let bounds = Bounds::centered(None, size(px(920.0), px(700.0)), cx);
// Set up the window options
let opts = WindowOptions {
window_background: WindowBackgroundAppearance::Opaque,
window_decorations: Some(WindowDecorations::Client),
window_bounds: Some(WindowBounds::Windowed(bounds)),
kind: WindowKind::Normal,
app_id: Some(APP_ID.to_owned()),
titlebar: Some(TitlebarOptions {
title: Some(SharedString::new_static(CLIENT_NAME)),
traffic_light_position: Some(point(px(9.0), px(9.0))),
appears_transparent: true,
}),
..Default::default()
};
// Open a window with default options
cx.open_window(opts, |window, cx| {
// Bring the app to the foreground
cx.activate(true);
// Initialize the tokio runtime
gpui_tokio::init(cx);
// Initialize components
gpui_component::init(cx);
// Initialize themes
themes::init(cx);
// Initialize account
account::init(cx);
// Initialize person registry
person::init(cx);
let workspace = cx.new(|cx| Workspace::new(window, cx));
cx.new(|cx| Root::new(workspace, window, cx))
})
.expect("Failed to open window. Please restart the application.");
})
}

104
crates/lume/src/menus.rs Normal file
View File

@@ -0,0 +1,104 @@
use gpui::{App, Entity, Menu, MenuItem, SharedString};
use gpui_component::menu::AppMenuBar;
use gpui_component::{ActiveTheme as _, Theme, ThemeMode, ThemeRegistry};
use crate::actions::{About, Open, Quit};
use crate::themes::{SwitchTheme, SwitchThemeMode};
pub fn init(title: impl Into<SharedString>, cx: &mut App) -> Entity<AppMenuBar> {
let app_menu_bar = AppMenuBar::new(cx);
let title: SharedString = title.into();
update_app_menu(title.clone(), app_menu_bar.clone(), cx);
// Observe theme changes to update the menu to refresh the checked state
cx.observe_global::<Theme>({
let title = title.clone();
let app_menu_bar = app_menu_bar.clone();
move |cx| {
update_app_menu(title.clone(), app_menu_bar.clone(), cx);
}
})
.detach();
app_menu_bar
}
fn update_app_menu(title: impl Into<SharedString>, app_menu_bar: Entity<AppMenuBar>, cx: &mut App) {
let mode = cx.theme().mode;
cx.set_menus(vec![
Menu {
name: title.into(),
items: vec![
MenuItem::action("About", About),
MenuItem::Separator,
MenuItem::action("Open...", Open),
MenuItem::Separator,
MenuItem::Submenu(Menu {
name: "Appearance".into(),
items: vec![
MenuItem::action("Light", SwitchThemeMode(ThemeMode::Light))
.checked(!mode.is_dark()),
MenuItem::action("Dark", SwitchThemeMode(ThemeMode::Dark))
.checked(mode.is_dark()),
],
}),
theme_menu(cx),
MenuItem::Separator,
MenuItem::action("Quit", Quit),
],
},
Menu {
name: "Edit".into(),
items: vec![
MenuItem::action("Undo", gpui_component::input::Undo),
MenuItem::action("Redo", gpui_component::input::Redo),
MenuItem::separator(),
MenuItem::action("Cut", gpui_component::input::Cut),
MenuItem::action("Copy", gpui_component::input::Copy),
MenuItem::action("Paste", gpui_component::input::Paste),
MenuItem::separator(),
MenuItem::action("Delete", gpui_component::input::Delete),
MenuItem::action(
"Delete Previous Word",
gpui_component::input::DeleteToPreviousWordStart,
),
MenuItem::action(
"Delete Next Word",
gpui_component::input::DeleteToNextWordEnd,
),
MenuItem::separator(),
MenuItem::action("Find", gpui_component::input::Search),
MenuItem::separator(),
MenuItem::action("Select All", gpui_component::input::SelectAll),
],
},
Menu {
name: "Help".into(),
items: vec![MenuItem::action("Open Website", Open)],
},
]);
app_menu_bar.update(cx, |menu_bar, cx| {
menu_bar.reload(cx);
})
}
fn theme_menu(cx: &App) -> MenuItem {
let themes = ThemeRegistry::global(cx).sorted_themes();
let current_name = cx.theme().theme_name();
MenuItem::Submenu(Menu {
name: "Theme".into(),
items: themes
.iter()
.map(|theme| {
let checked = current_name == &theme.name;
MenuItem::action(theme.name.clone(), SwitchTheme(theme.name.clone()))
.checked(checked)
})
.collect(),
})
}

View File

@@ -0,0 +1,180 @@
use std::time::Duration;
use anyhow::Error;
use gpui::prelude::FluentBuilder;
use gpui::{
div, img, App, AppContext, Context, Entity, EventEmitter, FocusHandle, Focusable, IntoElement,
ParentElement, Render, SharedString, Styled, Task, Window,
};
use gpui_component::dock::{Panel, PanelEvent};
use gpui_component::{h_flex, v_flex, ActiveTheme};
use nostr_sdk::prelude::*;
use person::PersonRegistry;
use smallvec::{smallvec, SmallVec};
use state::client;
/// Feed
pub struct Feed {
focus_handle: FocusHandle,
/// All notes that match the query
notes: Entity<Option<Events>>,
/// Public Key
public_key: Option<PublicKey>,
/// Relay Url
relay_url: Option<RelayUrl>,
/// Async operations
_tasks: SmallVec<[Task<Result<(), Error>>; 1]>,
}
impl Feed {
pub fn new(
public_key: Option<PublicKey>,
relay_url: Option<RelayUrl>,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
let notes = cx.new(|_| None);
let async_url = relay_url.clone();
let mut tasks = smallvec![];
tasks.push(
// Load newsfeed in the background
cx.spawn_in(window, async move |this, cx| {
let task: Task<Result<Events, Error>> = cx.background_spawn(async move {
let client = client();
let mut filter = Filter::new()
.kinds(vec![Kind::TextNote, Kind::Repost])
.limit(20);
if let Some(author) = public_key {
filter = filter.author(author);
};
let events = match async_url {
Some(url) => {
client
.fetch_events_from(vec![url], filter, Duration::from_secs(5))
.await?
}
None => client.fetch_events(filter, Duration::from_secs(5)).await?,
};
Ok(events)
});
if let Ok(events) = task.await {
this.update(cx, |this, cx| {
this.notes.update(cx, |this, cx| {
*this = Some(events);
cx.notify();
});
})
.ok();
}
Ok(())
}),
);
Self {
focus_handle: cx.focus_handle(),
notes,
public_key,
relay_url,
_tasks: tasks,
}
}
}
impl Panel for Feed {
fn panel_name(&self) -> &'static str {
"Feed"
}
fn title(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
div()
.when_some(self.public_key.as_ref(), |this, public_key| {
let person = PersonRegistry::global(cx);
let profile = person.read(cx).get(public_key, cx);
this.child(
h_flex()
.gap_1()
.when_some(profile.metadata().picture.as_ref(), |this, url| {
this.child(img(SharedString::from(url)).size_4().rounded_full())
})
.child(SharedString::from(profile.name())),
)
})
.when_some(self.relay_url.as_ref(), |this, url| {
this.child(SharedString::from(url.to_string()))
})
}
}
impl EventEmitter<PanelEvent> for Feed {}
impl Focusable for Feed {
fn focus_handle(&self, _cx: &App) -> FocusHandle {
self.focus_handle.clone()
}
}
impl Render for Feed {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let person = PersonRegistry::global(cx);
v_flex()
.p_2()
.gap_3()
.when_some(self.notes.read(cx).as_ref(), |this, notes| {
this.children({
let mut items = Vec::with_capacity(notes.len());
for note in notes.iter() {
let profile = person.read(cx).get(&note.pubkey, cx);
items.push(
v_flex()
.w_full()
.gap_2()
.child(
h_flex()
.w_full()
.items_center()
.justify_between()
.text_sm()
.text_color(cx.theme().muted_foreground)
.child(
h_flex()
.gap_1()
.when_some(
profile.metadata().picture.as_ref(),
|this, url| {
this.child(
img(SharedString::from(url))
.size_6()
.rounded_full(),
)
},
)
.child(SharedString::from(profile.name())),
)
.child(SharedString::from(
note.created_at.to_human_datetime(),
)),
)
.child(SharedString::from(note.content.clone())),
);
}
items
})
})
}
}

View File

@@ -0,0 +1,384 @@
use std::time::Duration;
use account::Account;
use anyhow::{anyhow, Error};
use common::NOSTR_CONNECT_TIMEOUT;
use gpui::prelude::FluentBuilder;
use gpui::{
div, relative, App, AppContext, Context, Entity, EventEmitter, FocusHandle, Focusable,
IntoElement, ParentElement, Render, SharedString, Styled, Subscription, Window,
};
use gpui_component::button::{Button, ButtonVariants};
use gpui_component::dock::{Panel, PanelEvent};
use gpui_component::input::{Input, InputEvent, InputState};
use gpui_component::notification::Notification;
use gpui_component::{v_flex, ActiveTheme, Disableable, StyledExt, WindowExt};
use nostr_connect::prelude::*;
use smallvec::{smallvec, SmallVec};
use state::client;
use crate::actions::LumeAuthUrlHandler;
pub fn init(window: &mut Window, cx: &mut App) -> Entity<Login> {
cx.new(|cx| Login::new(window, cx))
}
pub struct Login {
focus_handle: FocusHandle,
/// Input for nsec for bunker uri
key_input: Entity<InputState>,
/// Input for decryption password when available
pass_input: Entity<InputState>,
/// Error message
error: Entity<Option<SharedString>>,
/// Timeout countdown
countdown: Entity<Option<u64>>,
/// Whether password is required
require_password: bool,
/// Whether logging in is in progress
logging_in: bool,
/// Event subscriptions
_subscriptions: SmallVec<[Subscription; 1]>,
}
impl Login {
fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
let key_input = cx.new(|cx| InputState::new(window, cx));
let pass_input = cx.new(|cx| InputState::new(window, cx).masked(true));
let error = cx.new(|_| None);
let countdown = cx.new(|_| None);
let mut subscriptions = smallvec![];
subscriptions.push(
// Subscribe to key input events and process login when the user presses enter
cx.subscribe_in(&key_input, window, |this, input, event, window, cx| {
match event {
InputEvent::PressEnter { .. } => {
this.login(window, cx);
}
InputEvent::Change => {
if input.read(cx).value().starts_with("ncryptsec1") {
this.require_password = true;
cx.notify();
}
}
_ => {}
};
}),
);
Self {
focus_handle: cx.focus_handle(),
key_input,
pass_input,
error,
countdown,
logging_in: false,
require_password: false,
_subscriptions: subscriptions,
}
}
fn login(&mut self, window: &mut Window, cx: &mut Context<Self>) {
if self.logging_in {
return;
};
// Prevent duplicate login requests
self.set_logging_in(true, cx);
let value = self.key_input.read(cx).value();
let password = self.pass_input.read(cx).value();
if value.starts_with("bunker://") {
self.login_with_bunker(&value, window, cx);
} else if value.starts_with("ncryptsec1") {
self.login_with_password(&value, &password, cx);
} else if value.starts_with("nsec1") {
if let Ok(secret) = SecretKey::parse(&value) {
let keys = Keys::new(secret);
self.login_with_keys(keys, cx);
} else {
self.set_error("Invalid", cx);
}
} else {
self.set_error("Invalid", cx);
}
}
fn login_with_bunker(&mut self, content: &str, window: &mut Window, cx: &mut Context<Self>) {
let Ok(uri) = NostrConnectUri::parse(content) else {
self.set_error("Bunker URI is not valid", cx);
return;
};
let app_keys = Keys::generate();
let timeout = Duration::from_secs(NOSTR_CONNECT_TIMEOUT);
let mut signer = NostrConnect::new(uri, app_keys.clone(), timeout, None).unwrap();
// Handle auth url with the default browser
signer.auth_url_handler(LumeAuthUrlHandler);
// Start countdown
cx.spawn_in(window, async move |this, cx| {
for i in (0..=NOSTR_CONNECT_TIMEOUT).rev() {
if i == 0 {
this.update(cx, |this, cx| {
this.set_countdown(None, cx);
})
.ok();
} else {
this.update(cx, |this, cx| {
this.set_countdown(Some(i), cx);
})
.ok();
}
cx.background_executor().timer(Duration::from_secs(1)).await;
}
})
.detach();
// Handle connection
cx.spawn_in(window, async move |this, cx| {
let result: Result<PublicKey, Error> = cx
.background_executor()
.await_on_background(async move {
let client = client();
let public_key = signer.get_public_key().await?;
// Update the signer
client.set_signer(signer).await;
// Return the URI for storing the connection
Ok(public_key)
})
.await;
this.update_in(cx, |_this, window, cx| {
match result {
Ok(public_key) => {
let account = Account::global(cx);
account.update(cx, |this, cx| {
this.set_public_key(public_key, cx);
})
}
Err(e) => {
window.push_notification(Notification::error(e.to_string()), cx);
}
};
})
.ok();
})
.detach();
}
fn login_with_password(&mut self, content: &str, pwd: &str, cx: &mut Context<Self>) {
if pwd.is_empty() {
self.set_error("Password is required", cx);
return;
}
let Ok(enc) = EncryptedSecretKey::from_bech32(content) else {
self.set_error("Secret Key is invalid", cx);
return;
};
let password = pwd.to_owned();
// Decrypt in the background to ensure it doesn't block the UI
let task = cx.background_spawn(async move {
if let Ok(content) = enc.decrypt(&password) {
Ok(Keys::new(content))
} else {
Err(anyhow!("Invalid password"))
}
});
cx.spawn(async move |this, cx| {
let result = task.await;
this.update(cx, |this, cx| {
match result {
Ok(keys) => {
this.login_with_keys(keys, cx);
}
Err(e) => {
this.set_error(e.to_string(), cx);
}
};
})
.ok();
})
.detach();
}
fn login_with_keys(&mut self, keys: Keys, cx: &mut Context<Self>) {
let public_key = keys.public_key();
cx.spawn(async move |this, cx| {
cx.background_executor()
.await_on_background(async move {
let client = client();
client.set_signer(keys).await;
})
.await;
this.update(cx, |_this, cx| {
let account = Account::global(cx);
account.update(cx, |this, cx| {
this.set_public_key(public_key, cx);
});
})
.ok();
})
.detach();
}
fn set_error<S>(&mut self, message: S, cx: &mut Context<Self>)
where
S: Into<SharedString>,
{
// Reset the log in state
self.set_logging_in(false, cx);
// Reset the countdown
self.set_countdown(None, cx);
// Update error message
self.error.update(cx, |this, cx| {
*this = Some(message.into());
cx.notify();
});
// Clear the error message after 3 secs
cx.spawn(async move |this, cx| {
cx.background_executor().timer(Duration::from_secs(3)).await;
this.update(cx, |this, cx| {
this.error.update(cx, |this, cx| {
*this = None;
cx.notify();
});
})
.ok();
})
.detach();
}
fn set_logging_in(&mut self, status: bool, cx: &mut Context<Self>) {
self.logging_in = status;
cx.notify();
}
fn set_countdown(&mut self, i: Option<u64>, cx: &mut Context<Self>) {
self.countdown.update(cx, |this, cx| {
*this = i;
cx.notify();
});
}
}
impl Panel for Login {
fn panel_name(&self) -> &'static str {
"Login"
}
fn title(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
SharedString::from("Login")
}
}
impl EventEmitter<PanelEvent> for Login {}
impl Focusable for Login {
fn focus_handle(&self, _cx: &App) -> FocusHandle {
self.focus_handle.clone()
}
}
impl Render for Login {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
v_flex()
.relative()
.size_full()
.items_center()
.justify_center()
.child(
v_flex()
.w_96()
.gap_10()
.child(
div()
.text_center()
.text_lg()
.font_semibold()
.line_height(relative(1.3))
.child(SharedString::from("Continue with Private Key or Bunker")),
)
.child(
v_flex()
.gap_3()
.text_sm()
.child(
v_flex()
.gap_1()
.text_sm()
.text_color(cx.theme().muted_foreground)
.child("nsec or bunker://")
.child(Input::new(&self.key_input)),
)
.when(self.require_password, |this| {
this.child(
v_flex()
.gap_1()
.text_sm()
.text_color(cx.theme().muted_foreground)
.child("Password:")
.child(Input::new(&self.pass_input)),
)
})
.child(
Button::new("login")
.label("Continue")
.primary()
.loading(self.logging_in)
.disabled(self.logging_in)
.on_click(cx.listener(move |this, _, window, cx| {
this.login(window, cx);
})),
)
.when_some(self.countdown.read(cx).as_ref(), |this, i| {
let msg = format!(
"Approve connection request from your signer in {} seconds",
i
);
this.child(
div()
.text_xs()
.text_center()
.text_color(cx.theme().muted_foreground)
.child(SharedString::from(msg)),
)
})
.when_some(self.error.read(cx).as_ref(), |this, error| {
this.child(
div()
.text_xs()
.text_center()
.text_color(cx.theme().danger_foreground)
.child(error.clone()),
)
}),
),
)
}
}

View File

@@ -0,0 +1,5 @@
pub mod feed;
pub mod login;
pub mod new_account;
pub mod onboarding;
pub mod startup;

View File

@@ -0,0 +1,45 @@
use gpui::{
div, App, AppContext, Context, Entity, EventEmitter, FocusHandle, Focusable, IntoElement,
ParentElement, Render, SharedString, Window,
};
use gpui_component::dock::{Panel, PanelEvent};
pub fn init(window: &mut Window, cx: &mut App) -> Entity<NewAccount> {
cx.new(|cx| NewAccount::new(window, cx))
}
pub struct NewAccount {
focus_handle: FocusHandle,
}
impl NewAccount {
fn new(_window: &mut Window, cx: &mut Context<Self>) -> Self {
Self {
focus_handle: cx.focus_handle(),
}
}
}
impl Panel for NewAccount {
fn panel_name(&self) -> &'static str {
"New Account"
}
fn title(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
SharedString::from("New Account")
}
}
impl EventEmitter<PanelEvent> for NewAccount {}
impl Focusable for NewAccount {
fn focus_handle(&self, _cx: &App) -> FocusHandle {
self.focus_handle.clone()
}
}
impl Render for NewAccount {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
div().child("New Account")
}
}

View File

@@ -0,0 +1,306 @@
use std::sync::Arc;
use std::time::Duration;
use account::Account;
use anyhow::Error;
use common::{CLIENT_NAME, NOSTR_CONNECT_RELAY, NOSTR_CONNECT_TIMEOUT};
use gpui::prelude::FluentBuilder;
use gpui::{
div, img, px, relative, App, AppContext, Context, Entity, EventEmitter, FocusHandle, Focusable,
Image, InteractiveElement, IntoElement, ParentElement, Render, SharedString,
StatefulInteractiveElement, Styled, Task, Window,
};
use gpui_component::button::{Button, ButtonVariants};
use gpui_component::divider::Divider;
use gpui_component::dock::{Panel, PanelEvent};
use gpui_component::notification::Notification;
use gpui_component::{h_flex, v_flex, ActiveTheme, Icon, IconName, Sizable, StyledExt, WindowExt};
use nostr_connect::prelude::*;
use qrcode::render::svg;
use qrcode::QrCode;
use smallvec::{smallvec, SmallVec};
use state::client;
use crate::workspace::OpenPanel;
pub fn init(window: &mut Window, cx: &mut App) -> Entity<Onboarding> {
cx.new(|cx| Onboarding::new(window, cx))
}
#[derive(Debug, Clone)]
pub enum NostrConnectApp {
Nsec(String),
Amber(String),
Aegis(String),
}
impl NostrConnectApp {
pub fn all() -> Vec<Self> {
vec![
NostrConnectApp::Nsec("https://nsec.app".to_string()),
NostrConnectApp::Amber("https://github.com/greenart7c3/Amber".to_string()),
NostrConnectApp::Aegis("https://github.com/ZharlieW/Aegis".to_string()),
]
}
pub fn url(&self) -> &str {
match self {
Self::Nsec(url) | Self::Amber(url) | Self::Aegis(url) => url,
}
}
pub fn as_str(&self) -> String {
match self {
NostrConnectApp::Nsec(_) => "nsec.app (Desktop)".into(),
NostrConnectApp::Amber(_) => "Amber (Android)".into(),
NostrConnectApp::Aegis(_) => "Aegis (iOS)".into(),
}
}
}
/// Onboarding
pub struct Onboarding {
focus_handle: FocusHandle,
#[allow(dead_code)]
/// App keys for communicating with the remote signer
app_keys: Keys,
/// QR code for logging in with Nostr Connect
qr_code: Option<Arc<Image>>,
/// Background tasks
_tasks: SmallVec<[Task<()>; 1]>,
}
impl Onboarding {
fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
let app_keys = Keys::generate();
let timeout = Duration::from_secs(NOSTR_CONNECT_TIMEOUT);
let relay = RelayUrl::parse(NOSTR_CONNECT_RELAY).unwrap();
let uri = NostrConnectUri::client(app_keys.public_key(), vec![relay], CLIENT_NAME);
let qr_code = Self::generate_qr(uri.to_string().as_ref());
// NIP46: https://github.com/nostr-protocol/nips/blob/master/46.md
//
// Direct connection initiated by the client
let signer = NostrConnect::new(uri, app_keys.clone(), timeout, None).unwrap();
let mut tasks = smallvec![];
tasks.push(
// Wait for nostr connect
cx.spawn_in(window, async move |this, cx| {
let result: Result<PublicKey, Error> = cx
.background_executor()
.await_on_background(async move {
let client = client();
let public_key = signer.get_public_key().await?;
// Update the signer
client.set_signer(signer).await;
// Return the URI for storing the connection
Ok(public_key)
})
.await;
this.update_in(cx, |_this, window, cx| {
match result {
Ok(public_key) => {
let account = Account::global(cx);
account.update(cx, |this, cx| {
this.set_public_key(public_key, cx);
})
}
Err(e) => {
window.push_notification(Notification::error(e.to_string()), cx);
}
};
})
.ok();
}),
);
Self {
focus_handle: cx.focus_handle(),
qr_code,
app_keys,
_tasks: tasks,
}
}
fn generate_qr(value: &str) -> Option<Arc<Image>> {
let code = QrCode::new(value).unwrap();
let svg = code
.render()
.min_dimensions(256, 256)
.dark_color(svg::Color("#000000"))
.light_color(svg::Color("#FFFFFF"))
.build();
Some(Arc::new(Image::from_bytes(
gpui::ImageFormat::Svg,
svg.into_bytes(),
)))
}
fn render_app<T>(&self, ix: usize, label: T, url: &str, cx: &Context<Self>) -> impl IntoElement
where
T: Into<SharedString>,
{
div()
.id(ix)
.flex_1()
.rounded_md()
.py_0p5()
.px_2()
.bg(cx.theme().list)
.child(label.into())
.on_click({
let url = url.to_owned();
move |_e, _window, cx| {
cx.open_url(&url);
}
})
}
fn render_apps(&self, cx: &Context<Self>) -> impl IntoIterator<Item = impl IntoElement> {
let all_apps = NostrConnectApp::all();
let mut items = Vec::with_capacity(all_apps.len());
for (ix, item) in all_apps.into_iter().enumerate() {
items.push(self.render_app(ix, item.as_str(), item.url(), cx));
}
items
}
}
impl Panel for Onboarding {
fn panel_name(&self) -> &'static str {
"Onboarding"
}
fn title(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
SharedString::from("Onboarding")
}
}
impl EventEmitter<PanelEvent> for Onboarding {}
impl EventEmitter<OpenPanel> for Onboarding {}
impl Focusable for Onboarding {
fn focus_handle(&self, _: &App) -> gpui::FocusHandle {
self.focus_handle.clone()
}
}
impl Render for Onboarding {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
h_flex()
.size_full()
.child(
v_flex()
.flex_1()
.h_full()
.gap_10()
.items_center()
.justify_center()
.child(
v_flex()
.items_center()
.text_center()
.child(
div()
.text_xl()
.font_semibold()
.line_height(relative(1.3))
.child(SharedString::from("Welcome to Lume")),
)
.child(
div()
.text_color(cx.theme().muted_foreground)
.child(SharedString::from("An unambitious Nostr client")),
),
)
.child(
v_flex()
.w_80()
.gap_3()
.child(
Button::new("continue")
.icon(Icon::new(IconName::ArrowRight))
.label("Start Browsing")
.primary()
.on_click(cx.listener(move |_this, _ev, _window, cx| {
cx.emit(OpenPanel::Signup);
})),
)
.child(
Divider::horizontal()
.label("Already have an account? Continue with")
.text_xs(),
)
.child(
Button::new("key")
.label("Secret Key or Bunker")
.large()
.link()
.small()
.on_click(cx.listener(move |_this, _ev, _window, cx| {
cx.emit(OpenPanel::Login);
})),
),
),
)
.child(
v_flex()
.flex_1()
.size_full()
.items_center()
.justify_center()
.gap_5()
.bg(cx.theme().muted)
.when_some(self.qr_code.as_ref(), |this, qr| {
this.child(
img(qr.clone())
.size(px(256.))
.rounded_xl()
.shadow_lg()
.border_1()
.border_color(cx.theme().primary_active),
)
})
.child(
v_flex()
.justify_center()
.items_center()
.text_center()
.child(
div()
.font_semibold()
.line_height(relative(1.3))
.child(SharedString::from("Continue with Nostr Connect")),
)
.child(
div()
.text_sm()
.text_color(cx.theme().muted_foreground)
.child(SharedString::from(
"Use Nostr Connect apps to scan the code",
)),
)
.child(
h_flex()
.mt_2()
.gap_1()
.text_xs()
.justify_center()
.children(self.render_apps(cx)),
),
),
)
}
}

View File

@@ -0,0 +1,45 @@
use gpui::{
div, App, AppContext, Context, Entity, EventEmitter, FocusHandle, Focusable, IntoElement,
ParentElement, Render, SharedString, Window,
};
use gpui_component::dock::{Panel, PanelEvent};
pub fn init(window: &mut Window, cx: &mut App) -> Entity<Startup> {
cx.new(|cx| Startup::new(window, cx))
}
pub struct Startup {
focus_handle: FocusHandle,
}
impl Startup {
fn new(_window: &mut Window, cx: &mut Context<Self>) -> Self {
Self {
focus_handle: cx.focus_handle(),
}
}
}
impl Panel for Startup {
fn panel_name(&self) -> &'static str {
"Startup"
}
fn title(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
SharedString::from("Welcome")
}
}
impl EventEmitter<PanelEvent> for Startup {}
impl Focusable for Startup {
fn focus_handle(&self, _cx: &App) -> FocusHandle {
self.focus_handle.clone()
}
}
impl Render for Startup {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
div().child("Startup")
}
}

View File

@@ -0,0 +1,129 @@
use common::BOOTSTRAP_RELAYS;
use gpui::{
div, App, AppContext, Context, Entity, EventEmitter, FocusHandle, Focusable,
InteractiveElement, IntoElement, ParentElement, Render, SharedString,
StatefulInteractiveElement, Styled, Window,
};
use gpui_component::dock::{Panel, PanelEvent};
use gpui_component::scroll::ScrollableElement;
use gpui_component::{h_flex, v_flex, ActiveTheme, StyledExt};
use nostr_sdk::prelude::*;
use person::PersonRegistry;
use crate::workspace::OpenPanel;
pub fn init(window: &mut Window, cx: &mut App) -> Entity<Sidebar> {
cx.new(|cx| Sidebar::new(window, cx))
}
pub struct Sidebar {
focus_handle: FocusHandle,
}
impl Sidebar {
fn new(_window: &mut Window, cx: &mut Context<Self>) -> Self {
Self {
focus_handle: cx.focus_handle(),
}
}
}
impl Panel for Sidebar {
fn panel_name(&self) -> &'static str {
"Sidebar"
}
}
impl EventEmitter<PanelEvent> for Sidebar {}
impl EventEmitter<OpenPanel> for Sidebar {}
impl Focusable for Sidebar {
fn focus_handle(&self, _cx: &App) -> FocusHandle {
self.focus_handle.clone()
}
}
impl Render for Sidebar {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let person = PersonRegistry::global(cx);
let contacts = Vec::new();
v_flex()
.id("sidebar-wrapper")
.size_full()
.relative()
.px_2()
.overflow_y_scrollbar()
.child(
v_flex()
.gap_1p5()
.child(
div()
.mt_4()
.mb_2()
.font_semibold()
.text_xs()
.text_color(cx.theme().muted_foreground)
.child("Relays"),
)
.child(v_flex().gap_2().children({
let mut items = Vec::with_capacity(BOOTSTRAP_RELAYS.len());
for (ix, relay) in BOOTSTRAP_RELAYS.into_iter().enumerate() {
items.push(
h_flex()
.id(SharedString::from(format!("relay-{ix}")))
.h_7()
.px_2()
.rounded(cx.theme().radius)
.hover(|this| this.bg(cx.theme().list_hover))
.child(div().text_sm().child(SharedString::from(relay)))
.on_click(cx.listener(move |_this, _ev, _window, cx| {
if let Ok(url) = RelayUrl::parse(relay) {
cx.emit(OpenPanel::Relay(url));
}
})),
)
}
items
})),
)
.child(
v_flex()
.gap_1p5()
.child(
div()
.mt_4()
.mb_2()
.font_semibold()
.text_xs()
.text_color(cx.theme().muted_foreground)
.child("Contacts"),
)
.child(v_flex().gap_2().children({
let mut items = Vec::with_capacity(contacts.len());
for (ix, contact) in contacts.iter().enumerate() {
let profile = person.read(cx).get(contact, cx);
let name = SharedString::from(profile.name());
items.push(
h_flex()
.id(SharedString::from(format!("contact-{ix}")))
.h_7()
.px_2()
.rounded(cx.theme().radius)
.hover(|this| this.bg(cx.theme().list_hover))
.child(div().text_sm().child(name.clone()))
.on_click(cx.listener(move |_this, _ev, _window, cx| {
cx.emit(OpenPanel::PublicKey(profile.public_key()));
})),
)
}
items
})),
)
}
}

82
crates/lume/src/themes.rs Normal file
View File

@@ -0,0 +1,82 @@
use std::path::PathBuf;
use common::home_dir;
use gpui::{Action, App, SharedString};
use gpui_component::scroll::ScrollbarShow;
use gpui_component::{ActiveTheme, Theme, ThemeMode, ThemeRegistry};
use serde::{Deserialize, Serialize};
#[derive(Action, Clone, PartialEq)]
#[action(namespace = themes, no_json)]
pub(crate) struct SwitchTheme(pub(crate) SharedString);
#[derive(Action, Clone, PartialEq)]
#[action(namespace = themes, no_json)]
pub(crate) struct SwitchThemeMode(pub(crate) ThemeMode);
#[derive(Debug, Clone, Serialize, Deserialize)]
struct State {
theme: SharedString,
scrollbar_show: Option<ScrollbarShow>,
}
impl Default for State {
fn default() -> Self {
Self {
theme: "Default Light".into(),
scrollbar_show: None,
}
}
}
pub fn init(cx: &mut App) {
// Load last theme state
let path = home_dir().join("state.json");
let json = std::fs::read_to_string(path).unwrap_or_default();
let state = serde_json::from_str::<State>(&json).unwrap_or_default();
if let Err(err) = ThemeRegistry::watch_dir(PathBuf::from("./themes"), cx, move |cx| {
if let Some(theme) = ThemeRegistry::global(cx)
.themes()
.get(&state.theme)
.cloned()
{
Theme::global_mut(cx).apply_config(&theme);
}
}) {
log::error!("Failed to watch themes directory: {}", err);
}
if let Some(scrollbar_show) = state.scrollbar_show {
Theme::global_mut(cx).scrollbar_show = scrollbar_show;
}
cx.refresh_windows();
cx.observe_global::<Theme>(|cx| {
let state = State {
theme: cx.theme().theme_name().clone(),
scrollbar_show: Some(cx.theme().scrollbar_show),
};
if let Ok(json) = serde_json::to_string_pretty(&state) {
let path = home_dir().join("state.json");
// Ignore write errors - if STATE_FILE doesn't exist or can't be written, do nothing
let _ = std::fs::write(path, json);
}
})
.detach();
cx.on_action(|switch: &SwitchTheme, cx| {
let theme_name = switch.0.clone();
if let Some(theme_config) = ThemeRegistry::global(cx).themes().get(&theme_name).cloned() {
Theme::global_mut(cx).apply_config(&theme_config);
}
cx.refresh_windows();
});
cx.on_action(|switch: &SwitchThemeMode, cx| {
let mode = switch.0;
Theme::change(mode, None, cx);
cx.refresh_windows();
});
}

View File

@@ -0,0 +1,66 @@
use std::rc::Rc;
use gpui::{
div, AnyElement, App, Context, Entity, InteractiveElement as _, IntoElement, MouseButton,
ParentElement as _, Render, SharedString, Styled as _, Subscription, Window,
};
use gpui_component::menu::AppMenuBar;
use gpui_component::TitleBar;
use crate::menus;
#[allow(clippy::type_complexity)]
pub struct AppTitleBar {
/// The app menu bar
app_menu_bar: Entity<AppMenuBar>,
/// Child elements
child: Rc<dyn Fn(&mut Window, &mut App) -> AnyElement>,
/// Event subscriptions
_subscriptions: Vec<Subscription>,
}
impl AppTitleBar {
pub fn new(
title: impl Into<SharedString>,
_window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
let app_menu_bar = menus::init(title, cx);
Self {
app_menu_bar,
child: Rc::new(|_, _| div().into_any_element()),
_subscriptions: vec![],
}
}
#[allow(dead_code)]
pub fn child<F, E>(mut self, f: F) -> Self
where
E: IntoElement,
F: Fn(&mut Window, &mut App) -> E + 'static,
{
self.child = Rc::new(move |window, cx| f(window, cx).into_any_element());
self
}
}
impl Render for AppTitleBar {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
TitleBar::new()
// left side
.child(div().flex().items_center().child(self.app_menu_bar.clone()))
.child(
div()
.flex()
.items_center()
.justify_end()
.px_2()
.gap_2()
.on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
.child((self.child.clone())(window, cx)),
)
}
}

View File

@@ -0,0 +1,178 @@
use std::sync::Arc;
use account::Account;
use common::{CLIENT_NAME, DEFAULT_SIDEBAR_WIDTH};
use gpui::{
div, px, AppContext, Axis, Context, Entity, InteractiveElement, IntoElement, ParentElement,
Render, Styled, Subscription, Window,
};
use gpui_component::dock::{DockArea, DockItem, DockPlacement, PanelStyle};
use gpui_component::{v_flex, Root, Theme};
use nostr_sdk::prelude::*;
use smallvec::{smallvec, SmallVec};
use crate::panels::feed::Feed;
use crate::panels::{login, new_account, onboarding, startup};
use crate::sidebar;
use crate::title_bar::AppTitleBar;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum OpenPanel {
PublicKey(PublicKey),
Relay(RelayUrl),
Signup,
Login,
}
#[derive(Debug)]
pub struct Workspace {
/// The dock area for the workspace.
dock: Entity<DockArea>,
/// App's title bar.
title_bar: Entity<AppTitleBar>,
/// Event subscriptions
_subscriptions: SmallVec<[Subscription; 1]>,
}
impl Workspace {
pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
let account = Account::global(cx);
let onboarding = Arc::new(onboarding::init(window, cx));
// App's title bar
let title_bar = cx.new(|cx| AppTitleBar::new(CLIENT_NAME, window, cx));
// Dock area for the workspace.
let dock = cx.new(|cx| {
let mut this = DockArea::new("dock", None, window, cx).panel_style(PanelStyle::TabBar);
this.set_center(DockItem::panel(onboarding.clone()), window, cx);
this
});
let mut subscriptions = smallvec![];
// Observe account entity changes
subscriptions.push(
cx.observe_in(&account, window, move |this, state, window, cx| {
if state.read(cx).has_account() {
this.init_app_layout(window, cx);
}
}),
);
// Observe onboarding panel events
subscriptions.push(cx.subscribe_in(
&onboarding,
window,
|this, _sidebar, event: &OpenPanel, window, cx| {
match event {
OpenPanel::Login => {
let view = login::init(window, cx);
this.dock.update(cx, |this, cx| {
this.set_center(DockItem::panel(Arc::new(view)), window, cx);
});
}
OpenPanel::Signup => {
let view = new_account::init(window, cx);
this.dock.update(cx, |this, cx| {
this.set_center(DockItem::panel(Arc::new(view)), window, cx);
});
}
_ => {}
};
},
));
// Automatically sync theme with system appearance
subscriptions.push(window.observe_window_appearance(|window, cx| {
Theme::sync_system_appearance(Some(window), cx);
}));
Self {
dock,
title_bar,
_subscriptions: subscriptions,
}
}
fn init_app_layout(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let weak_dock = self.dock.downgrade();
let sidebar = sidebar::init(window, cx);
let startup = Arc::new(startup::init(window, cx));
self._subscriptions.push(cx.subscribe_in(
&sidebar,
window,
|this, _sidebar, event: &OpenPanel, window, cx| {
match event {
OpenPanel::PublicKey(public_key) => {
let view = cx.new(|cx| Feed::new(Some(*public_key), None, window, cx));
this.dock.update(cx, |this, cx| {
this.add_panel(Arc::new(view), DockPlacement::Center, None, window, cx);
});
}
OpenPanel::Relay(relay) => {
let view = cx.new(|cx| Feed::new(None, Some(relay.to_owned()), window, cx));
this.dock.update(cx, |this, cx| {
this.add_panel(Arc::new(view), DockPlacement::Center, None, window, cx);
});
}
_ => {}
};
},
));
// Construct left dock (sidebar)
let left = DockItem::panel(Arc::new(sidebar));
// Construct center dock
let center = DockItem::split_with_sizes(
Axis::Vertical,
vec![DockItem::tabs(vec![startup], &weak_dock, window, cx)],
vec![None],
&weak_dock,
window,
cx,
);
// Update dock layout
self.dock.update(cx, |this, cx| {
this.set_left_dock(left, Some(px(DEFAULT_SIDEBAR_WIDTH)), true, window, cx);
this.set_center(center, window, cx);
});
}
}
impl Render for Workspace {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let dialog_layer = Root::render_dialog_layer(window, cx);
let sheet_layer = Root::render_sheet_layer(window, cx);
let notification_layer = Root::render_notification_layer(window, cx);
div()
.id("root")
.relative()
.size_full()
.child(
v_flex()
.size_full()
// Title bar
.child(self.title_bar.clone())
// Dock
.child(self.dock.clone()),
)
// Notifications
.children(notification_layer)
// Sheets
.children(sheet_layer)
// Modals
.children(dialog_layer)
}
}

17
crates/note/Cargo.toml Normal file
View File

@@ -0,0 +1,17 @@
[package]
name = "note"
version.workspace = true
edition.workspace = true
publish.workspace = true
[dependencies]
common = { path = "../common" }
state = { path = "../state" }
gpui.workspace = true
nostr-sdk.workspace = true
anyhow.workspace = true
smallvec.workspace = true
flume.workspace = true
log.workspace = true

92
crates/note/src/lib.rs Normal file
View File

@@ -0,0 +1,92 @@
use std::collections::{HashMap, HashSet};
use gpui::{App, AppContext, Context, Entity, Global, Task};
use nostr_sdk::prelude::*;
use smallvec::{smallvec, SmallVec};
use state::client;
pub fn init(cx: &mut App) {
NoteRegistry::set_global(cx.new(NoteRegistry::new), cx);
}
struct GlobalNoteRegistry(Entity<NoteRegistry>);
impl Global for GlobalNoteRegistry {}
/// Note Registry
#[derive(Debug)]
pub struct NoteRegistry {
/// Collection of all notes
pub notes: HashMap<EventId, Event>,
/// Tasks for asynchronous operations
_tasks: SmallVec<[Task<()>; 2]>,
}
impl NoteRegistry {
/// Retrieve the global note registry state
pub fn global(cx: &App) -> Entity<Self> {
cx.global::<GlobalNoteRegistry>().0.clone()
}
/// Set the global note registry instance
pub(crate) fn set_global(state: Entity<Self>, cx: &mut App) {
cx.set_global(GlobalNoteRegistry(state));
}
/// Create a new note registry instance
pub(crate) fn new(cx: &mut Context<Self>) -> Self {
let mut tasks = smallvec![];
// Channel for communication between Nostr and GPUI
let (tx, rx) = flume::bounded::<Event>(2048);
tasks.push(
// Handle nostr notifications
cx.background_spawn(async move {
let client = client();
let mut notifications = client.notifications();
let mut processed_events: HashSet<EventId> = HashSet::default();
while let Ok(notification) = notifications.recv().await {
let RelayPoolNotification::Message { message, .. } = notification else {
continue;
};
if let RelayMessage::Event { event, .. } = message {
// Skip if already processed
if !processed_events.insert(event.id) {
continue;
}
if event.kind == Kind::TextNote || event.kind == Kind::Repost {
tx.send_async(event.into_owned()).await.ok();
}
}
}
}),
);
tasks.push(
// Update GPUI state
cx.spawn(async move |this, cx| {
while let Ok(event) = rx.recv_async().await {
this.update(cx, |this, cx| {
this.notes.insert(event.id, event);
cx.notify();
})
.ok();
}
}),
);
Self {
notes: HashMap::new(),
_tasks: tasks,
}
}
pub fn get(&self, id: &EventId) -> Option<&Event> {
self.notes.get(id)
}
}

17
crates/person/Cargo.toml Normal file
View File

@@ -0,0 +1,17 @@
[package]
name = "person"
version.workspace = true
edition.workspace = true
publish.workspace = true
[dependencies]
common = { path = "../common" }
state = { path = "../state" }
gpui.workspace = true
nostr-sdk.workspace = true
anyhow.workspace = true
smallvec.workspace = true
flume.workspace = true
log.workspace = true

160
crates/person/src/lib.rs Normal file
View File

@@ -0,0 +1,160 @@
use std::collections::{HashMap, HashSet};
use gpui::{App, AppContext, AsyncApp, Context, Entity, Global, Task};
use nostr_sdk::prelude::*;
use smallvec::{smallvec, SmallVec};
use state::client;
pub fn init(cx: &mut App) {
PersonRegistry::set_global(cx.new(PersonRegistry::new), cx);
}
struct GlobalPersonRegistry(Entity<PersonRegistry>);
impl Global for GlobalPersonRegistry {}
/// Person Registry
#[derive(Debug)]
pub struct PersonRegistry {
/// Collection of all profiels
pub persons: HashMap<PublicKey, Entity<Profile>>,
/// Tasks for asynchronous operations
_tasks: SmallVec<[Task<()>; 3]>,
}
impl PersonRegistry {
/// Retrieve the global person registry state
pub fn global(cx: &App) -> Entity<Self> {
cx.global::<GlobalPersonRegistry>().0.clone()
}
/// Set the global person registry instance
pub(crate) fn set_global(state: Entity<Self>, cx: &mut App) {
cx.set_global(GlobalPersonRegistry(state));
}
/// Create a new person registry instance
pub(crate) fn new(cx: &mut Context<Self>) -> Self {
let mut tasks = smallvec![];
// Channel for communication between Nostr and GPUI
let (tx, rx) = flume::bounded::<Profile>(1024);
tasks.push(
// Load all user profiles from the database
cx.spawn(async move |this, cx| {
let task = Self::init(cx);
match task.await {
Ok(profiles) => {
this.update(cx, |this, cx| {
this.bulk_insert(profiles, cx);
})
.ok();
}
Err(e) => {
log::error!("Failed to load user profiles from database: {e}");
}
}
}),
);
tasks.push(
// Handle nostr notifications
cx.background_spawn(async move {
let client = client();
let mut notifications = client.notifications();
let mut processed_events: HashSet<EventId> = HashSet::default();
while let Ok(notification) = notifications.recv().await {
let RelayPoolNotification::Message { message, .. } = notification else {
continue;
};
if let RelayMessage::Event { event, .. } = message {
// Skip if already processed
if !processed_events.insert(event.id) {
continue;
}
if event.kind == Kind::Metadata {
let metadata = Metadata::from_json(&event.content).unwrap_or_default();
let profile = Profile::new(event.pubkey, metadata);
tx.send_async(profile).await.ok();
}
}
}
}),
);
tasks.push(
// Update GPUI state
cx.spawn(async move |this, cx| {
while let Ok(profile) = rx.recv_async().await {
this.update(cx, |this, cx| {
this.insert_or_update(&profile, cx);
})
.ok();
}
}),
);
Self {
persons: HashMap::new(),
_tasks: tasks,
}
}
/// Load all user profiles from the database
fn init(cx: &AsyncApp) -> Task<Result<Vec<Profile>, Error>> {
cx.background_spawn(async move {
let client = client();
let filter = Filter::new().kind(Kind::Metadata).limit(200);
let events = client.database().query(filter).await?;
let mut profiles = vec![];
for event in events.into_iter() {
let metadata = Metadata::from_json(event.content).unwrap_or_default();
let profile = Profile::new(event.pubkey, metadata);
profiles.push(profile);
}
Ok(profiles)
})
}
/// Insert batch of persons
fn bulk_insert(&mut self, profiles: Vec<Profile>, cx: &mut Context<Self>) {
for profile in profiles.into_iter() {
self.persons
.insert(profile.public_key(), cx.new(|_| profile));
}
cx.notify();
}
/// Insert or update a person
pub fn insert_or_update(&mut self, profile: &Profile, cx: &mut App) {
let public_key = profile.public_key();
if let Some(person) = self.persons.get(&public_key) {
person.update(cx, |this, cx| {
*this = profile.to_owned();
cx.notify();
});
} else {
self.persons
.insert(public_key, cx.new(|_| profile.to_owned()));
}
}
/// Get person
pub fn get(&self, public_key: &PublicKey, cx: &App) -> Profile {
self.persons
.get(public_key)
.map(|e| e.read(cx).clone())
.unwrap_or(Profile::new(public_key.to_owned(), Metadata::default()))
}
}

22
crates/state/Cargo.toml Normal file
View File

@@ -0,0 +1,22 @@
[package]
name = "state"
version.workspace = true
edition.workspace = true
publish.workspace = true
[dependencies]
common = { path = "../common" }
nostr-sdk.workspace = true
nostr-lmdb.workspace = true
nostr-gossip-memory.workspace = true
gpui.workspace = true
smol.workspace = true
smallvec.workspace = true
log.workspace = true
anyhow.workspace = true
serde.workspace = true
serde_json.workspace = true
rustls = "0.23.23"

View File

@@ -0,0 +1,7 @@
use nostr_sdk::prelude::*;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum StateEvent {
ReceivedContactList,
ReceivedProfile(Box<Profile>),
}

46
crates/state/src/lib.rs Normal file
View File

@@ -0,0 +1,46 @@
use std::sync::OnceLock;
use std::time::Duration;
use common::config_dir;
pub use event::*;
use nostr_gossip_memory::prelude::*;
use nostr_lmdb::NostrLmdb;
use nostr_sdk::prelude::*;
mod event;
static NOSTR_CLIENT: OnceLock<Client> = OnceLock::new();
pub fn client() -> &'static Client {
NOSTR_CLIENT.get_or_init(|| {
// rustls uses the `aws_lc_rs` provider by default
// This only errors if the default provider has already
// been installed. We can ignore this `Result`.
rustls::crypto::aws_lc_rs::default_provider()
.install_default()
.ok();
// Construct the nostr client options
let opts = ClientOptions::new()
.automatic_authentication(false)
.verify_subscriptions(false)
.sleep_when_idle(SleepWhenIdle::Enabled {
timeout: Duration::from_secs(600),
});
// Construct the lmdb
let lmdb = smol::block_on(async move {
let path = config_dir().join("nostr");
NostrLmdb::open(path)
.await
.expect("Failed to initialize database")
});
// Construct the nostr client
ClientBuilder::default()
.database(lmdb)
.gossip(NostrGossipMemory::unbounded())
.opts(opts)
.build()
})
}

BIN
docs/coop.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

130
flake.lock generated
View File

@@ -1,130 +0,0 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1694529238,
"narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_2": {
"inputs": {
"systems": "systems_2"
},
"locked": {
"lastModified": 1681202837,
"narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "cfacdce06f30d2b68473a46042957675eebb3401",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1697723726,
"narHash": "sha256-SaTWPkI8a5xSHX/rrKzUe+/uVNy6zCGMXgoeMb7T9rg=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "7c9cc5a6e5d38010801741ac830a3f8fd667a7a0",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1681358109,
"narHash": "sha256-eKyxW4OohHQx9Urxi7TQlFBTDWII+F+x2hklDOQPB50=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "96ba1c52e54e74c3197f4d43026b3f3d92e83ff9",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs",
"rust-overlay": "rust-overlay"
}
},
"rust-overlay": {
"inputs": {
"flake-utils": "flake-utils_2",
"nixpkgs": "nixpkgs_2"
},
"locked": {
"lastModified": 1697940838,
"narHash": "sha256-eyk92QqAoRNC0V99KOcKcBZjLPixxNBS0PRc4KlSQVs=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "a3e829c06eadf848f13d109c7648570ce37ebccd",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_2": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

View File

@@ -1,71 +0,0 @@
# Nix.flake to build Lume based on Tauri's Guides:
# Prerequisites -> Installing -> Setting Up Linux -> NixOS
# https://tauri.app/v1/guides/getting-started/prerequisites/#1-system-dependencies
#
# To build Rust backend of Tauri `rust-overlay` is used
# https://github.com/oxalica/rust-overlay
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
rust-overlay.url = "github:oxalica/rust-overlay";
};
outputs = { self, nixpkgs, flake-utils, rust-overlay }:
flake-utils.lib.eachDefaultSystem (system:
let
overlays = [ (import rust-overlay) ];
pkgs = import nixpkgs {
inherit system overlays;
};
libraries = with pkgs;[
webkitgtk
gtk3
cairo
gdk-pixbuf
glib
dbus
openssl_3
librsvg
];
packages = with pkgs; [
curl
wget
pkg-config
dbus
openssl_3
glib
gtk3
libsoup
webkitgtk
librsvg
];
rustToolchain = pkgs.rust-bin.stable.latest.default.override {
extensions = [ "rust-src" ]; # needed by rust-analyzer
};
in
{
devShells.default = pkgs.mkShell {
buildInputs = [
rustToolchain
pkgs.nodejs
pkgs.nodePackages.pnpm
pkgs.bun # experimental in Lume
] ++ packages;
shellHook =
''
export LD_LIBRARY_PATH=${pkgs.lib.makeLibraryPath libraries}:$LD_LIBRARY_PATH
export XDG_DATA_DIRS=${pkgs.gsettings-desktop-schemas}/share/gsettings-schemas/${pkgs.gsettings-desktop-schemas.name}:${pkgs.gtk3}/share/gsettings-schemas/${pkgs.gtk3.name}:$XDG_DATA_DIRS
'';
# Avoid white screen running with Nix
# https://github.com/tauri-apps/tauri/issues/4315#issuecomment-1207755694
WEBKIT_DISABLE_COMPOSITING_MODE = 1;
};
});
}

View File

@@ -1,11 +0,0 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Lume</title>
</head>
<body class="relative cursor-default select-none overflow-hidden font-sans antialiased h-screen w-screen text-neutral-950 dark:text-neutral-50">
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>

View File

@@ -1,126 +0,0 @@
{
"name": "lume",
"description": "the communication app",
"private": true,
"version": "2.2.0",
"scripts": {
"dev": "vite",
"build": "vite build",
"tauri": "tauri",
"add-migrate": "cd src-tauri/ && sqlx migrate add",
"prepare": "husky install",
"lint": "eslint ./src --fix",
"format": "prettier ./src --write",
"dep-update": "pnpm update && cd src-tauri/ && cargo update"
},
"lint-staged": {
"**/*.{ts, tsx}": "eslint --fix",
"**/*.{ts, tsx, css, md, html, json}": "prettier --cache --write"
},
"dependencies": {
"@evilmartians/harmony": "^1.2.0",
"@getalby/sdk": "^2.7.0",
"@nostr-dev-kit/ndk": "^2.2.0",
"@nostr-fetch/adapter-ndk": "^0.13.1",
"@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-alert-dialog": "^1.0.5",
"@radix-ui/react-avatar": "^1.0.4",
"@radix-ui/react-collapsible": "^1.0.3",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-hover-card": "^1.0.7",
"@radix-ui/react-popover": "^1.0.7",
"@radix-ui/react-switch": "^1.0.3",
"@radix-ui/react-toolbar": "^1.0.4",
"@radix-ui/react-tooltip": "^1.0.7",
"@tanstack/react-query": "^5.12.1",
"@tanstack/react-query-devtools": "^5.12.1",
"@tauri-apps/api": "2.0.0-alpha.11",
"@tauri-apps/cli": "2.0.0-alpha.17",
"@tauri-apps/plugin-autostart": "2.0.0-alpha.3",
"@tauri-apps/plugin-clipboard-manager": "2.0.0-alpha.3",
"@tauri-apps/plugin-dialog": "2.0.0-alpha.3",
"@tauri-apps/plugin-fs": "2.0.0-alpha.3",
"@tauri-apps/plugin-http": "2.0.0-alpha.3",
"@tauri-apps/plugin-notification": "2.0.0-alpha.3",
"@tauri-apps/plugin-os": "2.0.0-alpha.4",
"@tauri-apps/plugin-process": "2.0.0-alpha.3",
"@tauri-apps/plugin-shell": "2.0.0-alpha.3",
"@tauri-apps/plugin-sql": "2.0.0-alpha.3",
"@tauri-apps/plugin-updater": "2.0.0-alpha.3",
"@tauri-apps/plugin-upload": "2.0.0-alpha.3",
"@tiptap/extension-character-count": "^2.1.13",
"@tiptap/extension-document": "^2.1.13",
"@tiptap/extension-image": "^2.1.13",
"@tiptap/extension-mention": "^2.1.13",
"@tiptap/extension-paragraph": "^2.1.13",
"@tiptap/extension-placeholder": "^2.1.13",
"@tiptap/extension-text": "^2.1.13",
"@tiptap/pm": "^2.1.13",
"@tiptap/react": "^2.1.13",
"@tiptap/starter-kit": "^2.1.13",
"@tiptap/suggestion": "^2.1.13",
"dayjs": "^1.11.10",
"framer-motion": "^10.16.12",
"html-to-text": "^9.0.5",
"idb-keyval": "^6.2.1",
"light-bolt11-decoder": "^3.0.0",
"lru-cache": "^10.1.0",
"markdown-to-jsx": "^7.3.2",
"media-chrome": "^1.5.4",
"minidenticons": "^4.2.0",
"nanoid": "^5.0.3",
"nostr-fetch": "^0.13.1",
"nostr-tools": "^1.17.0",
"qrcode.react": "^3.1.0",
"re-resizable": "^6.9.11",
"react": "^18.2.0",
"react-currency-input-field": "^3.6.12",
"react-dom": "^18.2.0",
"react-hook-form": "^7.48.2",
"react-hotkeys-hook": "^4.4.1",
"react-router-dom": "^6.20.1",
"react-string-replace": "^1.1.1",
"reactflow": "^11.10.1",
"sonner": "^1.2.4",
"tauri-controls": "github:reyamir/tauri-controls",
"tippy.js": "^6.3.7",
"tiptap-markdown": "^0.8.7",
"virtua": "^0.16.7",
"zustand": "^4.4.7"
},
"devDependencies": {
"@tailwindcss/forms": "^0.5.7",
"@tailwindcss/typography": "^0.5.10",
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
"@types/html-to-text": "^9.0.4",
"@types/node": "^20.10.2",
"@types/react": "^18.2.40",
"@types/react-dom": "^18.2.17",
"@typescript-eslint/eslint-plugin": "^6.13.1",
"@typescript-eslint/parser": "^6.13.1",
"@vitejs/plugin-react-swc": "^3.5.0",
"autoprefixer": "^10.4.16",
"clsx": "^2.0.0",
"cross-env": "^7.0.3",
"encoding": "^0.1.13",
"eslint": "^8.55.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-jsx-a11y": "^6.8.0",
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-simple-import-sort": "^10.0.0",
"husky": "^8.0.3",
"lint-staged": "^15.1.0",
"postcss": "^8.4.31",
"prettier": "^3.1.0",
"prettier-plugin-tailwindcss": "^0.5.7",
"prop-types": "^15.8.1",
"tailwind-merge": "^1.14.0",
"tailwind-scrollbar": "^3.0.5",
"tailwindcss": "^3.3.5",
"typescript": "^5.3.2",
"vite": "^4.5.0",
"vite-plugin-top-level-await": "^1.3.1",
"vite-tsconfig-paths": "^4.2.1"
}
}

6479
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +0,0 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 398 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 986 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 341 KiB

10
rust-toolchain.toml Normal file
View File

@@ -0,0 +1,10 @@
[toolchain]
channel = "1.90"
profile = "minimal"
components = ["rustfmt", "clippy"]
targets = [
"x86_64-apple-darwin",
"aarch64-apple-darwin",
"x86_64-unknown-linux-gnu",
"x86_64-pc-windows-msvc",
]

9
rustfmt.toml Normal file
View File

@@ -0,0 +1,9 @@
tab_spaces = 4
newline_style = "Auto"
reorder_imports = true
reorder_modules = true
reorder_impl_items = true
indent_style = "Block"
normalize_comments = false
imports_granularity = "Module"
group_imports = "StdExternalCrate"

0
script/.keep Normal file
View File

240
script/freebsd Normal file
View File

@@ -0,0 +1,240 @@
#!/usr/bin/env bash
set -xeuo pipefail
# if root or if sudo/unavailable, define an empty variable
if [ "$(id -u)" -eq 0 ]
then maysudo=''
else maysudo="$(command -v sudo || command -v doas || true)"
fi
function finalize {
# after packages install (curl, etc), get the rust toolchain
which rustup > /dev/null 2>&1 || curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
# verify the mold situation
if ! command -v mold >/dev/null 2>&1; then
echo "Warning: Mold binaries are unavailable on your system." >&2
echo " Builds will be slower without mold. Try: script/install-mold" >&2
fi
echo "Finished installing Linux dependencies with script/linux"
}
# Ubuntu, Debian, Mint, Kali, Pop!_OS, Raspbian, etc.
apt=$(command -v apt-get || true)
if [[ -n $apt ]]; then
deps=(
gcc
g++
libasound2-dev
libfontconfig-dev
libwayland-dev
libx11-xcb-dev
libxkbcommon-x11-dev
libssl-dev
libzstd-dev
libvulkan1
libgit2-dev
make
cmake
clang
jq
git
curl
gettext-base
elfutils
libsqlite3-dev
musl-tools
musl-dev
build-essential
)
if (grep -qP 'PRETTY_NAME="(Linux Mint 22|.+24\.(04|10))' /etc/os-release); then
deps+=( mold libstdc++-14-dev )
elif (grep -qP 'PRETTY_NAME="((Debian|Raspbian).+12|Linux Mint 21|.+22\.04)' /etc/os-release); then
deps+=( mold libstdc++-12-dev )
elif (grep -qP 'PRETTY_NAME="((Debian|Raspbian).+11|Linux Mint 20|.+20\.04)' /etc/os-release); then
deps+=( libstdc++-10-dev )
fi
$maysudo "$apt" update
$maysudo "$apt" install -y "${deps[@]}"
finalize
exit 0
fi
# Fedora, CentOS, RHEL, Alma, Amazon 2023, Oracle, etc.
dnf=$(command -v dnf || true)
# Old Redhat (yum only): Amazon Linux 2, Oracle Linux 7, etc.
yum=$(command -v yum || true)
if [[ -n $dnf ]] || [[ -n $yum ]]; then
pkg_cmd="${dnf:-${yum}}"
deps=(
musl-gcc
gcc
clang
cmake
alsa-lib-devel
fontconfig-devel
wayland-devel
libxcb-devel
libxkbcommon-x11-devel
openssl-devel
libzstd-devel
vulkan-loader
sqlite-devel
jq
git
tar
)
# perl used for building openssl-sys crate. See: https://docs.rs/openssl/latest/openssl/
if grep -qP '^ID="?(fedora)' /etc/os-release; then
deps+=(
perl-FindBin
perl-IPC-Cmd
perl-File-Compare
perl-File-Copy
mold
)
elif grep -qP '^ID="?(rhel|rocky|alma|centos|ol)' /etc/os-release; then
deps+=( perl-interpreter )
fi
# gcc-c++ is g++ on RHEL8 and 8.x clones
if grep -qP '^ID="?(rhel|rocky|alma|centos|ol)' /etc/os-release \
&& grep -qP '^VERSION_ID="?(8)' /etc/os-release; then
deps+=( gcc-c++ )
else
deps+=( g++ )
fi
# libxkbcommon-x11-devel is in a non-default repo on RHEL 8.x/9.x (except on AmazonLinux)
if grep -qP '^VERSION_ID="?(8|9)' /etc/os-release && grep -qP '^ID="?(rhel|rocky|centos|alma|ol)' /etc/os-release; then
$maysudo dnf install -y 'dnf-command(config-manager)'
if grep -qP '^PRETTY_NAME="(AlmaLinux 8|Rocky Linux 8)' /etc/os-release; then
$maysudo dnf config-manager --set-enabled powertools
elif grep -qP '^PRETTY_NAME="((AlmaLinux|Rocky|CentOS Stream) 9|Red Hat.+(8|9))' /etc/os-release; then
$maysudo dnf config-manager --set-enabled crb
elif grep -qP '^PRETTY_NAME="Oracle Linux Server 8' /etc/os-release; then
$maysudo dnf config-manager --set-enabled ol8_codeready_builder
elif grep -qP '^PRETTY_NAME="Oracle Linux Server 9' /etc/os-release; then
$maysudo dnf config-manager --set-enabled ol9_codeready_builder
else
echo "Unexpected distro" && grep 'PRETTY_NAME' /etc/os-release && exit 1
fi
fi
$maysudo "$pkg_cmd" install -y "${deps[@]}"
finalize
exit 0
fi
# openSUSE
# https://software.opensuse.org/
zyp=$(command -v zypper || true)
if [[ -n $zyp ]]; then
deps=(
alsa-devel
clang
cmake
fontconfig-devel
gcc
gcc-c++
git
gzip
jq
libvulkan1
libxcb-devel
libxkbcommon-devel
libxkbcommon-x11-devel
libzstd-devel
make
mold
openssl-devel
sqlite3-devel
tar
wayland-devel
xcb-util-devel
)
$maysudo "$zyp" install -y "${deps[@]}"
finalize
exit 0
fi
# Arch, Manjaro, etc.
# https://archlinux.org/packages
pacman=$(command -v pacman || true)
if [[ -n $pacman ]]; then
deps=(
gcc
clang
musl
cmake
alsa-lib
fontconfig
wayland
libgit2
libxcb
libxkbcommon-x11
openssl
zstd
pkgconf
mold
sqlite
jq
git
)
$maysudo "$pacman" -Syu --needed --noconfirm "${deps[@]}"
finalize
exit 0
fi
# Void
# https://voidlinux.org/packages/
xbps=$(command -v xbps-install || true)
if [[ -n $xbps ]]; then
deps=(
gettext-devel
clang
cmake
jq
elfutils-devel
gcc
alsa-lib-devel
fontconfig-devel
libxcb-devel
libxkbcommon-devel
libzstd-devel
openssl-devel
wayland-devel
vulkan-loader
mold
)
$maysudo "$xbps" -Syu "${deps[@]}"
finalize
exit 0
fi
# Gentoo
# https://packages.gentoo.org/
emerge=$(command -v emerge || true)
if [[ -n $emerge ]]; then
deps=(
app-arch/zstd
app-misc/jq
dev-libs/openssl
dev-libs/wayland
dev-util/cmake
media-libs/alsa-lib
media-libs/fontconfig
media-libs/vulkan-loader
x11-libs/libxcb
x11-libs/libxkbcommon
sys-devel/mold
)
$maysudo "$emerge" -u "${deps[@]}"
finalize
exit 0
fi
echo "Unsupported Linux distribution in script/linux"
exit 1

237
script/linux Normal file
View File

@@ -0,0 +1,237 @@
#!/usr/bin/env bash
set -xeuo pipefail
# if root or if sudo/unavailable, define an empty variable
if [ "$(id -u)" -eq 0 ]
then maysudo=''
else maysudo="$(command -v sudo || command -v doas || true)"
fi
function finalize {
# after packages install (curl, etc), get the rust toolchain
which rustup > /dev/null 2>&1 || curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
# verify the mold situation
if ! command -v mold >/dev/null 2>&1; then
echo "Warning: Mold binaries are unavailable on your system." >&2
echo " Builds will be slower without mold. Try: script/install-mold" >&2
fi
echo "Finished installing Linux dependencies with script/linux"
}
# Ubuntu, Debian, Mint, Kali, Pop!_OS, Raspbian, etc.
apt=$(command -v apt-get || true)
if [[ -n $apt ]]; then
deps=(
gcc
g++
libasound2-dev
libfontconfig-dev
libwayland-dev
libx11-xcb-dev
libxkbcommon-x11-dev
libssl-dev
libzstd-dev
libvulkan1
libgit2-dev
libx11-dev
make
cmake
clang
jq
git
curl
gettext-base
elfutils
musl-tools
musl-dev
build-essential
)
if (grep -qP 'PRETTY_NAME="(Linux Mint 22|.+24\.(04|10))' /etc/os-release); then
deps+=( mold libstdc++-14-dev )
elif (grep -qP 'PRETTY_NAME="((Debian|Raspbian).+12|Linux Mint 21|.+22\.04)' /etc/os-release); then
deps+=( mold libstdc++-12-dev )
elif (grep -qP 'PRETTY_NAME="((Debian|Raspbian).+11|Linux Mint 20|.+20\.04)' /etc/os-release); then
deps+=( libstdc++-10-dev )
fi
$maysudo "$apt" update
$maysudo "$apt" install -y "${deps[@]}"
finalize
exit 0
fi
# Fedora, CentOS, RHEL, Alma, Amazon 2023, Oracle, etc.
dnf=$(command -v dnf || true)
# Old Redhat (yum only): Amazon Linux 2, Oracle Linux 7, etc.
yum=$(command -v yum || true)
if [[ -n $dnf ]] || [[ -n $yum ]]; then
pkg_cmd="${dnf:-${yum}}"
deps=(
musl-gcc
gcc
clang
cmake
alsa-lib-devel
fontconfig-devel
wayland-devel
libxcb-devel
libxkbcommon-x11-devel
openssl-devel
libzstd-devel
vulkan-loader
jq
git
tar
)
# perl used for building openssl-sys crate. See: https://docs.rs/openssl/latest/openssl/
if grep -qP '^ID="?(fedora)' /etc/os-release; then
deps+=(
perl-FindBin
perl-IPC-Cmd
perl-File-Compare
perl-File-Copy
mold
)
elif grep -qP '^ID="?(rhel|rocky|alma|centos|ol)' /etc/os-release; then
deps+=( perl-interpreter )
fi
# gcc-c++ is g++ on RHEL8 and 8.x clones
if grep -qP '^ID="?(rhel|rocky|alma|centos|ol)' /etc/os-release \
&& grep -qP '^VERSION_ID="?(8)' /etc/os-release; then
deps+=( gcc-c++ )
else
deps+=( g++ )
fi
# libxkbcommon-x11-devel is in a non-default repo on RHEL 8.x/9.x (except on AmazonLinux)
if grep -qP '^VERSION_ID="?(8|9)' /etc/os-release && grep -qP '^ID="?(rhel|rocky|centos|alma|ol)' /etc/os-release; then
$maysudo dnf install -y 'dnf-command(config-manager)'
if grep -qP '^PRETTY_NAME="(AlmaLinux 8|Rocky Linux 8)' /etc/os-release; then
$maysudo dnf config-manager --set-enabled powertools
elif grep -qP '^PRETTY_NAME="((AlmaLinux|Rocky|CentOS Stream) 9|Red Hat.+(8|9))' /etc/os-release; then
$maysudo dnf config-manager --set-enabled crb
elif grep -qP '^PRETTY_NAME="Oracle Linux Server 8' /etc/os-release; then
$maysudo dnf config-manager --set-enabled ol8_codeready_builder
elif grep -qP '^PRETTY_NAME="Oracle Linux Server 9' /etc/os-release; then
$maysudo dnf config-manager --set-enabled ol9_codeready_builder
else
echo "Unexpected distro" && grep 'PRETTY_NAME' /etc/os-release && exit 1
fi
fi
$maysudo "$pkg_cmd" install -y "${deps[@]}"
finalize
exit 0
fi
# openSUSE
# https://software.opensuse.org/
zyp=$(command -v zypper || true)
if [[ -n $zyp ]]; then
deps=(
alsa-devel
clang
cmake
fontconfig-devel
gcc
gcc-c++
git
gzip
jq
libvulkan1
libxcb-devel
libxkbcommon-devel
libxkbcommon-x11-devel
libzstd-devel
make
mold
openssl-devel
tar
wayland-devel
xcb-util-devel
)
$maysudo "$zyp" install -y "${deps[@]}"
finalize
exit 0
fi
# Arch, Manjaro, etc.
# https://archlinux.org/packages
pacman=$(command -v pacman || true)
if [[ -n $pacman ]]; then
deps=(
gcc
clang
musl
cmake
alsa-lib
fontconfig
wayland
libgit2
libxcb
libxkbcommon-x11
openssl
zstd
pkgconf
mold
jq
git
)
$maysudo "$pacman" -Syu --needed --noconfirm "${deps[@]}"
finalize
exit 0
fi
# Void
# https://voidlinux.org/packages/
xbps=$(command -v xbps-install || true)
if [[ -n $xbps ]]; then
deps=(
gettext-devel
clang
cmake
jq
elfutils-devel
gcc
alsa-lib-devel
fontconfig-devel
libxcb-devel
libxkbcommon-devel
libzstd-devel
openssl-devel
wayland-devel
vulkan-loader
mold
)
$maysudo "$xbps" -Syu "${deps[@]}"
finalize
exit 0
fi
# Gentoo
# https://packages.gentoo.org/
emerge=$(command -v emerge || true)
if [[ -n $emerge ]]; then
deps=(
app-arch/zstd
app-misc/jq
dev-libs/openssl
dev-libs/wayland
dev-util/cmake
media-libs/alsa-lib
media-libs/fontconfig
media-libs/vulkan-loader
x11-libs/libxcb
x11-libs/libxkbcommon
sys-devel/mold
)
$maysudo "$emerge" -u "${deps[@]}"
finalize
exit 0
fi
echo "Unsupported Linux distribution in script/linux"
exit 1

51
script/macos Normal file
View File

@@ -0,0 +1,51 @@
#!/usr/bin/env bash
set -xeuo pipefail
export HOMEBREW_NO_INSTALL_CLEANUP=1
# if root or if sudo/unavailable, define an empty variable
if [ "$(id -u)" -eq 0 ]
then maysudo=''
else maysudo="$(command -v sudo || command -v doas || true)"
fi
function finalize {
# after packages install (curl, etc), get the rust toolchain
which rustup > /dev/null 2>&1 || curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
# verify the mold situation
if ! command -v mold >/dev/null 2>&1; then
echo "Warning: Mold binaries are unavailable on your system." >&2
echo " Builds will be slower without mold. Try: script/install-mold" >&2
fi
echo "Finished installing MacOS dependencies with script/macos"
}
# MacOS
brew=$(command -v brew || true)
if [[ -n $brew ]]; then
deps=(
gcc
libx11
libxkbcommon
openssl
zstd
vulkan-headers
libgit2
libx11
make
cmake
jq
git
curl
gettext
)
$brew update
for dep in "${deps[@]}";do
$brew search "$dep";
done
$brew install "${deps[@]}"
finalize
exit 0
fi

View File

@@ -1,3 +0,0 @@
# Generated by Cargo
# will have compiled files and executables
/target/

View File

@@ -1 +0,0 @@
tab_spaces=2

6767
src-tauri/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,60 +0,0 @@
[package]
name = "lume"
version = "2.2.0"
description = "the communication app"
authors = ["Ren Amamiya"]
license = "GPL-3.0"
repository = "https://github.com/luminous-devs/lume"
edition = "2021"
rust-version = "1.66"
[build-dependencies]
tauri-build = { version = "2.0.0-alpha", features = [] }
[dependencies]
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
tauri = { version = "2.0.0-alpha", features = [
"macos-private-api",
"native-tls-vendored",
] }
tauri-plugin-cli = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
tauri-plugin-clipboard-manager = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
tauri-plugin-dialog = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
tauri-plugin-fs = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
tauri-plugin-http = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
tauri-plugin-notification = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
tauri-plugin-os = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
tauri-plugin-process = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
tauri-plugin-shell = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
tauri-plugin-updater = { 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-store = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
tauri-plugin-upload = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
tauri-plugin-window-state = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
tauri-plugin-theme = { git = "https://github.com/wyhaya/tauri-plugin-theme" }
tauri-plugin-sql = { git = "hhttps://github.com/tauri-apps/plugins-workspace", branch = "v2", features = [
"sqlite",
] }
sqlx-cli = { version = "0.7.0", default-features = false, features = [
"sqlite",
] }
webpage = { version = "1.6.0", features = ["serde"] }
keyring = "2"
[features]
# by default Tauri runs in production mode
# when `tauri dev` runs it is executed with `cargo run --no-default-features` if `devPath` is an URL
default = ["custom-protocol"]
# this feature is used used for production builds where `devPath` points to the filesystem
# DO NOT remove this
custom-protocol = ["tauri/custom-protocol"]
# Optimized for bundle size. If you want faster builds comment out/delete this section.
[profile.release]
lto = true # Enable Link Time Optimization
opt-level = "z" # Optimize for size.
codegen-units = 1 # Reduce number of codegen units to increase optimizations.
panic = "abort" # Abort on panic
strip = true # Automatically strip symbols from the binary.
debug = false

View File

@@ -1,3 +0,0 @@
fn main() {
tauri_build::build()
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 886 B

Some files were not shown because too many files have changed in this diff Show More