Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
9e49e8c2f1
|
@@ -1,4 +1,4 @@
|
|||||||
use nix
|
#use nix
|
||||||
|
|
||||||
export TODO_FILE=$PWD/TODO
|
export TODO_FILE=$PWD/TODO
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- "master"
|
- "master"
|
||||||
- "ci"
|
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- "*"
|
- "*"
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
xcuserdata
|
xcuserdata
|
||||||
/.direnv
|
/.direnv
|
||||||
damus/TestingPrivate.swift
|
damus/TestingPrivate.swift
|
||||||
damus.xcodeproj/xcshareddata/xcbaselines
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
TODO.bak
|
TODO.bak
|
||||||
tags
|
|
||||||
build-git-hash.txt
|
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
Terry Yiu <git@tyiu.xyz> <963907+tyiu@users.noreply.github.com>
|
|
||||||
Ben Weeks <ben.weeks@knowall.ai> <ben.weeks@outlook.com>
|
|
||||||
Suhail Saqan <suhail.saqan@gmail.com> <43693074+suhailsaqan@users.noreply.github.com>
|
|
||||||
cr0bar <cr0bar@cr0.bar> <cr0bar@users.noreply.github.com>
|
|
||||||
Swift <scoder1747@gmail.com> <120697811+scoder1747@users.noreply.github.com>
|
|
||||||
Daniel D'Aquino <daniel@daquino.me> <patches@damus.io>
|
|
||||||
-298
@@ -1,300 +1,3 @@
|
|||||||
## [1.6-18] - 2023-09-21
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- Add followed hashtags to your following list (Daniel D’Aquino)
|
|
||||||
- Add "Do not show #nsfw tagged posts" setting (Daniel D’Aquino)
|
|
||||||
- Hold tap to preview status URL (Jericho Hasselbush)
|
|
||||||
- Finnish translations (etrikaj)
|
|
||||||
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- Switch to nostrdb for @'s and user search (William Casarin)
|
|
||||||
- Use nostrdb for profiles (William Casarin)
|
|
||||||
- Updated relay view (ericholguin)
|
|
||||||
- Increase size of the hitbox on note ellipsis button (Daniel D’Aquino)
|
|
||||||
- Make carousel tab dots tappable (Bryan Montz)
|
|
||||||
- Move the "Follow you" badge into the profile header (Grimless)
|
|
||||||
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Fix text composer wrapping issue when mentioning npub (Daniel D’Aquino)
|
|
||||||
- Make blurred videos viewable by allowing blur to disappear once tapped (Daniel D’Aquino)
|
|
||||||
- Fix parsing issue with NIP-47 compliant NWC urls without double-slashes (Daniel D’Aquino)
|
|
||||||
- Fix padding of username next to pfp on some views (William Casarin)
|
|
||||||
- Fixes issue where username with multiple emojis would place cursor in strange position. (Jericho Hasselbush)
|
|
||||||
- Fixed audio in video playing twice (Bryan Montz)
|
|
||||||
- Fix crash when long pressing custom reactions (William Casarin)
|
|
||||||
- Fix random crashom due to old profile database (William Casarin)
|
|
||||||
|
|
||||||
[1.6-18]: https://github.com/damus-io/damus/releases/tag/v1.6-18
|
|
||||||
|
|
||||||
## [1.6-17] - 2023-08-23
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- Add support for status URLs (William Casarin)
|
|
||||||
- Click music statuses to display in spotify (William Casarin)
|
|
||||||
- Add settings for disabling user statuses (William Casarin)
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- clear statuses if they only contain whitespace (William Casarin)
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Fix long status lines (William Casarin)
|
|
||||||
- Fix status events not expiring locally (William Casarin)
|
|
||||||
|
|
||||||
[1.6-17]: https://github.com/damus-io/damus/releases/tag/v1.6-17
|
|
||||||
|
|
||||||
## [1.6-16] - 2023-08-23
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- Added live music statuses (William Casarin)
|
|
||||||
- Added generic user statuses (William Casarin)
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Avoid notification for zaps from muted profiles (tappu75e@duck.com)
|
|
||||||
- Fix text editing issues on characters added right after mention link (Daniel D’Aquino)
|
|
||||||
- Mute hellthreads everywhere (William Casarin)
|
|
||||||
|
|
||||||
|
|
||||||
[1.6-16]: https://github.com/damus-io/damus/releases/tag/v1.6-16
|
|
||||||
|
|
||||||
## [1.6-13] - 2023-08-18
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Fix bug where it would sometimes show -1 in replies (tappu75e@duck.com)
|
|
||||||
- Fix images and links occasionally appearing with escaped slashes (Daniel D‘Aquino)
|
|
||||||
- Fixed nostrscript not working on smaller phones (William Casarin)
|
|
||||||
- Fix zaps sometimes not appearing (William Casarin)
|
|
||||||
- Fixed issue where reposts would sometimes repost the wrong thing (William Casarin)
|
|
||||||
- Fixed issue where sometimes there would be empty entries on your profile (William Casarin)
|
|
||||||
|
|
||||||
[1.6-13]: https://github.com/damus-io/damus/releases/tag/v1.6-13
|
|
||||||
|
|
||||||
|
|
||||||
## [1.6-11]: "Bugfix Sunday" - 2023-08-07
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- Add close button to custom reactions (Suhail Saqan)
|
|
||||||
- Add ability to change order of custom reactions (Suhail Saqan)
|
|
||||||
- Adjustable font size (William Casarin)
|
|
||||||
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- Show renotes in Notes timeline (William Casarin)
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Ensure the person you're replying to is the first entry in the reply description (William Casarin)
|
|
||||||
- Don't cutoff text in notifications (William Casarin)
|
|
||||||
- Fix wikipedia url detection with parenthesis (William Casarin)
|
|
||||||
- Fixed old notifications always appearing on first start (William Casarin)
|
|
||||||
- Fix issue with slashes on relay urls causing relay connection problems (William Casarin)
|
|
||||||
- Fix rare crash triggered by local notifications (William Casarin)
|
|
||||||
- Fix crash when long-pressing reactions (William Casarin)
|
|
||||||
- Fixed nostr reporting decoding (William Casarin)
|
|
||||||
- Dismiss qr screen on scan (Suhail Saqan)
|
|
||||||
- Show QRCameraView regardless of same user (Suhail Saqan)
|
|
||||||
- Fix wiggle when long press reactions (Suhail Saqan)
|
|
||||||
- Fix reaction button breaking scrolling (Suhail Saqan)
|
|
||||||
- Fix crash when muting threads (Bryan Montz)
|
|
||||||
|
|
||||||
|
|
||||||
[1.6-11]: https://github.com/damus-io/damus/releases/tag/v1.6-11
|
|
||||||
|
|
||||||
## [1.6-8]: "nostrdb prep" 2023-08-03
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- Suggested Users to Follow (Joel Klabo)
|
|
||||||
- Add support for multiple reactions (Suhail Saqan)
|
|
||||||
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- Improved memory usage and performance when processing events (William Casarin)
|
|
||||||
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Fixed disappearing text on iOS17 (cr0bar)
|
|
||||||
- Fix UTF support for hashtags (Daniel D‘Aquino)
|
|
||||||
- Fix compilation error on test target in UserSearchCacheTests (Daniel D‘Aquino)
|
|
||||||
- Fix nav crashing and buggyness (William Casarin)
|
|
||||||
- Allow relay logs to be opened in dev mode even if relay (Daniel D'Aquino)
|
|
||||||
- endless connection attempt loop after user removes relay (Bryan Montz)
|
|
||||||
|
|
||||||
|
|
||||||
[1.6-8]: https://github.com/damus-io/damus/releases/tag/v1.6-8
|
|
||||||
|
|
||||||
## 1.6 (7): "Less bad" - 2023-07-16
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- Show nostr address username and support abbreviated _ usernames (William Casarin)
|
|
||||||
- Re-add nip05 badges to profiles (William Casarin)
|
|
||||||
- Add space when tagging users in posts if needed (William Casarin)
|
|
||||||
- Added padding under word count on longform account (William Casarin)
|
|
||||||
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Don't spam lnurls when validating zaps (William Casarin)
|
|
||||||
- Eliminate nostr address validation bandwidth on startup (William Casarin)
|
|
||||||
- Allow user to login to deleted profile (William Casarin)
|
|
||||||
- Fix issue where typing cc@bob would produce brokenb ccnostr:bob mention (William Casarin)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[1.6-7]: https://github.com/damus-io/damus/releases/tag/v1.6-7
|
|
||||||
|
|
||||||
## [1.6-6] - 2023-07-16
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- New markdown renderer (William Casarin)
|
|
||||||
- Added feedback when user adds a relay that is already on the list (Daniel D'Aquino)
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- Hide nsec when logging in (cr0bar)
|
|
||||||
- Remove nip05 on events (William Casarin)
|
|
||||||
- Rename NIP05 to "nostr address" (William Casarin)
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Fixed issue where hashtags were leaking in DMs (William Casarin)
|
|
||||||
- Fix issue with emojis next to hashtags and urls (William Casarin)
|
|
||||||
- relay detail view is not immediately available after adding new relay (Bryan Montz)
|
|
||||||
- Fix nostr:nostr:... bugs (William Casarin)
|
|
||||||
|
|
||||||
|
|
||||||
[1.6-6]: https://github.com/damus-io/damus/releases/tag/v1.6-6
|
|
||||||
|
|
||||||
## [1.6-4] - 2023-07-13
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- Add the ability to follow hashtags (William Casarin)
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- Remove note size restriction for longform events (William Casarin)
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Hide users and hashtags from home timeline when you unfollow (William Casarin)
|
|
||||||
- Fixed a bug where following a user might not work due to poor connectivity (William Casarin)
|
|
||||||
- Icon color for developer mode setting is incorrect in low-light mode (Bryan Montz)
|
|
||||||
- Fixed nav bar color on login, eula, and account creation (ericholguin)
|
|
||||||
|
|
||||||
|
|
||||||
### Removed
|
|
||||||
|
|
||||||
- Remove following Damus Will by default (William Casarin)
|
|
||||||
|
|
||||||
[1.6-4]: https://github.com/damus-io/damus/releases/tag/v1.6-4
|
|
||||||
|
|
||||||
## [1.6-3] - 2023-07-11
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- Start at top when reading longform events (William Casarin)
|
|
||||||
- Allow reposting and quote reposting multiple times (William Casarin)
|
|
||||||
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Show longform previews in notifications instead of the entire post (William Casarin)
|
|
||||||
- Fix padding on longform events (William Casarin)
|
|
||||||
- Fix action bar appearing on quoted longform previews (William Casarin)
|
|
||||||
|
|
||||||
|
|
||||||
[1.6-3]: https://github.com/damus-io/damus/releases/tag/v1.6-3
|
|
||||||
## [1.6-2] - 2023-07-11
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- Add support for multilingual hashtags (cr0bar)
|
|
||||||
- Add r tag when mentioning a url (William Casarin)
|
|
||||||
- Add initial longform note support (William Casarin)
|
|
||||||
- Enable banner image editing (Joel Klabo)
|
|
||||||
- Add relay log in developer mode (Bryan Montz)
|
|
||||||
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Fix lag when creating large posts (William Casarin)
|
|
||||||
- Fix npub mentions failing to parse in some cases (William Casarin)
|
|
||||||
- Fix PostView initial string to skip mentioning self when on own profile (Terry Yiu)
|
|
||||||
- Fix freezing bug when tapping Developer settings menu (Terry Yiu)
|
|
||||||
- Fix potential fake profile zap attacks (William Casarin)
|
|
||||||
- Fix issue where malicious zappers can send fake zaps to another user's posts (William Casarin)
|
|
||||||
- Fix profile post button mentions (cr0bar)
|
|
||||||
- Fix icons on settings view (cr0bar)
|
|
||||||
- Fix Invalid Zap bug in reposts (William Casarin)
|
|
||||||
|
|
||||||
|
|
||||||
### Removed
|
|
||||||
|
|
||||||
- Remove old @ and & hex key mentions (William Casarin)
|
|
||||||
|
|
||||||
|
|
||||||
[1.6-2]: https://github.com/damus-io/damus/releases/tag/v1.6-2
|
|
||||||
## [1.6] - 2023-07-04
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- Speed up user search (Terry Yiu)
|
|
||||||
- Add post button to profile pages (William Casarin)
|
|
||||||
- Add post button when logged in with private key and on own profile view (Terry Yiu)
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- Drop iOS15 support (Scott Penrose)
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Load more content on profile view (William Casarin)
|
|
||||||
- Fix reports to conform to NIP-56 (Terry Yiu)
|
|
||||||
- Fix profile navigation bugs from muted users list and relay list views (Terry Yiu)
|
|
||||||
- Fix navigation to translation settings view (Terry Yiu)
|
|
||||||
- Fixed all navigation issues (Scott Penrose)
|
|
||||||
- Disable post button when media upload in progress (Terry Yiu)
|
|
||||||
- Fix taps on mentions in note drafts to not redirect to other Nostr clients (Terry Yiu)
|
|
||||||
- Fix missing profile zap notification text (Terry Yiu)
|
|
||||||
|
|
||||||
|
|
||||||
[1.6]: https://github.com/damus-io/damus/releases/tag/v1.6
|
|
||||||
|
|
||||||
## [1.5-5] - 2023-06-24
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Remove note zaps to fit apples appstore guidelines
|
|
||||||
- Fix zap sheet popping (William Casarin)
|
|
||||||
- Fix CustomizeZapView from randomly disappearing (William Casarin)
|
|
||||||
- Fix "zapped your profile" strings to say "zapped you" (Terry Yiu)
|
|
||||||
- Fix reconnect loop issues on iOS17 (William Casarin)
|
|
||||||
- Fix some more thread jankiness (William Casarin)
|
|
||||||
- Fix spelling of Nostr to use Titlecase instead of lowercase (Terry Yiu)
|
|
||||||
- Rename all usages of the term Post as a noun to Note to conform to the Nostr spec (Terry Yiu)
|
|
||||||
- Fix text cutoff on login with npub (gladiusKatana)
|
|
||||||
- Fix hangs due to video player (William Casarin)
|
|
||||||
|
|
||||||
|
|
||||||
[1.5-5]: https://github.com/damus-io/damus/releases/tag/v1.5-5
|
|
||||||
|
|
||||||
## [1.5-2] - 2023-05-30
|
## [1.5-2] - 2023-05-30
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
@@ -1559,4 +1262,3 @@
|
|||||||
|
|
||||||
[0.1.2]: https://github.com/damus-io/damus/releases/tag/v0.1.2
|
[0.1.2]: https://github.com/damus-io/damus/releases/tag/v0.1.2
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
|
|
||||||
all: nostrscript/primal.wasm
|
|
||||||
|
|
||||||
nostrscript/%.wasm: nostrscript/%.ts nostrscript/nostr.ts Makefile
|
|
||||||
asc $< --runtime stub --outFile $@ --optimize
|
|
||||||
|
|
||||||
tags:
|
|
||||||
find damus-c -name '*.c' -or -name '*.h' | xargs ctags
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm nostrscript/*.wasm
|
|
||||||
|
|
||||||
.PHONY: tags
|
|
||||||
@@ -94,23 +94,11 @@ damus implements the following [Nostr Implementation Possibilities][nips]
|
|||||||
|
|
||||||
Contributors welcome! Start by examining known issues: https://github.com/damus-io/damus/issues.
|
Contributors welcome! Start by examining known issues: https://github.com/damus-io/damus/issues.
|
||||||
|
|
||||||
### Mailing lists
|
### Code
|
||||||
|
|
||||||
We have a few mailing lists that anyone can join to get involved in damus development:
|
[Email patches][git-send-email] to jb55@jb55.com are preferred, but I accept PRs on GitHub as well.
|
||||||
|
|
||||||
- [dev][dev-list] - development discussions
|
[git-send-email]: http://git-send-email.io
|
||||||
- [patches][patches-list] - code submission and review
|
|
||||||
- [product][product-list] - product discussions
|
|
||||||
- [design][design-list] - design discussions
|
|
||||||
|
|
||||||
[dev-list]: https://damus.io/list/dev
|
|
||||||
[patches-list]: https://damus.io/list/patches
|
|
||||||
[product-list]: https://damus.io/list/product
|
|
||||||
[design-list]: https://damus.io/list/design
|
|
||||||
|
|
||||||
### Contributing
|
|
||||||
|
|
||||||
See [docs/CONTRIBUTING.md](./docs/CONTRIBUTING.md)
|
|
||||||
|
|
||||||
### Privacy
|
### Privacy
|
||||||
Your internet protocol (IP) address is exposed to the relays you connect to, and third party media hosters (e.g. nostr.build, imgur.com, giphy.com, youtube.com etc.) that render on Damus. If you want to improve your privacy, consider utilizing a service that masks your IP address (e.g. a VPN) from trackers online.
|
Your internet protocol (IP) address is exposed to the relays you connect to, and third party media hosters (e.g. nostr.build, imgur.com, giphy.com, youtube.com etc.) that render on Damus. If you want to improve your privacy, consider utilizing a service that masks your IP address (e.g. a VPN) from trackers online.
|
||||||
|
|||||||
+5
-6
@@ -35,7 +35,7 @@ typedef struct mention_bech32_block {
|
|||||||
struct nostr_bech32 bech32;
|
struct nostr_bech32 bech32;
|
||||||
} mention_bech32_block_t;
|
} mention_bech32_block_t;
|
||||||
|
|
||||||
typedef struct note_block {
|
typedef struct block {
|
||||||
enum block_type type;
|
enum block_type type;
|
||||||
union {
|
union {
|
||||||
struct str_block str;
|
struct str_block str;
|
||||||
@@ -45,13 +45,12 @@ typedef struct note_block {
|
|||||||
} block;
|
} block;
|
||||||
} block_t;
|
} block_t;
|
||||||
|
|
||||||
typedef struct note_blocks {
|
typedef struct blocks {
|
||||||
int words;
|
|
||||||
int num_blocks;
|
int num_blocks;
|
||||||
struct note_block *blocks;
|
struct block *blocks;
|
||||||
} blocks_t;
|
} blocks_t;
|
||||||
|
|
||||||
void blocks_init(struct note_blocks *blocks);
|
void blocks_init(struct blocks *blocks);
|
||||||
void blocks_free(struct note_blocks *blocks);
|
void blocks_free(struct blocks *blocks);
|
||||||
|
|
||||||
#endif /* block_h */
|
#endif /* block_h */
|
||||||
|
|||||||
+90
-600
@@ -1,629 +1,57 @@
|
|||||||
|
//
|
||||||
|
// cursor.h
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by William Casarin on 2023-04-09.
|
||||||
|
//
|
||||||
|
|
||||||
#ifndef JB55_CURSOR_H
|
#ifndef cursor_h
|
||||||
#define JB55_CURSOR_H
|
#define cursor_h
|
||||||
|
|
||||||
#include "typedefs.h"
|
|
||||||
#include "varint.h"
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
#include <assert.h>
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#define unlikely(x) __builtin_expect((x),0)
|
typedef unsigned char u8;
|
||||||
#define likely(x) __builtin_expect((x),1)
|
|
||||||
|
|
||||||
struct cursor {
|
struct cursor {
|
||||||
unsigned char *start;
|
const u8 *p;
|
||||||
unsigned char *p;
|
const u8 *start;
|
||||||
unsigned char *end;
|
const u8 *end;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct array {
|
|
||||||
struct cursor cur;
|
|
||||||
unsigned int elem_size;
|
|
||||||
};
|
|
||||||
|
|
||||||
static inline void reset_cursor(struct cursor *cursor)
|
|
||||||
{
|
|
||||||
cursor->p = cursor->start;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void wipe_cursor(struct cursor *cursor)
|
|
||||||
{
|
|
||||||
reset_cursor(cursor);
|
|
||||||
memset(cursor->start, 0, cursor->end - cursor->start);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void make_cursor(u8 *start, u8 *end, struct cursor *cursor)
|
|
||||||
{
|
|
||||||
cursor->start = start;
|
|
||||||
cursor->p = start;
|
|
||||||
cursor->end = end;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void make_array(struct array *a, u8* start, u8 *end, unsigned int elem_size)
|
|
||||||
{
|
|
||||||
make_cursor(start, end, &a->cur);
|
|
||||||
a->elem_size = elem_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline int cursor_eof(struct cursor *c)
|
|
||||||
{
|
|
||||||
return c->p == c->end;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void *cursor_malloc(struct cursor *mem, unsigned long size)
|
|
||||||
{
|
|
||||||
void *ret;
|
|
||||||
|
|
||||||
if (mem->p + size > mem->end) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = mem->p;
|
|
||||||
mem->p += size;
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void *cursor_alloc(struct cursor *mem, unsigned long size)
|
|
||||||
{
|
|
||||||
void *ret;
|
|
||||||
if (!(ret = cursor_malloc(mem, size))) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
memset(ret, 0, size);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline int cursor_slice(struct cursor *mem, struct cursor *slice, size_t size)
|
|
||||||
{
|
|
||||||
u8 *p;
|
|
||||||
if (!(p = cursor_alloc(mem, size))) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
make_cursor(p, mem->p, slice);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static inline void copy_cursor(struct cursor *src, struct cursor *dest)
|
|
||||||
{
|
|
||||||
dest->start = src->start;
|
|
||||||
dest->p = src->p;
|
|
||||||
dest->end = src->end;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline int pull_byte(struct cursor *cursor, u8 *c)
|
|
||||||
{
|
|
||||||
if (unlikely(cursor->p >= cursor->end))
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
*c = *cursor->p;
|
|
||||||
cursor->p++;
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline int parse_byte(struct cursor *cursor, u8 *c)
|
|
||||||
{
|
|
||||||
if (unlikely(cursor->p >= cursor->end))
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
*c = *cursor->p;
|
|
||||||
//cursor->p++;
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline int parse_char(struct cursor *cur, char c) {
|
|
||||||
if (cur->p >= cur->end)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
if (*cur->p == c) {
|
|
||||||
cur->p++;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline int peek_char(struct cursor *cur, int ind) {
|
|
||||||
if ((cur->p + ind < cur->start) || (cur->p + ind >= cur->end))
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
return *(cur->p + ind);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline int cursor_pull_c_str(struct cursor *cursor, const char **str)
|
|
||||||
{
|
|
||||||
*str = (const char*)cursor->p;
|
|
||||||
|
|
||||||
for (; cursor->p < cursor->end; cursor->p++) {
|
|
||||||
if (*cursor->p == 0) {
|
|
||||||
cursor->p++;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static inline int cursor_push_byte(struct cursor *cursor, u8 c)
|
|
||||||
{
|
|
||||||
if (unlikely(cursor->p + 1 > cursor->end)) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
*cursor->p = c;
|
|
||||||
cursor->p++;
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline int cursor_pull(struct cursor *cursor, u8 *data, int len)
|
|
||||||
{
|
|
||||||
if (unlikely(cursor->p + len > cursor->end)) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
memcpy(data, cursor->p, len);
|
|
||||||
cursor->p += len;
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline int pull_data_into_cursor(struct cursor *cursor,
|
|
||||||
struct cursor *dest,
|
|
||||||
unsigned char **data,
|
|
||||||
int len)
|
|
||||||
{
|
|
||||||
int ok;
|
|
||||||
|
|
||||||
if (unlikely(dest->p + len > dest->end)) {
|
|
||||||
printf("not enough room in dest buffer\n");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ok = cursor_pull(cursor, dest->p, len);
|
|
||||||
if (!ok) return 0;
|
|
||||||
|
|
||||||
*data = dest->p;
|
|
||||||
dest->p += len;
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline int cursor_dropn(struct cursor *cur, int size, int n)
|
|
||||||
{
|
|
||||||
if (n == 0)
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
if (unlikely(cur->p - size*n < cur->start)) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
cur->p -= size*n;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline int cursor_drop(struct cursor *cur, int size)
|
|
||||||
{
|
|
||||||
return cursor_dropn(cur, size, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline unsigned char *cursor_topn(struct cursor *cur, int len, int n)
|
|
||||||
{
|
|
||||||
n += 1;
|
|
||||||
if (unlikely(cur->p - len*n < cur->start)) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
return cur->p - len*n;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline unsigned char *cursor_top(struct cursor *cur, int len)
|
|
||||||
{
|
|
||||||
if (unlikely(cur->p - len < cur->start)) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
return cur->p - len;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline int cursor_top_int(struct cursor *cur, int *i)
|
|
||||||
{
|
|
||||||
u8 *p;
|
|
||||||
if (unlikely(!(p = cursor_top(cur, sizeof(*i))))) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
*i = *((int*)p);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline int cursor_pop(struct cursor *cur, u8 *data, int len)
|
|
||||||
{
|
|
||||||
if (unlikely(cur->p - len < cur->start)) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
cur->p -= len;
|
|
||||||
memcpy(data, cur->p, len);
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline int cursor_push(struct cursor *cursor, u8 *data, int len)
|
|
||||||
{
|
|
||||||
if (unlikely(cursor->p + len >= cursor->end)) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cursor->p != data)
|
|
||||||
memcpy(cursor->p, data, len);
|
|
||||||
|
|
||||||
cursor->p += len;
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline int cursor_push_int(struct cursor *cursor, int i)
|
|
||||||
{
|
|
||||||
return cursor_push(cursor, (u8*)&i, sizeof(i));
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline size_t cursor_count(struct cursor *cursor, size_t elem_size)
|
|
||||||
{
|
|
||||||
return (cursor->p - cursor->start)/elem_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* TODO: push_varint */
|
|
||||||
static inline int push_varint(struct cursor *cursor, int n)
|
|
||||||
{
|
|
||||||
int ok, len;
|
|
||||||
unsigned char b;
|
|
||||||
len = 0;
|
|
||||||
|
|
||||||
while (1) {
|
|
||||||
b = (n & 0xFF) | 0x80;
|
|
||||||
n >>= 7;
|
|
||||||
if (n == 0) {
|
|
||||||
b &= 0x7F;
|
|
||||||
ok = cursor_push_byte(cursor, b);
|
|
||||||
len++;
|
|
||||||
if (!ok) return 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
ok = cursor_push_byte(cursor, b);
|
|
||||||
len++;
|
|
||||||
if (!ok) return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return len;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* TODO: pull_varint */
|
|
||||||
static inline int pull_varint(struct cursor *cursor, int *n)
|
|
||||||
{
|
|
||||||
int ok, i;
|
|
||||||
unsigned char b;
|
|
||||||
*n = 0;
|
|
||||||
|
|
||||||
for (i = 0;; i++) {
|
|
||||||
ok = pull_byte(cursor, &b);
|
|
||||||
if (!ok) return 0;
|
|
||||||
|
|
||||||
*n |= ((int)b & 0x7F) << (i * 7);
|
|
||||||
|
|
||||||
/* is_last */
|
|
||||||
if ((b & 0x80) == 0) {
|
|
||||||
return i+1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i == 4) return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline int cursor_pull_int(struct cursor *cursor, int *i)
|
|
||||||
{
|
|
||||||
return cursor_pull(cursor, (u8*)i, sizeof(*i));
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline int cursor_push_u32(struct cursor *cursor, uint32_t i) {
|
|
||||||
return cursor_push(cursor, (unsigned char*)&i, sizeof(i));
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline int cursor_push_u16(struct cursor *cursor, u16 i)
|
|
||||||
{
|
|
||||||
return cursor_push(cursor, (u8*)&i, sizeof(i));
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void *index_cursor(struct cursor *cursor, unsigned int index, int elem_size)
|
|
||||||
{
|
|
||||||
u8 *p;
|
|
||||||
p = &cursor->start[elem_size * index];
|
|
||||||
|
|
||||||
if (unlikely(p >= cursor->end))
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
return (void*)p;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static inline int push_sized_str(struct cursor *cursor, const char *str, int len)
|
|
||||||
{
|
|
||||||
return cursor_push(cursor, (u8*)str, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline int cursor_push_str(struct cursor *cursor, const char *str)
|
|
||||||
{
|
|
||||||
return cursor_push(cursor, (u8*)str, (int)strlen(str));
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline int cursor_push_c_str(struct cursor *cursor, const char *str)
|
|
||||||
{
|
|
||||||
return cursor_push_str(cursor, str) && cursor_push_byte(cursor, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* TODO: push varint size */
|
|
||||||
static inline int push_prefixed_str(struct cursor *cursor, const char *str)
|
|
||||||
{
|
|
||||||
int ok, len;
|
|
||||||
len = (int)strlen(str);
|
|
||||||
ok = push_varint(cursor, len);
|
|
||||||
if (!ok) return 0;
|
|
||||||
return push_sized_str(cursor, str, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline int pull_prefixed_str(struct cursor *cursor, struct cursor *dest_buf, const char **str)
|
|
||||||
{
|
|
||||||
int len, ok;
|
|
||||||
|
|
||||||
ok = pull_varint(cursor, &len);
|
|
||||||
if (!ok) return 0;
|
|
||||||
|
|
||||||
if (unlikely(dest_buf->p + len > dest_buf->end)) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ok = pull_data_into_cursor(cursor, dest_buf, (unsigned char**)str, len);
|
|
||||||
if (!ok) return 0;
|
|
||||||
|
|
||||||
ok = cursor_push_byte(dest_buf, 0);
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline int cursor_remaining_capacity(struct cursor *cursor)
|
|
||||||
{
|
|
||||||
return (int)(cursor->end - cursor->p);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#define max(a,b) ((a) > (b) ? (a) : (b))
|
|
||||||
static inline void cursor_print_around(struct cursor *cur, int range)
|
|
||||||
{
|
|
||||||
unsigned char *c;
|
|
||||||
|
|
||||||
printf("[%ld/%ld]\n", cur->p - cur->start, cur->end - cur->start);
|
|
||||||
|
|
||||||
c = max(cur->p - range, cur->start);
|
|
||||||
for (; c < cur->end && c < (cur->p + range); c++) {
|
|
||||||
printf("%02x", *c);
|
|
||||||
}
|
|
||||||
printf("\n");
|
|
||||||
|
|
||||||
c = max(cur->p - range, cur->start);
|
|
||||||
for (; c < cur->end && c < (cur->p + range); c++) {
|
|
||||||
if (c == cur->p) {
|
|
||||||
printf("^");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
printf(" ");
|
|
||||||
}
|
|
||||||
printf("\n");
|
|
||||||
}
|
|
||||||
#undef max
|
|
||||||
|
|
||||||
static inline int pull_bytes(struct cursor *cur, int count, const u8 **bytes) {
|
|
||||||
if (cur->p + count > cur->end)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
*bytes = cur->p;
|
|
||||||
cur->p += count;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline int parse_str(struct cursor *cur, const char *str) {
|
|
||||||
int i;
|
|
||||||
char c, cs;
|
|
||||||
unsigned long len;
|
|
||||||
|
|
||||||
len = strlen(str);
|
|
||||||
|
|
||||||
if (cur->p + len >= cur->end)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
for (i = 0; i < len; i++) {
|
|
||||||
c = tolower(cur->p[i]);
|
|
||||||
cs = tolower(str[i]);
|
|
||||||
|
|
||||||
if (c != cs)
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
cur->p += len;
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline int is_whitespace(char c) {
|
static inline int is_whitespace(char c) {
|
||||||
return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r';
|
return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r';
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline int is_underscore(char c) {
|
static inline int is_boundary(char c) {
|
||||||
return c == '_';
|
return !isalnum(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline int is_utf8_byte(u8 c) {
|
static inline int is_invalid_url_ending(char c) {
|
||||||
return c & 0x80;
|
return c == '!' || c == '?' || c == ')' || c == '.' || c == ',' || c == ';';
|
||||||
}
|
|
||||||
|
|
||||||
static inline int parse_utf8_char(struct cursor *cursor, unsigned int *code_point, unsigned int *utf8_length)
|
|
||||||
{
|
|
||||||
u8 first_byte;
|
|
||||||
if (!parse_byte(cursor, &first_byte))
|
|
||||||
return 0; // Not enough data
|
|
||||||
|
|
||||||
// Determine the number of bytes in this UTF-8 character
|
|
||||||
int remaining_bytes = 0;
|
|
||||||
if (first_byte < 0x80) {
|
|
||||||
*code_point = first_byte;
|
|
||||||
return 1;
|
|
||||||
} else if ((first_byte & 0xE0) == 0xC0) {
|
|
||||||
remaining_bytes = 1;
|
|
||||||
*utf8_length = remaining_bytes + 1;
|
|
||||||
*code_point = first_byte & 0x1F;
|
|
||||||
} else if ((first_byte & 0xF0) == 0xE0) {
|
|
||||||
remaining_bytes = 2;
|
|
||||||
*utf8_length = remaining_bytes + 1;
|
|
||||||
*code_point = first_byte & 0x0F;
|
|
||||||
} else if ((first_byte & 0xF8) == 0xF0) {
|
|
||||||
remaining_bytes = 3;
|
|
||||||
*utf8_length = remaining_bytes + 1;
|
|
||||||
*code_point = first_byte & 0x07;
|
|
||||||
} else {
|
|
||||||
remaining_bytes = 0;
|
|
||||||
*utf8_length = 1; // Assume 1 byte length for unrecognized UTF-8 characters
|
|
||||||
// TODO: We need to gracefully handle unrecognized UTF-8 characters
|
|
||||||
printf("Invalid UTF-8 byte: %x\n", *code_point);
|
|
||||||
*code_point = ((first_byte & 0xF0) << 6); // Prevent testing as punctuation
|
|
||||||
return 0; // Invalid first byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// Peek at remaining bytes
|
|
||||||
for (int i = 0; i < remaining_bytes; ++i) {
|
|
||||||
signed char next_byte;
|
|
||||||
if ((next_byte = peek_char(cursor, i+1)) == -1) {
|
|
||||||
*utf8_length = 1;
|
|
||||||
return 0; // Not enough data
|
|
||||||
}
|
|
||||||
|
|
||||||
// Debugging lines
|
|
||||||
//printf("Cursor: %s\n", cursor->p);
|
|
||||||
//printf("Codepoint: %x\n", *code_point);
|
|
||||||
//printf("Codepoint <<6: %x\n", ((*code_point << 6) | (next_byte & 0x3F)));
|
|
||||||
//printf("Remaining bytes: %x\n", remaining_bytes);
|
|
||||||
//printf("First byte: %x\n", first_byte);
|
|
||||||
//printf("Next byte: %x\n", next_byte);
|
|
||||||
//printf("Bitwise AND result: %x\n", (next_byte & 0xC0));
|
|
||||||
|
|
||||||
if ((next_byte & 0xC0) != 0x80) {
|
|
||||||
*utf8_length = 1;
|
|
||||||
return 0; // Invalid byte in sequence
|
|
||||||
}
|
|
||||||
|
|
||||||
*code_point = (*code_point << 6) | (next_byte & 0x3F);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if a given Unicode code point is a punctuation character
|
|
||||||
*
|
|
||||||
* @param codepoint The Unicode code point to check. @return true if the
|
|
||||||
* code point is a punctuation character, false otherwise.
|
|
||||||
*/
|
|
||||||
static inline int is_punctuation(unsigned int codepoint) {
|
|
||||||
|
|
||||||
// Check for underscore (underscore is not treated as punctuation)
|
|
||||||
if (is_underscore(codepoint))
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
// Check for ASCII punctuation
|
|
||||||
if (ispunct(codepoint))
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
// Check for Unicode punctuation exceptions (punctuation allowed in hashtags)
|
|
||||||
if (codepoint == 0x301C || codepoint == 0xFF5E) // Japanese Wave Dash / Tilde
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
// Check for Unicode punctuation
|
|
||||||
// NOTE: We may need to adjust the codepoint ranges in the future,
|
|
||||||
// to include/exclude certain types of Unicode characters in hashtags.
|
|
||||||
// Unicode Blocks Reference: https://www.compart.com/en/unicode/block
|
|
||||||
return (
|
|
||||||
// Latin-1 Supplement No-Break Space (NBSP): U+00A0
|
|
||||||
(codepoint == 0x00A0) ||
|
|
||||||
|
|
||||||
// Latin-1 Supplement Punctuation: U+00A1 to U+00BF
|
|
||||||
(codepoint >= 0x00A1 && codepoint <= 0x00BF) ||
|
|
||||||
|
|
||||||
// General Punctuation: U+2000 to U+206F
|
|
||||||
(codepoint >= 0x2000 && codepoint <= 0x206F) ||
|
|
||||||
|
|
||||||
// Currency Symbols: U+20A0 to U+20CF
|
|
||||||
(codepoint >= 0x20A0 && codepoint <= 0x20CF) ||
|
|
||||||
|
|
||||||
// Supplemental Punctuation: U+2E00 to U+2E7F
|
|
||||||
(codepoint >= 0x2E00 && codepoint <= 0x2E7F) ||
|
|
||||||
|
|
||||||
// CJK Symbols and Punctuation: U+3000 to U+303F
|
|
||||||
(codepoint >= 0x3000 && codepoint <= 0x303F) ||
|
|
||||||
|
|
||||||
// Ideographic Description Characters: U+2FF0 to U+2FFF
|
|
||||||
(codepoint >= 0x2FF0 && codepoint <= 0x2FFF)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline int is_right_boundary(int c) {
|
|
||||||
return is_whitespace(c) || is_punctuation(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline int is_left_boundary(char c) {
|
|
||||||
return is_right_boundary(c) || is_utf8_byte(c);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline int is_alphanumeric(char c) {
|
static inline int is_alphanumeric(char c) {
|
||||||
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9');
|
return (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9');
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void make_cursor(struct cursor *c, const u8 *content, size_t len)
|
||||||
|
{
|
||||||
|
c->start = content;
|
||||||
|
c->end = content + len;
|
||||||
|
c->p = content;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline int consume_until_boundary(struct cursor *cur) {
|
static inline int consume_until_boundary(struct cursor *cur) {
|
||||||
unsigned int c;
|
char c;
|
||||||
unsigned int char_length = 1;
|
|
||||||
unsigned int *utf8_char_length = &char_length;
|
|
||||||
|
|
||||||
while (cur->p < cur->end) {
|
while (cur->p < cur->end) {
|
||||||
c = *cur->p;
|
c = *cur->p;
|
||||||
|
|
||||||
*utf8_char_length = 1;
|
if (is_boundary(c))
|
||||||
|
|
||||||
if (is_whitespace(c))
|
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
// Need to check for UTF-8 characters, which can be multiple bytes long
|
cur->p++;
|
||||||
if (is_utf8_byte(c)) {
|
|
||||||
if (!parse_utf8_char(cur, &c, utf8_char_length)) {
|
|
||||||
if (!is_right_boundary(c)){
|
|
||||||
// TODO: We should work towards handling all UTF-8 characters.
|
|
||||||
printf("Invalid UTF-8 code point: %x\n", c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_right_boundary(c))
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
// Need to use a variable character byte length for UTF-8 (2-4 bytes)
|
|
||||||
if (cur->p + *utf8_char_length <= cur->end)
|
|
||||||
cur->p += *utf8_char_length;
|
|
||||||
else
|
|
||||||
cur->p++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
@@ -663,4 +91,66 @@ static inline int consume_until_non_alphanumeric(struct cursor *cur, int or_end)
|
|||||||
return or_end;
|
return or_end;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
static inline int parse_char(struct cursor *cur, char c) {
|
||||||
|
if (cur->p >= cur->end)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (*cur->p == c) {
|
||||||
|
cur->p++;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int peek_char(struct cursor *cur, int ind) {
|
||||||
|
if ((cur->p + ind < cur->start) || (cur->p + ind >= cur->end))
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
return *(cur->p + ind);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static inline int pull_byte(struct cursor *cur, u8 *byte) {
|
||||||
|
if (cur->p >= cur->end)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
*byte = *cur->p;
|
||||||
|
cur->p++;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int pull_bytes(struct cursor *cur, int count, const u8 **bytes) {
|
||||||
|
if (cur->p + count > cur->end)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
*bytes = cur->p;
|
||||||
|
cur->p += count;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int parse_str(struct cursor *cur, const char *str) {
|
||||||
|
int i;
|
||||||
|
char c, cs;
|
||||||
|
unsigned long len;
|
||||||
|
|
||||||
|
len = strlen(str);
|
||||||
|
|
||||||
|
if (cur->p + len >= cur->end)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
for (i = 0; i < len; i++) {
|
||||||
|
c = tolower(cur->p[i]);
|
||||||
|
cs = tolower(str[i]);
|
||||||
|
|
||||||
|
if (c != cs)
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
cur->p += len;
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* cursor_h */
|
||||||
|
|||||||
@@ -5,9 +5,3 @@
|
|||||||
#include "damus.h"
|
#include "damus.h"
|
||||||
#include "bolt11.h"
|
#include "bolt11.h"
|
||||||
#include "amount.h"
|
#include "amount.h"
|
||||||
#include "nostr_bech32.h"
|
|
||||||
#include "wasm.h"
|
|
||||||
#include "nostrscript.h"
|
|
||||||
#include "nostrdb.h"
|
|
||||||
#include "lmdb.h"
|
|
||||||
|
|
||||||
|
|||||||
+32
-111
@@ -28,9 +28,9 @@ static int parse_digit(struct cursor *cur, int *digit) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static int parse_mention_index(struct cursor *cur, struct note_block *block) {
|
static int parse_mention_index(struct cursor *cur, struct block *block) {
|
||||||
int d1, d2, d3, ind;
|
int d1, d2, d3, ind;
|
||||||
u8 *start = cur->p;
|
const u8 *start = cur->p;
|
||||||
|
|
||||||
if (!parse_str(cur, "#["))
|
if (!parse_str(cur, "#["))
|
||||||
return 0;
|
return 0;
|
||||||
@@ -59,9 +59,9 @@ static int parse_mention_index(struct cursor *cur, struct note_block *block) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int parse_hashtag(struct cursor *cur, struct note_block *block) {
|
static int parse_hashtag(struct cursor *cur, struct block *block) {
|
||||||
int c;
|
int c;
|
||||||
u8 *start = cur->p;
|
const u8 *start = cur->p;
|
||||||
|
|
||||||
if (!parse_char(cur, '#'))
|
if (!parse_char(cur, '#'))
|
||||||
return 0;
|
return 0;
|
||||||
@@ -81,7 +81,7 @@ static int parse_hashtag(struct cursor *cur, struct note_block *block) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int add_block(struct note_blocks *blocks, struct note_block block)
|
static int add_block(struct blocks *blocks, struct block block)
|
||||||
{
|
{
|
||||||
if (blocks->num_blocks + 1 >= MAX_BLOCKS)
|
if (blocks->num_blocks + 1 >= MAX_BLOCKS)
|
||||||
return 0;
|
return 0;
|
||||||
@@ -90,9 +90,9 @@ static int add_block(struct note_blocks *blocks, struct note_block block)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int add_text_block(struct note_blocks *blocks, const u8 *start, const u8 *end)
|
static int add_text_block(struct blocks *blocks, const u8 *start, const u8 *end)
|
||||||
{
|
{
|
||||||
struct note_block b;
|
struct block b;
|
||||||
|
|
||||||
if (start == end)
|
if (start == end)
|
||||||
return 1;
|
return 1;
|
||||||
@@ -104,71 +104,8 @@ static int add_text_block(struct note_blocks *blocks, const u8 *start, const u8
|
|||||||
return add_block(blocks, b);
|
return add_block(blocks, b);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int consume_url_fragment(struct cursor *cur)
|
static int parse_url(struct cursor *cur, struct block *block) {
|
||||||
{
|
const u8 *start = cur->p;
|
||||||
int c;
|
|
||||||
|
|
||||||
if ((c = peek_char(cur, 0)) < 0)
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
if (c != '#' && c != '?') {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
cur->p++;
|
|
||||||
|
|
||||||
return consume_until_whitespace(cur, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int consume_url_path(struct cursor *cur)
|
|
||||||
{
|
|
||||||
int c;
|
|
||||||
|
|
||||||
if ((c = peek_char(cur, 0)) < 0)
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
if (c != '/') {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (cur->p < cur->end) {
|
|
||||||
c = *cur->p;
|
|
||||||
|
|
||||||
if (c == '?' || c == '#' || is_whitespace(c)) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
cur->p++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int consume_url_host(struct cursor *cur)
|
|
||||||
{
|
|
||||||
char c;
|
|
||||||
int count = 0;
|
|
||||||
|
|
||||||
while (cur->p < cur->end) {
|
|
||||||
c = *cur->p;
|
|
||||||
// TODO: handle IDNs
|
|
||||||
if (is_alphanumeric(c) || c == '.' || c == '-')
|
|
||||||
{
|
|
||||||
count++;
|
|
||||||
cur->p++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
return count != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// this means the end of the URL hostname is the end of the buffer and we finished
|
|
||||||
return count != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int parse_url(struct cursor *cur, struct note_block *block) {
|
|
||||||
u8 *start = cur->p;
|
|
||||||
|
|
||||||
if (!parse_str(cur, "http"))
|
if (!parse_str(cur, "http"))
|
||||||
return 0;
|
return 0;
|
||||||
@@ -184,25 +121,15 @@ static int parse_url(struct cursor *cur, struct note_block *block) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(consume_url_host(cur) &&
|
if (!consume_until_whitespace(cur, 1)) {
|
||||||
consume_url_path(cur) &&
|
|
||||||
consume_url_fragment(cur)))
|
|
||||||
{
|
|
||||||
cur->p = start;
|
cur->p = start;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// smart parens
|
// strip any unwanted characters
|
||||||
if (start - 1 >= 0 &&
|
while(is_invalid_url_ending(peek_char(cur, -1))) cur->p--;
|
||||||
start < cur->end &&
|
|
||||||
*(start - 1) == '(' &&
|
|
||||||
(cur->p - 1) < cur->end &&
|
|
||||||
*(cur->p - 1) == ')')
|
|
||||||
{
|
|
||||||
cur->p--;
|
|
||||||
}
|
|
||||||
|
|
||||||
block->type = BLOCK_URL;
|
block->type = BLOCK_URL;
|
||||||
block->block.str.start = (const char *)start;
|
block->block.str.start = (const char *)start;
|
||||||
block->block.str.end = (const char *)cur->p;
|
block->block.str.end = (const char *)cur->p;
|
||||||
@@ -210,8 +137,8 @@ static int parse_url(struct cursor *cur, struct note_block *block) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int parse_invoice(struct cursor *cur, struct note_block *block) {
|
static int parse_invoice(struct cursor *cur, struct block *block) {
|
||||||
u8 *start, *end;
|
const u8 *start, *end;
|
||||||
char *fail;
|
char *fail;
|
||||||
struct bolt11 *bolt11;
|
struct bolt11 *bolt11;
|
||||||
// optional
|
// optional
|
||||||
@@ -250,12 +177,12 @@ static int parse_invoice(struct cursor *cur, struct note_block *block) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static int parse_mention_bech32(struct cursor *cur, struct note_block *block) {
|
static int parse_mention_bech32(struct cursor *cur, struct block *block) {
|
||||||
u8 *start = cur->p;
|
const u8 *start = cur->p;
|
||||||
|
|
||||||
|
if (!parse_str(cur, "nostr:"))
|
||||||
|
return 0;
|
||||||
|
|
||||||
parse_char(cur, '@');
|
|
||||||
parse_str(cur, "nostr:");
|
|
||||||
|
|
||||||
block->block.str.start = (const char *)cur->p;
|
block->block.str.start = (const char *)cur->p;
|
||||||
|
|
||||||
if (!parse_nostr_bech32(cur, &block->block.mention_bech32.bech32)) {
|
if (!parse_nostr_bech32(cur, &block->block.mention_bech32.bech32)) {
|
||||||
@@ -270,7 +197,7 @@ static int parse_mention_bech32(struct cursor *cur, struct note_block *block) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int add_text_then_block(struct cursor *cur, struct note_blocks *blocks, struct note_block block, u8 **start, const u8 *pre_mention)
|
static int add_text_then_block(struct cursor *cur, struct blocks *blocks, struct block block, const u8 **start, const u8 *pre_mention)
|
||||||
{
|
{
|
||||||
if (!add_text_block(blocks, *start, pre_mention))
|
if (!add_text_block(blocks, *start, pre_mention))
|
||||||
return 0;
|
return 0;
|
||||||
@@ -283,28 +210,22 @@ static int add_text_then_block(struct cursor *cur, struct note_blocks *blocks, s
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int damus_parse_content(struct note_blocks *blocks, const char *content) {
|
int damus_parse_content(struct blocks *blocks, const char *content) {
|
||||||
int cp, c;
|
int cp, c;
|
||||||
struct cursor cur;
|
struct cursor cur;
|
||||||
struct note_block block;
|
struct block block;
|
||||||
u8 *start, *pre_mention;
|
const u8 *start, *pre_mention;
|
||||||
|
|
||||||
blocks->words = 0;
|
|
||||||
blocks->num_blocks = 0;
|
blocks->num_blocks = 0;
|
||||||
make_cursor((u8*)content, (u8*)content + strlen(content), &cur);
|
make_cursor(&cur, (const u8*)content, strlen(content));
|
||||||
|
|
||||||
start = cur.p;
|
start = cur.p;
|
||||||
while (cur.p < cur.end && blocks->num_blocks < MAX_BLOCKS) {
|
while (cur.p < cur.end && blocks->num_blocks < MAX_BLOCKS) {
|
||||||
cp = peek_char(&cur, -1);
|
cp = peek_char(&cur, -1);
|
||||||
c = peek_char(&cur, 0);
|
c = peek_char(&cur, 0);
|
||||||
|
|
||||||
// new word
|
|
||||||
if (is_whitespace(cp) && !is_whitespace(c)) {
|
|
||||||
blocks->words++;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre_mention = cur.p;
|
pre_mention = cur.p;
|
||||||
if (cp == -1 || is_left_boundary(cp) || c == '#') {
|
if (cp == -1 || is_whitespace(cp) || c == '#') {
|
||||||
if (c == '#' && (parse_mention_index(&cur, &block) || parse_hashtag(&cur, &block))) {
|
if (c == '#' && (parse_mention_index(&cur, &block) || parse_hashtag(&cur, &block))) {
|
||||||
if (!add_text_then_block(&cur, blocks, block, &start, pre_mention))
|
if (!add_text_then_block(&cur, blocks, block, &start, pre_mention))
|
||||||
return 0;
|
return 0;
|
||||||
@@ -317,7 +238,7 @@ int damus_parse_content(struct note_blocks *blocks, const char *content) {
|
|||||||
if (!add_text_then_block(&cur, blocks, block, &start, pre_mention))
|
if (!add_text_then_block(&cur, blocks, block, &start, pre_mention))
|
||||||
return 0;
|
return 0;
|
||||||
continue;
|
continue;
|
||||||
} else if ((c == 'n' || c == '@') && parse_mention_bech32(&cur, &block)) {
|
} else if (c == 'n' && parse_mention_bech32(&cur, &block)) {
|
||||||
if (!add_text_then_block(&cur, blocks, block, &start, pre_mention))
|
if (!add_text_then_block(&cur, blocks, block, &start, pre_mention))
|
||||||
return 0;
|
return 0;
|
||||||
continue;
|
continue;
|
||||||
@@ -335,12 +256,12 @@ int damus_parse_content(struct note_blocks *blocks, const char *content) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void blocks_init(struct note_blocks *blocks) {
|
void blocks_init(struct blocks *blocks) {
|
||||||
blocks->blocks = malloc(sizeof(struct note_block) * MAX_BLOCKS);
|
blocks->blocks = malloc(sizeof(struct block) * MAX_BLOCKS);
|
||||||
blocks->num_blocks = 0;
|
blocks->num_blocks = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void blocks_free(struct note_blocks *blocks) {
|
void blocks_free(struct blocks *blocks) {
|
||||||
if (!blocks->blocks) {
|
if (!blocks->blocks) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -9,10 +9,10 @@
|
|||||||
#define damus_h
|
#define damus_h
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include "nostr_bech32.h"
|
||||||
#include "block.h"
|
#include "block.h"
|
||||||
|
|
||||||
typedef unsigned char u8;
|
typedef unsigned char u8;
|
||||||
|
|
||||||
int damus_parse_content(struct note_blocks *blocks, const char *content);
|
int damus_parse_content(struct blocks *blocks, const char *content);
|
||||||
|
|
||||||
#endif /* damus_h */
|
#endif /* damus_h */
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
|
|
||||||
#ifndef PROTOVERSE_DEBUG_H
|
|
||||||
#define PROTOVERSE_DEBUG_H
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
#define unusual(...) fprintf(stderr, "UNUSUAL: " __VA_ARGS__)
|
|
||||||
|
|
||||||
#ifdef DEBUG
|
|
||||||
#define debug(...) printf(__VA_ARGS__)
|
|
||||||
#else
|
|
||||||
#define debug(...)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif /* PROTOVERSE_DEBUG_H */
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
|
|
||||||
#include "error.h"
|
|
||||||
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <stdarg.h>
|
|
||||||
|
|
||||||
int note_error_(struct errors *errs_, struct cursor *p, const char *fmt, ...)
|
|
||||||
{
|
|
||||||
static char buf[512];
|
|
||||||
struct error err;
|
|
||||||
struct cursor *errs;
|
|
||||||
va_list ap;
|
|
||||||
|
|
||||||
errs = &errs_->cur;
|
|
||||||
|
|
||||||
if (errs_->enabled == 0)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
va_start(ap, fmt);
|
|
||||||
vsprintf(buf, fmt, ap);
|
|
||||||
va_end(ap);
|
|
||||||
|
|
||||||
err.msg = buf;
|
|
||||||
err.pos = p ? (int)(p->p - p->start) : 0;
|
|
||||||
|
|
||||||
if (!cursor_push_error(errs, &err)) {
|
|
||||||
fprintf(stderr, "arena OOM when recording error, ");
|
|
||||||
fprintf(stderr, "errs->p at %ld, remaining %ld, strlen %ld\n",
|
|
||||||
errs->p - errs->start, errs->end - errs->p, strlen(buf));
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
|
|
||||||
#ifndef PROTOVERSE_ERROR_H
|
|
||||||
#define PROTOVERSE_ERROR_H
|
|
||||||
|
|
||||||
#include "cursor.h"
|
|
||||||
|
|
||||||
struct error {
|
|
||||||
int pos;
|
|
||||||
const char *msg;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct errors {
|
|
||||||
struct cursor cur;
|
|
||||||
int enabled;
|
|
||||||
};
|
|
||||||
|
|
||||||
#define note_error(errs, p, fmt, ...) note_error_(errs, p, "%s: " fmt, __FUNCTION__, ##__VA_ARGS__)
|
|
||||||
|
|
||||||
static inline int cursor_push_error(struct cursor *cur, struct error *err)
|
|
||||||
{
|
|
||||||
return cursor_push_int(cur, err->pos) &&
|
|
||||||
cursor_push_c_str(cur, err->msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline int cursor_pull_error(struct cursor *cur, struct error *err)
|
|
||||||
{
|
|
||||||
return cursor_pull_int(cur, &err->pos) &&
|
|
||||||
cursor_pull_c_str(cur, &err->msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
int note_error_(struct errors *errs, struct cursor *p, const char *fmt, ...);
|
|
||||||
|
|
||||||
#endif /* PROTOVERSE_ERROR_H */
|
|
||||||
@@ -39,6 +39,15 @@ bool hex_decode(const char *str, size_t slen, void *buf, size_t bufsize)
|
|||||||
return slen == 0 && bufsize == 0;
|
return slen == 0 && bufsize == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static char hexchar(unsigned int val)
|
||||||
|
{
|
||||||
|
if (val < 10)
|
||||||
|
return '0' + val;
|
||||||
|
if (val < 16)
|
||||||
|
return 'a' + val - 10;
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
bool hex_encode(const void *buf, size_t bufsize, char *dest, size_t destsize)
|
bool hex_encode(const void *buf, size_t bufsize, char *dest, size_t destsize)
|
||||||
{
|
{
|
||||||
size_t i;
|
size_t i;
|
||||||
|
|||||||
@@ -70,15 +70,4 @@ static inline size_t hex_data_size(size_t strlen)
|
|||||||
{
|
{
|
||||||
return strlen / 2;
|
return strlen / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline char hexchar(unsigned int val)
|
|
||||||
{
|
|
||||||
if (val < 10)
|
|
||||||
return '0' + val;
|
|
||||||
if (val < 16)
|
|
||||||
return 'a' + val - 10;
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#endif /* CCAN_HEX_H */
|
#endif /* CCAN_HEX_H */
|
||||||
|
|||||||
@@ -52,13 +52,9 @@
|
|||||||
*/
|
*/
|
||||||
#define unlikely(cond) __builtin_expect(!!(cond), 0)
|
#define unlikely(cond) __builtin_expect(!!(cond), 0)
|
||||||
#else
|
#else
|
||||||
#ifndef likely
|
|
||||||
#define likely(cond) (!!(cond))
|
#define likely(cond) (!!(cond))
|
||||||
#endif
|
|
||||||
#ifndef unlikely
|
|
||||||
#define unlikely(cond) (!!(cond))
|
#define unlikely(cond) (!!(cond))
|
||||||
#endif
|
#endif
|
||||||
#endif
|
|
||||||
#else /* CCAN_LIKELY_DEBUG versions */
|
#else /* CCAN_LIKELY_DEBUG versions */
|
||||||
#include <ccan/str/str.h>
|
#include <ccan/str/str.h>
|
||||||
|
|
||||||
|
|||||||
+2
-13
@@ -91,9 +91,6 @@ static int parse_nostr_bech32_type(const char *prefix, enum nostr_bech32_type *t
|
|||||||
} else if (strcmp(prefix, "npub") == 0) {
|
} else if (strcmp(prefix, "npub") == 0) {
|
||||||
*type = NOSTR_BECH32_NPUB;
|
*type = NOSTR_BECH32_NPUB;
|
||||||
return 1;
|
return 1;
|
||||||
} else if (strcmp(prefix, "nsec") == 0) {
|
|
||||||
*type = NOSTR_BECH32_NSEC;
|
|
||||||
return 1;
|
|
||||||
} else if (strcmp(prefix, "nprofile") == 0) {
|
} else if (strcmp(prefix, "nprofile") == 0) {
|
||||||
*type = NOSTR_BECH32_NPROFILE;
|
*type = NOSTR_BECH32_NPROFILE;
|
||||||
return 1;
|
return 1;
|
||||||
@@ -119,10 +116,6 @@ static int parse_nostr_bech32_npub(struct cursor *cur, struct bech32_npub *npub)
|
|||||||
return pull_bytes(cur, 32, &npub->pubkey);
|
return pull_bytes(cur, 32, &npub->pubkey);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int parse_nostr_bech32_nsec(struct cursor *cur, struct bech32_nsec *nsec) {
|
|
||||||
return pull_bytes(cur, 32, &nsec->nsec);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int tlvs_to_relays(struct nostr_tlvs *tlvs, struct relays *relays) {
|
static int tlvs_to_relays(struct nostr_tlvs *tlvs, struct relays *relays) {
|
||||||
struct nostr_tlv *tlv;
|
struct nostr_tlv *tlv;
|
||||||
struct str_block *str;
|
struct str_block *str;
|
||||||
@@ -225,7 +218,7 @@ static int parse_nostr_bech32_nrelay(struct cursor *cur, struct bech32_nrelay *n
|
|||||||
}
|
}
|
||||||
|
|
||||||
int parse_nostr_bech32(struct cursor *cur, struct nostr_bech32 *obj) {
|
int parse_nostr_bech32(struct cursor *cur, struct nostr_bech32 *obj) {
|
||||||
u8 *start, *end;
|
const u8 *start, *end;
|
||||||
|
|
||||||
start = cur->p;
|
start = cur->p;
|
||||||
|
|
||||||
@@ -264,7 +257,7 @@ int parse_nostr_bech32(struct cursor *cur, struct nostr_bech32 *obj) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct cursor bcur;
|
struct cursor bcur;
|
||||||
make_cursor(obj->buffer, obj->buffer + obj->buflen, &bcur);
|
make_cursor(&bcur, obj->buffer, obj->buflen);
|
||||||
|
|
||||||
switch (obj->type) {
|
switch (obj->type) {
|
||||||
case NOSTR_BECH32_NOTE:
|
case NOSTR_BECH32_NOTE:
|
||||||
@@ -275,10 +268,6 @@ int parse_nostr_bech32(struct cursor *cur, struct nostr_bech32 *obj) {
|
|||||||
if (!parse_nostr_bech32_npub(&bcur, &obj->data.npub))
|
if (!parse_nostr_bech32_npub(&bcur, &obj->data.npub))
|
||||||
goto fail;
|
goto fail;
|
||||||
break;
|
break;
|
||||||
case NOSTR_BECH32_NSEC:
|
|
||||||
if (!parse_nostr_bech32_nsec(&bcur, &obj->data.nsec))
|
|
||||||
goto fail;
|
|
||||||
break;
|
|
||||||
case NOSTR_BECH32_NEVENT:
|
case NOSTR_BECH32_NEVENT:
|
||||||
if (!parse_nostr_bech32_nevent(&bcur, &obj->data.nevent))
|
if (!parse_nostr_bech32_nevent(&bcur, &obj->data.nevent))
|
||||||
goto fail;
|
goto fail;
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ enum nostr_bech32_type {
|
|||||||
NOSTR_BECH32_NEVENT = 4,
|
NOSTR_BECH32_NEVENT = 4,
|
||||||
NOSTR_BECH32_NRELAY = 5,
|
NOSTR_BECH32_NRELAY = 5,
|
||||||
NOSTR_BECH32_NADDR = 6,
|
NOSTR_BECH32_NADDR = 6,
|
||||||
NOSTR_BECH32_NSEC = 7,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct bech32_note {
|
struct bech32_note {
|
||||||
@@ -37,10 +36,6 @@ struct bech32_npub {
|
|||||||
const u8 *pubkey;
|
const u8 *pubkey;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct bech32_nsec {
|
|
||||||
const u8 *nsec;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct bech32_nevent {
|
struct bech32_nevent {
|
||||||
struct relays relays;
|
struct relays relays;
|
||||||
const u8 *event_id;
|
const u8 *event_id;
|
||||||
@@ -70,7 +65,6 @@ typedef struct nostr_bech32 {
|
|||||||
union {
|
union {
|
||||||
struct bech32_note note;
|
struct bech32_note note;
|
||||||
struct bech32_npub npub;
|
struct bech32_npub npub;
|
||||||
struct bech32_nsec nsec;
|
|
||||||
struct bech32_nevent nevent;
|
struct bech32_nevent nevent;
|
||||||
struct bech32_nprofile nprofile;
|
struct bech32_nprofile nprofile;
|
||||||
struct bech32_naddr naddr;
|
struct bech32_naddr naddr;
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
|
|
||||||
#ifndef CURSOR_PARSER
|
|
||||||
#define CURSOR_PARSER
|
|
||||||
|
|
||||||
#include "cursor.h"
|
|
||||||
|
|
||||||
static int consume_bytes(struct cursor *cursor, const unsigned char *match, int len)
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
|
|
||||||
if (cursor->p + len > cursor->end) {
|
|
||||||
fprintf(stderr, "consume_bytes overflow\n");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i = 0; i < len; i++) {
|
|
||||||
if (cursor->p[i] != match[i])
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
cursor->p += len;
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline int consume_byte(struct cursor *cursor, unsigned char match)
|
|
||||||
{
|
|
||||||
if (unlikely(cursor->p >= cursor->end))
|
|
||||||
return 0;
|
|
||||||
if (*cursor->p != match)
|
|
||||||
return 0;
|
|
||||||
cursor->p++;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline int consume_u32(struct cursor *cursor, unsigned int match)
|
|
||||||
{
|
|
||||||
return consume_bytes(cursor, (unsigned char*)&match, sizeof(match));
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif /* CURSOR_PARSER */
|
|
||||||
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
|
|
||||||
#ifndef PROTOVERSE_TYPEDEFS_H
|
|
||||||
#define PROTOVERSE_TYPEDEFS_H
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
typedef unsigned char u8;
|
|
||||||
typedef unsigned int u32;
|
|
||||||
typedef unsigned short u16;
|
|
||||||
typedef uint64_t u64;
|
|
||||||
typedef int64_t s64;
|
|
||||||
|
|
||||||
|
|
||||||
#endif /* PROTOVERSE_TYPEDEFS_H */
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
|
|
||||||
#ifndef PROTOVERSE_VARINT_H
|
|
||||||
#define PROTOVERSE_VARINT_H
|
|
||||||
|
|
||||||
#define VARINT_MAX_LEN 9
|
|
||||||
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
size_t varint_put(unsigned char buf[VARINT_MAX_LEN], uint64_t v);
|
|
||||||
size_t varint_size(uint64_t v);
|
|
||||||
size_t varint_get(const unsigned char *p, size_t max, int64_t *val);
|
|
||||||
|
|
||||||
#endif /* PROTOVERSE_VARINT_H */
|
|
||||||
-7299
File diff suppressed because it is too large
Load Diff
-850
@@ -1,850 +0,0 @@
|
|||||||
|
|
||||||
#ifndef PROTOVERSE_WASM_H
|
|
||||||
#define PROTOVERSE_WASM_H
|
|
||||||
|
|
||||||
static const unsigned char WASM_MAGIC[] = {0,'a','s','m'};
|
|
||||||
|
|
||||||
#define WASM_VERSION 0x01
|
|
||||||
#define MAX_U32_LEB128_BYTES 5
|
|
||||||
#define MAX_U64_LEB128_BYTES 10
|
|
||||||
#define MAX_CUSTOM_SECTIONS 32
|
|
||||||
#define MAX_BUILTINS 64
|
|
||||||
#define BUILTIN_SUSPEND 42
|
|
||||||
|
|
||||||
#define FUNC_TYPE_TAG 0x60
|
|
||||||
|
|
||||||
|
|
||||||
#include "cursor.h"
|
|
||||||
#include "error.h"
|
|
||||||
|
|
||||||
#ifdef NOINLINE
|
|
||||||
#define INLINE __attribute__((noinline))
|
|
||||||
#else
|
|
||||||
#define INLINE inline
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
#define interp_error(p, fmt, ...) note_error(&((p)->errors), interp_codeptr(p), fmt, ##__VA_ARGS__)
|
|
||||||
#define parse_err(p, fmt, ...) note_error(&((p)->errs), &(p)->cur, fmt, ##__VA_ARGS__)
|
|
||||||
|
|
||||||
enum valtype {
|
|
||||||
val_i32 = 0x7F,
|
|
||||||
val_i64 = 0x7E,
|
|
||||||
val_f32 = 0x7D,
|
|
||||||
val_f64 = 0x7C,
|
|
||||||
val_ref_null = 0xD0,
|
|
||||||
val_ref_func = 0x70,
|
|
||||||
val_ref_extern = 0x6F,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum const_instr {
|
|
||||||
ci_const_i32 = 0x41,
|
|
||||||
ci_const_i64 = 0x42,
|
|
||||||
ci_const_f32 = 0x43,
|
|
||||||
ci_const_f64 = 0x44,
|
|
||||||
ci_ref_null = 0xD0,
|
|
||||||
ci_ref_func = 0xD2,
|
|
||||||
ci_global_get = 0x23,
|
|
||||||
ci_end = 0x0B,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum limit_type {
|
|
||||||
limit_min = 0x00,
|
|
||||||
limit_min_max = 0x01,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct limits {
|
|
||||||
u32 min;
|
|
||||||
u32 max;
|
|
||||||
enum limit_type type;
|
|
||||||
};
|
|
||||||
|
|
||||||
enum section_tag {
|
|
||||||
section_custom,
|
|
||||||
section_type,
|
|
||||||
section_import,
|
|
||||||
section_function,
|
|
||||||
section_table,
|
|
||||||
section_memory,
|
|
||||||
section_global,
|
|
||||||
section_export,
|
|
||||||
section_start,
|
|
||||||
section_element,
|
|
||||||
section_code,
|
|
||||||
section_data,
|
|
||||||
section_data_count,
|
|
||||||
section_name,
|
|
||||||
num_sections,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum name_subsection_tag {
|
|
||||||
name_subsection_module,
|
|
||||||
name_subsection_funcs,
|
|
||||||
name_subsection_locals,
|
|
||||||
num_name_subsections,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum reftype {
|
|
||||||
funcref = 0x70,
|
|
||||||
externref = 0x6F,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct resulttype {
|
|
||||||
unsigned char *valtypes; /* enum valtype */
|
|
||||||
u32 num_valtypes;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct functype {
|
|
||||||
struct resulttype params;
|
|
||||||
struct resulttype result;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct table {
|
|
||||||
enum reftype reftype;
|
|
||||||
struct limits limits;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct tablesec {
|
|
||||||
struct table *tables;
|
|
||||||
u32 num_tables;
|
|
||||||
};
|
|
||||||
|
|
||||||
enum elem_mode {
|
|
||||||
elem_mode_passive,
|
|
||||||
elem_mode_active,
|
|
||||||
elem_mode_declarative,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct expr {
|
|
||||||
u8 *code;
|
|
||||||
u32 code_len;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct refval {
|
|
||||||
u32 addr;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct table_inst {
|
|
||||||
struct refval *refs;
|
|
||||||
enum reftype reftype;
|
|
||||||
u32 num_refs;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct numval {
|
|
||||||
union {
|
|
||||||
int i32;
|
|
||||||
u32 u32;
|
|
||||||
int64_t i64;
|
|
||||||
uint64_t u64;
|
|
||||||
float f32;
|
|
||||||
double f64;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
struct val {
|
|
||||||
enum valtype type;
|
|
||||||
union {
|
|
||||||
struct numval num;
|
|
||||||
struct refval ref;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
struct elem_inst {
|
|
||||||
struct val val;
|
|
||||||
u16 elem;
|
|
||||||
u16 init;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct elem {
|
|
||||||
struct expr offset;
|
|
||||||
u32 tableidx;
|
|
||||||
struct expr *inits;
|
|
||||||
u32 num_inits;
|
|
||||||
enum elem_mode mode;
|
|
||||||
enum reftype reftype;
|
|
||||||
struct val val;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct customsec {
|
|
||||||
const char *name;
|
|
||||||
unsigned char *data;
|
|
||||||
u32 data_len;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct elemsec {
|
|
||||||
struct elem *elements;
|
|
||||||
u32 num_elements;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct memsec {
|
|
||||||
struct limits *mems; /* memtype */
|
|
||||||
u32 num_mems;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct funcsec {
|
|
||||||
u32 *type_indices;
|
|
||||||
u32 num_indices;
|
|
||||||
};
|
|
||||||
|
|
||||||
enum mut {
|
|
||||||
mut_const,
|
|
||||||
mut_var,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct globaltype {
|
|
||||||
enum valtype valtype;
|
|
||||||
enum mut mut;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct globalsec {
|
|
||||||
struct global *globals;
|
|
||||||
u32 num_globals;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct typesec {
|
|
||||||
struct functype *functypes;
|
|
||||||
u32 num_functypes;
|
|
||||||
};
|
|
||||||
|
|
||||||
enum import_type {
|
|
||||||
import_func,
|
|
||||||
import_table,
|
|
||||||
import_mem,
|
|
||||||
import_global,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct importdesc {
|
|
||||||
enum import_type type;
|
|
||||||
union {
|
|
||||||
u32 typeidx;
|
|
||||||
struct limits tabletype;
|
|
||||||
struct limits memtype;
|
|
||||||
struct globaltype globaltype;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
struct import {
|
|
||||||
const char *module_name;
|
|
||||||
const char *name;
|
|
||||||
struct importdesc desc;
|
|
||||||
int resolved_builtin;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct importsec {
|
|
||||||
struct import *imports;
|
|
||||||
u32 num_imports;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct global {
|
|
||||||
struct globaltype type;
|
|
||||||
struct expr init;
|
|
||||||
struct val val;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct local_def {
|
|
||||||
u32 num_types;
|
|
||||||
enum valtype type;
|
|
||||||
};
|
|
||||||
|
|
||||||
/* "code" */
|
|
||||||
struct wasm_func {
|
|
||||||
struct expr code;
|
|
||||||
struct local_def *local_defs;
|
|
||||||
u32 num_local_defs;
|
|
||||||
};
|
|
||||||
|
|
||||||
enum func_type {
|
|
||||||
func_type_wasm,
|
|
||||||
func_type_builtin,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct func {
|
|
||||||
union {
|
|
||||||
struct wasm_func *wasm_func;
|
|
||||||
struct builtin *builtin;
|
|
||||||
};
|
|
||||||
u32 num_locals;
|
|
||||||
struct functype *functype;
|
|
||||||
enum func_type type;
|
|
||||||
const char *name;
|
|
||||||
u32 idx;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct codesec {
|
|
||||||
struct wasm_func *funcs;
|
|
||||||
u32 num_funcs;
|
|
||||||
};
|
|
||||||
|
|
||||||
enum exportdesc {
|
|
||||||
export_func,
|
|
||||||
export_table,
|
|
||||||
export_mem,
|
|
||||||
export_global,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct wexport {
|
|
||||||
const char *name;
|
|
||||||
u32 index;
|
|
||||||
enum exportdesc desc;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct exportsec {
|
|
||||||
struct wexport *exports;
|
|
||||||
u32 num_exports;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct nameassoc {
|
|
||||||
u32 index;
|
|
||||||
const char *name;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct namemap {
|
|
||||||
struct nameassoc *names;
|
|
||||||
u32 num_names;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct namesec {
|
|
||||||
const char *module_name;
|
|
||||||
struct namemap func_names;
|
|
||||||
int parsed;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct wsection {
|
|
||||||
enum section_tag tag;
|
|
||||||
};
|
|
||||||
|
|
||||||
enum bulk_tag {
|
|
||||||
i_memory_copy = 10,
|
|
||||||
i_memory_fill = 11,
|
|
||||||
i_table_init = 12,
|
|
||||||
i_elem_drop = 13,
|
|
||||||
i_table_copy = 14,
|
|
||||||
i_table_grow = 15,
|
|
||||||
i_table_size = 16,
|
|
||||||
i_table_fill = 17,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum instr_tag {
|
|
||||||
/* control instructions */
|
|
||||||
i_unreachable = 0x00,
|
|
||||||
i_nop = 0x01,
|
|
||||||
i_block = 0x02,
|
|
||||||
i_loop = 0x03,
|
|
||||||
i_if = 0x04,
|
|
||||||
i_else = 0x05,
|
|
||||||
i_end = 0x0B,
|
|
||||||
i_br = 0x0C,
|
|
||||||
i_br_if = 0x0D,
|
|
||||||
i_br_table = 0x0E,
|
|
||||||
i_return = 0x0F,
|
|
||||||
i_call = 0x10,
|
|
||||||
i_call_indirect = 0x11,
|
|
||||||
|
|
||||||
/* parametric instructions */
|
|
||||||
i_drop = 0x1A,
|
|
||||||
i_select = 0x1B,
|
|
||||||
i_selects = 0x1C,
|
|
||||||
|
|
||||||
/* variable instructions */
|
|
||||||
i_local_get = 0x20,
|
|
||||||
i_local_set = 0x21,
|
|
||||||
i_local_tee = 0x22,
|
|
||||||
i_global_get = 0x23,
|
|
||||||
i_global_set = 0x24,
|
|
||||||
i_table_get = 0x25,
|
|
||||||
i_table_set = 0x26,
|
|
||||||
|
|
||||||
/* memory instructions */
|
|
||||||
i_i32_load = 0x28,
|
|
||||||
i_i64_load = 0x29,
|
|
||||||
i_f32_load = 0x2A,
|
|
||||||
i_f64_load = 0x2B,
|
|
||||||
i_i32_load8_s = 0x2C,
|
|
||||||
i_i32_load8_u = 0x2D,
|
|
||||||
i_i32_load16_s = 0x2E,
|
|
||||||
i_i32_load16_u = 0x2F,
|
|
||||||
i_i64_load8_s = 0x30,
|
|
||||||
i_i64_load8_u = 0x31,
|
|
||||||
i_i64_load16_s = 0x32,
|
|
||||||
i_i64_load16_u = 0x33,
|
|
||||||
i_i64_load32_s = 0x34,
|
|
||||||
i_i64_load32_u = 0x35,
|
|
||||||
i_i32_store = 0x36,
|
|
||||||
i_i64_store = 0x37,
|
|
||||||
i_f32_store = 0x38,
|
|
||||||
i_f64_store = 0x39,
|
|
||||||
i_i32_store8 = 0x3A,
|
|
||||||
i_i32_store16 = 0x3B,
|
|
||||||
i_i64_store8 = 0x3C,
|
|
||||||
i_i64_store16 = 0x3D,
|
|
||||||
i_i64_store32 = 0x3E,
|
|
||||||
i_memory_size = 0x3F,
|
|
||||||
i_memory_grow = 0x40,
|
|
||||||
|
|
||||||
/* numeric instructions */
|
|
||||||
i_i32_const = 0x41,
|
|
||||||
i_i64_const = 0x42,
|
|
||||||
i_f32_const = 0x43,
|
|
||||||
i_f64_const = 0x44,
|
|
||||||
|
|
||||||
i_i32_eqz = 0x45,
|
|
||||||
i_i32_eq = 0x46,
|
|
||||||
i_i32_ne = 0x47,
|
|
||||||
i_i32_lt_s = 0x48,
|
|
||||||
i_i32_lt_u = 0x49,
|
|
||||||
i_i32_gt_s = 0x4A,
|
|
||||||
i_i32_gt_u = 0x4B,
|
|
||||||
i_i32_le_s = 0x4C,
|
|
||||||
i_i32_le_u = 0x4D,
|
|
||||||
i_i32_ge_s = 0x4E,
|
|
||||||
i_i32_ge_u = 0x4F,
|
|
||||||
|
|
||||||
i_i64_eqz = 0x50,
|
|
||||||
i_i64_eq = 0x51,
|
|
||||||
i_i64_ne = 0x52,
|
|
||||||
i_i64_lt_s = 0x53,
|
|
||||||
i_i64_lt_u = 0x54,
|
|
||||||
i_i64_gt_s = 0x55,
|
|
||||||
i_i64_gt_u = 0x56,
|
|
||||||
i_i64_le_s = 0x57,
|
|
||||||
i_i64_le_u = 0x58,
|
|
||||||
i_i64_ge_s = 0x59,
|
|
||||||
i_i64_ge_u = 0x5A,
|
|
||||||
|
|
||||||
i_f32_eq = 0x5B,
|
|
||||||
i_f32_ne = 0x5C,
|
|
||||||
i_f32_lt = 0x5D,
|
|
||||||
i_f32_gt = 0x5E,
|
|
||||||
i_f32_le = 0x5F,
|
|
||||||
i_f32_ge = 0x60,
|
|
||||||
|
|
||||||
i_f64_eq = 0x61,
|
|
||||||
i_f64_ne = 0x62,
|
|
||||||
i_f64_lt = 0x63,
|
|
||||||
i_f64_gt = 0x64,
|
|
||||||
i_f64_le = 0x65,
|
|
||||||
i_f64_ge = 0x66,
|
|
||||||
|
|
||||||
i_i32_clz = 0x67,
|
|
||||||
i_i32_ctz = 0x68,
|
|
||||||
i_i32_popcnt = 0x69,
|
|
||||||
|
|
||||||
i_i32_add = 0x6A,
|
|
||||||
i_i32_sub = 0x6B,
|
|
||||||
i_i32_mul = 0x6C,
|
|
||||||
i_i32_div_s = 0x6D,
|
|
||||||
i_i32_div_u = 0x6E,
|
|
||||||
i_i32_rem_s = 0x6F,
|
|
||||||
i_i32_rem_u = 0x70,
|
|
||||||
i_i32_and = 0x71,
|
|
||||||
i_i32_or = 0x72,
|
|
||||||
i_i32_xor = 0x73,
|
|
||||||
i_i32_shl = 0x74,
|
|
||||||
i_i32_shr_s = 0x75,
|
|
||||||
i_i32_shr_u = 0x76,
|
|
||||||
i_i32_rotl = 0x77,
|
|
||||||
i_i32_rotr = 0x78,
|
|
||||||
|
|
||||||
i_i64_clz = 0x79,
|
|
||||||
i_i64_ctz = 0x7A,
|
|
||||||
i_i64_popcnt = 0x7B,
|
|
||||||
i_i64_add = 0x7C,
|
|
||||||
i_i64_sub = 0x7D,
|
|
||||||
i_i64_mul = 0x7E,
|
|
||||||
i_i64_div_s = 0x7F,
|
|
||||||
i_i64_div_u = 0x80,
|
|
||||||
i_i64_rem_s = 0x81,
|
|
||||||
i_i64_rem_u = 0x82,
|
|
||||||
i_i64_and = 0x83,
|
|
||||||
i_i64_or = 0x84,
|
|
||||||
i_i64_xor = 0x85,
|
|
||||||
i_i64_shl = 0x86,
|
|
||||||
i_i64_shr_s = 0x87,
|
|
||||||
i_i64_shr_u = 0x88,
|
|
||||||
i_i64_rotl = 0x89,
|
|
||||||
i_i64_rotr = 0x8A,
|
|
||||||
|
|
||||||
i_f32_abs = 0x8b,
|
|
||||||
i_f32_neg = 0x8c,
|
|
||||||
i_f32_ceil = 0x8d,
|
|
||||||
i_f32_floor = 0x8e,
|
|
||||||
i_f32_trunc = 0x8f,
|
|
||||||
i_f32_nearest = 0x90,
|
|
||||||
i_f32_sqrt = 0x91,
|
|
||||||
i_f32_add = 0x92,
|
|
||||||
i_f32_sub = 0x93,
|
|
||||||
i_f32_mul = 0x94,
|
|
||||||
i_f32_div = 0x95,
|
|
||||||
i_f32_min = 0x96,
|
|
||||||
i_f32_max = 0x97,
|
|
||||||
i_f32_copysign = 0x98,
|
|
||||||
|
|
||||||
i_f64_abs = 0x99,
|
|
||||||
i_f64_neg = 0x9a,
|
|
||||||
i_f64_ceil = 0x9b,
|
|
||||||
i_f64_floor = 0x9c,
|
|
||||||
i_f64_trunc = 0x9d,
|
|
||||||
i_f64_nearest = 0x9e,
|
|
||||||
i_f64_sqrt = 0x9f,
|
|
||||||
i_f64_add = 0xa0,
|
|
||||||
i_f64_sub = 0xa1,
|
|
||||||
i_f64_mul = 0xa2,
|
|
||||||
i_f64_div = 0xa3,
|
|
||||||
i_f64_min = 0xa4,
|
|
||||||
i_f64_max = 0xa5,
|
|
||||||
i_f64_copysign = 0xa6,
|
|
||||||
|
|
||||||
i_i32_wrap_i64 = 0xa7,
|
|
||||||
i_i32_trunc_f32_s = 0xa8,
|
|
||||||
i_i32_trunc_f32_u = 0xa9,
|
|
||||||
i_i32_trunc_f64_s = 0xaa,
|
|
||||||
i_i32_trunc_f64_u = 0xab,
|
|
||||||
i_i64_extend_i32_s = 0xac,
|
|
||||||
i_i64_extend_i32_u = 0xad,
|
|
||||||
i_i64_trunc_f32_s = 0xae,
|
|
||||||
i_i64_trunc_f32_u = 0xaf,
|
|
||||||
i_i64_trunc_f64_s = 0xb0,
|
|
||||||
i_i64_trunc_f64_u = 0xb1,
|
|
||||||
i_f32_convert_i32_s = 0xb2,
|
|
||||||
i_f32_convert_i32_u = 0xb3,
|
|
||||||
i_f32_convert_i64_s = 0xb4,
|
|
||||||
i_f32_convert_i64_u = 0xb5,
|
|
||||||
i_f32_demote_f64 = 0xb6,
|
|
||||||
i_f64_convert_i32_s = 0xb7,
|
|
||||||
i_f64_convert_i32_u = 0xb8,
|
|
||||||
i_f64_convert_i64_s = 0xb9,
|
|
||||||
i_f64_convert_i64_u = 0xba,
|
|
||||||
i_f64_promote_f32 = 0xbb,
|
|
||||||
|
|
||||||
i_i32_reinterpret_f32 = 0xbc,
|
|
||||||
i_i64_reinterpret_f64 = 0xbd,
|
|
||||||
i_f32_reinterpret_i32 = 0xbe,
|
|
||||||
i_f64_reinterpret_i64 = 0xbf,
|
|
||||||
|
|
||||||
i_i32_extend8_s = 0xc0,
|
|
||||||
i_i32_extend16_s = 0xc1,
|
|
||||||
i_i64_extend8_s = 0xc2,
|
|
||||||
i_i64_extend16_s = 0xc3,
|
|
||||||
i_i64_extend32_s = 0xc4,
|
|
||||||
|
|
||||||
i_ref_null = 0xD0,
|
|
||||||
i_ref_is_null = 0xD1,
|
|
||||||
i_ref_func = 0xD2,
|
|
||||||
|
|
||||||
i_bulk_op = 0xFC,
|
|
||||||
/* TODO: more instrs */
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
enum blocktype_tag {
|
|
||||||
blocktype_empty,
|
|
||||||
blocktype_valtype,
|
|
||||||
blocktype_index,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct blocktype {
|
|
||||||
enum blocktype_tag tag;
|
|
||||||
union {
|
|
||||||
enum valtype valtype;
|
|
||||||
int type_index;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
struct instrs {
|
|
||||||
unsigned char *data;
|
|
||||||
u32 len;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct block {
|
|
||||||
struct blocktype type;
|
|
||||||
struct expr instrs;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct memarg {
|
|
||||||
u32 offset;
|
|
||||||
u32 align;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct br_table {
|
|
||||||
u32 num_label_indices;
|
|
||||||
u32 label_indices[512];
|
|
||||||
u32 default_label;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct call_indirect {
|
|
||||||
u32 tableidx;
|
|
||||||
u32 typeidx;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct table_init {
|
|
||||||
u32 tableidx;
|
|
||||||
u32 elemidx;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct table_copy {
|
|
||||||
u32 from;
|
|
||||||
u32 to;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct bulk_op {
|
|
||||||
enum bulk_tag tag;
|
|
||||||
union {
|
|
||||||
struct table_init table_init;
|
|
||||||
struct table_copy table_copy;
|
|
||||||
u32 idx;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
struct select_instr {
|
|
||||||
u8 *valtypes;
|
|
||||||
u32 num_valtypes;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct instr {
|
|
||||||
enum instr_tag tag;
|
|
||||||
int pos;
|
|
||||||
union {
|
|
||||||
struct br_table br_table;
|
|
||||||
struct bulk_op bulk_op;
|
|
||||||
struct call_indirect call_indirect;
|
|
||||||
struct memarg memarg;
|
|
||||||
struct select_instr select;
|
|
||||||
struct block block;
|
|
||||||
struct expr else_block;
|
|
||||||
double f64;
|
|
||||||
float f32;
|
|
||||||
int i32;
|
|
||||||
u32 u32;
|
|
||||||
int64_t i64;
|
|
||||||
u64 u64;
|
|
||||||
unsigned char memidx;
|
|
||||||
enum reftype reftype;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
enum datamode {
|
|
||||||
datamode_active,
|
|
||||||
datamode_passive,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct wdata_active {
|
|
||||||
u32 mem_index;
|
|
||||||
struct expr offset_expr;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct wdata {
|
|
||||||
struct wdata_active active;
|
|
||||||
u8 *bytes;
|
|
||||||
u32 bytes_len;
|
|
||||||
enum datamode mode;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct datasec {
|
|
||||||
struct wdata *datas;
|
|
||||||
u32 num_datas;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct startsec {
|
|
||||||
u32 start_fn;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct module {
|
|
||||||
unsigned int parsed;
|
|
||||||
unsigned int custom_sections;
|
|
||||||
|
|
||||||
struct func *funcs;
|
|
||||||
|
|
||||||
u32 num_funcs;
|
|
||||||
|
|
||||||
struct customsec custom_section[MAX_CUSTOM_SECTIONS];
|
|
||||||
struct typesec type_section;
|
|
||||||
struct funcsec func_section;
|
|
||||||
struct importsec import_section;
|
|
||||||
struct exportsec export_section;
|
|
||||||
struct codesec code_section;
|
|
||||||
struct tablesec table_section;
|
|
||||||
struct memsec memory_section;
|
|
||||||
struct globalsec global_section;
|
|
||||||
struct startsec start_section;
|
|
||||||
struct elemsec element_section;
|
|
||||||
struct datasec data_section;
|
|
||||||
struct namesec name_section;
|
|
||||||
};
|
|
||||||
|
|
||||||
// make sure the struct is packed so that
|
|
||||||
struct label {
|
|
||||||
u32 instr_pos; // resolved status is stored in HOB of pos
|
|
||||||
u32 jump;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct callframe {
|
|
||||||
struct cursor code;
|
|
||||||
struct val *locals;
|
|
||||||
struct func *func;
|
|
||||||
u16 prev_stack_items;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct resolver {
|
|
||||||
u16 label;
|
|
||||||
u8 end_tag;
|
|
||||||
u8 start_tag;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct global_inst {
|
|
||||||
struct val val;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct module_inst {
|
|
||||||
struct table_inst *tables;
|
|
||||||
struct global_inst *globals;
|
|
||||||
struct elem_inst *elements;
|
|
||||||
|
|
||||||
u32 num_tables;
|
|
||||||
u32 num_globals;
|
|
||||||
u32 num_elements;
|
|
||||||
|
|
||||||
int start_fn;
|
|
||||||
unsigned char *globals_init;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct wasi {
|
|
||||||
int argc;
|
|
||||||
const char **argv;
|
|
||||||
|
|
||||||
int environc;
|
|
||||||
const char **environ;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct wasm_interp;
|
|
||||||
|
|
||||||
struct builtin {
|
|
||||||
const char *name;
|
|
||||||
int (*fn)(struct wasm_interp *);
|
|
||||||
int (*prepare_args)(struct wasm_interp *);
|
|
||||||
};
|
|
||||||
|
|
||||||
struct wasm_interp {
|
|
||||||
struct module *module;
|
|
||||||
struct module_inst module_inst;
|
|
||||||
struct wasi wasi;
|
|
||||||
void *context;
|
|
||||||
|
|
||||||
struct builtin builtins[MAX_BUILTINS];
|
|
||||||
int num_builtins;
|
|
||||||
|
|
||||||
int prev_resolvers, quitting;
|
|
||||||
|
|
||||||
struct errors errors; /* struct error */
|
|
||||||
size_t ops;
|
|
||||||
|
|
||||||
struct cursor callframes; /* struct callframe */
|
|
||||||
struct cursor stack; /* struct val */
|
|
||||||
struct cursor mem; /* u8/mixed */
|
|
||||||
|
|
||||||
struct cursor memory; /* memory pages (65536 blocks) */
|
|
||||||
|
|
||||||
struct cursor locals; /* struct val */
|
|
||||||
struct cursor labels; /* struct labels */
|
|
||||||
struct cursor num_labels;
|
|
||||||
|
|
||||||
// resolve stack for the current function. every time a control
|
|
||||||
// instruction is encountered, the label index is pushed. When an
|
|
||||||
// instruction is popped, we can resolve the label
|
|
||||||
struct cursor resolver_stack; /* struct resolver */
|
|
||||||
struct cursor resolver_offsets; /* int */
|
|
||||||
};
|
|
||||||
|
|
||||||
struct wasm_parser {
|
|
||||||
struct module module;
|
|
||||||
struct builtin *builtins;
|
|
||||||
u32 num_builtins;
|
|
||||||
struct cursor cur;
|
|
||||||
struct cursor mem;
|
|
||||||
struct errors errs;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
int run_wasm(unsigned char *wasm, unsigned long len, int argc, const char **argv, char **env, int *retval);
|
|
||||||
int parse_wasm(struct wasm_parser *p);
|
|
||||||
int wasm_interp_init(struct wasm_interp *interp, struct module *module);
|
|
||||||
void wasm_parser_free(struct wasm_parser *parser);
|
|
||||||
void wasm_parser_init(struct wasm_parser *p, u8 *wasm, size_t wasm_len, size_t arena_size, struct builtin *, int num_builtins);
|
|
||||||
void wasm_interp_free(struct wasm_interp *interp);
|
|
||||||
int interp_wasm_module(struct wasm_interp *interp, int *retval);
|
|
||||||
int interp_wasm_module_resume(struct wasm_interp *interp, int *retval);
|
|
||||||
void print_error_backtrace(struct errors *errors);
|
|
||||||
void setup_wasi(struct wasm_interp *interp, int argc, const char **argv, char **env);
|
|
||||||
void print_callstack(struct wasm_interp *interp);
|
|
||||||
|
|
||||||
// builtin helpers
|
|
||||||
int get_params(struct wasm_interp *interp, struct val** vals, u32 num_vals);
|
|
||||||
int get_var_params(struct wasm_interp *interp, struct val** vals, u32 *num_vals);
|
|
||||||
u8 *interp_mem_ptr(struct wasm_interp *interp, u32 ptr, int size);
|
|
||||||
|
|
||||||
static INLINE struct callframe *top_callframe(struct cursor *cur)
|
|
||||||
{
|
|
||||||
return (struct callframe*)cursor_top(cur, sizeof(struct callframe));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static INLINE struct cursor *interp_codeptr(struct wasm_interp *interp)
|
|
||||||
{
|
|
||||||
struct callframe *frame;
|
|
||||||
if (unlikely(!(frame = top_callframe(&interp->callframes))))
|
|
||||||
return 0;
|
|
||||||
return &frame->code;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static INLINE int mem_ptr_str(struct wasm_interp *interp, u32 ptr,
|
|
||||||
const char **str)
|
|
||||||
{
|
|
||||||
// still technically unsafe if the string runs over the end of memory...
|
|
||||||
if (!(*str = (const char*)interp_mem_ptr(interp, ptr, 1))) {
|
|
||||||
return interp_error(interp, "int memptr");
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static INLINE int mem_ptr_i32(struct wasm_interp *interp, u32 ptr, int **i)
|
|
||||||
{
|
|
||||||
if (!(*i = (int*)interp_mem_ptr(interp, ptr, sizeof(int))))
|
|
||||||
return interp_error(interp, "int memptr");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static INLINE int cursor_pushval(struct cursor *cur, struct val *val)
|
|
||||||
{
|
|
||||||
return cursor_push(cur, (u8*)val, sizeof(*val));
|
|
||||||
}
|
|
||||||
|
|
||||||
static INLINE int cursor_push_i32(struct cursor *stack, int i)
|
|
||||||
{
|
|
||||||
struct val val;
|
|
||||||
val.type = val_i32;
|
|
||||||
val.num.i32 = i;
|
|
||||||
|
|
||||||
return cursor_pushval(stack, &val);
|
|
||||||
}
|
|
||||||
|
|
||||||
static INLINE int stack_push_i32(struct wasm_interp *interp, int i)
|
|
||||||
{
|
|
||||||
return cursor_push_i32(&interp->stack, i);
|
|
||||||
}
|
|
||||||
|
|
||||||
static INLINE struct callframe *top_callframes(struct cursor *cur, int top)
|
|
||||||
{
|
|
||||||
return (struct callframe*)cursor_topn(cur, sizeof(struct callframe), top);
|
|
||||||
}
|
|
||||||
|
|
||||||
static INLINE int was_section_parsed(struct module *module,
|
|
||||||
enum section_tag section)
|
|
||||||
{
|
|
||||||
if (section == section_custom)
|
|
||||||
return module->custom_sections > 0;
|
|
||||||
|
|
||||||
return module->parsed & (1 << section);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#endif /* PROTOVERSE_WASM_H */
|
|
||||||
+99
-1079
File diff suppressed because it is too large
Load Diff
@@ -25,14 +25,6 @@
|
|||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "40b4b38b3b1c83f7088c76189a742870e0ca06a9"
|
"revision" : "40b4b38b3b1c83f7088c76189a742870e0ca06a9"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
"identity" : "swift-markdown-ui",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/damus-io/swift-markdown-ui",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "76bb7971da7fbf429de1c84f1244adf657242fee"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"version" : 2
|
"version" : 2
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "1500"
|
LastUpgradeVersion = "1420"
|
||||||
version = "1.3">
|
version = "1.3">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "1500"
|
LastUpgradeVersion = "1420"
|
||||||
version = "1.3">
|
version = "1.3">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
@@ -26,8 +26,7 @@
|
|||||||
buildConfiguration = "Debug"
|
buildConfiguration = "Debug"
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||||
codeCoverageEnabled = "YES">
|
|
||||||
<Testables>
|
<Testables>
|
||||||
<TestableReference
|
<TestableReference
|
||||||
skipped = "NO">
|
skipped = "NO">
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
{
|
|
||||||
"colors" : [
|
|
||||||
{
|
|
||||||
"color" : {
|
|
||||||
"color-space" : "srgb",
|
|
||||||
"components" : {
|
|
||||||
"alpha" : "1.000",
|
|
||||||
"blue" : "0xE3",
|
|
||||||
"green" : "0xD7",
|
|
||||||
"red" : "0xF7"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"idiom" : "universal"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"appearances" : [
|
|
||||||
{
|
|
||||||
"appearance" : "luminosity",
|
|
||||||
"value" : "dark"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"color" : {
|
|
||||||
"color-space" : "srgb",
|
|
||||||
"components" : {
|
|
||||||
"alpha" : "1.000",
|
|
||||||
"blue" : "0x20",
|
|
||||||
"green" : "0x13",
|
|
||||||
"red" : "0x61"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"idiom" : "universal"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
{
|
|
||||||
"colors" : [
|
|
||||||
{
|
|
||||||
"color" : {
|
|
||||||
"color-space" : "srgb",
|
|
||||||
"components" : {
|
|
||||||
"alpha" : "1.000",
|
|
||||||
"blue" : "0x63",
|
|
||||||
"green" : "0x11",
|
|
||||||
"red" : "0xF5"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"idiom" : "universal"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"appearances" : [
|
|
||||||
{
|
|
||||||
"appearance" : "luminosity",
|
|
||||||
"value" : "dark"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"color" : {
|
|
||||||
"color-space" : "srgb",
|
|
||||||
"components" : {
|
|
||||||
"alpha" : "1.000",
|
|
||||||
"blue" : "0x6E",
|
|
||||||
"green" : "0x20",
|
|
||||||
"red" : "0xF8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"idiom" : "universal"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
{
|
|
||||||
"colors" : [
|
|
||||||
{
|
|
||||||
"color" : {
|
|
||||||
"color-space" : "srgb",
|
|
||||||
"components" : {
|
|
||||||
"alpha" : "1.000",
|
|
||||||
"blue" : "0xEE",
|
|
||||||
"green" : "0xE8",
|
|
||||||
"red" : "0xF7"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"idiom" : "universal"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"appearances" : [
|
|
||||||
{
|
|
||||||
"appearance" : "luminosity",
|
|
||||||
"value" : "dark"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"color" : {
|
|
||||||
"color-space" : "srgb",
|
|
||||||
"components" : {
|
|
||||||
"alpha" : "1.000",
|
|
||||||
"blue" : "0x35",
|
|
||||||
"green" : "0x04",
|
|
||||||
"red" : "0x8B"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"idiom" : "universal"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
{
|
|
||||||
"colors" : [
|
|
||||||
{
|
|
||||||
"color" : {
|
|
||||||
"color-space" : "srgb",
|
|
||||||
"components" : {
|
|
||||||
"alpha" : "1.000",
|
|
||||||
"blue" : "0x3D",
|
|
||||||
"green" : "0x07",
|
|
||||||
"red" : "0x9C"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"idiom" : "universal"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"appearances" : [
|
|
||||||
{
|
|
||||||
"appearance" : "luminosity",
|
|
||||||
"value" : "dark"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"color" : {
|
|
||||||
"color-space" : "srgb",
|
|
||||||
"components" : {
|
|
||||||
"alpha" : "1.000",
|
|
||||||
"blue" : "0x44",
|
|
||||||
"green" : "0x06",
|
|
||||||
"red" : "0xB2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"idiom" : "universal"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
{
|
|
||||||
"colors" : [
|
|
||||||
{
|
|
||||||
"color" : {
|
|
||||||
"color-space" : "srgb",
|
|
||||||
"components" : {
|
|
||||||
"alpha" : "1.000",
|
|
||||||
"blue" : "0x2D",
|
|
||||||
"green" : "0x05",
|
|
||||||
"red" : "0x75"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"idiom" : "universal"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"appearances" : [
|
|
||||||
{
|
|
||||||
"appearance" : "luminosity",
|
|
||||||
"value" : "dark"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"color" : {
|
|
||||||
"color-space" : "srgb",
|
|
||||||
"components" : {
|
|
||||||
"alpha" : "1.000",
|
|
||||||
"blue" : "0xD8",
|
|
||||||
"green" : "0xC2",
|
|
||||||
"red" : "0xFF"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"idiom" : "universal"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
{
|
|
||||||
"colors" : [
|
|
||||||
{
|
|
||||||
"color" : {
|
|
||||||
"color-space" : "srgb",
|
|
||||||
"components" : {
|
|
||||||
"alpha" : "1.000",
|
|
||||||
"blue" : "0xFA",
|
|
||||||
"green" : "0xFA",
|
|
||||||
"red" : "0xF9"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"idiom" : "universal"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"appearances" : [
|
|
||||||
{
|
|
||||||
"appearance" : "luminosity",
|
|
||||||
"value" : "dark"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"color" : {
|
|
||||||
"color-space" : "srgb",
|
|
||||||
"components" : {
|
|
||||||
"alpha" : "1.000",
|
|
||||||
"blue" : "0x24",
|
|
||||||
"green" : "0x22",
|
|
||||||
"red" : "0x20"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"idiom" : "universal"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
{
|
|
||||||
"colors" : [
|
|
||||||
{
|
|
||||||
"color" : {
|
|
||||||
"color-space" : "srgb",
|
|
||||||
"components" : {
|
|
||||||
"alpha" : "1.000",
|
|
||||||
"blue" : "0xE3",
|
|
||||||
"green" : "0xE1",
|
|
||||||
"red" : "0xDD"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"idiom" : "universal"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"appearances" : [
|
|
||||||
{
|
|
||||||
"appearance" : "luminosity",
|
|
||||||
"value" : "dark"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"color" : {
|
|
||||||
"color-space" : "srgb",
|
|
||||||
"components" : {
|
|
||||||
"alpha" : "1.000",
|
|
||||||
"blue" : "0x2A",
|
|
||||||
"green" : "0x26",
|
|
||||||
"red" : "0x23"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"idiom" : "universal"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
{
|
|
||||||
"colors" : [
|
|
||||||
{
|
|
||||||
"color" : {
|
|
||||||
"color-space" : "srgb",
|
|
||||||
"components" : {
|
|
||||||
"alpha" : "1.000",
|
|
||||||
"blue" : "0x59",
|
|
||||||
"green" : "0x53",
|
|
||||||
"red" : "0x4A"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"idiom" : "universal"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"appearances" : [
|
|
||||||
{
|
|
||||||
"appearance" : "luminosity",
|
|
||||||
"value" : "dark"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"color" : {
|
|
||||||
"color-space" : "srgb",
|
|
||||||
"components" : {
|
|
||||||
"alpha" : "1.000",
|
|
||||||
"blue" : "0x85",
|
|
||||||
"green" : "0x7A",
|
|
||||||
"red" : "0x6A"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"idiom" : "universal"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
{
|
|
||||||
"colors" : [
|
|
||||||
{
|
|
||||||
"color" : {
|
|
||||||
"color-space" : "srgb",
|
|
||||||
"components" : {
|
|
||||||
"alpha" : "1.000",
|
|
||||||
"blue" : "0xE4",
|
|
||||||
"green" : "0xF1",
|
|
||||||
"red" : "0xD6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"idiom" : "universal"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"appearances" : [
|
|
||||||
{
|
|
||||||
"appearance" : "luminosity",
|
|
||||||
"value" : "dark"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"color" : {
|
|
||||||
"color-space" : "srgb",
|
|
||||||
"components" : {
|
|
||||||
"alpha" : "1.000",
|
|
||||||
"blue" : "0x38",
|
|
||||||
"green" : "0x5C",
|
|
||||||
"red" : "0x12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"idiom" : "universal"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
{
|
|
||||||
"colors" : [
|
|
||||||
{
|
|
||||||
"color" : {
|
|
||||||
"color-space" : "srgb",
|
|
||||||
"components" : {
|
|
||||||
"alpha" : "1.000",
|
|
||||||
"blue" : "0x5A",
|
|
||||||
"green" : "0xAB",
|
|
||||||
"red" : "0x04"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"idiom" : "universal"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"appearances" : [
|
|
||||||
{
|
|
||||||
"appearance" : "luminosity",
|
|
||||||
"value" : "dark"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"color" : {
|
|
||||||
"color-space" : "srgb",
|
|
||||||
"components" : {
|
|
||||||
"alpha" : "1.000",
|
|
||||||
"blue" : "0x64",
|
|
||||||
"green" : "0xBF",
|
|
||||||
"red" : "0x03"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"idiom" : "universal"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
{
|
|
||||||
"colors" : [
|
|
||||||
{
|
|
||||||
"color" : {
|
|
||||||
"color-space" : "srgb",
|
|
||||||
"components" : {
|
|
||||||
"alpha" : "1.000",
|
|
||||||
"blue" : "0xF0",
|
|
||||||
"green" : "0xF7",
|
|
||||||
"red" : "0xE8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"idiom" : "universal"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"appearances" : [
|
|
||||||
{
|
|
||||||
"appearance" : "luminosity",
|
|
||||||
"value" : "dark"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"color" : {
|
|
||||||
"color-space" : "srgb",
|
|
||||||
"components" : {
|
|
||||||
"alpha" : "1.000",
|
|
||||||
"blue" : "0x1F",
|
|
||||||
"green" : "0x33",
|
|
||||||
"red" : "0x0A"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"idiom" : "universal"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
{
|
|
||||||
"colors" : [
|
|
||||||
{
|
|
||||||
"color" : {
|
|
||||||
"color-space" : "srgb",
|
|
||||||
"components" : {
|
|
||||||
"alpha" : "1.000",
|
|
||||||
"blue" : "0x34",
|
|
||||||
"green" : "0x64",
|
|
||||||
"red" : "0x02"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"idiom" : "universal"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"appearances" : [
|
|
||||||
{
|
|
||||||
"appearance" : "luminosity",
|
|
||||||
"value" : "dark"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"color" : {
|
|
||||||
"color-space" : "srgb",
|
|
||||||
"components" : {
|
|
||||||
"alpha" : "1.000",
|
|
||||||
"blue" : "0x3F",
|
|
||||||
"green" : "0x79",
|
|
||||||
"red" : "0x02"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"idiom" : "universal"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
{
|
|
||||||
"colors" : [
|
|
||||||
{
|
|
||||||
"color" : {
|
|
||||||
"color-space" : "srgb",
|
|
||||||
"components" : {
|
|
||||||
"alpha" : "1.000",
|
|
||||||
"blue" : "0x1F",
|
|
||||||
"green" : "0x3C",
|
|
||||||
"red" : "0x01"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"idiom" : "universal"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"appearances" : [
|
|
||||||
{
|
|
||||||
"appearance" : "luminosity",
|
|
||||||
"value" : "dark"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"color" : {
|
|
||||||
"color-space" : "srgb",
|
|
||||||
"components" : {
|
|
||||||
"alpha" : "1.000",
|
|
||||||
"blue" : "0xE4",
|
|
||||||
"green" : "0xFF",
|
|
||||||
"red" : "0xAD"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"idiom" : "universal"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
{
|
|
||||||
"colors" : [
|
|
||||||
{
|
|
||||||
"color" : {
|
|
||||||
"color-space" : "srgb",
|
|
||||||
"components" : {
|
|
||||||
"alpha" : "1.000",
|
|
||||||
"blue" : "0xD1",
|
|
||||||
"green" : "0xEE",
|
|
||||||
"red" : "0xFE"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"idiom" : "universal"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"appearances" : [
|
|
||||||
{
|
|
||||||
"appearance" : "luminosity",
|
|
||||||
"value" : "dark"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"color" : {
|
|
||||||
"color-space" : "srgb",
|
|
||||||
"components" : {
|
|
||||||
"alpha" : "1.000",
|
|
||||||
"blue" : "0x12",
|
|
||||||
"green" : "0x43",
|
|
||||||
"red" : "0x5C"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"idiom" : "universal"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
{
|
|
||||||
"colors" : [
|
|
||||||
{
|
|
||||||
"color" : {
|
|
||||||
"color-space" : "srgb",
|
|
||||||
"components" : {
|
|
||||||
"alpha" : "1.000",
|
|
||||||
"blue" : "0x1C",
|
|
||||||
"green" : "0xAD",
|
|
||||||
"red" : "0xF9"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"idiom" : "universal"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"appearances" : [
|
|
||||||
{
|
|
||||||
"appearance" : "luminosity",
|
|
||||||
"value" : "dark"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"color" : {
|
|
||||||
"color-space" : "srgb",
|
|
||||||
"components" : {
|
|
||||||
"alpha" : "1.000",
|
|
||||||
"blue" : "0x2C",
|
|
||||||
"green" : "0xB5",
|
|
||||||
"red" : "0xFC"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"idiom" : "universal"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
{
|
|
||||||
"colors" : [
|
|
||||||
{
|
|
||||||
"color" : {
|
|
||||||
"color-space" : "srgb",
|
|
||||||
"components" : {
|
|
||||||
"alpha" : "1.000",
|
|
||||||
"blue" : "0xE1",
|
|
||||||
"green" : "0xF4",
|
|
||||||
"red" : "0xFE"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"idiom" : "universal"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"appearances" : [
|
|
||||||
{
|
|
||||||
"appearance" : "luminosity",
|
|
||||||
"value" : "dark"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"color" : {
|
|
||||||
"color-space" : "srgb",
|
|
||||||
"components" : {
|
|
||||||
"alpha" : "1.000",
|
|
||||||
"blue" : "0x0A",
|
|
||||||
"green" : "0x25",
|
|
||||||
"red" : "0x33"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"idiom" : "universal"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
{
|
|
||||||
"colors" : [
|
|
||||||
{
|
|
||||||
"color" : {
|
|
||||||
"color-space" : "srgb",
|
|
||||||
"components" : {
|
|
||||||
"alpha" : "1.000",
|
|
||||||
"blue" : "0x06",
|
|
||||||
"green" : "0x85",
|
|
||||||
"red" : "0xC6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"idiom" : "universal"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"appearances" : [
|
|
||||||
{
|
|
||||||
"appearance" : "luminosity",
|
|
||||||
"value" : "dark"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"color" : {
|
|
||||||
"color-space" : "srgb",
|
|
||||||
"components" : {
|
|
||||||
"alpha" : "1.000",
|
|
||||||
"blue" : "0x03",
|
|
||||||
"green" : "0x93",
|
|
||||||
"red" : "0xDD"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"idiom" : "universal"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
{
|
|
||||||
"colors" : [
|
|
||||||
{
|
|
||||||
"color" : {
|
|
||||||
"color-space" : "srgb",
|
|
||||||
"components" : {
|
|
||||||
"alpha" : "1.000",
|
|
||||||
"blue" : "0x04",
|
|
||||||
"green" : "0x6A",
|
|
||||||
"red" : "0x9F"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"idiom" : "universal"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"appearances" : [
|
|
||||||
{
|
|
||||||
"appearance" : "luminosity",
|
|
||||||
"value" : "dark"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"color" : {
|
|
||||||
"color-space" : "srgb",
|
|
||||||
"components" : {
|
|
||||||
"alpha" : "1.000",
|
|
||||||
"blue" : "0xCC",
|
|
||||||
"green" : "0xF5",
|
|
||||||
"red" : "0xFF"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"idiom" : "universal"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -22,23 +22,5 @@ class DamusColors {
|
|||||||
static let purple = Color("DamusPurple")
|
static let purple = Color("DamusPurple")
|
||||||
static let deepPurple = Color("DamusDeepPurple")
|
static let deepPurple = Color("DamusDeepPurple")
|
||||||
static let blue = Color("DamusBlue")
|
static let blue = Color("DamusBlue")
|
||||||
static let success = Color("DamusSuccessPrimary")
|
|
||||||
static let successSecondary = Color("DamusSuccessSecondary")
|
|
||||||
static let successTertiary = Color("DamusSuccessTertiary")
|
|
||||||
static let successQuaternary = Color("DamusSuccessQuaternary")
|
|
||||||
static let successBorder = Color("DamusSuccessBorder")
|
|
||||||
static let warning = Color("DamusWarningPrimary")
|
|
||||||
static let warningSecondary = Color("DamusWarningSecondary")
|
|
||||||
static let warningTertiary = Color("DamusWarningTertiary")
|
|
||||||
static let warningQuaternary = Color("DamusWarningQuaternary")
|
|
||||||
static let warningBorder = Color("DamusWarningBorder")
|
|
||||||
static let danger = Color("DamusDangerPrimary")
|
|
||||||
static let dangerSecondary = Color("DamusDangerSecondary")
|
|
||||||
static let dangerTertiary = Color("DamusDangerTertiary")
|
|
||||||
static let dangerQuaternary = Color("DamusDangerQuaternary")
|
|
||||||
static let dangerBorder = Color("DamusDangerBorder")
|
|
||||||
static let neutral1 = Color("DamusNeutral1")
|
|
||||||
static let neutral3 = Color("DamusNeutral3")
|
|
||||||
static let neutral6 = Color("DamusNeutral6")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,11 @@ import SwiftUI
|
|||||||
struct EndBlock: View {
|
struct EndBlock: View {
|
||||||
let height: CGFloat
|
let height: CGFloat
|
||||||
|
|
||||||
init(height: Float = 10) {
|
init () {
|
||||||
|
self.height = 10.0
|
||||||
|
}
|
||||||
|
|
||||||
|
init (height: Float) {
|
||||||
self.height = CGFloat(height)
|
self.height = CGFloat(height)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,21 +8,15 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct GradientButtonStyle: ButtonStyle {
|
struct GradientButtonStyle: ButtonStyle {
|
||||||
let padding: CGFloat
|
|
||||||
|
|
||||||
init(padding: CGFloat = 16.0) {
|
|
||||||
self.padding = padding
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeBody(configuration: Self.Configuration) -> some View {
|
func makeBody(configuration: Self.Configuration) -> some View {
|
||||||
return configuration.label
|
return configuration.label
|
||||||
.padding(padding)
|
.padding()
|
||||||
.foregroundColor(Color.white)
|
.foregroundColor(Color.white)
|
||||||
.background {
|
.background {
|
||||||
RoundedRectangle(cornerRadius: 12)
|
RoundedRectangle(cornerRadius: 12)
|
||||||
.fill(PinkGradient)
|
.fill(PinkGradient.gradient)
|
||||||
}
|
}
|
||||||
.scaleEffect(configuration.isPressed ? 0.95 : 1)
|
.scaleEffect(configuration.isPressed ? 0.8 : 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,11 +24,9 @@ struct GradientButtonStyle: ButtonStyle {
|
|||||||
struct GradientButtonStyle_Previews: PreviewProvider {
|
struct GradientButtonStyle_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
VStack {
|
VStack {
|
||||||
Button(action: {
|
Button("Dynamic Size", action: {
|
||||||
print("dynamic size")
|
print("dynamic size")
|
||||||
}) {
|
})
|
||||||
Text(verbatim: "Dynamic Size")
|
|
||||||
}
|
|
||||||
.buttonStyle(GradientButtonStyle())
|
.buttonStyle(GradientButtonStyle())
|
||||||
|
|
||||||
|
|
||||||
@@ -42,7 +34,7 @@ struct GradientButtonStyle_Previews: PreviewProvider {
|
|||||||
print("infinite width")
|
print("infinite width")
|
||||||
}) {
|
}) {
|
||||||
HStack {
|
HStack {
|
||||||
Text(verbatim: "Infinite Width")
|
Text("Infinite Width")
|
||||||
}
|
}
|
||||||
.frame(minWidth: 300, maxWidth: .infinity, alignment: .center)
|
.frame(minWidth: 300, maxWidth: .infinity, alignment: .center)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
//
|
|
||||||
// DamusBackground.swift
|
|
||||||
// damus
|
|
||||||
//
|
|
||||||
// Created by William Casarin on 2023-07-12.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct DamusBackground: View {
|
|
||||||
let maxHeight: CGFloat
|
|
||||||
|
|
||||||
init(maxHeight: CGFloat = 250.0) {
|
|
||||||
self.maxHeight = maxHeight
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
Image("login-header")
|
|
||||||
.resizable()
|
|
||||||
.frame(maxWidth: .infinity, maxHeight: maxHeight, alignment: .center)
|
|
||||||
.ignoresSafeArea()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct DamusBackground_Previews: PreviewProvider {
|
|
||||||
static var previews: some View {
|
|
||||||
DamusBackground()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
//
|
|
||||||
// DamusLightGradient.swift
|
|
||||||
// damus
|
|
||||||
//
|
|
||||||
// Created by eric on 9/8/23.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
fileprivate let damus_grad_c1 = hex_col(r: 0xd3, g: 0x2d, b: 0xc3)
|
|
||||||
fileprivate let damus_grad_c2 = hex_col(r: 0x33, g: 0xc5, b: 0xbc)
|
|
||||||
fileprivate let damus_grad = [damus_grad_c1, damus_grad_c2]
|
|
||||||
|
|
||||||
struct DamusLightGradient: View {
|
|
||||||
var body: some View {
|
|
||||||
DamusLightGradient.gradient
|
|
||||||
.opacity(0.5)
|
|
||||||
.edgesIgnoringSafeArea([.top,.bottom])
|
|
||||||
}
|
|
||||||
|
|
||||||
static var gradient: LinearGradient {
|
|
||||||
LinearGradient(colors: damus_grad, startPoint: .topLeading, endPoint: .bottomTrailing)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct DamusLightGradient_Previews: PreviewProvider {
|
|
||||||
static var previews: some View {
|
|
||||||
DamusLightGradient()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
//
|
|
||||||
// GrayGradient.swift
|
|
||||||
// damus
|
|
||||||
//
|
|
||||||
// Created by klabo on 7/20/23.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
let GrayGradient = LinearGradient(gradient:
|
|
||||||
Gradient(colors: [Color(#colorLiteral(red: 0.9764705882, green: 0.9803921569, blue: 0.9803921569, alpha: 1))]),
|
|
||||||
startPoint: .leading,
|
|
||||||
endPoint: .trailing)
|
|
||||||
|
|
||||||
struct GrayGradientView: View {
|
|
||||||
var body: some View {
|
|
||||||
GrayGradient
|
|
||||||
.edgesIgnoringSafeArea([.top, .bottom])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct GrayGradient_Previews: PreviewProvider {
|
|
||||||
static var previews: some View {
|
|
||||||
GrayGradientView()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -11,18 +11,20 @@ fileprivate let damus_grad_c1 = hex_col(r: 0xd3, g: 0x4c, b: 0xd9)
|
|||||||
fileprivate let damus_grad_c2 = hex_col(r: 0xf8, g: 0x69, b: 0xb6)
|
fileprivate let damus_grad_c2 = hex_col(r: 0xf8, g: 0x69, b: 0xb6)
|
||||||
fileprivate let pink_grad = [damus_grad_c1, damus_grad_c2]
|
fileprivate let pink_grad = [damus_grad_c1, damus_grad_c2]
|
||||||
|
|
||||||
let PinkGradient = LinearGradient(colors: pink_grad, startPoint: .topTrailing, endPoint: .bottom)
|
struct PinkGradient: View {
|
||||||
|
|
||||||
struct PinkGradientView: View {
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
PinkGradient
|
PinkGradient.gradient
|
||||||
.edgesIgnoringSafeArea([.top,.bottom])
|
.edgesIgnoringSafeArea([.top,.bottom])
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
static var gradient: LinearGradient {
|
||||||
struct PinkGradientView_Previews: PreviewProvider {
|
LinearGradient(colors: pink_grad, startPoint: .topTrailing, endPoint: .bottom)
|
||||||
static var previews: some View {
|
}
|
||||||
PinkGradientView()
|
}
|
||||||
|
|
||||||
|
struct PinkGradient_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
PinkGradient()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -53,11 +53,10 @@ enum ImageShape {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Image Carousel
|
// MARK: - Image Carousel
|
||||||
@MainActor
|
|
||||||
struct ImageCarousel: View {
|
struct ImageCarousel: View {
|
||||||
var urls: [MediaUrl]
|
var urls: [MediaUrl]
|
||||||
|
|
||||||
let evid: NoteId
|
let evid: String
|
||||||
|
|
||||||
let state: DamusState
|
let state: DamusState
|
||||||
|
|
||||||
@@ -72,7 +71,7 @@ struct ImageCarousel: View {
|
|||||||
@State private var selectedIndex = 0
|
@State private var selectedIndex = 0
|
||||||
@State private var video_size: CGSize? = nil
|
@State private var video_size: CGSize? = nil
|
||||||
|
|
||||||
init(state: DamusState, evid: NoteId, urls: [MediaUrl]) {
|
init(state: DamusState, evid: String, urls: [MediaUrl]) {
|
||||||
_open_sheet = State(initialValue: false)
|
_open_sheet = State(initialValue: false)
|
||||||
_current_url = State(initialValue: nil)
|
_current_url = State(initialValue: nil)
|
||||||
let media_model = state.events.get_cache_data(evid).media_metadata_model
|
let media_model = state.events.get_cache_data(evid).media_metadata_model
|
||||||
@@ -105,13 +104,17 @@ struct ImageCarousel: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
if self.image_fill == nil, let size = state.video.size_for_url(url) {
|
if self.image_fill == nil, let size = state.events.lookup_media_size(url: url) {
|
||||||
let fill = ImageFill.calculate_image_fill(geo_size: geo_size, img_size: size, maxHeight: maxHeight, fillHeight: fillHeight)
|
let fill = ImageFill.calculate_image_fill(geo_size: geo_size, img_size: size, maxHeight: maxHeight, fillHeight: fillHeight)
|
||||||
self.image_fill = fill
|
self.image_fill = fill
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func video_model(_ url: URL) -> VideoPlayerModel {
|
||||||
|
return state.events.get_video_player_model(url: url)
|
||||||
|
}
|
||||||
|
|
||||||
func Media(geo: GeometryProxy, url: MediaUrl, index: Int) -> some View {
|
func Media(geo: GeometryProxy, url: MediaUrl, index: Int) -> some View {
|
||||||
Group {
|
Group {
|
||||||
switch url {
|
switch url {
|
||||||
@@ -121,7 +124,7 @@ struct ImageCarousel: View {
|
|||||||
open_sheet = true
|
open_sheet = true
|
||||||
}
|
}
|
||||||
case .video(let url):
|
case .video(let url):
|
||||||
DamusVideoPlayer(url: url, video_size: $video_size, controller: state.video)
|
DamusVideoPlayer(url: url, model: video_model(url), video_size: $video_size)
|
||||||
.onChange(of: video_size) { size in
|
.onChange(of: video_size) { size in
|
||||||
guard let size else { return }
|
guard let size else { return }
|
||||||
|
|
||||||
@@ -190,7 +193,7 @@ struct ImageCarousel: View {
|
|||||||
}
|
}
|
||||||
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
|
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
|
||||||
.fullScreenCover(isPresented: $open_sheet) {
|
.fullScreenCover(isPresented: $open_sheet) {
|
||||||
ImageView(video_controller: state.video, urls: urls, disable_animation: state.settings.disable_animation)
|
ImageView(cache: state.events, urls: urls, disable_animation: state.settings.disable_animation)
|
||||||
}
|
}
|
||||||
.frame(height: height)
|
.frame(height: height)
|
||||||
.onChange(of: selectedIndex) { value in
|
.onChange(of: selectedIndex) { value in
|
||||||
@@ -285,7 +288,7 @@ public struct ImageFill {
|
|||||||
struct ImageCarousel_Previews: PreviewProvider {
|
struct ImageCarousel_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
let url: MediaUrl = .image(URL(string: "https://jb55.com/red-me.jpg")!)
|
let url: MediaUrl = .image(URL(string: "https://jb55.com/red-me.jpg")!)
|
||||||
ImageCarousel(state: test_damus_state, evid: test_note.id, urls: [url, url])
|
ImageCarousel(state: test_damus_state(), evid: "evid", urls: [url, url])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import SwiftUI
|
|||||||
|
|
||||||
struct InvoiceView: View {
|
struct InvoiceView: View {
|
||||||
@Environment(\.colorScheme) var colorScheme
|
@Environment(\.colorScheme) var colorScheme
|
||||||
let our_pubkey: Pubkey
|
let our_pubkey: String
|
||||||
let invoice: Invoice
|
let invoice: Invoice
|
||||||
@State var showing_select_wallet: Bool = false
|
@State var showing_select_wallet: Bool = false
|
||||||
@State var copied = false
|
@State var copied = false
|
||||||
@@ -37,7 +37,7 @@ struct InvoiceView: View {
|
|||||||
var PayButton: some View {
|
var PayButton: some View {
|
||||||
Button {
|
Button {
|
||||||
if settings.show_wallet_selector {
|
if settings.show_wallet_selector {
|
||||||
present_sheet(.select_wallet(invoice: invoice.string))
|
showing_select_wallet = true
|
||||||
} else {
|
} else {
|
||||||
open_with_wallet(wallet: settings.default_wallet.model, invoice: invoice.string)
|
open_with_wallet(wallet: settings.default_wallet.model, invoice: invoice.string)
|
||||||
}
|
}
|
||||||
@@ -79,6 +79,9 @@ struct InvoiceView: View {
|
|||||||
}
|
}
|
||||||
.padding(30)
|
.padding(30)
|
||||||
}
|
}
|
||||||
|
.sheet(isPresented: $showing_select_wallet, onDismiss: {showing_select_wallet = false}) {
|
||||||
|
SelectWalletView(default_wallet: settings.default_wallet, showingSelectWallet: $showing_select_wallet, our_pubkey: our_pubkey, invoice: invoice.string)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,12 +111,8 @@ let test_invoice = Invoice(description: .description("this is a description"), a
|
|||||||
|
|
||||||
struct InvoiceView_Previews: PreviewProvider {
|
struct InvoiceView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
InvoiceView(our_pubkey: .empty, invoice: test_invoice, settings: test_damus_state.settings)
|
InvoiceView(our_pubkey: "", invoice: test_invoice, settings: test_damus_state().settings)
|
||||||
.frame(width: 300, height: 200)
|
.frame(width: 300, height: 200)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func present_sheet(_ sheet: Sheets) {
|
|
||||||
notify(.present_sheet(sheet))
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct InvoicesView: View {
|
struct InvoicesView: View {
|
||||||
let our_pubkey: Pubkey
|
let our_pubkey: String
|
||||||
var invoices: [Invoice]
|
var invoices: [Invoice]
|
||||||
let settings: UserSettingsStore
|
let settings: UserSettingsStore
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ struct InvoicesView: View {
|
|||||||
|
|
||||||
struct InvoicesView_Previews: PreviewProvider {
|
struct InvoicesView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
InvoicesView(our_pubkey: test_note.pubkey, invoices: [Invoice.init(description: .description("description"), amount: .specific(10000), string: "invstr", expiry: 100000, payment_hash: Data(), created_at: 1000000)], settings: test_damus_state.settings)
|
InvoicesView(our_pubkey: "", invoices: [Invoice.init(description: .description("description"), amount: .specific(10000), string: "invstr", expiry: 100000, payment_hash: Data(), created_at: 1000000)], settings: test_damus_state().settings)
|
||||||
.frame(width: 300)
|
.frame(width: 300)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,19 +9,19 @@ import SwiftUI
|
|||||||
|
|
||||||
struct NIP05Badge: View {
|
struct NIP05Badge: View {
|
||||||
let nip05: NIP05
|
let nip05: NIP05
|
||||||
let pubkey: Pubkey
|
let pubkey: String
|
||||||
let contacts: Contacts
|
let contacts: Contacts
|
||||||
let show_domain: Bool
|
let show_domain: Bool
|
||||||
let profiles: Profiles
|
let clickable: Bool
|
||||||
|
|
||||||
@Environment(\.openURL) var openURL
|
@Environment(\.openURL) var openURL
|
||||||
|
|
||||||
init(nip05: NIP05, pubkey: Pubkey, contacts: Contacts, show_domain: Bool, profiles: Profiles) {
|
init (nip05: NIP05, pubkey: String, contacts: Contacts, show_domain: Bool, clickable: Bool) {
|
||||||
self.nip05 = nip05
|
self.nip05 = nip05
|
||||||
self.pubkey = pubkey
|
self.pubkey = pubkey
|
||||||
self.contacts = contacts
|
self.contacts = contacts
|
||||||
self.show_domain = show_domain
|
self.show_domain = show_domain
|
||||||
self.profiles = profiles
|
self.clickable = clickable
|
||||||
}
|
}
|
||||||
|
|
||||||
var nip05_color: Bool {
|
var nip05_color: Bool {
|
||||||
@@ -32,47 +32,34 @@ struct NIP05Badge: View {
|
|||||||
Group {
|
Group {
|
||||||
if nip05_color {
|
if nip05_color {
|
||||||
LINEAR_GRADIENT
|
LINEAR_GRADIENT
|
||||||
.mask(Image("verified.fill")
|
.mask(Image("check-circle.fill")
|
||||||
.resizable()
|
.resizable()
|
||||||
).frame(width: 18, height: 18)
|
).frame(width: 14, height: 14)
|
||||||
} else if show_domain {
|
} else if show_domain {
|
||||||
Image("verified")
|
Image("check-circle.fill")
|
||||||
.resizable()
|
.font(.footnote)
|
||||||
.frame(width: 18, height: 18)
|
|
||||||
.nip05_colorized(gradient: nip05_color)
|
.nip05_colorized(gradient: nip05_color)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var username_matches_nip05: Bool {
|
|
||||||
guard let name = profiles.lookup(id: pubkey).map({ p in p?.name }).value
|
|
||||||
else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return name.lowercased() == nip05.username.lowercased()
|
|
||||||
}
|
|
||||||
|
|
||||||
var nip05_string: String {
|
|
||||||
if nip05.username == "_" || username_matches_nip05 {
|
|
||||||
return nip05.host
|
|
||||||
} else {
|
|
||||||
return "\(nip05.username)@\(nip05.host)"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack(spacing: 2) {
|
HStack(spacing: 2) {
|
||||||
Seal
|
Seal
|
||||||
|
|
||||||
if show_domain {
|
if show_domain {
|
||||||
Text(nip05_string)
|
if clickable {
|
||||||
.nip05_colorized(gradient: nip05_color)
|
Text(nip05.host)
|
||||||
.onTapGesture {
|
.nip05_colorized(gradient: nip05_color)
|
||||||
if let nip5url = nip05.siteUrl {
|
.onTapGesture {
|
||||||
openURL(nip5url)
|
if let nip5url = nip05.siteUrl {
|
||||||
|
openURL(nip5url)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
Text(nip05.host)
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,22 +77,14 @@ extension View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func use_nip05_color(pubkey: Pubkey, contacts: Contacts) -> Bool {
|
func use_nip05_color(pubkey: String, contacts: Contacts) -> Bool {
|
||||||
return contacts.is_friend_or_self(pubkey) ? true : false
|
return contacts.is_friend_or_self(pubkey) ? true : false
|
||||||
}
|
}
|
||||||
|
|
||||||
struct NIP05Badge_Previews: PreviewProvider {
|
struct NIP05Badge_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
let test_state = test_damus_state
|
let test_state = test_damus_state()
|
||||||
VStack {
|
NIP05Badge(nip05: NIP05(username: "jb55", host: "jb55.com"), pubkey: test_state.pubkey, contacts: test_state.contacts, show_domain: true, clickable: false)
|
||||||
NIP05Badge(nip05: NIP05(username: "jb55", host: "jb55.com"), pubkey: test_state.pubkey, contacts: test_state.contacts, show_domain: true, profiles: test_state.profiles)
|
|
||||||
|
|
||||||
NIP05Badge(nip05: NIP05(username: "_", host: "jb55.com"), pubkey: test_state.pubkey, contacts: test_state.contacts, show_domain: true, profiles: test_state.profiles)
|
|
||||||
|
|
||||||
NIP05Badge(nip05: NIP05(username: "jb55", host: "jb55.com"), pubkey: test_state.pubkey, contacts: test_state.contacts, show_domain: true, profiles: test_state.profiles)
|
|
||||||
|
|
||||||
NIP05Badge(nip05: NIP05(username: "jb55", host: "jb55.com"), pubkey: test_state.pubkey, contacts: Contacts(our_pubkey: test_pubkey), show_domain: true, profiles: test_state.profiles)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,49 +0,0 @@
|
|||||||
//
|
|
||||||
// NeutralButtonStyle.swift
|
|
||||||
// damus
|
|
||||||
//
|
|
||||||
// Created by eric on 9/1/23.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct NeutralButtonStyle: ButtonStyle {
|
|
||||||
func makeBody(configuration: Self.Configuration) -> some View {
|
|
||||||
return configuration.label
|
|
||||||
.background(DamusColors.neutral1)
|
|
||||||
.cornerRadius(12)
|
|
||||||
.overlay(
|
|
||||||
RoundedRectangle(cornerRadius: 12)
|
|
||||||
.stroke(DamusColors.neutral3, lineWidth: 1)
|
|
||||||
)
|
|
||||||
.scaleEffect(configuration.isPressed ? 0.95 : 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
struct NeutralButtonStyle_Previews: PreviewProvider {
|
|
||||||
static var previews: some View {
|
|
||||||
VStack {
|
|
||||||
Button(action: {
|
|
||||||
print("dynamic size")
|
|
||||||
}) {
|
|
||||||
Text(verbatim: "Dynamic Size")
|
|
||||||
.padding()
|
|
||||||
}
|
|
||||||
.buttonStyle(NeutralButtonStyle())
|
|
||||||
|
|
||||||
|
|
||||||
Button(action: {
|
|
||||||
print("infinite width")
|
|
||||||
}) {
|
|
||||||
HStack {
|
|
||||||
Text(verbatim: "Infinite Width")
|
|
||||||
.padding()
|
|
||||||
}
|
|
||||||
.frame(minWidth: 300, maxWidth: .infinity, alignment: .center)
|
|
||||||
}
|
|
||||||
.buttonStyle(NeutralButtonStyle())
|
|
||||||
.padding()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -9,15 +9,16 @@ import SwiftUI
|
|||||||
|
|
||||||
struct Reposted: View {
|
struct Reposted: View {
|
||||||
let damus: DamusState
|
let damus: DamusState
|
||||||
let pubkey: Pubkey
|
let pubkey: String
|
||||||
|
let profile: Profile?
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack(alignment: .center) {
|
HStack(alignment: .center) {
|
||||||
Image("repost")
|
Image("repost")
|
||||||
.foregroundColor(Color.gray)
|
.foregroundColor(Color.gray)
|
||||||
ProfileName(pubkey: pubkey, damus: damus, show_nip5_domain: false)
|
ProfileName(pubkey: pubkey, profile: profile, damus: damus, show_nip5_domain: false)
|
||||||
.foregroundColor(Color.gray)
|
.foregroundColor(Color.gray)
|
||||||
Text("Reposted", comment: "Text indicating that the note was reposted (i.e. re-shared).")
|
Text("Reposted", comment: "Text indicating that the post was reposted (i.e. re-shared).")
|
||||||
.foregroundColor(Color.gray)
|
.foregroundColor(Color.gray)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -25,7 +26,7 @@ struct Reposted: View {
|
|||||||
|
|
||||||
struct Reposted_Previews: PreviewProvider {
|
struct Reposted_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
let test_state = test_damus_state
|
let test_state = test_damus_state()
|
||||||
Reposted(damus: test_state, pubkey: test_state.pubkey)
|
Reposted(damus: test_state, pubkey: test_state.pubkey, profile: make_test_profile())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,176 +0,0 @@
|
|||||||
//
|
|
||||||
// SearchIconView.swift
|
|
||||||
// damus
|
|
||||||
//
|
|
||||||
// Created by William Casarin on 2023-07-12.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct SearchHeaderView: View {
|
|
||||||
let state: DamusState
|
|
||||||
let described: DescribedSearch
|
|
||||||
@State var is_following: Bool
|
|
||||||
|
|
||||||
init(state: DamusState, described: DescribedSearch) {
|
|
||||||
self.state = state
|
|
||||||
self.described = described
|
|
||||||
|
|
||||||
let is_following = (described.is_hashtag.map {
|
|
||||||
ht in is_following_hashtag(contacts: state.contacts.event, hashtag: ht)
|
|
||||||
}) ?? false
|
|
||||||
|
|
||||||
self._is_following = State(wrappedValue: is_following)
|
|
||||||
}
|
|
||||||
|
|
||||||
var Icon: some View {
|
|
||||||
ZStack {
|
|
||||||
switch described {
|
|
||||||
case .hashtag:
|
|
||||||
SingleCharacterAvatar(character: "#")
|
|
||||||
case .unknown:
|
|
||||||
SystemIconAvatar(system_name: "magnifyingglass")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var SearchText: Text {
|
|
||||||
Text(described.description)
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
HStack(alignment: .center, spacing: 30) {
|
|
||||||
Icon
|
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 10.0) {
|
|
||||||
SearchText
|
|
||||||
.foregroundStyle(DamusLogoGradient.gradient)
|
|
||||||
.font(.title.bold())
|
|
||||||
|
|
||||||
if state.is_privkey_user, case .hashtag(let ht) = described {
|
|
||||||
if is_following {
|
|
||||||
HashtagUnfollowButton(damus_state: state, hashtag: ht, is_following: $is_following)
|
|
||||||
} else {
|
|
||||||
HashtagFollowButton(damus_state: state, hashtag: ht, is_following: $is_following)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.onReceive(handle_notify(.followed)) { ref in
|
|
||||||
guard hashtag_matches_search(desc: self.described, ref: ref) else { return }
|
|
||||||
self.is_following = true
|
|
||||||
}
|
|
||||||
.onReceive(handle_notify(.unfollowed)) { ref in
|
|
||||||
guard hashtag_matches_search(desc: self.described, ref: ref) else { return }
|
|
||||||
self.is_following = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SystemIconAvatar: View {
|
|
||||||
let system_name: String
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
NonImageAvatar {
|
|
||||||
Image(systemName: system_name)
|
|
||||||
.font(.title.bold())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SingleCharacterAvatar: View {
|
|
||||||
let character: String
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
NonImageAvatar {
|
|
||||||
Text(verbatim: character)
|
|
||||||
.font(.largeTitle.bold())
|
|
||||||
.mask(Text(verbatim: character)
|
|
||||||
.font(.largeTitle.bold()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct NonImageAvatar<Content: View>: View {
|
|
||||||
let content: Content
|
|
||||||
|
|
||||||
init(@ViewBuilder content: () -> Content) {
|
|
||||||
self.content = content()
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
ZStack {
|
|
||||||
Circle()
|
|
||||||
.fill(Color(red: 0xF8/255.0, green: 0xE7/255.0, blue: 0xF8/255.0))
|
|
||||||
.frame(width: 54, height: 54)
|
|
||||||
|
|
||||||
content
|
|
||||||
.foregroundStyle(PinkGradient)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct HashtagUnfollowButton: View {
|
|
||||||
let damus_state: DamusState
|
|
||||||
let hashtag: String
|
|
||||||
@Binding var is_following: Bool
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
return Button(action: { unfollow(hashtag) }) {
|
|
||||||
Text("Unfollow hashtag", comment: "Button to unfollow a given hashtag.")
|
|
||||||
.font(.footnote.bold())
|
|
||||||
}
|
|
||||||
.buttonStyle(GradientButtonStyle(padding: 10))
|
|
||||||
}
|
|
||||||
|
|
||||||
func unfollow(_ hashtag: String) {
|
|
||||||
is_following = false
|
|
||||||
handle_unfollow(state: damus_state, unfollow: FollowRef.hashtag(hashtag))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct HashtagFollowButton: View {
|
|
||||||
let damus_state: DamusState
|
|
||||||
let hashtag: String
|
|
||||||
@Binding var is_following: Bool
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
return Button(action: { follow(hashtag) }) {
|
|
||||||
Text("Follow hashtag", comment: "Button to follow a given hashtag.")
|
|
||||||
.font(.footnote.bold())
|
|
||||||
}
|
|
||||||
.buttonStyle(GradientButtonStyle(padding: 10))
|
|
||||||
}
|
|
||||||
|
|
||||||
func follow(_ hashtag: String) {
|
|
||||||
is_following = true
|
|
||||||
handle_follow(state: damus_state, follow: .hashtag(hashtag))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func hashtag_matches_search(desc: DescribedSearch, ref: FollowRef) -> Bool {
|
|
||||||
guard case .hashtag(let follow_ht) = ref,
|
|
||||||
case .hashtag(let search_ht) = desc,
|
|
||||||
follow_ht == search_ht
|
|
||||||
else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func is_following_hashtag(contacts: NostrEvent?, hashtag: String) -> Bool {
|
|
||||||
guard let contacts else { return false }
|
|
||||||
return is_already_following(contacts: contacts, follow: .hashtag(hashtag))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
struct SearchHeaderView_Previews: PreviewProvider {
|
|
||||||
static var previews: some View {
|
|
||||||
VStack(alignment: .leading) {
|
|
||||||
SearchHeaderView(state: test_damus_state, described: .hashtag("damus"))
|
|
||||||
|
|
||||||
SearchHeaderView(state: test_damus_state, described: .unknown)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -28,11 +28,7 @@ struct SelectableText: View {
|
|||||||
)
|
)
|
||||||
.padding([.leading, .trailing], -1.0)
|
.padding([.leading, .trailing], -1.0)
|
||||||
.onAppear {
|
.onAppear {
|
||||||
if geo.size.width == .zero {
|
self.selectedTextWidth = geo.size.width
|
||||||
self.selectedTextHeight = 1000.0
|
|
||||||
} else {
|
|
||||||
self.selectedTextWidth = geo.size.width
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.onChange(of: geo.size) { newSize in
|
.onChange(of: geo.size) { newSize in
|
||||||
self.selectedTextWidth = newSize.width
|
self.selectedTextWidth = newSize.width
|
||||||
|
|||||||
@@ -1,48 +0,0 @@
|
|||||||
//
|
|
||||||
// MusicController.swift
|
|
||||||
// damus
|
|
||||||
//
|
|
||||||
// Created by William Casarin on 2023-08-21.
|
|
||||||
//
|
|
||||||
import SwiftUI
|
|
||||||
import MediaPlayer
|
|
||||||
|
|
||||||
enum MusicState {
|
|
||||||
case playback_state(MPMusicPlaybackState)
|
|
||||||
case song(MPMediaItem?)
|
|
||||||
}
|
|
||||||
|
|
||||||
class MusicController {
|
|
||||||
let player: MPMusicPlayerController
|
|
||||||
|
|
||||||
let onChange: (MusicState) -> ()
|
|
||||||
|
|
||||||
init(onChange: @escaping (MusicState) -> ()) {
|
|
||||||
player = .systemMusicPlayer
|
|
||||||
|
|
||||||
player.beginGeneratingPlaybackNotifications()
|
|
||||||
|
|
||||||
self.onChange = onChange
|
|
||||||
|
|
||||||
print("Playback State: \(player.playbackState)")
|
|
||||||
print("Now Playing Item: \(player.nowPlayingItem?.title ?? "None")")
|
|
||||||
|
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(self.songChanged(notification:)), name: .MPMusicPlayerControllerNowPlayingItemDidChange, object: player)
|
|
||||||
|
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(self.playbackStatusChanged(notification:)), name: .MPMusicPlayerControllerPlaybackStateDidChange, object: player)
|
|
||||||
}
|
|
||||||
|
|
||||||
deinit {
|
|
||||||
print("deinit musiccontroller")
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc
|
|
||||||
func songChanged(notification: Notification) {
|
|
||||||
onChange(.song(player.nowPlayingItem))
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc
|
|
||||||
func playbackStatusChanged(notification: Notification) {
|
|
||||||
onChange(.playback_state(player.playbackState))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,183 +0,0 @@
|
|||||||
//
|
|
||||||
// UserStatus.swift
|
|
||||||
// damus
|
|
||||||
//
|
|
||||||
// Created by William Casarin on 2023-08-22.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import MediaPlayer
|
|
||||||
|
|
||||||
struct Song {
|
|
||||||
let started_playing: Date
|
|
||||||
let content: String
|
|
||||||
}
|
|
||||||
|
|
||||||
struct UserStatus {
|
|
||||||
let type: UserStatusType
|
|
||||||
let expires_at: Date?
|
|
||||||
var content: String
|
|
||||||
let created_at: UInt32
|
|
||||||
var url: URL?
|
|
||||||
|
|
||||||
func to_note(keypair: FullKeypair) -> NostrEvent? {
|
|
||||||
return make_user_status_note(status: self, keypair: keypair)
|
|
||||||
}
|
|
||||||
|
|
||||||
init(type: UserStatusType, expires_at: Date?, content: String, created_at: UInt32, url: URL? = nil) {
|
|
||||||
self.type = type
|
|
||||||
self.expires_at = expires_at
|
|
||||||
self.content = content
|
|
||||||
self.created_at = created_at
|
|
||||||
self.url = url
|
|
||||||
}
|
|
||||||
|
|
||||||
func expired() -> Bool {
|
|
||||||
guard let expires_at else { return false }
|
|
||||||
return Date.now >= expires_at
|
|
||||||
}
|
|
||||||
|
|
||||||
init?(ev: NostrEvent) {
|
|
||||||
guard let tag = ev.referenced_params.just_one() else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
let str = tag.param.string()
|
|
||||||
if str == "general" {
|
|
||||||
self.type = .general
|
|
||||||
} else if str == "music" {
|
|
||||||
self.type = .music
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if let tag = ev.tags.first(where: { t in t.count >= 2 && t[0].matches_char("r") }),
|
|
||||||
tag.count >= 2,
|
|
||||||
let url = URL(string: tag[1].string())
|
|
||||||
{
|
|
||||||
self.url = url
|
|
||||||
} else {
|
|
||||||
self.url = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if let tag = ev.tags.first(where: { t in t.count >= 2 && t[0].matches_str("expiration") }),
|
|
||||||
tag.count == 2,
|
|
||||||
let expires = UInt32(tag[1].string())
|
|
||||||
{
|
|
||||||
self.expires_at = Date(timeIntervalSince1970: TimeInterval(expires))
|
|
||||||
} else {
|
|
||||||
self.expires_at = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
self.content = ev.content
|
|
||||||
self.created_at = ev.created_at
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
enum UserStatusType: String {
|
|
||||||
case music
|
|
||||||
case general
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class UserStatusModel: ObservableObject {
|
|
||||||
@Published var general: UserStatus?
|
|
||||||
@Published var music: UserStatus?
|
|
||||||
|
|
||||||
func update_status(_ s: UserStatus) {
|
|
||||||
// whitespace = delete
|
|
||||||
let del = s.content.allSatisfy({ c in c.isWhitespace })
|
|
||||||
|
|
||||||
switch s.type {
|
|
||||||
case .music:
|
|
||||||
if del {
|
|
||||||
self.music = nil
|
|
||||||
} else {
|
|
||||||
self.music = s
|
|
||||||
}
|
|
||||||
case .general:
|
|
||||||
if del {
|
|
||||||
self.general = nil
|
|
||||||
} else {
|
|
||||||
self.general = s
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func try_expire() {
|
|
||||||
if let general, general.expired() {
|
|
||||||
self.general = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if let music, music.expired() {
|
|
||||||
self.music = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var _playing_enabled: Bool
|
|
||||||
var playing_enabled: Bool {
|
|
||||||
set {
|
|
||||||
var new_val = newValue
|
|
||||||
|
|
||||||
if newValue {
|
|
||||||
MPMediaLibrary.requestAuthorization { astatus in
|
|
||||||
switch astatus {
|
|
||||||
case .notDetermined: new_val = false
|
|
||||||
case .denied: new_val = false
|
|
||||||
case .restricted: new_val = false
|
|
||||||
case .authorized: new_val = true
|
|
||||||
@unknown default:
|
|
||||||
new_val = false
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if new_val != playing_enabled {
|
|
||||||
_playing_enabled = new_val
|
|
||||||
self.objectWillChange.send()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get {
|
|
||||||
return _playing_enabled
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
init(playing: UserStatus? = nil, status: UserStatus? = nil) {
|
|
||||||
self.general = status
|
|
||||||
self.music = playing
|
|
||||||
self._playing_enabled = false
|
|
||||||
self.playing_enabled = false
|
|
||||||
}
|
|
||||||
|
|
||||||
static var current_track: String? {
|
|
||||||
let player = MPMusicPlayerController.systemMusicPlayer
|
|
||||||
guard let nowPlayingItem = player.nowPlayingItem else { return nil }
|
|
||||||
return nowPlayingItem.title
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func make_user_status_note(status: UserStatus, keypair: FullKeypair, expiry: Date? = nil) -> NostrEvent?
|
|
||||||
{
|
|
||||||
var tags: [[String]] = [ ["d", status.type.rawValue] ]
|
|
||||||
|
|
||||||
if let expiry {
|
|
||||||
tags.append(["expiration", String(UInt32(expiry.timeIntervalSince1970))])
|
|
||||||
} else if let expiry = status.expires_at {
|
|
||||||
tags.append(["expiration", String(UInt32(expiry.timeIntervalSince1970))])
|
|
||||||
}
|
|
||||||
|
|
||||||
if let url = status.url {
|
|
||||||
tags.append(["r", url.absoluteString])
|
|
||||||
}
|
|
||||||
|
|
||||||
let kind = NostrKind.status.rawValue
|
|
||||||
guard let ev = NostrEvent(content: status.content, keypair: keypair.to_keypair(), kind: kind, tags: tags) else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return ev
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,160 +0,0 @@
|
|||||||
//
|
|
||||||
// UserStatusSheet.swift
|
|
||||||
// damus
|
|
||||||
//
|
|
||||||
// Created by William Casarin on 2023-08-23.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
enum StatusDuration: CustomStringConvertible, CaseIterable {
|
|
||||||
case never
|
|
||||||
case thirty_mins
|
|
||||||
case hour
|
|
||||||
case four_hours
|
|
||||||
case day
|
|
||||||
case week
|
|
||||||
|
|
||||||
var timeInterval: TimeInterval? {
|
|
||||||
switch self {
|
|
||||||
case .never:
|
|
||||||
return nil
|
|
||||||
case .thirty_mins:
|
|
||||||
return 60 * 30
|
|
||||||
case .hour:
|
|
||||||
return 60 * 60
|
|
||||||
case .four_hours:
|
|
||||||
return 60 * 60 * 4
|
|
||||||
case .day:
|
|
||||||
return 60 * 60 * 24
|
|
||||||
case .week:
|
|
||||||
return 60 * 60 * 24 * 7
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var expiration: Date? {
|
|
||||||
guard let timeInterval else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return Date.now.addingTimeInterval(timeInterval)
|
|
||||||
}
|
|
||||||
|
|
||||||
var description: String {
|
|
||||||
guard let timeInterval else {
|
|
||||||
return NSLocalizedString("Never", comment: "Profile status duration setting of never expiring.")
|
|
||||||
}
|
|
||||||
|
|
||||||
let formatter = DateComponentsFormatter()
|
|
||||||
formatter.unitsStyle = .full
|
|
||||||
formatter.allowedUnits = [.minute, .hour, .day, .weekOfMonth]
|
|
||||||
return formatter.string(from: timeInterval) ?? "\(timeInterval) seconds"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct UserStatusSheet: View {
|
|
||||||
let postbox: PostBox
|
|
||||||
let keypair: Keypair
|
|
||||||
|
|
||||||
@State var duration: StatusDuration = .never
|
|
||||||
|
|
||||||
@ObservedObject var status: UserStatusModel
|
|
||||||
@Environment(\.dismiss) var dismiss
|
|
||||||
|
|
||||||
var status_binding: Binding<String> {
|
|
||||||
Binding(get: {
|
|
||||||
status.general?.content ?? ""
|
|
||||||
}, set: { v in
|
|
||||||
if let general = status.general {
|
|
||||||
status.general = UserStatus(type: .general, expires_at: duration.expiration, content: v, created_at: UInt32(Date.now.timeIntervalSince1970), url: general.url)
|
|
||||||
} else {
|
|
||||||
status.general = UserStatus(type: .general, expires_at: duration.expiration, content: v, created_at: UInt32(Date.now.timeIntervalSince1970), url: nil)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
var url_binding: Binding<String> {
|
|
||||||
Binding(get: {
|
|
||||||
status.general?.url?.absoluteString ?? ""
|
|
||||||
}, set: { v in
|
|
||||||
if let general = status.general {
|
|
||||||
status.general = UserStatus(type: .general, expires_at: duration.expiration, content: general.content, created_at: UInt32(Date.now.timeIntervalSince1970), url: URL(string: v))
|
|
||||||
} else {
|
|
||||||
status.general = UserStatus(type: .general, expires_at: duration.expiration, content: "", created_at: UInt32(Date.now.timeIntervalSince1970), url: URL(string: v))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
VStack(alignment: .leading, spacing: 20) {
|
|
||||||
Text("Set Status", comment: "Title of view that allows the user to set their profile status (e.g. working, studying, coding)")
|
|
||||||
.font(.largeTitle)
|
|
||||||
|
|
||||||
TextField(text: status_binding, label: {
|
|
||||||
Text("📋 Working", comment: "Placeholder as an example of what the user could set as their profile status.")
|
|
||||||
})
|
|
||||||
|
|
||||||
HStack {
|
|
||||||
Image("link")
|
|
||||||
|
|
||||||
TextField(text: url_binding, label: {
|
|
||||||
Text("https://example.com", comment: "Placeholder as an example of what the user could set so that the link is opened when the status is tapped.")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
HStack {
|
|
||||||
Text("Clear status", comment: "Label to prompt user to select an expiration time for the profile status to clear.")
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
|
|
||||||
Picker(NSLocalizedString("Duration", comment: "Label for profile status expiration duration picker."), selection: $duration) {
|
|
||||||
ForEach(StatusDuration.allCases, id: \.self) { d in
|
|
||||||
Text(verbatim: d.description)
|
|
||||||
.tag(d)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Toggle(isOn: $status.playing_enabled, label: {
|
|
||||||
Text("Broadcast music playing on Apple Music", comment: "Toggle to enable or disable broadcasting what music is being played on Apple Music in their profile status.")
|
|
||||||
})
|
|
||||||
|
|
||||||
HStack(alignment: .center) {
|
|
||||||
Button(action: {
|
|
||||||
dismiss()
|
|
||||||
}, label: {
|
|
||||||
Text("Cancel", comment: "Cancel button text for dismissing profile status settings view.")
|
|
||||||
})
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
|
|
||||||
Button(action: {
|
|
||||||
guard let status = self.status.general,
|
|
||||||
let kp = keypair.to_full(),
|
|
||||||
let ev = make_user_status_note(status: status, keypair: kp, expiry: duration.expiration)
|
|
||||||
else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
postbox.send(ev)
|
|
||||||
|
|
||||||
dismiss()
|
|
||||||
}, label: {
|
|
||||||
Text("Save", comment: "Save button text for saving profile status settings.")
|
|
||||||
})
|
|
||||||
.buttonStyle(GradientButtonStyle())
|
|
||||||
}
|
|
||||||
.padding([.top], 30)
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
.padding(30)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
struct UserStatusSheet_Previews: PreviewProvider {
|
|
||||||
static var previews: some View {
|
|
||||||
UserStatusSheet(postbox: test_damus_state.postbox, keypair: test_keypair, status: .init())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
//
|
|
||||||
// UserStatus.swift
|
|
||||||
// damus
|
|
||||||
//
|
|
||||||
// Created by William Casarin on 2023-08-21.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
import MediaPlayer
|
|
||||||
import WebKit
|
|
||||||
|
|
||||||
struct UserStatusView: View {
|
|
||||||
@ObservedObject var status: UserStatusModel
|
|
||||||
|
|
||||||
var show_general: Bool
|
|
||||||
var show_music: Bool
|
|
||||||
|
|
||||||
@Environment(\.openURL) var openURL
|
|
||||||
|
|
||||||
func Status(st: UserStatus, prefix: String = "") -> some View {
|
|
||||||
HStack {
|
|
||||||
Text(verbatim: "\(prefix)\(st.content)")
|
|
||||||
.lineLimit(1)
|
|
||||||
.foregroundColor(.gray)
|
|
||||||
.font(.callout.italic())
|
|
||||||
if st.url != nil {
|
|
||||||
Image("link")
|
|
||||||
.resizable()
|
|
||||||
.frame(width: 16, height: 16)
|
|
||||||
.foregroundColor(.gray)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.onTapGesture {
|
|
||||||
if let url = st.url {
|
|
||||||
openURL(url)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.contextMenu(
|
|
||||||
menuItems: {
|
|
||||||
if let url = st.url {
|
|
||||||
Button(url.absoluteString, action: { openURL(url) }) }
|
|
||||||
}, preview: {
|
|
||||||
if let url = st.url {
|
|
||||||
URLPreview(url: url)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
VStack(alignment: .leading, spacing: 2) {
|
|
||||||
if show_general, let general = status.general {
|
|
||||||
Status(st: general)
|
|
||||||
}
|
|
||||||
|
|
||||||
if show_music, let playing = status.music {
|
|
||||||
Status(st: playing, prefix: "🎵")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
struct URLPreview: UIViewRepresentable {
|
|
||||||
var url: URL
|
|
||||||
|
|
||||||
func makeUIView(context: Context) -> WKWebView {
|
|
||||||
return WKWebView()
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateUIView(_ wkView: WKWebView, context: Context) {
|
|
||||||
let request = URLRequest(url: url)
|
|
||||||
wkView.load(request)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
struct UserStatusView_Previews: PreviewProvider {
|
|
||||||
static var previews: some View {
|
|
||||||
UserStatusView(status: UserStatus(type: .music, expires_at: nil, content: "Track - Artist", created_at: 0, url: URL(string: "spotify:search:abc")), show_general: true, show_music: true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
*/
|
|
||||||
@@ -10,7 +10,7 @@ import NaturalLanguage
|
|||||||
|
|
||||||
|
|
||||||
struct Translated: Equatable {
|
struct Translated: Equatable {
|
||||||
let artifacts: NoteArtifactsSeparated
|
let artifacts: NoteArtifacts
|
||||||
let language: String
|
let language: String
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,10 +42,9 @@ struct TranslateView: View {
|
|||||||
.translate_button_style()
|
.translate_button_style()
|
||||||
}
|
}
|
||||||
|
|
||||||
func TranslatedView(lang: String?, artifacts: NoteArtifactsSeparated, font_size: Double) -> some View {
|
func TranslatedView(lang: String?, artifacts: NoteArtifacts) -> some View {
|
||||||
return VStack(alignment: .leading) {
|
return VStack(alignment: .leading) {
|
||||||
let translatedFromLanguageString = String(format: NSLocalizedString("Translated from %@", comment: "Button to indicate that the note has been translated from a different language."), lang ?? "ja")
|
Text(String(format: NSLocalizedString("Translated from %@", comment: "Button to indicate that the note has been translated from a different language."), lang ?? "ja"))
|
||||||
Text(translatedFromLanguageString)
|
|
||||||
.foregroundColor(.gray)
|
.foregroundColor(.gray)
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
.padding([.top, .bottom], 10)
|
.padding([.top, .bottom], 10)
|
||||||
@@ -54,7 +53,7 @@ struct TranslateView: View {
|
|||||||
SelectableText(attributedString: artifacts.content.attributed, size: self.size)
|
SelectableText(attributedString: artifacts.content.attributed, size: self.size)
|
||||||
} else {
|
} else {
|
||||||
artifacts.content.text
|
artifacts.content.text
|
||||||
.font(eventviewsize_to_font(self.size, font_size: font_size))
|
.font(eventviewsize_to_font(self.size))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -64,7 +63,7 @@ struct TranslateView: View {
|
|||||||
guard let note_language = translations_model.note_language else {
|
guard let note_language = translations_model.note_language else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let res = await translate_note(profiles: damus_state.profiles, keypair: damus_state.keypair, event: event, settings: damus_state.settings, note_lang: note_language)
|
let res = await translate_note(profiles: damus_state.profiles, privkey: damus_state.keypair.privkey, event: event, settings: damus_state.settings, note_lang: note_language)
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.translations_model.state = res
|
self.translations_model.state = res
|
||||||
}
|
}
|
||||||
@@ -98,7 +97,7 @@ struct TranslateView: View {
|
|||||||
Text("")
|
Text("")
|
||||||
case .translated(let translated):
|
case .translated(let translated):
|
||||||
let languageName = Locale.current.localizedString(forLanguageCode: translated.language)
|
let languageName = Locale.current.localizedString(forLanguageCode: translated.language)
|
||||||
TranslatedView(lang: languageName, artifacts: translated.artifacts, font_size: damus_state.settings.font_size)
|
TranslatedView(lang: languageName, artifacts: translated.artifacts)
|
||||||
case .not_needed:
|
case .not_needed:
|
||||||
Text("")
|
Text("")
|
||||||
}
|
}
|
||||||
@@ -120,16 +119,16 @@ extension View {
|
|||||||
|
|
||||||
struct TranslateView_Previews: PreviewProvider {
|
struct TranslateView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
let ds = test_damus_state
|
let ds = test_damus_state()
|
||||||
TranslateView(damus_state: ds, event: test_note, size: .normal)
|
TranslateView(damus_state: ds, event: test_event, size: .normal)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func translate_note(profiles: Profiles, keypair: Keypair, event: NostrEvent, settings: UserSettingsStore, note_lang: String) async -> TranslateStatus {
|
func translate_note(profiles: Profiles, privkey: String?, event: NostrEvent, settings: UserSettingsStore, note_lang: String) async -> TranslateStatus {
|
||||||
|
|
||||||
// If the note language is different from our preferred languages, send a translation request.
|
// If the note language is different from our preferred languages, send a translation request.
|
||||||
let translator = Translator(settings)
|
let translator = Translator(settings)
|
||||||
let originalContent = event.get_content(keypair)
|
let originalContent = event.get_content(privkey)
|
||||||
let translated_note = try? await translator.translate(originalContent, from: note_lang, to: current_language())
|
let translated_note = try? await translator.translate(originalContent, from: note_lang, to: current_language())
|
||||||
|
|
||||||
guard let translated_note else {
|
guard let translated_note else {
|
||||||
@@ -143,7 +142,7 @@ func translate_note(profiles: Profiles, keypair: Keypair, event: NostrEvent, set
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Render translated note
|
// Render translated note
|
||||||
let translated_blocks = parse_note_content(content: .content(translated_note, event.tags))
|
let translated_blocks = event.get_blocks(content: translated_note)
|
||||||
let artifacts = render_blocks(blocks: translated_blocks, profiles: profiles)
|
let artifacts = render_blocks(blocks: translated_blocks, profiles: profiles)
|
||||||
|
|
||||||
// and cache it
|
// and cache it
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ struct TruncatedText: View {
|
|||||||
let maxChars: Int = 280
|
let maxChars: Int = 280
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
let truncatedAttributedString: AttributedString? = text.attributed.truncateOrNil(maxLength: maxChars)
|
let truncatedAttributedString: AttributedString? = getTruncatedString()
|
||||||
|
|
||||||
if let truncatedAttributedString {
|
if let truncatedAttributedString {
|
||||||
Text(truncatedAttributedString)
|
Text(truncatedAttributedString)
|
||||||
@@ -28,6 +28,16 @@ struct TruncatedText: View {
|
|||||||
.allowsHitTesting(false)
|
.allowsHitTesting(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getTruncatedString() -> AttributedString? {
|
||||||
|
let nsAttributedString = NSAttributedString(text.attributed)
|
||||||
|
if nsAttributedString.length < maxChars { return nil }
|
||||||
|
|
||||||
|
let range = NSRange(location: 0, length: maxChars)
|
||||||
|
let truncatedAttributedString = nsAttributedString.attributedSubstring(from: range)
|
||||||
|
|
||||||
|
return AttributedString(truncatedAttributedString) + "..."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TruncatedText_Previews: PreviewProvider {
|
struct TruncatedText_Previews: PreviewProvider {
|
||||||
|
|||||||
@@ -9,45 +9,49 @@ import SwiftUI
|
|||||||
|
|
||||||
struct UserViewRow: View {
|
struct UserViewRow: View {
|
||||||
let damus_state: DamusState
|
let damus_state: DamusState
|
||||||
let pubkey: Pubkey
|
let pubkey: String
|
||||||
|
|
||||||
|
@State var navigating: Bool = false
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
let dest = ProfileView(damus_state: damus_state, pubkey: pubkey)
|
||||||
|
|
||||||
UserView(damus_state: damus_state, pubkey: pubkey)
|
UserView(damus_state: damus_state, pubkey: pubkey)
|
||||||
.contentShape(Rectangle())
|
.contentShape(Rectangle())
|
||||||
.background(.clear)
|
.background(
|
||||||
|
NavigationLink(destination: dest, isActive: $navigating) {
|
||||||
|
EmptyView()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.onTapGesture {
|
||||||
|
navigating = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct UserView: View {
|
struct UserView: View {
|
||||||
let damus_state: DamusState
|
let damus_state: DamusState
|
||||||
let pubkey: Pubkey
|
let pubkey: String
|
||||||
let spacer: Bool
|
|
||||||
|
|
||||||
@State var about_text: Text? = nil
|
|
||||||
|
|
||||||
init(damus_state: DamusState, pubkey: Pubkey, spacer: Bool = true) {
|
|
||||||
self.damus_state = damus_state
|
|
||||||
self.pubkey = pubkey
|
|
||||||
self.spacer = spacer
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
|
||||||
VStack {
|
VStack {
|
||||||
HStack {
|
HStack {
|
||||||
ProfilePicView(pubkey: pubkey, size: PFP_SIZE, highlight: .none, profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation)
|
ProfilePicView(pubkey: pubkey, size: PFP_SIZE, highlight: .none, profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation)
|
||||||
|
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
ProfileName(pubkey: pubkey, damus: damus_state, show_nip5_domain: false)
|
let profile = damus_state.profiles.lookup(id: pubkey)
|
||||||
if let about_text {
|
ProfileName(pubkey: pubkey, profile: profile, damus: damus_state, show_nip5_domain: false)
|
||||||
about_text
|
if let about = profile?.about {
|
||||||
|
let blocks = parse_mentions(content: about, tags: [])
|
||||||
|
let about_string = render_blocks(blocks: blocks, profiles: damus_state.profiles).content.attributed
|
||||||
|
Text(about_string)
|
||||||
.lineLimit(3)
|
.lineLimit(3)
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if spacer {
|
Spacer()
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -55,6 +59,6 @@ struct UserView: View {
|
|||||||
|
|
||||||
struct UserView_Previews: PreviewProvider {
|
struct UserView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
UserView(damus_state: test_damus_state, pubkey: test_note.pubkey)
|
UserView(damus_state: test_damus_state(), pubkey: "pk")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,8 +23,6 @@ struct WebsiteLink: View {
|
|||||||
Text(link_text)
|
Text(link_text)
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
.foregroundColor(.accentColor)
|
.foregroundColor(.accentColor)
|
||||||
.truncationMode(.tail)
|
|
||||||
.lineLimit(1)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,9 +32,10 @@ struct ZapButton: View {
|
|||||||
let lnurl: String
|
let lnurl: String
|
||||||
|
|
||||||
@ObservedObject var zaps: ZapsDataModel
|
@ObservedObject var zaps: ZapsDataModel
|
||||||
|
@StateObject var button: ZapButtonModel = ZapButtonModel()
|
||||||
|
|
||||||
var our_zap: Zapping? {
|
var our_zap: Zapping? {
|
||||||
zaps.zaps.first(where: { z in z.request.ev.pubkey == damus_state.pubkey })
|
zaps.zaps.first(where: { z in z.request.pubkey == damus_state.pubkey })
|
||||||
}
|
}
|
||||||
|
|
||||||
var zap_img: String {
|
var zap_img: String {
|
||||||
@@ -55,6 +56,13 @@ struct ZapButton: View {
|
|||||||
|
|
||||||
// always orange !
|
// always orange !
|
||||||
return Color.orange
|
return Color.orange
|
||||||
|
/*
|
||||||
|
if our_zap.is_paid {
|
||||||
|
return Color.orange
|
||||||
|
} else {
|
||||||
|
return Color.yellow
|
||||||
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
func tap() {
|
func tap() {
|
||||||
@@ -106,17 +114,15 @@ struct ZapButton: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack(spacing: 4) {
|
HStack(spacing: 4) {
|
||||||
if !damus_state.settings.nozaps || zaps.zap_total > 0 {
|
Button(action: {
|
||||||
Button(action: {
|
}, label: {
|
||||||
}, label: {
|
Image(zap_img)
|
||||||
Image(zap_img)
|
.resizable()
|
||||||
.resizable()
|
.foregroundColor(zap_color)
|
||||||
.foregroundColor(zap_color)
|
.font(.footnote.weight(.medium))
|
||||||
.font(.footnote.weight(.medium))
|
.aspectRatio(contentMode: .fit)
|
||||||
.aspectRatio(contentMode: .fit)
|
.frame(width:20, height: 20)
|
||||||
.frame(width:20, height: 20)
|
})
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if zaps.zap_total > 0 {
|
if zaps.zap_total > 0 {
|
||||||
Text(verbatim: format_msats_abbrev(zaps.zap_total))
|
Text(verbatim: format_msats_abbrev(zaps.zap_total))
|
||||||
@@ -126,25 +132,53 @@ struct ZapButton: View {
|
|||||||
}
|
}
|
||||||
.accessibilityLabel(NSLocalizedString("Zap", comment: "Accessibility label for zap button"))
|
.accessibilityLabel(NSLocalizedString("Zap", comment: "Accessibility label for zap button"))
|
||||||
.simultaneousGesture(LongPressGesture().onEnded {_ in
|
.simultaneousGesture(LongPressGesture().onEnded {_ in
|
||||||
guard !damus_state.settings.nozaps else { return }
|
button.showing_zap_customizer = true
|
||||||
|
|
||||||
present_sheet(.zap(target: target, lnurl: lnurl))
|
|
||||||
})
|
})
|
||||||
.highPriorityGesture(TapGesture().onEnded {
|
.highPriorityGesture(TapGesture().onEnded {
|
||||||
guard !damus_state.settings.nozaps else { return }
|
|
||||||
|
|
||||||
tap()
|
tap()
|
||||||
})
|
})
|
||||||
|
.sheet(isPresented: $button.showing_zap_customizer) {
|
||||||
|
CustomizeZapView(state: damus_state, target: target, lnurl: lnurl)
|
||||||
|
}
|
||||||
|
.sheet(isPresented: $button.showing_select_wallet, onDismiss: {button.showing_select_wallet = false}) {
|
||||||
|
SelectWalletView(default_wallet: damus_state.settings.default_wallet, showingSelectWallet: $button.showing_select_wallet, our_pubkey: damus_state.pubkey, invoice: button.invoice ?? "")
|
||||||
|
}
|
||||||
|
.onReceive(handle_notify(.zapping)) { notif in
|
||||||
|
let zap_ev = notif.object as! ZappingEvent
|
||||||
|
|
||||||
|
guard zap_ev.target.id == self.target.id else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard !zap_ev.is_custom else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch zap_ev.type {
|
||||||
|
case .failed:
|
||||||
|
break
|
||||||
|
case .got_zap_invoice(let inv):
|
||||||
|
if damus_state.settings.show_wallet_selector {
|
||||||
|
self.button.invoice = inv
|
||||||
|
self.button.showing_select_wallet = true
|
||||||
|
} else {
|
||||||
|
let wallet = damus_state.settings.default_wallet.model
|
||||||
|
open_with_wallet(wallet: wallet, invoice: inv)
|
||||||
|
}
|
||||||
|
case .sent_from_nwc:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
struct ZapButton_Previews: PreviewProvider {
|
struct ZapButton_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
let pending_zap = PendingZap(amount_msat: 1000, target: ZapTarget.note(id: test_note.id, author: test_note.pubkey), request: .normal(test_zap_request), type: .pub, state: .external(.init(state: .fetching_invoice)))
|
let pending_zap = PendingZap(amount_msat: 1000, target: ZapTarget.note(id: "noteid", author: "author"), request: .normal(test_zap_request), type: .pub, state: .external(.init(state: .fetching_invoice)))
|
||||||
let zaps = ZapsDataModel([.pending(pending_zap)])
|
let zaps = ZapsDataModel([.pending(pending_zap)])
|
||||||
|
|
||||||
ZapButton(damus_state: test_damus_state, target: ZapTarget.note(id: test_note.id, author: test_note.pubkey), lnurl: "lnurl", zaps: zaps)
|
ZapButton(damus_state: test_damus_state(), target: ZapTarget.note(id: test_event.id, author: test_event.pubkey), lnurl: "lnurl", zaps: zaps)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,74 +217,86 @@ func send_zap(damus_state: DamusState, target: ZapTarget, lnurl: String, is_cust
|
|||||||
UIImpactFeedbackGenerator(style: .heavy).impactOccurred()
|
UIImpactFeedbackGenerator(style: .heavy).impactOccurred()
|
||||||
damus_state.add_zap(zap: .pending(pending_zap))
|
damus_state.add_zap(zap: .pending(pending_zap))
|
||||||
|
|
||||||
Task { @MainActor in
|
Task {
|
||||||
guard let payreq = await damus_state.lnurls.lookup_or_fetch(pubkey: target.pubkey, lnurl: lnurl) else {
|
var mpayreq = damus_state.lnurls.lookup(target.pubkey)
|
||||||
|
if mpayreq == nil {
|
||||||
|
mpayreq = await fetch_static_payreq(lnurl)
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let payreq = mpayreq else {
|
||||||
// TODO: show error
|
// TODO: show error
|
||||||
remove_zap(reqid: reqid, zapcache: damus_state.zaps, evcache: damus_state.events)
|
DispatchQueue.main.async {
|
||||||
let typ = ZappingEventType.failed(.bad_lnurl)
|
|
||||||
let ev = ZappingEvent(is_custom: is_custom, type: typ, target: target)
|
|
||||||
notify(.zapping(ev))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let inv = await fetch_zap_invoice(payreq, zapreq: zapreq, msats: amount_msat, zap_type: zap_type, comment: comment) else {
|
|
||||||
remove_zap(reqid: reqid, zapcache: damus_state.zaps, evcache: damus_state.events)
|
|
||||||
let typ = ZappingEventType.failed(.fetching_invoice)
|
|
||||||
let ev = ZappingEvent(is_custom: is_custom, type: typ, target: target)
|
|
||||||
notify(.zapping(ev))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch pending_zap_state {
|
|
||||||
case .nwc(let nwc_state):
|
|
||||||
// don't both continuing, user has canceled
|
|
||||||
if case .cancel_fetching_invoice = nwc_state.state {
|
|
||||||
remove_zap(reqid: reqid, zapcache: damus_state.zaps, evcache: damus_state.events)
|
remove_zap(reqid: reqid, zapcache: damus_state.zaps, evcache: damus_state.events)
|
||||||
let typ = ZappingEventType.failed(.canceled)
|
let typ = ZappingEventType.failed(.bad_lnurl)
|
||||||
let ev = ZappingEvent(is_custom: is_custom, type: typ, target: target)
|
let ev = ZappingEvent(is_custom: is_custom, type: typ, target: target)
|
||||||
notify(.zapping(ev))
|
notify(.zapping, ev)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
return
|
||||||
var flusher: OnFlush? = nil
|
}
|
||||||
|
|
||||||
// donations are only enabled on one-tap zaps and off appstore
|
DispatchQueue.main.async {
|
||||||
if !damus_state.settings.nozaps && !is_custom && damus_state.settings.donation_percent > 0 {
|
damus_state.lnurls.endpoints[target.pubkey] = payreq
|
||||||
flusher = .once({ pe in
|
}
|
||||||
// send donation zap when the pending zap is flushed, this allows user to cancel and not send a donation
|
|
||||||
Task { @MainActor in
|
guard let inv = await fetch_zap_invoice(payreq, zapreq: zapreq, msats: amount_msat, zap_type: zap_type, comment: comment) else {
|
||||||
await send_donation_zap(pool: damus_state.pool, postbox: damus_state.postbox, nwc: nwc_state.url, percent: damus_state.settings.donation_percent, base_msats: amount_msat)
|
DispatchQueue.main.async {
|
||||||
}
|
remove_zap(reqid: reqid, zapcache: damus_state.zaps, evcache: damus_state.events)
|
||||||
})
|
let typ = ZappingEventType.failed(.fetching_invoice)
|
||||||
}
|
|
||||||
|
|
||||||
// we don't have a delay on one-tap nozaps (since this will be from customize zap view)
|
|
||||||
let delay = damus_state.settings.nozaps ? nil : 5.0
|
|
||||||
|
|
||||||
let nwc_req = nwc_pay(url: nwc_state.url, pool: damus_state.pool, post: damus_state.postbox, invoice: inv, delay: delay, on_flush: flusher)
|
|
||||||
|
|
||||||
guard let nwc_req, case .nwc(let pzap_state) = pending_zap_state else {
|
|
||||||
print("nwc: failed to send nwc request for zapreq \(reqid.reqid)")
|
|
||||||
|
|
||||||
let typ = ZappingEventType.failed(.send_failed)
|
|
||||||
let ev = ZappingEvent(is_custom: is_custom, type: typ, target: target)
|
let ev = ZappingEvent(is_custom: is_custom, type: typ, target: target)
|
||||||
notify(.zapping(ev))
|
notify(.zapping, ev)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
|
||||||
|
switch pending_zap_state {
|
||||||
|
case .nwc(let nwc_state):
|
||||||
|
// don't both continuing, user has canceled
|
||||||
|
if case .cancel_fetching_invoice = nwc_state.state {
|
||||||
|
remove_zap(reqid: reqid, zapcache: damus_state.zaps, evcache: damus_state.events)
|
||||||
|
let typ = ZappingEventType.failed(.canceled)
|
||||||
|
let ev = ZappingEvent(is_custom: is_custom, type: typ, target: target)
|
||||||
|
notify(.zapping, ev)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var flusher: OnFlush? = nil
|
||||||
|
// Don't donate on custom zaps
|
||||||
|
if !is_custom && damus_state.settings.donation_percent > 0 {
|
||||||
|
flusher = .once({ pe in
|
||||||
|
// send donation zap when the pending zap is flushed, this allows user to cancel and not send a donation
|
||||||
|
Task.init { @MainActor in
|
||||||
|
await send_donation_zap(pool: damus_state.pool, postbox: damus_state.postbox, nwc: nwc_state.url, percent: damus_state.settings.donation_percent, base_msats: amount_msat)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let nwc_req = nwc_pay(url: nwc_state.url, pool: damus_state.pool, post: damus_state.postbox, invoice: inv, on_flush: flusher)
|
||||||
|
|
||||||
|
guard let nwc_req, case .nwc(let pzap_state) = pending_zap_state else {
|
||||||
|
print("nwc: failed to send nwc request for zapreq \(reqid.reqid)")
|
||||||
|
|
||||||
print("nwc: sending request \(nwc_req.id) zap_req_id \(reqid.reqid)")
|
let typ = ZappingEventType.failed(.send_failed)
|
||||||
|
let ev = ZappingEvent(is_custom: is_custom, type: typ, target: target)
|
||||||
|
notify(.zapping, ev)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
print("nwc: sending request \(nwc_req.id) zap_req_id \(reqid.reqid)")
|
||||||
|
|
||||||
|
if pzap_state.update_state(state: .postbox_pending(nwc_req)) {
|
||||||
|
// we don't need to trigger a ZapsDataModel update here
|
||||||
|
}
|
||||||
|
|
||||||
if pzap_state.update_state(state: .postbox_pending(nwc_req)) {
|
let ev = ZappingEvent(is_custom: is_custom, type: .sent_from_nwc, target: target)
|
||||||
// we don't need to trigger a ZapsDataModel update here
|
notify(.zapping, ev)
|
||||||
|
|
||||||
|
case .external(let pending_ext):
|
||||||
|
pending_ext.state = .done
|
||||||
|
let ev = ZappingEvent(is_custom: is_custom, type: .got_zap_invoice(inv), target: target)
|
||||||
|
notify(.zapping, ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
let ev = ZappingEvent(is_custom: is_custom, type: .sent_from_nwc, target: target)
|
|
||||||
notify(.zapping(ev))
|
|
||||||
|
|
||||||
case .external(let pending_ext):
|
|
||||||
pending_ext.state = .done
|
|
||||||
let ev = ZappingEvent(is_custom: is_custom, type: .got_zap_invoice(inv), target: target)
|
|
||||||
notify(.zapping(ev))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,124 +0,0 @@
|
|||||||
//
|
|
||||||
// ContentParsing.swift
|
|
||||||
// damus
|
|
||||||
//
|
|
||||||
// Created by William Casarin on 2023-07-22.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
enum NoteContent {
|
|
||||||
case note(NostrEvent)
|
|
||||||
case content(String, TagsSequence?)
|
|
||||||
|
|
||||||
init(note: NostrEvent, keypair: Keypair) {
|
|
||||||
if note.known_kind == .dm {
|
|
||||||
self = .content(note.get_content(keypair), note.tags)
|
|
||||||
} else {
|
|
||||||
self = .note(note)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func parsed_blocks_finish(bs: inout note_blocks, tags: TagsSequence?) -> Blocks {
|
|
||||||
var out: [Block] = []
|
|
||||||
|
|
||||||
var i = 0
|
|
||||||
while (i < bs.num_blocks) {
|
|
||||||
let block = bs.blocks[i]
|
|
||||||
|
|
||||||
if let converted = Block(block, tags: tags) {
|
|
||||||
out.append(converted)
|
|
||||||
}
|
|
||||||
|
|
||||||
i += 1
|
|
||||||
}
|
|
||||||
|
|
||||||
let words = Int(bs.words)
|
|
||||||
blocks_free(&bs)
|
|
||||||
|
|
||||||
return Blocks(words: words, blocks: out)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func parse_note_content(content: NoteContent) -> Blocks {
|
|
||||||
var bs = note_blocks()
|
|
||||||
bs.num_blocks = 0;
|
|
||||||
|
|
||||||
blocks_init(&bs)
|
|
||||||
|
|
||||||
switch content {
|
|
||||||
case .content(let s, let tags):
|
|
||||||
return s.withCString { cptr in
|
|
||||||
damus_parse_content(&bs, cptr)
|
|
||||||
return parsed_blocks_finish(bs: &bs, tags: tags)
|
|
||||||
}
|
|
||||||
case .note(let note):
|
|
||||||
damus_parse_content(&bs, note.content_raw)
|
|
||||||
return parsed_blocks_finish(bs: &bs, tags: note.tags)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func interpret_event_refs_ndb(blocks: [Block], tags: TagsSequence) -> [EventRef] {
|
|
||||||
if tags.count == 0 {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
/// build a set of indices for each event mention
|
|
||||||
let mention_indices = build_mention_indices(blocks, type: .e)
|
|
||||||
|
|
||||||
/// simpler case with no mentions
|
|
||||||
if mention_indices.count == 0 {
|
|
||||||
return interp_event_refs_without_mentions_ndb(tags.note.referenced_noterefs)
|
|
||||||
}
|
|
||||||
|
|
||||||
return interp_event_refs_with_mentions_ndb(tags: tags, mention_indices: mention_indices)
|
|
||||||
}
|
|
||||||
|
|
||||||
func interp_event_refs_without_mentions_ndb(_ ev_tags: References<NoteRef>) -> [EventRef] {
|
|
||||||
|
|
||||||
var count = 0
|
|
||||||
var evrefs: [EventRef] = []
|
|
||||||
var first: Bool = true
|
|
||||||
var first_ref: NoteRef? = nil
|
|
||||||
|
|
||||||
for ref in ev_tags {
|
|
||||||
if first {
|
|
||||||
first_ref = ref
|
|
||||||
evrefs.append(.thread_id(ref))
|
|
||||||
first = false
|
|
||||||
} else {
|
|
||||||
|
|
||||||
evrefs.append(.reply(ref))
|
|
||||||
}
|
|
||||||
count += 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if let first_ref, count == 1 {
|
|
||||||
let r = first_ref
|
|
||||||
return [.reply_to_root(r)]
|
|
||||||
}
|
|
||||||
|
|
||||||
return evrefs
|
|
||||||
}
|
|
||||||
|
|
||||||
func interp_event_refs_with_mentions_ndb(tags: TagsSequence, mention_indices: Set<Int>) -> [EventRef] {
|
|
||||||
var mentions: [EventRef] = []
|
|
||||||
var ev_refs: [NoteRef] = []
|
|
||||||
var i: Int = 0
|
|
||||||
|
|
||||||
for tag in tags {
|
|
||||||
if let note_id = NoteRef.from_tag(tag: tag) {
|
|
||||||
if mention_indices.contains(i) {
|
|
||||||
mentions.append(.mention(.noteref(note_id, index: i)))
|
|
||||||
} else {
|
|
||||||
ev_refs.append(note_id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
i += 1
|
|
||||||
}
|
|
||||||
|
|
||||||
var replies = interp_event_refs_without_mentions(ev_refs)
|
|
||||||
replies.append(contentsOf: mentions)
|
|
||||||
return replies
|
|
||||||
}
|
|
||||||
+310
-418
File diff suppressed because it is too large
Load Diff
@@ -68,8 +68,6 @@
|
|||||||
</dict>
|
</dict>
|
||||||
<key>NSCameraUsageDescription</key>
|
<key>NSCameraUsageDescription</key>
|
||||||
<string>Damus needs access to your camera if you want to upload photos from it</string>
|
<string>Damus needs access to your camera if you want to upload photos from it</string>
|
||||||
<key>NSAppleMusicUsageDescription</key>
|
|
||||||
<string>Damus needs access to your media library for playback statuses</string>
|
|
||||||
<key>NSMicrophoneUsageDescription</key>
|
<key>NSMicrophoneUsageDescription</key>
|
||||||
<string>Damus needs access to your microphone if you want to upload recorded videos from it</string>
|
<string>Damus needs access to your microphone if you want to upload recorded videos from it</string>
|
||||||
</dict>
|
</dict>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ class ActionBarModel: ObservableObject {
|
|||||||
@Published var our_zap: Zapping?
|
@Published var our_zap: Zapping?
|
||||||
@Published var likes: Int
|
@Published var likes: Int
|
||||||
@Published var boosts: Int
|
@Published var boosts: Int
|
||||||
@Published private(set) var zaps: Int
|
@Published var zaps: Int
|
||||||
@Published var zap_total: Int64
|
@Published var zap_total: Int64
|
||||||
@Published var replies: Int
|
@Published var replies: Int
|
||||||
|
|
||||||
@@ -28,7 +28,19 @@ class ActionBarModel: ObservableObject {
|
|||||||
return ActionBarModel(likes: 0, boosts: 0, zaps: 0, zap_total: 0, replies: 0, our_like: nil, our_boost: nil, our_zap: nil, our_reply: nil)
|
return ActionBarModel(likes: 0, boosts: 0, zaps: 0, zap_total: 0, replies: 0, our_like: nil, our_boost: nil, our_zap: nil, our_reply: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
init(likes: Int = 0, boosts: Int = 0, zaps: Int = 0, zap_total: Int64 = 0, replies: Int = 0, our_like: NostrEvent? = nil, our_boost: NostrEvent? = nil, our_zap: Zapping? = nil, our_reply: NostrEvent? = nil) {
|
init() {
|
||||||
|
self.our_like = nil
|
||||||
|
self.our_boost = nil
|
||||||
|
self.our_reply = nil
|
||||||
|
self.our_zap = nil
|
||||||
|
self.likes = 0
|
||||||
|
self.boosts = 0
|
||||||
|
self.zaps = 0
|
||||||
|
self.zap_total = 0
|
||||||
|
self.replies = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
init(likes: Int, boosts: Int, zaps: Int, zap_total: Int64, replies: Int, our_like: NostrEvent?, our_boost: NostrEvent?, our_zap: Zapping?, our_reply: NostrEvent?) {
|
||||||
self.likes = likes
|
self.likes = likes
|
||||||
self.boosts = boosts
|
self.boosts = boosts
|
||||||
self.zaps = zaps
|
self.zaps = zaps
|
||||||
@@ -40,7 +52,7 @@ class ActionBarModel: ObservableObject {
|
|||||||
self.our_reply = our_reply
|
self.our_reply = our_reply
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(damus: DamusState, evid: NoteId) {
|
func update(damus: DamusState, evid: String) {
|
||||||
self.likes = damus.likes.counts[evid] ?? 0
|
self.likes = damus.likes.counts[evid] ?? 0
|
||||||
self.boosts = damus.boosts.counts[evid] ?? 0
|
self.boosts = damus.boosts.counts[evid] ?? 0
|
||||||
self.zaps = damus.zaps.event_counts[evid] ?? 0
|
self.zaps = damus.zaps.event_counts[evid] ?? 0
|
||||||
|
|||||||
@@ -7,18 +7,18 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
fileprivate func get_bookmarks_key(pubkey: Pubkey) -> String {
|
fileprivate func get_bookmarks_key(pubkey: String) -> String {
|
||||||
pk_setting_key(pubkey, key: "bookmarks")
|
pk_setting_key(pubkey, key: "bookmarks")
|
||||||
}
|
}
|
||||||
|
|
||||||
func load_bookmarks(pubkey: Pubkey) -> [NostrEvent] {
|
func load_bookmarks(pubkey: String) -> [NostrEvent] {
|
||||||
let key = get_bookmarks_key(pubkey: pubkey)
|
let key = get_bookmarks_key(pubkey: pubkey)
|
||||||
return (UserDefaults.standard.stringArray(forKey: key) ?? []).compactMap {
|
return (UserDefaults.standard.stringArray(forKey: key) ?? []).compactMap {
|
||||||
event_from_json(dat: $0)
|
event_from_json(dat: $0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func save_bookmarks(pubkey: Pubkey, current_value: [NostrEvent], value: [NostrEvent]) -> Bool {
|
func save_bookmarks(pubkey: String, current_value: [NostrEvent], value: [NostrEvent]) -> Bool {
|
||||||
let uniq_bookmarks = uniq(value)
|
let uniq_bookmarks = uniq(value)
|
||||||
|
|
||||||
if uniq_bookmarks != current_value {
|
if uniq_bookmarks != current_value {
|
||||||
@@ -32,8 +32,8 @@ func save_bookmarks(pubkey: Pubkey, current_value: [NostrEvent], value: [NostrEv
|
|||||||
|
|
||||||
class BookmarksManager: ObservableObject {
|
class BookmarksManager: ObservableObject {
|
||||||
|
|
||||||
private let pubkey: Pubkey
|
private let pubkey: String
|
||||||
|
|
||||||
private var _bookmarks: [NostrEvent]
|
private var _bookmarks: [NostrEvent]
|
||||||
var bookmarks: [NostrEvent] {
|
var bookmarks: [NostrEvent] {
|
||||||
get {
|
get {
|
||||||
@@ -47,7 +47,7 @@ class BookmarksManager: ObservableObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init(pubkey: Pubkey) {
|
init(pubkey: String) {
|
||||||
self._bookmarks = load_bookmarks(pubkey: pubkey)
|
self._bookmarks = load_bookmarks(pubkey: pubkey)
|
||||||
self.pubkey = pubkey
|
self.pubkey = pubkey
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
//
|
|
||||||
// ImageResizer.swift
|
|
||||||
// damus
|
|
||||||
//
|
|
||||||
// Created by Suhail Saqan on 8/5/23.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import UIKit
|
|
||||||
|
|
||||||
public enum ImageResizingError: Error {
|
|
||||||
case cannotRetrieveFromURL
|
|
||||||
case cannotRetrieveFromData
|
|
||||||
}
|
|
||||||
|
|
||||||
public struct ImageResizer {
|
|
||||||
public var targetWidth: CGFloat
|
|
||||||
|
|
||||||
public init(targetWidth: CGFloat) {
|
|
||||||
self.targetWidth = targetWidth
|
|
||||||
}
|
|
||||||
|
|
||||||
public func resize(at url: URL) -> UIImage? {
|
|
||||||
guard let image = UIImage(contentsOfFile: url.path) else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return self.resize(image: image)
|
|
||||||
}
|
|
||||||
|
|
||||||
public func resize(image: UIImage) -> UIImage {
|
|
||||||
let originalSize = image.size
|
|
||||||
let targetSize = CGSize(width: targetWidth, height: targetWidth*originalSize.height/originalSize.width)
|
|
||||||
let renderer = UIGraphicsImageRenderer(size: targetSize)
|
|
||||||
return renderer.image { (context) in
|
|
||||||
image.draw(in: CGRect(origin: .zero, size: targetSize))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
+72
-105
@@ -9,162 +9,136 @@ import Foundation
|
|||||||
|
|
||||||
|
|
||||||
class Contacts {
|
class Contacts {
|
||||||
private var friends: Set<Pubkey> = Set()
|
private var friends: Set<String> = Set()
|
||||||
private var friend_of_friends: Set<Pubkey> = Set()
|
private var friend_of_friends: Set<String> = Set()
|
||||||
/// Tracks which friends are friends of a given pubkey.
|
private var muted: Set<String> = Set()
|
||||||
private var pubkey_to_our_friends = [Pubkey : Set<Pubkey>]()
|
|
||||||
private var muted: Set<Pubkey> = Set()
|
let our_pubkey: String
|
||||||
|
|
||||||
let our_pubkey: Pubkey
|
|
||||||
var event: NostrEvent?
|
var event: NostrEvent?
|
||||||
var mutelist: NostrEvent?
|
var mutelist: NostrEvent?
|
||||||
|
|
||||||
init(our_pubkey: Pubkey) {
|
init(our_pubkey: String) {
|
||||||
self.our_pubkey = our_pubkey
|
self.our_pubkey = our_pubkey
|
||||||
}
|
}
|
||||||
|
|
||||||
func is_muted(_ pk: Pubkey) -> Bool {
|
func is_muted(_ pk: String) -> Bool {
|
||||||
return muted.contains(pk)
|
return muted.contains(pk)
|
||||||
}
|
}
|
||||||
|
|
||||||
func set_mutelist(_ ev: NostrEvent) {
|
func set_mutelist(_ ev: NostrEvent) {
|
||||||
let oldlist = self.mutelist
|
let oldlist = self.mutelist
|
||||||
self.mutelist = ev
|
self.mutelist = ev
|
||||||
|
|
||||||
let old = oldlist.map({ ev in Set(ev.referenced_pubkeys) }) ?? Set<Pubkey>()
|
let old = Set(oldlist?.referenced_pubkeys.map({ $0.ref_id }) ?? [])
|
||||||
let new = Set(ev.referenced_pubkeys)
|
let new = Set(ev.referenced_pubkeys.map({ $0.ref_id }))
|
||||||
let diff = old.symmetricDifference(new)
|
let diff = old.symmetricDifference(new)
|
||||||
|
|
||||||
var new_mutes = Set<Pubkey>()
|
var new_mutes = Array<String>()
|
||||||
var new_unmutes = Set<Pubkey>()
|
var new_unmutes = Array<String>()
|
||||||
|
|
||||||
for d in diff {
|
for d in diff {
|
||||||
if new.contains(d) {
|
if new.contains(d) {
|
||||||
new_mutes.insert(d)
|
new_mutes.append(d)
|
||||||
} else {
|
} else {
|
||||||
new_unmutes.insert(d)
|
new_unmutes.append(d)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: set local mutelist here
|
// TODO: set local mutelist here
|
||||||
self.muted = Set(ev.referenced_pubkeys)
|
self.muted = Set(ev.referenced_pubkeys.map({ $0.ref_id }))
|
||||||
|
|
||||||
if new_mutes.count > 0 {
|
if new_mutes.count > 0 {
|
||||||
notify(.new_mutes(new_mutes))
|
notify(.new_mutes, new_mutes)
|
||||||
}
|
}
|
||||||
|
|
||||||
if new_unmutes.count > 0 {
|
if new_unmutes.count > 0 {
|
||||||
notify(.new_unmutes(new_unmutes))
|
notify(.new_unmutes, new_unmutes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func remove_friend(_ pubkey: Pubkey) {
|
func remove_friend(_ pubkey: String) {
|
||||||
friends.remove(pubkey)
|
friends.remove(pubkey)
|
||||||
|
|
||||||
pubkey_to_our_friends.forEach {
|
|
||||||
pubkey_to_our_friends[$0.key]?.remove(pubkey)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func get_friend_list() -> Set<Pubkey> {
|
func get_friend_list() -> [String] {
|
||||||
return friends
|
return Array(friends)
|
||||||
}
|
|
||||||
|
|
||||||
func get_followed_hashtags() -> Set<String> {
|
|
||||||
guard let ev = self.event else { return Set() }
|
|
||||||
return Set(ev.referenced_hashtags.map({ $0.hashtag }))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func follows(hashtag: Hashtag) -> Bool {
|
func add_friend_pubkey(_ pubkey: String) {
|
||||||
guard let ev = self.event else { return false }
|
|
||||||
return ev.referenced_hashtags.first(where: { $0 == hashtag }) != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func add_friend_pubkey(_ pubkey: Pubkey) {
|
|
||||||
friends.insert(pubkey)
|
friends.insert(pubkey)
|
||||||
}
|
}
|
||||||
|
|
||||||
func add_friend_contact(_ contact: NostrEvent) {
|
func add_friend_contact(_ contact: NostrEvent) {
|
||||||
friends.insert(contact.pubkey)
|
friends.insert(contact.pubkey)
|
||||||
for pk in contact.referenced_pubkeys {
|
for tag in contact.tags {
|
||||||
friend_of_friends.insert(pk)
|
if tag.count >= 2 && tag[0] == "p" {
|
||||||
|
friend_of_friends.insert(tag[1])
|
||||||
// Exclude themself and us.
|
|
||||||
if contact.pubkey != our_pubkey && contact.pubkey != pk {
|
|
||||||
if pubkey_to_our_friends[pk] == nil {
|
|
||||||
pubkey_to_our_friends[pk] = Set<Pubkey>()
|
|
||||||
}
|
|
||||||
|
|
||||||
pubkey_to_our_friends[pk]?.insert(contact.pubkey)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func is_friend_of_friend(_ pubkey: Pubkey) -> Bool {
|
func is_friend_of_friend(_ pubkey: String) -> Bool {
|
||||||
return friend_of_friends.contains(pubkey)
|
return friend_of_friends.contains(pubkey)
|
||||||
}
|
}
|
||||||
|
|
||||||
func is_in_friendosphere(_ pubkey: Pubkey) -> Bool {
|
func is_in_friendosphere(_ pubkey: String) -> Bool {
|
||||||
return friends.contains(pubkey) || friend_of_friends.contains(pubkey)
|
return friends.contains(pubkey) || friend_of_friends.contains(pubkey)
|
||||||
}
|
}
|
||||||
|
|
||||||
func is_friend(_ pubkey: Pubkey) -> Bool {
|
func is_friend(_ pubkey: String) -> Bool {
|
||||||
return friends.contains(pubkey)
|
return friends.contains(pubkey)
|
||||||
}
|
}
|
||||||
|
|
||||||
func is_friend_or_self(_ pubkey: Pubkey) -> Bool {
|
func is_friend_or_self(_ pubkey: String) -> Bool {
|
||||||
return pubkey == our_pubkey || is_friend(pubkey)
|
return pubkey == our_pubkey || is_friend(pubkey)
|
||||||
}
|
}
|
||||||
|
|
||||||
func follow_state(_ pubkey: Pubkey) -> FollowState {
|
func follow_state(_ pubkey: String) -> FollowState {
|
||||||
return is_friend(pubkey) ? .follows : .unfollows
|
return is_friend(pubkey) ? .follows : .unfollows
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the list of pubkeys of our friends who follow the given pubkey.
|
|
||||||
func get_friended_followers(_ pubkey: Pubkey) -> [Pubkey] {
|
|
||||||
return Array((pubkey_to_our_friends[pubkey] ?? Set()))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func follow_reference(box: PostBox, our_contacts: NostrEvent?, keypair: FullKeypair, follow: FollowRef) -> NostrEvent? {
|
func follow_user(pool: RelayPool, our_contacts: NostrEvent?, pubkey: String, privkey: String, follow: ReferencedId) -> NostrEvent? {
|
||||||
guard let ev = follow_user_event(our_contacts: our_contacts, keypair: keypair, follow: follow) else {
|
guard let ev = follow_user_event(our_contacts: our_contacts, our_pubkey: pubkey, follow: follow) else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
box.send(ev)
|
ev.calculate_id()
|
||||||
|
ev.sign(privkey: privkey)
|
||||||
|
|
||||||
|
|
||||||
|
pool.send(.event(ev))
|
||||||
|
|
||||||
return ev
|
return ev
|
||||||
}
|
}
|
||||||
|
|
||||||
func unfollow_reference(postbox: PostBox, our_contacts: NostrEvent?, keypair: FullKeypair, unfollow: FollowRef) -> NostrEvent? {
|
func unfollow_user(postbox: PostBox, our_contacts: NostrEvent?, pubkey: String, privkey: String, unfollow: String) -> NostrEvent? {
|
||||||
guard let cs = our_contacts else {
|
guard let cs = our_contacts else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let ev = unfollow_reference_event(our_contacts: cs, keypair: keypair, unfollow: unfollow) else {
|
let ev = unfollow_user_event(our_contacts: cs, our_pubkey: pubkey, unfollow: unfollow)
|
||||||
return nil
|
ev.calculate_id()
|
||||||
}
|
ev.sign(privkey: privkey)
|
||||||
|
|
||||||
postbox.send(ev)
|
postbox.send(ev)
|
||||||
|
|
||||||
return ev
|
return ev
|
||||||
}
|
}
|
||||||
|
|
||||||
func unfollow_reference_event(our_contacts: NostrEvent, keypair: FullKeypair, unfollow: FollowRef) -> NostrEvent? {
|
func unfollow_user_event(our_contacts: NostrEvent, our_pubkey: String, unfollow: String) -> NostrEvent {
|
||||||
let tags = our_contacts.tags.reduce(into: [[String]]()) { ts, tag in
|
let tags = our_contacts.tags.filter { tag in
|
||||||
if let tag = FollowRef.from_tag(tag: tag), tag == unfollow {
|
if tag.count >= 2 && tag[0] == "p" && tag[1] == unfollow {
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
|
return true
|
||||||
ts.append(tag.strings())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let kind = NostrKind.contacts.rawValue
|
let kind = NostrKind.contacts.rawValue
|
||||||
|
return NostrEvent(content: our_contacts.content, pubkey: our_pubkey, kind: kind, tags: tags)
|
||||||
return NostrEvent(content: our_contacts.content, keypair: keypair.to_keypair(), kind: kind, tags: Array(tags))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func follow_user_event(our_contacts: NostrEvent?, keypair: FullKeypair, follow: FollowRef) -> NostrEvent? {
|
func follow_user_event(our_contacts: NostrEvent?, our_pubkey: String, follow: ReferencedId) -> NostrEvent? {
|
||||||
guard let cs = our_contacts else {
|
guard let cs = our_contacts else {
|
||||||
// don't create contacts for now so we don't nuke our contact list due to connectivity issues
|
// don't create contacts for now so we don't nuke our contact list due to connectivity issues
|
||||||
// we should only create contacts during profile creation
|
// we should only create contacts during profile creation
|
||||||
@@ -172,7 +146,7 @@ func follow_user_event(our_contacts: NostrEvent?, keypair: FullKeypair, follow:
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let ev = follow_with_existing_contacts(keypair: keypair, our_contacts: cs, follow: follow) else {
|
guard let ev = follow_with_existing_contacts(our_pubkey: our_pubkey, our_contacts: cs, follow: follow) else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,18 +158,23 @@ func decode_json_relays(_ content: String) -> [String: RelayInfo]? {
|
|||||||
return decode_json(content)
|
return decode_json(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
func remove_relay(ev: NostrEvent, current_relays: [RelayDescriptor], keypair: FullKeypair, relay: String) -> NostrEvent?{
|
func remove_relay(ev: NostrEvent, current_relays: [RelayDescriptor], privkey: String, relay: String) -> NostrEvent? {
|
||||||
var relays = ensure_relay_info(relays: current_relays, content: ev.content)
|
var relays = ensure_relay_info(relays: current_relays, content: ev.content)
|
||||||
|
|
||||||
relays.removeValue(forKey: relay)
|
relays.removeValue(forKey: relay)
|
||||||
|
|
||||||
|
print("remove_relay \(relays)")
|
||||||
guard let content = encode_json(relays) else {
|
guard let content = encode_json(relays) else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return NostrEvent(content: content, keypair: keypair.to_keypair(), kind: 3, tags: ev.tags.strings())
|
let new_ev = NostrEvent(content: content, pubkey: ev.pubkey, kind: 3, tags: ev.tags)
|
||||||
|
new_ev.calculate_id()
|
||||||
|
new_ev.sign(privkey: privkey)
|
||||||
|
return new_ev
|
||||||
}
|
}
|
||||||
|
|
||||||
func add_relay(ev: NostrEvent, keypair: FullKeypair, current_relays: [RelayDescriptor], relay: String, info: RelayInfo) -> NostrEvent? {
|
func add_relay(ev: NostrEvent, privkey: String, current_relays: [RelayDescriptor], relay: String, info: RelayInfo) -> NostrEvent? {
|
||||||
var relays = ensure_relay_info(relays: current_relays, content: ev.content)
|
var relays = ensure_relay_info(relays: current_relays, content: ev.content)
|
||||||
|
|
||||||
guard relays.index(forKey: relay) == nil else {
|
guard relays.index(forKey: relay) == nil else {
|
||||||
@@ -208,7 +187,10 @@ func add_relay(ev: NostrEvent, keypair: FullKeypair, current_relays: [RelayDescr
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return NostrEvent(content: content, keypair: keypair.to_keypair(), kind: 3, tags: ev.tags.strings())
|
let new_ev = NostrEvent(content: content, pubkey: ev.pubkey, kind: 3, tags: ev.tags)
|
||||||
|
new_ev.calculate_id()
|
||||||
|
new_ev.sign(privkey: privkey)
|
||||||
|
return new_ev
|
||||||
}
|
}
|
||||||
|
|
||||||
func ensure_relay_info(relays: [RelayDescriptor], content: String) -> [String: RelayInfo] {
|
func ensure_relay_info(relays: [RelayDescriptor], content: String) -> [String: RelayInfo] {
|
||||||
@@ -218,31 +200,16 @@ func ensure_relay_info(relays: [RelayDescriptor], content: String) -> [String: R
|
|||||||
return relay_info
|
return relay_info
|
||||||
}
|
}
|
||||||
|
|
||||||
func is_already_following(contacts: NostrEvent, follow: FollowRef) -> Bool {
|
func follow_with_existing_contacts(our_pubkey: String, our_contacts: NostrEvent, follow: ReferencedId) -> NostrEvent? {
|
||||||
return contacts.references.contains { ref in
|
|
||||||
switch (ref, follow) {
|
|
||||||
case let (.hashtag(ht), .hashtag(follow_ht)):
|
|
||||||
return ht.string() == follow_ht
|
|
||||||
case let (.pubkey(pk), .pubkey(follow_pk)):
|
|
||||||
return pk == follow_pk
|
|
||||||
case (.hashtag, .pubkey), (.pubkey, .hashtag),
|
|
||||||
(.event, _), (.quote, _), (.param, _):
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func follow_with_existing_contacts(keypair: FullKeypair, our_contacts: NostrEvent, follow: FollowRef) -> NostrEvent? {
|
|
||||||
// don't update if we're already following
|
// don't update if we're already following
|
||||||
if is_already_following(contacts: our_contacts, follow: follow) {
|
if our_contacts.references(id: follow.ref_id, key: "p") {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
let kind = NostrKind.contacts.rawValue
|
let kind = NostrKind.contacts.rawValue
|
||||||
|
var tags = our_contacts.tags
|
||||||
var tags = our_contacts.tags.strings()
|
tags.append(refid_to_tag(follow))
|
||||||
tags.append(follow.tag)
|
return NostrEvent(content: our_contacts.content, pubkey: our_pubkey, kind: kind, tags: tags)
|
||||||
|
|
||||||
return NostrEvent(content: our_contacts.content, keypair: keypair.to_keypair(), kind: kind, tags: tags)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func make_contact_relays(_ relays: [RelayDescriptor]) -> [String: RelayInfo] {
|
func make_contact_relays(_ relays: [RelayDescriptor]) -> [String: RelayInfo] {
|
||||||
|
|||||||
@@ -1,54 +0,0 @@
|
|||||||
//
|
|
||||||
// ContentFilters.swift
|
|
||||||
// damus
|
|
||||||
//
|
|
||||||
// Created by Daniel D’Aquino on 2023-09-18.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
|
|
||||||
/// Simple filter to determine whether to show posts or all posts and replies.
|
|
||||||
enum FilterState : Int {
|
|
||||||
case posts_and_replies = 1
|
|
||||||
case posts = 0
|
|
||||||
|
|
||||||
func filter(ev: NostrEvent) -> Bool {
|
|
||||||
switch self {
|
|
||||||
case .posts:
|
|
||||||
return ev.known_kind == .boost || !ev.is_reply(.empty)
|
|
||||||
case .posts_and_replies:
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Simple filter to determine whether to show posts with #nsfw tags
|
|
||||||
func nsfw_tag_filter(ev: NostrEvent) -> Bool {
|
|
||||||
return ev.referenced_hashtags.first(where: { t in t.hashtag == "nsfw" }) == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generic filter with various tweakable settings
|
|
||||||
struct ContentFilters {
|
|
||||||
var filters: [(NostrEvent) -> Bool]
|
|
||||||
|
|
||||||
func filter(ev: NostrEvent) -> Bool {
|
|
||||||
for filter in filters {
|
|
||||||
if !filter(ev) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension ContentFilters {
|
|
||||||
static func defaults(_ settings: UserSettingsStore) -> [(NostrEvent) -> Bool] {
|
|
||||||
var filters = Array<(NostrEvent) -> Bool>()
|
|
||||||
if settings.hide_nsfw_tagged_content {
|
|
||||||
filters.append(nsfw_tag_filter)
|
|
||||||
}
|
|
||||||
return filters
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -12,10 +12,18 @@ class CreateAccountModel: ObservableObject {
|
|||||||
@Published var real_name: String = ""
|
@Published var real_name: String = ""
|
||||||
@Published var nick_name: String = ""
|
@Published var nick_name: String = ""
|
||||||
@Published var about: String = ""
|
@Published var about: String = ""
|
||||||
@Published var pubkey: Pubkey = .empty
|
@Published var pubkey: String = ""
|
||||||
@Published var privkey: Privkey = .empty
|
@Published var privkey: String = ""
|
||||||
@Published var profile_image: URL? = nil
|
@Published var profile_image: String? = nil
|
||||||
|
|
||||||
|
var pubkey_bech32: String {
|
||||||
|
return bech32_pubkey(self.pubkey) ?? ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var privkey_bech32: String {
|
||||||
|
return bech32_privkey(self.privkey) ?? ""
|
||||||
|
}
|
||||||
|
|
||||||
var rendered_name: String {
|
var rendered_name: String {
|
||||||
if real_name.isEmpty {
|
if real_name.isEmpty {
|
||||||
return nick_name
|
return nick_name
|
||||||
@@ -27,11 +35,17 @@ class CreateAccountModel: ObservableObject {
|
|||||||
return Keypair(pubkey: self.pubkey, privkey: self.privkey)
|
return Keypair(pubkey: self.pubkey, privkey: self.privkey)
|
||||||
}
|
}
|
||||||
|
|
||||||
init(real: String = "", nick: String = "", about: String = "") {
|
init() {
|
||||||
let keypair = generate_new_keypair()
|
let keypair = generate_new_keypair()
|
||||||
self.pubkey = keypair.pubkey
|
self.pubkey = keypair.pubkey
|
||||||
self.privkey = keypair.privkey
|
self.privkey = keypair.privkey!
|
||||||
|
}
|
||||||
|
|
||||||
|
init(real: String, nick: String, about: String) {
|
||||||
|
let keypair = generate_new_keypair()
|
||||||
|
self.pubkey = keypair.pubkey
|
||||||
|
self.privkey = keypair.privkey!
|
||||||
|
|
||||||
self.real_name = real
|
self.real_name = real
|
||||||
self.nick_name = nick
|
self.nick_name = nick
|
||||||
self.about = about
|
self.about = about
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ struct DamusState {
|
|||||||
let lnurls: LNUrls
|
let lnurls: LNUrls
|
||||||
let settings: UserSettingsStore
|
let settings: UserSettingsStore
|
||||||
let relay_filters: RelayFilters
|
let relay_filters: RelayFilters
|
||||||
let relay_model_cache: RelayModelCache
|
let relay_metadata: RelayMetadatas
|
||||||
let drafts: Drafts
|
let drafts: Drafts
|
||||||
let events: EventCache
|
let events: EventCache
|
||||||
let bookmarks: BookmarksManager
|
let bookmarks: BookmarksManager
|
||||||
@@ -30,29 +30,16 @@ struct DamusState {
|
|||||||
let replies: ReplyCounter
|
let replies: ReplyCounter
|
||||||
let muted_threads: MutedThreadsManager
|
let muted_threads: MutedThreadsManager
|
||||||
let wallet: WalletModel
|
let wallet: WalletModel
|
||||||
let nav: NavigationCoordinator
|
|
||||||
let music: MusicController?
|
|
||||||
let video: VideoController
|
|
||||||
let ndb: Ndb
|
|
||||||
|
|
||||||
@discardableResult
|
@discardableResult
|
||||||
func add_zap(zap: Zapping) -> Bool {
|
func add_zap(zap: Zapping) -> Bool {
|
||||||
// store generic zap mapping
|
// store generic zap mapping
|
||||||
self.zaps.add_zap(zap: zap)
|
self.zaps.add_zap(zap: zap)
|
||||||
let stored = self.events.store_zap(zap: zap)
|
|
||||||
|
|
||||||
// thread zaps
|
|
||||||
if let ev = zap.event, !settings.nozaps, zap.is_in_thread {
|
|
||||||
// [nozaps]: thread zaps are only available outside of the app store
|
|
||||||
replies.count_replies(ev, keypair: self.keypair)
|
|
||||||
events.add_replies(ev: ev, keypair: self.keypair)
|
|
||||||
}
|
|
||||||
|
|
||||||
// associate with events as well
|
// associate with events as well
|
||||||
return stored
|
return self.events.store_zap(zap: zap)
|
||||||
}
|
}
|
||||||
|
|
||||||
var pubkey: Pubkey {
|
var pubkey: String {
|
||||||
return keypair.pubkey
|
return keypair.pubkey
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,36 +48,5 @@ struct DamusState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static var empty: DamusState {
|
static var empty: DamusState {
|
||||||
let empty_pub: Pubkey = .empty
|
return DamusState.init(pool: RelayPool(), keypair: Keypair(pubkey: "", privkey: ""), likes: EventCounter(our_pubkey: ""), boosts: EventCounter(our_pubkey: ""), contacts: Contacts(our_pubkey: ""), profiles: Profiles(), dms: DirectMessagesModel(our_pubkey: ""), previews: PreviewCache(), zaps: Zaps(our_pubkey: ""), lnurls: LNUrls(), settings: UserSettingsStore(), relay_filters: RelayFilters(our_pubkey: ""), relay_metadata: RelayMetadatas(), drafts: Drafts(), events: EventCache(), bookmarks: BookmarksManager(pubkey: ""), postbox: PostBox(pool: RelayPool()), bootstrap_relays: [], replies: ReplyCounter(our_pubkey: ""), muted_threads: MutedThreadsManager(keypair: Keypair(pubkey: "", privkey: nil)), wallet: WalletModel(settings: UserSettingsStore())) }
|
||||||
let empty_sec: Privkey = .empty
|
|
||||||
let kp = Keypair(pubkey: empty_pub, privkey: nil)
|
|
||||||
|
|
||||||
return DamusState.init(
|
|
||||||
pool: RelayPool(ndb: .empty),
|
|
||||||
keypair: Keypair(pubkey: empty_pub, privkey: empty_sec),
|
|
||||||
likes: EventCounter(our_pubkey: empty_pub),
|
|
||||||
boosts: EventCounter(our_pubkey: empty_pub),
|
|
||||||
contacts: Contacts(our_pubkey: empty_pub),
|
|
||||||
profiles: Profiles(ndb: .empty),
|
|
||||||
dms: DirectMessagesModel(our_pubkey: empty_pub),
|
|
||||||
previews: PreviewCache(),
|
|
||||||
zaps: Zaps(our_pubkey: empty_pub),
|
|
||||||
lnurls: LNUrls(),
|
|
||||||
settings: UserSettingsStore(),
|
|
||||||
relay_filters: RelayFilters(our_pubkey: empty_pub),
|
|
||||||
relay_model_cache: RelayModelCache(),
|
|
||||||
drafts: Drafts(),
|
|
||||||
events: EventCache(ndb: .empty),
|
|
||||||
bookmarks: BookmarksManager(pubkey: empty_pub),
|
|
||||||
postbox: PostBox(pool: RelayPool(ndb: .empty)),
|
|
||||||
bootstrap_relays: [],
|
|
||||||
replies: ReplyCounter(our_pubkey: empty_pub),
|
|
||||||
muted_threads: MutedThreadsManager(keypair: kp),
|
|
||||||
wallet: WalletModel(settings: UserSettingsStore()),
|
|
||||||
nav: NavigationCoordinator(),
|
|
||||||
music: nil,
|
|
||||||
video: VideoController(),
|
|
||||||
ndb: .empty
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,13 +14,13 @@ class DirectMessageModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Published var draft: String = ""
|
@Published var draft: String
|
||||||
|
|
||||||
|
let pubkey: String
|
||||||
|
|
||||||
|
var is_request: Bool
|
||||||
|
var our_pubkey: String
|
||||||
|
|
||||||
let pubkey: Pubkey
|
|
||||||
|
|
||||||
var is_request = false
|
|
||||||
var our_pubkey: Pubkey
|
|
||||||
|
|
||||||
func determine_is_request() -> Bool {
|
func determine_is_request() -> Bool {
|
||||||
for event in events {
|
for event in events {
|
||||||
if event.pubkey == our_pubkey {
|
if event.pubkey == our_pubkey {
|
||||||
@@ -31,9 +31,19 @@ class DirectMessageModel: ObservableObject {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
init(events: [NostrEvent] = [], our_pubkey: Pubkey, pubkey: Pubkey) {
|
init(events: [NostrEvent], our_pubkey: String, pubkey: String) {
|
||||||
self.events = events
|
self.events = events
|
||||||
|
self.is_request = false
|
||||||
self.our_pubkey = our_pubkey
|
self.our_pubkey = our_pubkey
|
||||||
|
self.draft = ""
|
||||||
|
self.pubkey = pubkey
|
||||||
|
}
|
||||||
|
|
||||||
|
init(our_pubkey: String, pubkey: String) {
|
||||||
|
self.events = []
|
||||||
|
self.is_request = false
|
||||||
|
self.our_pubkey = our_pubkey
|
||||||
|
self.draft = ""
|
||||||
self.pubkey = pubkey
|
self.pubkey = pubkey
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ class DirectMessagesModel: ObservableObject {
|
|||||||
@Published var dms: [DirectMessageModel] = []
|
@Published var dms: [DirectMessageModel] = []
|
||||||
@Published var loading: Bool = false
|
@Published var loading: Bool = false
|
||||||
@Published var open_dm: Bool = false
|
@Published var open_dm: Bool = false
|
||||||
@Published private(set) var active_model: DirectMessageModel = DirectMessageModel(our_pubkey: .empty, pubkey: .empty)
|
@Published private(set) var active_model: DirectMessageModel = DirectMessageModel(our_pubkey: "", pubkey: "")
|
||||||
let our_pubkey: Pubkey
|
let our_pubkey: String
|
||||||
|
|
||||||
init(our_pubkey: Pubkey) {
|
init(our_pubkey: String) {
|
||||||
self.our_pubkey = our_pubkey
|
self.our_pubkey = our_pubkey
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,14 +30,24 @@ class DirectMessagesModel: ObservableObject {
|
|||||||
self.active_model = model
|
self.active_model = model
|
||||||
}
|
}
|
||||||
|
|
||||||
func set_active_dm(_ pubkey: Pubkey) {
|
func open_dm_by_pk(_ pubkey: String) {
|
||||||
|
self.set_active_dm(pubkey)
|
||||||
|
self.open_dm = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func open_dm_by_model(_ model: DirectMessageModel) {
|
||||||
|
self.set_active_dm_model(model)
|
||||||
|
self.open_dm = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func set_active_dm(_ pubkey: String) {
|
||||||
for model in self.dms where model.pubkey == pubkey {
|
for model in self.dms where model.pubkey == pubkey {
|
||||||
self.set_active_dm_model(model)
|
self.set_active_dm_model(model)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func lookup_or_create(_ pubkey: Pubkey) -> DirectMessageModel {
|
func lookup_or_create(_ pubkey: String) -> DirectMessageModel {
|
||||||
if let dm = lookup(pubkey) {
|
if let dm = lookup(pubkey) {
|
||||||
return dm
|
return dm
|
||||||
}
|
}
|
||||||
@@ -47,7 +57,7 @@ class DirectMessagesModel: ObservableObject {
|
|||||||
return new
|
return new
|
||||||
}
|
}
|
||||||
|
|
||||||
func lookup(_ pubkey: Pubkey) -> DirectMessageModel? {
|
func lookup(_ pubkey: String) -> DirectMessageModel? {
|
||||||
for dm in dms {
|
for dm in dms {
|
||||||
if pubkey == dm.pubkey {
|
if pubkey == dm.pubkey {
|
||||||
return dm
|
return dm
|
||||||
|
|||||||
@@ -11,7 +11,12 @@ class DraftArtifacts {
|
|||||||
var content: NSMutableAttributedString
|
var content: NSMutableAttributedString
|
||||||
var media: [UploadedMedia]
|
var media: [UploadedMedia]
|
||||||
|
|
||||||
init(content: NSMutableAttributedString = NSMutableAttributedString(string: ""), media: [UploadedMedia] = []) {
|
init() {
|
||||||
|
self.content = NSMutableAttributedString(string: "")
|
||||||
|
self.media = []
|
||||||
|
}
|
||||||
|
|
||||||
|
init(content: NSMutableAttributedString, media: [UploadedMedia]) {
|
||||||
self.content = content
|
self.content = content
|
||||||
self.media = media
|
self.media = media
|
||||||
}
|
}
|
||||||
|
|||||||
+31
-25
@@ -7,18 +7,20 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
enum EventRef: Equatable {
|
enum EventRef {
|
||||||
case mention(Mention<NoteRef>)
|
case mention(Mention)
|
||||||
case thread_id(NoteRef)
|
case thread_id(ReferencedId)
|
||||||
case reply(NoteRef)
|
case reply(ReferencedId)
|
||||||
case reply_to_root(NoteRef)
|
case reply_to_root(ReferencedId)
|
||||||
|
|
||||||
var is_mention: NoteRef? {
|
var is_mention: Mention? {
|
||||||
if case .mention(let m) = self { return m.ref }
|
if case .mention(let m) = self {
|
||||||
|
return m
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var is_direct_reply: NoteRef? {
|
var is_direct_reply: ReferencedId? {
|
||||||
switch self {
|
switch self {
|
||||||
case .mention:
|
case .mention:
|
||||||
return nil
|
return nil
|
||||||
@@ -31,7 +33,7 @@ enum EventRef: Equatable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var is_thread_id: NoteRef? {
|
var is_thread_id: ReferencedId? {
|
||||||
switch self {
|
switch self {
|
||||||
case .mention:
|
case .mention:
|
||||||
return nil
|
return nil
|
||||||
@@ -44,7 +46,7 @@ enum EventRef: Equatable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var is_reply: NoteRef? {
|
var is_reply: ReferencedId? {
|
||||||
switch self {
|
switch self {
|
||||||
case .mention:
|
case .mention:
|
||||||
return nil
|
return nil
|
||||||
@@ -62,8 +64,10 @@ func build_mention_indices(_ blocks: [Block], type: MentionType) -> Set<Int> {
|
|||||||
return blocks.reduce(into: []) { acc, block in
|
return blocks.reduce(into: []) { acc, block in
|
||||||
switch block {
|
switch block {
|
||||||
case .mention(let m):
|
case .mention(let m):
|
||||||
if m.ref.key == type, let idx = m.index {
|
if m.type == type {
|
||||||
acc.insert(idx)
|
if let idx = m.index {
|
||||||
|
acc.insert(idx)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case .relay:
|
case .relay:
|
||||||
return
|
return
|
||||||
@@ -79,7 +83,7 @@ func build_mention_indices(_ blocks: [Block], type: MentionType) -> Set<Int> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func interp_event_refs_without_mentions(_ refs: [NoteRef]) -> [EventRef] {
|
func interp_event_refs_without_mentions(_ refs: [ReferencedId]) -> [EventRef] {
|
||||||
if refs.count == 0 {
|
if refs.count == 0 {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
@@ -101,15 +105,16 @@ func interp_event_refs_without_mentions(_ refs: [NoteRef]) -> [EventRef] {
|
|||||||
return evrefs
|
return evrefs
|
||||||
}
|
}
|
||||||
|
|
||||||
func interp_event_refs_with_mentions(tags: Tags, mention_indices: Set<Int>) -> [EventRef] {
|
func interp_event_refs_with_mentions(tags: [[String]], mention_indices: Set<Int>) -> [EventRef] {
|
||||||
var mentions: [EventRef] = []
|
var mentions: [EventRef] = []
|
||||||
var ev_refs: [NoteRef] = []
|
var ev_refs: [ReferencedId] = []
|
||||||
var i: Int = 0
|
var i: Int = 0
|
||||||
|
|
||||||
for tag in tags {
|
for tag in tags {
|
||||||
if let ref = NoteRef.from_tag(tag: tag) {
|
if tag.count >= 2 && tag[0] == "e" {
|
||||||
|
let ref = tag_to_refid(tag)!
|
||||||
if mention_indices.contains(i) {
|
if mention_indices.contains(i) {
|
||||||
let mention = Mention<NoteRef>(index: i, ref: ref)
|
let mention = Mention(index: i, type: .event, ref: ref)
|
||||||
mentions.append(.mention(mention))
|
mentions.append(.mention(mention))
|
||||||
} else {
|
} else {
|
||||||
ev_refs.append(ref)
|
ev_refs.append(ref)
|
||||||
@@ -123,25 +128,26 @@ func interp_event_refs_with_mentions(tags: Tags, mention_indices: Set<Int>) -> [
|
|||||||
return replies
|
return replies
|
||||||
}
|
}
|
||||||
|
|
||||||
func interpret_event_refs(blocks: [Block], tags: Tags) -> [EventRef] {
|
func interpret_event_refs(blocks: [Block], tags: [[String]]) -> [EventRef] {
|
||||||
if tags.count == 0 {
|
if tags.count == 0 {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
/// build a set of indices for each event mention
|
/// build a set of indices for each event mention
|
||||||
let mention_indices = build_mention_indices(blocks, type: .e)
|
let mention_indices = build_mention_indices(blocks, type: .event)
|
||||||
|
|
||||||
/// simpler case with no mentions
|
/// simpler case with no mentions
|
||||||
if mention_indices.count == 0 {
|
if mention_indices.count == 0 {
|
||||||
return interp_event_refs_without_mentions_ndb(References<NoteRef>(tags: tags))
|
let ev_refs = get_referenced_ids(tags: tags, key: "e")
|
||||||
|
return interp_event_refs_without_mentions(ev_refs)
|
||||||
}
|
}
|
||||||
|
|
||||||
return interp_event_refs_with_mentions(tags: tags, mention_indices: mention_indices)
|
return interp_event_refs_with_mentions(tags: tags, mention_indices: mention_indices)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func event_is_reply(_ refs: [EventRef]) -> Bool {
|
func event_is_reply(_ ev: NostrEvent, privkey: String?) -> Bool {
|
||||||
return refs.contains { evref in
|
return ev.event_refs(privkey).contains { evref in
|
||||||
return evref.is_reply != nil
|
return evref.is_reply != nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,14 +10,14 @@ import Foundation
|
|||||||
|
|
||||||
class EventsModel: ObservableObject {
|
class EventsModel: ObservableObject {
|
||||||
let state: DamusState
|
let state: DamusState
|
||||||
let target: NoteId
|
let target: String
|
||||||
let kind: NostrKind
|
let kind: NostrKind
|
||||||
let sub_id = UUID().uuidString
|
let sub_id = UUID().uuidString
|
||||||
let profiles_id = UUID().uuidString
|
let profiles_id = UUID().uuidString
|
||||||
|
|
||||||
@Published var events: [NostrEvent] = []
|
@Published var events: [NostrEvent] = []
|
||||||
|
|
||||||
init(state: DamusState, target: NoteId, kind: NostrKind) {
|
init(state: DamusState, target: String, kind: NostrKind) {
|
||||||
self.state = state
|
self.state = state
|
||||||
self.target = target
|
self.target = target
|
||||||
self.kind = kind
|
self.kind = kind
|
||||||
@@ -41,11 +41,14 @@ class EventsModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func handle_event(relay_id: String, ev: NostrEvent) {
|
private func handle_event(relay_id: String, ev: NostrEvent) {
|
||||||
guard ev.kind == kind.rawValue,
|
guard ev.kind == kind.rawValue else {
|
||||||
ev.referenced_ids.last == target else {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
guard last_etag(tags: ev.tags) == target else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if insert_uniq_sorted_event(events: &self.events, new_ev: ev, cmp: { a, b in a.created_at < b.created_at } ) {
|
if insert_uniq_sorted_event(events: &self.events, new_ev: ev, cmp: { a, b in a.created_at < b.created_at } ) {
|
||||||
objectWillChange.send()
|
objectWillChange.send()
|
||||||
}
|
}
|
||||||
@@ -59,11 +62,11 @@ class EventsModel: ObservableObject {
|
|||||||
switch nev {
|
switch nev {
|
||||||
case .event(_, let ev):
|
case .event(_, let ev):
|
||||||
handle_event(relay_id: relay_id, ev: ev)
|
handle_event(relay_id: relay_id, ev: ev)
|
||||||
case .notice:
|
case .notice(_):
|
||||||
break
|
break
|
||||||
case .ok:
|
case .ok:
|
||||||
break
|
break
|
||||||
case .eose:
|
case .eose(_):
|
||||||
load_profiles(profiles_subid: profiles_id, relay_id: relay_id, load: .from_events(events), damus_state: state)
|
load_profiles(profiles_subid: profiles_id, relay_id: relay_id, load: .from_events(events), damus_state: state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,18 +7,17 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
|
||||||
enum FollowTarget {
|
enum FollowTarget {
|
||||||
case pubkey(Pubkey)
|
case pubkey(String)
|
||||||
case contact(NostrEvent)
|
case contact(NostrEvent)
|
||||||
|
|
||||||
var follow_ref: FollowRef {
|
var pubkey: String {
|
||||||
FollowRef.pubkey(pubkey)
|
|
||||||
}
|
|
||||||
|
|
||||||
var pubkey: Pubkey {
|
|
||||||
switch self {
|
switch self {
|
||||||
case .pubkey(let pk): return pk
|
case .pubkey(let pk):
|
||||||
case .contact(let ev): return ev.pubkey
|
return pk
|
||||||
|
case .contact(let ev):
|
||||||
|
return ev.pubkey
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,11 +9,11 @@ import Foundation
|
|||||||
|
|
||||||
class FollowersModel: ObservableObject {
|
class FollowersModel: ObservableObject {
|
||||||
let damus_state: DamusState
|
let damus_state: DamusState
|
||||||
let target: Pubkey
|
let target: String
|
||||||
|
|
||||||
@Published var contacts: [Pubkey]? = nil
|
@Published var contacts: [String]? = nil
|
||||||
var has_contact: Set<Pubkey> = Set()
|
var has_contact: Set<String> = Set()
|
||||||
|
|
||||||
let sub_id: String = UUID().description
|
let sub_id: String = UUID().description
|
||||||
let profiles_id: String = UUID().description
|
let profiles_id: String = UUID().description
|
||||||
|
|
||||||
@@ -24,19 +24,20 @@ class FollowersModel: ObservableObject {
|
|||||||
return contacts.count
|
return contacts.count
|
||||||
}
|
}
|
||||||
|
|
||||||
init(damus_state: DamusState, target: Pubkey) {
|
init(damus_state: DamusState, target: String) {
|
||||||
self.damus_state = damus_state
|
self.damus_state = damus_state
|
||||||
self.target = target
|
self.target = target
|
||||||
}
|
}
|
||||||
|
|
||||||
func get_filter() -> NostrFilter {
|
func get_filter() -> NostrFilter {
|
||||||
NostrFilter(kinds: [.contacts], pubkeys: [target])
|
NostrFilter(kinds: [.contacts],
|
||||||
|
pubkeys: [target])
|
||||||
}
|
}
|
||||||
|
|
||||||
func subscribe() {
|
func subscribe() {
|
||||||
let filter = get_filter()
|
let filter = get_filter()
|
||||||
let filters = [filter]
|
let filters = [filter]
|
||||||
//print_filters(relay_id: "following", filters: [filters])
|
print_filters(relay_id: "following", filters: [filters])
|
||||||
self.damus_state.pool.subscribe(sub_id: sub_id, filters: filters, handler: handle_event)
|
self.damus_state.pool.subscribe(sub_id: sub_id, filters: filters, handler: handle_event)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,7 +78,10 @@ class FollowersModel: ObservableObject {
|
|||||||
|
|
||||||
if ev.known_kind == .contacts {
|
if ev.known_kind == .contacts {
|
||||||
handle_contact_event(ev)
|
handle_contact_event(ev)
|
||||||
|
} else if ev.known_kind == .metadata {
|
||||||
|
process_metadata_event(events: damus_state.events, our_pubkey: damus_state.pubkey, profiles: damus_state.profiles, ev: ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
case .notice(let msg):
|
case .notice(let msg):
|
||||||
print("followingmodel notice: \(msg)")
|
print("followingmodel notice: \(msg)")
|
||||||
|
|
||||||
|
|||||||
@@ -11,20 +11,18 @@ class FollowingModel {
|
|||||||
let damus_state: DamusState
|
let damus_state: DamusState
|
||||||
var needs_sub: Bool = true
|
var needs_sub: Bool = true
|
||||||
|
|
||||||
let contacts: [Pubkey]
|
let contacts: [String]
|
||||||
let hashtags: [Hashtag]
|
|
||||||
|
|
||||||
let sub_id: String = UUID().description
|
let sub_id: String = UUID().description
|
||||||
|
|
||||||
init(damus_state: DamusState, contacts: [Pubkey], hashtags: [Hashtag]) {
|
init(damus_state: DamusState, contacts: [String]) {
|
||||||
self.damus_state = damus_state
|
self.damus_state = damus_state
|
||||||
self.contacts = contacts
|
self.contacts = contacts
|
||||||
self.hashtags = hashtags
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func get_filter() -> NostrFilter {
|
func get_filter() -> NostrFilter {
|
||||||
var f = NostrFilter(kinds: [.metadata])
|
var f = NostrFilter(kinds: [.metadata])
|
||||||
f.authors = self.contacts.reduce(into: Array<Pubkey>()) { acc, pk in
|
f.authors = self.contacts.reduce(into: Array<String>()) { acc, pk in
|
||||||
// don't fetch profiles we already have
|
// don't fetch profiles we already have
|
||||||
if damus_state.profiles.has_fresh_profile(id: pk) {
|
if damus_state.profiles.has_fresh_profile(id: pk) {
|
||||||
return
|
return
|
||||||
@@ -41,7 +39,7 @@ class FollowingModel {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
let filters = [filter]
|
let filters = [filter]
|
||||||
//print_filters(relay_id: "following", filters: [filters])
|
print_filters(relay_id: "following", filters: [filters])
|
||||||
self.damus_state.pool.subscribe(sub_id: sub_id, filters: filters, handler: handle_event)
|
self.damus_state.pool.subscribe(sub_id: sub_id, filters: filters, handler: handle_event)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,6 +52,22 @@ class FollowingModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func handle_event(relay_id: String, ev: NostrConnectionEvent) {
|
func handle_event(relay_id: String, ev: NostrConnectionEvent) {
|
||||||
// don't need to do anything here really
|
switch ev {
|
||||||
|
case .ws_event:
|
||||||
|
break
|
||||||
|
case .nostr_event(let nev):
|
||||||
|
switch nev {
|
||||||
|
case .ok:
|
||||||
|
break
|
||||||
|
case .event(_, let ev):
|
||||||
|
if ev.kind == 0 {
|
||||||
|
process_metadata_event(events: damus_state.events, our_pubkey: damus_state.pubkey, profiles: damus_state.profiles, ev: ev)
|
||||||
|
}
|
||||||
|
case .notice(let msg):
|
||||||
|
print("followingmodel notice: \(msg)")
|
||||||
|
case .eose:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+299
-449
File diff suppressed because it is too large
Load Diff
@@ -47,8 +47,8 @@ enum MediaUpload {
|
|||||||
class ImageUploadModel: NSObject, URLSessionTaskDelegate, ObservableObject {
|
class ImageUploadModel: NSObject, URLSessionTaskDelegate, ObservableObject {
|
||||||
@Published var progress: Double? = nil
|
@Published var progress: Double? = nil
|
||||||
|
|
||||||
func start(media: MediaUpload, uploader: MediaUploader, keypair: Keypair? = nil) async -> ImageUploadResult {
|
func start(media: MediaUpload, uploader: MediaUploader) async -> ImageUploadResult {
|
||||||
let res = await create_upload_request(mediaToUpload: media, mediaUploader: uploader, progress: self, keypair: keypair)
|
let res = await create_upload_request(mediaToUpload: media, mediaUploader: uploader, progress: self)
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.progress = nil
|
self.progress = nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,10 +28,16 @@ enum LibreTranslateServer: String, CaseIterable, Identifiable, StringCodable {
|
|||||||
self = libreTranslateServer
|
self = libreTranslateServer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case argosopentech
|
||||||
|
case terraprint
|
||||||
case custom
|
case custom
|
||||||
|
|
||||||
var model: Model {
|
var model: Model {
|
||||||
switch self {
|
switch self {
|
||||||
|
case .argosopentech:
|
||||||
|
return .init(tag: self.rawValue, displayName: "translate.argosopentech.com", url: "https://translate.argosopentech.com")
|
||||||
|
case .terraprint:
|
||||||
|
return .init(tag: self.rawValue, displayName: "translate.terraprint.co", url: "https://translate.terraprint.co")
|
||||||
case .custom:
|
case .custom:
|
||||||
return .init(tag: self.rawValue, displayName: NSLocalizedString("Custom", comment: "Dropdown option for selecting a custom translation server."), url: nil)
|
return .init(tag: self.rawValue, displayName: NSLocalizedString("Custom", comment: "Dropdown option for selecting a custom translation server."), url: nil)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,16 +13,16 @@ enum CountResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class EventCounter {
|
class EventCounter {
|
||||||
var counts: [NoteId: Int] = [:]
|
var counts: [String: Int] = [:]
|
||||||
var user_events: [Pubkey: Set<NoteId>] = [:]
|
var user_events: [String: Set<String>] = [:]
|
||||||
var our_events: [NoteId: NostrEvent] = [:]
|
var our_events: [String: NostrEvent] = [:]
|
||||||
var our_pubkey: Pubkey
|
var our_pubkey: String
|
||||||
|
|
||||||
init(our_pubkey: Pubkey) {
|
init (our_pubkey: String) {
|
||||||
self.our_pubkey = our_pubkey
|
self.our_pubkey = our_pubkey
|
||||||
}
|
}
|
||||||
|
|
||||||
func add_event(_ ev: NostrEvent, target: NoteId) -> CountResult {
|
func add_event(_ ev: NostrEvent, target: String) -> CountResult {
|
||||||
let pubkey = ev.pubkey
|
let pubkey = ev.pubkey
|
||||||
|
|
||||||
if self.user_events[pubkey] == nil {
|
if self.user_events[pubkey] == nil {
|
||||||
|
|||||||
@@ -9,6 +9,6 @@ import Foundation
|
|||||||
|
|
||||||
struct Counted {
|
struct Counted {
|
||||||
let event: NostrEvent
|
let event: NostrEvent
|
||||||
let id: NoteId
|
let id: String
|
||||||
let total: Int
|
let total: Int
|
||||||
}
|
}
|
||||||
|
|||||||
+377
-106
@@ -7,94 +7,24 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
enum MentionType: AsciiCharacter, TagKey {
|
enum MentionType {
|
||||||
case p
|
case pubkey
|
||||||
case e
|
case event
|
||||||
|
|
||||||
var keychar: AsciiCharacter {
|
var ref: String {
|
||||||
self.rawValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum MentionRef: TagKeys, TagConvertible, Equatable, Hashable {
|
|
||||||
case pubkey(Pubkey) // TODO: handle nprofile
|
|
||||||
case note(NoteId)
|
|
||||||
|
|
||||||
var key: MentionType {
|
|
||||||
switch self {
|
switch self {
|
||||||
case .pubkey: return .p
|
case .pubkey:
|
||||||
case .note: return .e
|
return "p"
|
||||||
}
|
case .event:
|
||||||
}
|
return "e"
|
||||||
|
|
||||||
var bech32: String {
|
|
||||||
switch self {
|
|
||||||
case .pubkey(let pubkey): return bech32_pubkey(pubkey)
|
|
||||||
case .note(let noteId): return bech32_note_id(noteId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static func from_bech32(str: String) -> MentionRef? {
|
|
||||||
switch Bech32Object.parse(str) {
|
|
||||||
case .note(let noteid): return .note(noteid)
|
|
||||||
case .npub(let pubkey): return .pubkey(pubkey)
|
|
||||||
default: return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var pubkey: Pubkey? {
|
|
||||||
switch self {
|
|
||||||
case .pubkey(let pubkey): return pubkey
|
|
||||||
case .note: return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var tag: [String] {
|
|
||||||
switch self {
|
|
||||||
case .pubkey(let pubkey): return ["p", pubkey.hex()]
|
|
||||||
case .note(let noteId): return ["e", noteId.hex()]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static func from_tag(tag: TagSequence) -> MentionRef? {
|
|
||||||
guard tag.count >= 2 else { return nil }
|
|
||||||
|
|
||||||
var i = tag.makeIterator()
|
|
||||||
|
|
||||||
guard let t0 = i.next(),
|
|
||||||
let chr = t0.single_char,
|
|
||||||
let mention_type = MentionType(rawValue: chr),
|
|
||||||
let id = i.next()?.id()
|
|
||||||
else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
switch mention_type {
|
|
||||||
case .p: return .pubkey(Pubkey(id))
|
|
||||||
case .e: return .note(NoteId(id))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Mention<T: Equatable>: Equatable {
|
struct Mention: Equatable {
|
||||||
let index: Int?
|
let index: Int?
|
||||||
let ref: T
|
let type: MentionType
|
||||||
|
let ref: ReferencedId
|
||||||
static func any(_ mention_id: MentionRef, index: Int? = nil) -> Mention<MentionRef> {
|
|
||||||
return Mention<MentionRef>(index: index, ref: mention_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
static func noteref(_ id: NoteRef, index: Int? = nil) -> Mention<NoteRef> {
|
|
||||||
return Mention<NoteRef>(index: index, ref: id)
|
|
||||||
}
|
|
||||||
|
|
||||||
static func note(_ id: NoteId, index: Int? = nil) -> Mention<NoteId> {
|
|
||||||
return Mention<NoteId>(index: index, ref: id)
|
|
||||||
}
|
|
||||||
|
|
||||||
static func pubkey(_ pubkey: Pubkey, index: Int? = nil) -> Mention<Pubkey> {
|
|
||||||
return Mention<Pubkey>(index: index, ref: pubkey)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
typealias Invoice = LightningInvoice<Amount>
|
typealias Invoice = LightningInvoice<Amount>
|
||||||
@@ -123,9 +53,170 @@ struct LightningInvoice<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Blocks: Equatable {
|
enum Block: Equatable {
|
||||||
let words: Int
|
static func == (lhs: Block, rhs: Block) -> Bool {
|
||||||
let blocks: [Block]
|
switch (lhs, rhs) {
|
||||||
|
case (.text(let a), .text(let b)):
|
||||||
|
return a == b
|
||||||
|
case (.mention(let a), .mention(let b)):
|
||||||
|
return a == b
|
||||||
|
case (.hashtag(let a), .hashtag(let b)):
|
||||||
|
return a == b
|
||||||
|
case (.url(let a), .url(let b)):
|
||||||
|
return a == b
|
||||||
|
case (.invoice(let a), .invoice(let b)):
|
||||||
|
return a.string == b.string
|
||||||
|
case (_, _):
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case text(String)
|
||||||
|
case mention(Mention)
|
||||||
|
case hashtag(String)
|
||||||
|
case url(URL)
|
||||||
|
case invoice(Invoice)
|
||||||
|
case relay(String)
|
||||||
|
|
||||||
|
var is_invoice: Invoice? {
|
||||||
|
if case .invoice(let invoice) = self {
|
||||||
|
return invoice
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var is_hashtag: String? {
|
||||||
|
if case .hashtag(let htag) = self {
|
||||||
|
return htag
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var is_url: URL? {
|
||||||
|
if case .url(let url) = self {
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var is_text: String? {
|
||||||
|
if case .text(let txt) = self {
|
||||||
|
return txt
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var is_note_mention: Bool {
|
||||||
|
guard case .mention(let mention) = self else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return mention.type == .event
|
||||||
|
}
|
||||||
|
|
||||||
|
var is_mention: Bool {
|
||||||
|
if case .mention = self {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func render_blocks(blocks: [Block]) -> String {
|
||||||
|
return blocks.reduce("") { str, block in
|
||||||
|
switch block {
|
||||||
|
case .mention(let m):
|
||||||
|
if let idx = m.index {
|
||||||
|
return str + "#[\(idx)]"
|
||||||
|
} else if m.type == .pubkey, let pk = bech32_pubkey(m.ref.ref_id) {
|
||||||
|
return str + "nostr:\(pk)"
|
||||||
|
} else if let note_id = bech32_note_id(m.ref.ref_id) {
|
||||||
|
return str + "nostr:\(note_id)"
|
||||||
|
} else {
|
||||||
|
return str + m.ref.ref_id
|
||||||
|
}
|
||||||
|
case .relay(let relay):
|
||||||
|
return str + relay
|
||||||
|
case .text(let txt):
|
||||||
|
return str + txt
|
||||||
|
case .hashtag(let htag):
|
||||||
|
return str + "#" + htag
|
||||||
|
case .url(let url):
|
||||||
|
return str + url.absoluteString
|
||||||
|
case .invoice(let inv):
|
||||||
|
return str + inv.string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parse_mentions(content: String, tags: [[String]]) -> [Block] {
|
||||||
|
var out: [Block] = []
|
||||||
|
|
||||||
|
var bs = blocks()
|
||||||
|
bs.num_blocks = 0;
|
||||||
|
|
||||||
|
blocks_init(&bs)
|
||||||
|
|
||||||
|
let bytes = content.utf8CString
|
||||||
|
let _ = bytes.withUnsafeBufferPointer { p in
|
||||||
|
damus_parse_content(&bs, p.baseAddress)
|
||||||
|
}
|
||||||
|
|
||||||
|
var i = 0
|
||||||
|
while (i < bs.num_blocks) {
|
||||||
|
let block = bs.blocks[i]
|
||||||
|
|
||||||
|
if let converted = convert_block(block, tags: tags) {
|
||||||
|
out.append(converted)
|
||||||
|
}
|
||||||
|
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
blocks_free(&bs)
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func strblock_to_string(_ s: str_block_t) -> String? {
|
||||||
|
let len = s.end - s.start
|
||||||
|
let bytes = Data(bytes: s.start, count: len)
|
||||||
|
return String(bytes: bytes, encoding: .utf8)
|
||||||
|
}
|
||||||
|
|
||||||
|
func convert_block(_ b: block_t, tags: [[String]]) -> Block? {
|
||||||
|
if b.type == BLOCK_HASHTAG {
|
||||||
|
guard let str = strblock_to_string(b.block.str) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return .hashtag(str)
|
||||||
|
} else if b.type == BLOCK_TEXT {
|
||||||
|
guard let str = strblock_to_string(b.block.str) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return .text(str)
|
||||||
|
} else if b.type == BLOCK_MENTION_INDEX {
|
||||||
|
return convert_mention_index_block(ind: b.block.mention_index, tags: tags)
|
||||||
|
} else if b.type == BLOCK_URL {
|
||||||
|
return convert_url_block(b.block.str)
|
||||||
|
} else if b.type == BLOCK_INVOICE {
|
||||||
|
return convert_invoice_block(b.block.invoice)
|
||||||
|
} else if b.type == BLOCK_MENTION_BECH32 {
|
||||||
|
return convert_mention_bech32_block(b.block.mention_bech32)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func convert_url_block(_ b: str_block) -> Block? {
|
||||||
|
guard let str = strblock_to_string(b) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
guard let url = URL(string: str) else {
|
||||||
|
return .text(str)
|
||||||
|
}
|
||||||
|
return .url(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
func maybe_pointee<T>(_ p: UnsafeMutablePointer<T>!) -> T? {
|
func maybe_pointee<T>(_ p: UnsafeMutablePointer<T>!) -> T? {
|
||||||
@@ -190,6 +281,81 @@ func format_msats(_ msat: Int64, locale: Locale = Locale.current) -> String {
|
|||||||
return String(format: format, locale: locale, sats.decimalValue as NSDecimalNumber, formattedSats)
|
return String(format: format, locale: locale, sats.decimalValue as NSDecimalNumber, formattedSats)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func convert_invoice_block(_ b: invoice_block) -> Block? {
|
||||||
|
guard let invstr = strblock_to_string(b.invstr) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
guard var b11 = maybe_pointee(b.bolt11) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let description = convert_invoice_description(b11: b11) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let amount: Amount = maybe_pointee(b11.msat).map { .specific(Int64($0.millisatoshis)) } ?? .any
|
||||||
|
let payment_hash = Data(bytes: &b11.payment_hash, count: 32)
|
||||||
|
let created_at = b11.timestamp
|
||||||
|
|
||||||
|
tal_free(b.bolt11)
|
||||||
|
return .invoice(Invoice(description: description, amount: amount, string: invstr, expiry: b11.expiry, payment_hash: payment_hash, created_at: created_at))
|
||||||
|
}
|
||||||
|
|
||||||
|
func convert_mention_bech32_block(_ b: mention_bech32_block) -> Block?
|
||||||
|
{
|
||||||
|
switch b.bech32.type {
|
||||||
|
case NOSTR_BECH32_NOTE:
|
||||||
|
let note = b.bech32.data.note;
|
||||||
|
let event_id = hex_encode(Data(bytes: note.event_id, count: 32))
|
||||||
|
let event_id_ref = ReferencedId(ref_id: event_id, relay_id: nil, key: "e")
|
||||||
|
return .mention(Mention(index: nil, type: .event, ref: event_id_ref))
|
||||||
|
|
||||||
|
case NOSTR_BECH32_NEVENT:
|
||||||
|
let nevent = b.bech32.data.nevent;
|
||||||
|
let event_id = hex_encode(Data(bytes: nevent.event_id, count: 32))
|
||||||
|
var relay_id: String? = nil
|
||||||
|
if nevent.relays.num_relays > 0 {
|
||||||
|
relay_id = strblock_to_string(nevent.relays.relays.0)
|
||||||
|
}
|
||||||
|
let event_id_ref = ReferencedId(ref_id: event_id, relay_id: relay_id, key: "e")
|
||||||
|
return .mention(Mention(index: nil, type: .event, ref: event_id_ref))
|
||||||
|
|
||||||
|
case NOSTR_BECH32_NPUB:
|
||||||
|
let npub = b.bech32.data.npub
|
||||||
|
let pubkey = hex_encode(Data(bytes: npub.pubkey, count: 32))
|
||||||
|
let pubkey_ref = ReferencedId(ref_id: pubkey, relay_id: nil, key: "p")
|
||||||
|
return .mention(Mention(index: nil, type: .pubkey, ref: pubkey_ref))
|
||||||
|
|
||||||
|
case NOSTR_BECH32_NPROFILE:
|
||||||
|
let nprofile = b.bech32.data.nprofile
|
||||||
|
let pubkey = hex_encode(Data(bytes: nprofile.pubkey, count: 32))
|
||||||
|
var relay_id: String? = nil
|
||||||
|
if nprofile.relays.num_relays > 0 {
|
||||||
|
relay_id = strblock_to_string(nprofile.relays.relays.0)
|
||||||
|
}
|
||||||
|
let pubkey_ref = ReferencedId(ref_id: pubkey, relay_id: relay_id, key: "p")
|
||||||
|
return .mention(Mention(index: nil, type: .pubkey, ref: pubkey_ref))
|
||||||
|
|
||||||
|
case NOSTR_BECH32_NRELAY:
|
||||||
|
let nrelay = b.bech32.data.nrelay
|
||||||
|
guard let relay_str = strblock_to_string(nrelay.relay) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return .relay(relay_str)
|
||||||
|
|
||||||
|
case NOSTR_BECH32_NADDR:
|
||||||
|
// TODO: wtf do I do with this
|
||||||
|
guard let naddr = strblock_to_string(b.str) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return .text("nostr:" + naddr)
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func convert_invoice_description(b11: bolt11) -> InvoiceDescription? {
|
func convert_invoice_description(b11: bolt11) -> InvoiceDescription? {
|
||||||
if let desc = b11.description {
|
if let desc = b11.description {
|
||||||
return .description(String(cString: desc))
|
return .description(String(cString: desc))
|
||||||
@@ -202,6 +368,85 @@ func convert_invoice_description(b11: bolt11) -> InvoiceDescription? {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func convert_mention_index_block(ind: Int32, tags: [[String]]) -> Block?
|
||||||
|
{
|
||||||
|
let ind = Int(ind)
|
||||||
|
|
||||||
|
if ind < 0 || (ind + 1 > tags.count) || tags[ind].count < 2 {
|
||||||
|
return .text("#[\(ind)]")
|
||||||
|
}
|
||||||
|
|
||||||
|
let tag = tags[ind]
|
||||||
|
guard let mention_type = parse_mention_type(tag[0]) else {
|
||||||
|
return .text("#[\(ind)]")
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let ref = tag_to_refid(tag) else {
|
||||||
|
return .text("#[\(ind)]")
|
||||||
|
}
|
||||||
|
|
||||||
|
return .mention(Mention(index: ind, type: mention_type, ref: ref))
|
||||||
|
}
|
||||||
|
|
||||||
|
func parse_while(_ p: Parser, match: (Character) -> Bool) -> String? {
|
||||||
|
var i: Int = 0
|
||||||
|
let sub = substring(p.str, start: p.pos, end: p.str.count)
|
||||||
|
let start = p.pos
|
||||||
|
for c in sub {
|
||||||
|
if match(c) {
|
||||||
|
p.pos += 1
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
let end = start + i
|
||||||
|
if start == end {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return String(substring(p.str, start: start, end: end))
|
||||||
|
}
|
||||||
|
|
||||||
|
func is_hashtag_char(_ c: Character) -> Bool {
|
||||||
|
return c.isLetter || c.isNumber
|
||||||
|
}
|
||||||
|
|
||||||
|
func prev_char(_ p: Parser, n: Int) -> Character? {
|
||||||
|
if p.pos - n < 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let ind = p.str.index(p.str.startIndex, offsetBy: p.pos - n)
|
||||||
|
return p.str[ind]
|
||||||
|
}
|
||||||
|
|
||||||
|
func is_punctuation(_ c: Character) -> Bool {
|
||||||
|
return c.isWhitespace || c.isPunctuation
|
||||||
|
}
|
||||||
|
|
||||||
|
func parse_hashtag(_ p: Parser) -> String? {
|
||||||
|
let start = p.pos
|
||||||
|
|
||||||
|
if !parse_char(p, "#") {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if let prev = prev_char(p, n: 2) {
|
||||||
|
// we don't allow adjacent hashtags
|
||||||
|
if !is_punctuation(prev) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let str = parse_while(p, match: is_hashtag_char) else {
|
||||||
|
p.pos = start
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
func find_tag_ref(type: String, id: String, tags: [[String]]) -> Int? {
|
func find_tag_ref(type: String, id: String, tags: [[String]]) -> Int? {
|
||||||
var i: Int = 0
|
var i: Int = 0
|
||||||
for tag in tags {
|
for tag in tags {
|
||||||
@@ -221,39 +466,65 @@ struct PostTags {
|
|||||||
let tags: [[String]]
|
let tags: [[String]]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert
|
func parse_mention_type(_ c: String) -> MentionType? {
|
||||||
func make_post_tags(post_blocks: [Block], tags: [[String]]) -> PostTags {
|
if c == "e" {
|
||||||
var new_tags = tags
|
return .event
|
||||||
|
} else if c == "p" {
|
||||||
|
return .pubkey
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert
|
||||||
|
func make_post_tags(post_blocks: [PostBlock], tags: [[String]], silent_mentions: Bool) -> PostTags {
|
||||||
|
var new_tags = tags
|
||||||
|
var blocks: [Block] = []
|
||||||
|
|
||||||
for post_block in post_blocks {
|
for post_block in post_blocks {
|
||||||
switch post_block {
|
switch post_block {
|
||||||
case .mention(let mention):
|
case .ref(let ref):
|
||||||
if case .note = mention.ref {
|
guard let mention_type = parse_mention_type(ref.key) else {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
new_tags.append(mention.ref.tag)
|
if silent_mentions || mention_type == .event {
|
||||||
|
let mention = Mention(index: nil, type: mention_type, ref: ref)
|
||||||
|
let block = Block.mention(mention)
|
||||||
|
blocks.append(block)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if let ind = find_tag_ref(type: ref.key, id: ref.ref_id, tags: tags) {
|
||||||
|
let mention = Mention(index: ind, type: mention_type, ref: ref)
|
||||||
|
let block = Block.mention(mention)
|
||||||
|
blocks.append(block)
|
||||||
|
} else {
|
||||||
|
let ind = new_tags.count
|
||||||
|
new_tags.append(refid_to_tag(ref))
|
||||||
|
let mention = Mention(index: ind, type: mention_type, ref: ref)
|
||||||
|
let block = Block.mention(mention)
|
||||||
|
blocks.append(block)
|
||||||
|
}
|
||||||
case .hashtag(let hashtag):
|
case .hashtag(let hashtag):
|
||||||
new_tags.append(["t", hashtag.lowercased()])
|
new_tags.append(["t", hashtag.lowercased()])
|
||||||
case .text: break
|
blocks.append(.hashtag(hashtag))
|
||||||
case .invoice: break
|
case .text(let txt):
|
||||||
case .relay: break
|
blocks.append(Block.text(txt))
|
||||||
case .url(let url):
|
|
||||||
new_tags.append(["r", url.absoluteString])
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return PostTags(blocks: post_blocks, tags: new_tags)
|
return PostTags(blocks: blocks, tags: new_tags)
|
||||||
}
|
}
|
||||||
|
|
||||||
func post_to_event(post: NostrPost, keypair: FullKeypair) -> NostrEvent? {
|
func post_to_event(post: NostrPost, privkey: String, pubkey: String) -> NostrEvent {
|
||||||
let tags = post.references.map({ r in r.tag }) + post.tags
|
let tags = post.references.map(refid_to_tag) + post.tags
|
||||||
let post_blocks = parse_post_blocks(content: post.content)
|
let post_blocks = parse_post_blocks(content: post.content)
|
||||||
let post_tags = make_post_tags(post_blocks: post_blocks, tags: tags)
|
let post_tags = make_post_tags(post_blocks: post_blocks, tags: tags, silent_mentions: false)
|
||||||
let content = post_tags.blocks
|
let content = render_blocks(blocks: post_tags.blocks)
|
||||||
.map(\.asString)
|
let new_ev = NostrEvent(content: content, pubkey: pubkey, kind: post.kind.rawValue, tags: post_tags.tags)
|
||||||
.joined(separator: "")
|
new_ev.calculate_id()
|
||||||
return NostrEvent(content: content, keypair: keypair.to_keypair(), kind: post.kind.rawValue, tags: post_tags.tags)
|
new_ev.sign(privkey: privkey)
|
||||||
|
return new_ev
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,25 +7,20 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
fileprivate func getMutedThreadsKey(pubkey: Pubkey) -> String {
|
fileprivate func getMutedThreadsKey(pubkey: String) -> String {
|
||||||
pk_setting_key(pubkey, key: "muted_threads")
|
pk_setting_key(pubkey, key: "muted_threads")
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadMutedThreads(pubkey: Pubkey) -> [NoteId] {
|
func loadMutedThreads(pubkey: String) -> [String] {
|
||||||
let key = getMutedThreadsKey(pubkey: pubkey)
|
let key = getMutedThreadsKey(pubkey: pubkey)
|
||||||
let xs = UserDefaults.standard.stringArray(forKey: key) ?? []
|
return UserDefaults.standard.stringArray(forKey: key) ?? []
|
||||||
return xs.reduce(into: [NoteId]()) { ids, k in
|
|
||||||
guard let note_id = hex_decode(k) else { return }
|
|
||||||
ids.append(NoteId(Data(note_id)))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func saveMutedThreads(pubkey: Pubkey, currentValue: [NoteId], value: [NoteId]) -> Bool {
|
func saveMutedThreads(pubkey: String, currentValue: [String], value: [String]) -> Bool {
|
||||||
let uniqueMutedThreads = Array(Set(value))
|
let uniqueMutedThreads = Array(Set(value))
|
||||||
|
|
||||||
if uniqueMutedThreads != currentValue {
|
if uniqueMutedThreads != currentValue {
|
||||||
let ids = uniqueMutedThreads.map { note_id in return note_id.hex() }
|
UserDefaults.standard.set(uniqueMutedThreads, forKey: getMutedThreadsKey(pubkey: pubkey))
|
||||||
UserDefaults.standard.set(ids, forKey: getMutedThreadsKey(pubkey: pubkey))
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,9 +31,9 @@ class MutedThreadsManager: ObservableObject {
|
|||||||
|
|
||||||
private let keypair: Keypair
|
private let keypair: Keypair
|
||||||
|
|
||||||
private var _mutedThreadsSet: Set<NoteId>
|
private var _mutedThreadsSet: Set<String>
|
||||||
private var _mutedThreads: [NoteId]
|
private var _mutedThreads: [String]
|
||||||
var mutedThreads: [NoteId] {
|
var mutedThreads: [String] {
|
||||||
get {
|
get {
|
||||||
return _mutedThreads
|
return _mutedThreads
|
||||||
}
|
}
|
||||||
@@ -56,20 +51,20 @@ class MutedThreadsManager: ObservableObject {
|
|||||||
self.keypair = keypair
|
self.keypair = keypair
|
||||||
}
|
}
|
||||||
|
|
||||||
func isMutedThread(_ ev: NostrEvent, keypair: Keypair) -> Bool {
|
func isMutedThread(_ ev: NostrEvent, privkey: String?) -> Bool {
|
||||||
return _mutedThreadsSet.contains(ev.thread_id(keypair: keypair))
|
return _mutedThreadsSet.contains(ev.thread_id(privkey: privkey))
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateMutedThread(_ ev: NostrEvent) {
|
func updateMutedThread(_ ev: NostrEvent) {
|
||||||
let threadId = ev.thread_id(keypair: keypair)
|
let threadId = ev.thread_id(privkey: nil)
|
||||||
if isMutedThread(ev, keypair: keypair) {
|
if isMutedThread(ev, privkey: keypair.privkey) {
|
||||||
mutedThreads = mutedThreads.filter { $0 != threadId }
|
mutedThreads = mutedThreads.filter { $0 != threadId }
|
||||||
_mutedThreadsSet.remove(threadId)
|
_mutedThreadsSet.remove(threadId)
|
||||||
notify(.unmute_thread(ev))
|
notify(.unmute_thread, ev)
|
||||||
} else {
|
} else {
|
||||||
mutedThreads.append(threadId)
|
mutedThreads.append(threadId)
|
||||||
_mutedThreadsSet.insert(threadId)
|
_mutedThreadsSet.insert(threadId)
|
||||||
notify(.mute_thread(ev))
|
notify(.mute_thread, ev)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import Foundation
|
|||||||
class EventGroup {
|
class EventGroup {
|
||||||
var events: [NostrEvent]
|
var events: [NostrEvent]
|
||||||
|
|
||||||
var last_event_at: UInt32 {
|
var last_event_at: Int64 {
|
||||||
guard let first = self.events.first else {
|
guard let first = self.events.first else {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@@ -18,7 +18,11 @@ class EventGroup {
|
|||||||
return first.created_at
|
return first.created_at
|
||||||
}
|
}
|
||||||
|
|
||||||
init(events: [NostrEvent] = []) {
|
init() {
|
||||||
|
self.events = []
|
||||||
|
}
|
||||||
|
|
||||||
|
init(events: [NostrEvent]) {
|
||||||
self.events = events
|
self.events = events
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
//
|
|
||||||
// NotificationStatusModel.swift
|
|
||||||
// damus
|
|
||||||
//
|
|
||||||
// Created by William Casarin on 2023-06-23.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
class NotificationStatusModel: ObservableObject {
|
|
||||||
@Published var new_events: NewEventsBits = NewEventsBits()
|
|
||||||
}
|
|
||||||
@@ -8,11 +8,11 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
class ZapGroup {
|
class ZapGroup {
|
||||||
var zaps: [Zapping] = []
|
var zaps: [Zapping]
|
||||||
var msat_total: Int64 = 0
|
var msat_total: Int64
|
||||||
var zappers = Set<Pubkey>()
|
var zappers: Set<String>
|
||||||
|
|
||||||
var last_event_at: UInt32 {
|
var last_event_at: Int64 {
|
||||||
guard let first = zaps.first else {
|
guard let first = zaps.first else {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@@ -21,12 +21,12 @@ class ZapGroup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func zap_requests() -> [NostrEvent] {
|
func zap_requests() -> [NostrEvent] {
|
||||||
zaps.map { z in z.request.ev }
|
zaps.map { z in z.request }
|
||||||
}
|
}
|
||||||
|
|
||||||
func would_filter(_ isIncluded: (NostrEvent) -> Bool) -> Bool {
|
func would_filter(_ isIncluded: (NostrEvent) -> Bool) -> Bool {
|
||||||
for zap in zaps {
|
for zap in zaps {
|
||||||
if !isIncluded(zap.request.ev) {
|
if !isIncluded(zap.request) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -35,7 +35,7 @@ class ZapGroup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func filter(_ isIncluded: (NostrEvent) -> Bool) -> ZapGroup? {
|
func filter(_ isIncluded: (NostrEvent) -> Bool) -> ZapGroup? {
|
||||||
let new_zaps = zaps.filter { isIncluded($0.request.ev) }
|
let new_zaps = zaps.filter { isIncluded($0.request) }
|
||||||
guard new_zaps.count > 0 else {
|
guard new_zaps.count > 0 else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -46,6 +46,12 @@ class ZapGroup {
|
|||||||
return grp
|
return grp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
self.zaps = []
|
||||||
|
self.msat_total = 0
|
||||||
|
self.zappers = Set()
|
||||||
|
}
|
||||||
|
|
||||||
@discardableResult
|
@discardableResult
|
||||||
func insert(_ zap: Zapping) -> Bool {
|
func insert(_ zap: Zapping) -> Bool {
|
||||||
if !insert_uniq_sorted_zap_by_created(zaps: &zaps, new_zap: zap) {
|
if !insert_uniq_sorted_zap_by_created(zaps: &zaps, new_zap: zap) {
|
||||||
@@ -54,8 +60,8 @@ class ZapGroup {
|
|||||||
|
|
||||||
msat_total += zap.amount
|
msat_total += zap.amount
|
||||||
|
|
||||||
if !zappers.contains(zap.request.ev.pubkey) {
|
if !zappers.contains(zap.request.pubkey) {
|
||||||
zappers.insert(zap.request.ev.pubkey)
|
zappers.insert(zap.request.pubkey)
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user