Add initial longform note support

Changelog-Added: Add initial longform note support
This commit is contained in:
William Casarin
2023-07-10 18:29:18 -07:00
34 changed files with 841 additions and 164 deletions
+1
View File
@@ -46,6 +46,7 @@ typedef struct note_block {
} block_t; } block_t;
typedef struct note_blocks { typedef struct note_blocks {
int words;
int num_blocks; int num_blocks;
struct note_block *blocks; struct note_block *blocks;
} blocks_t; } blocks_t;
+6
View File
@@ -216,6 +216,7 @@ int damus_parse_content(struct note_blocks *blocks, const char *content) {
struct note_block block; struct note_block block;
u8 *start, *pre_mention; 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((u8*)content, (u8*)content + strlen(content), &cur);
@@ -224,6 +225,11 @@ int damus_parse_content(struct note_blocks *blocks, const char *content) {
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_whitespace(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))) {
+52 -1
View File
@@ -192,6 +192,14 @@
4CA2EFA0280E37AC0044ACD8 /* TimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA2EF9F280E37AC0044ACD8 /* TimelineView.swift */; }; 4CA2EFA0280E37AC0044ACD8 /* TimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA2EF9F280E37AC0044ACD8 /* TimelineView.swift */; };
4CA3FA1029F593D000FDB3C3 /* ZapTypePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA3FA0F29F593D000FDB3C3 /* ZapTypePicker.swift */; }; 4CA3FA1029F593D000FDB3C3 /* ZapTypePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA3FA0F29F593D000FDB3C3 /* ZapTypePicker.swift */; };
4CA5588329F33F5B00DC6A45 /* StringCodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA5588229F33F5B00DC6A45 /* StringCodable.swift */; }; 4CA5588329F33F5B00DC6A45 /* StringCodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA5588229F33F5B00DC6A45 /* StringCodable.swift */; };
4CA9275D2A28FF630098A105 /* LongformView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA9275C2A28FF630098A105 /* LongformView.swift */; };
4CA9275F2A2902B20098A105 /* LongformPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA9275E2A2902B20098A105 /* LongformPreview.swift */; };
4CA927612A290E340098A105 /* EventShell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA927602A290E340098A105 /* EventShell.swift */; };
4CA927632A290EB10098A105 /* EventTop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA927622A290EB10098A105 /* EventTop.swift */; };
4CA927652A290F1A0098A105 /* TimeDot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA927642A290F1A0098A105 /* TimeDot.swift */; };
4CA927672A290F8B0098A105 /* RelativeTime.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA927662A290F8B0098A105 /* RelativeTime.swift */; };
4CA9276A2A290FC00098A105 /* ContextButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA927692A290FC00098A105 /* ContextButton.swift */; };
4CA9276C2A2910D10098A105 /* ReplyPart.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA9276B2A2910D10098A105 /* ReplyPart.swift */; };
4CAAD8AD298851D000060CEA /* AccountDeletion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CAAD8AC298851D000060CEA /* AccountDeletion.swift */; }; 4CAAD8AD298851D000060CEA /* AccountDeletion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CAAD8AC298851D000060CEA /* AccountDeletion.swift */; };
4CAAD8B029888AD200060CEA /* RelayConfigView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CAAD8AF29888AD200060CEA /* RelayConfigView.swift */; }; 4CAAD8B029888AD200060CEA /* RelayConfigView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CAAD8AF29888AD200060CEA /* RelayConfigView.swift */; };
4CACA9D5280C31E100D9BBE8 /* ReplyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CACA9D4280C31E100D9BBE8 /* ReplyView.swift */; }; 4CACA9D5280C31E100D9BBE8 /* ReplyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CACA9D4280C31E100D9BBE8 /* ReplyView.swift */; };
@@ -279,6 +287,7 @@
4CF0ABEE29844B5500D66079 /* AnyEncodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABED29844B5500D66079 /* AnyEncodable.swift */; }; 4CF0ABEE29844B5500D66079 /* AnyEncodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABED29844B5500D66079 /* AnyEncodable.swift */; };
4CF0ABF029857E9200D66079 /* Bech32Object.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABEF29857E9200D66079 /* Bech32Object.swift */; }; 4CF0ABF029857E9200D66079 /* Bech32Object.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABEF29857E9200D66079 /* Bech32Object.swift */; };
4CF0ABF62985CD5500D66079 /* UserSearch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABF52985CD5500D66079 /* UserSearch.swift */; }; 4CF0ABF62985CD5500D66079 /* UserSearch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABF52985CD5500D66079 /* UserSearch.swift */; };
4CFD502F2A2DA45800A229DB /* MediaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFD502E2A2DA45800A229DB /* MediaView.swift */; };
4CFF8F6329CC9AD7008DB934 /* ImageContextMenuModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF8F6229CC9AD7008DB934 /* ImageContextMenuModifier.swift */; }; 4CFF8F6329CC9AD7008DB934 /* ImageContextMenuModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF8F6229CC9AD7008DB934 /* ImageContextMenuModifier.swift */; };
4CFF8F6729CC9E3A008DB934 /* ImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF8F6629CC9E3A008DB934 /* ImageView.swift */; }; 4CFF8F6729CC9E3A008DB934 /* ImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF8F6629CC9E3A008DB934 /* ImageView.swift */; };
4CFF8F6929CC9ED1008DB934 /* ImageContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF8F6829CC9ED1008DB934 /* ImageContainerView.swift */; }; 4CFF8F6929CC9ED1008DB934 /* ImageContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF8F6829CC9ED1008DB934 /* ImageContainerView.swift */; };
@@ -664,6 +673,14 @@
4CA927712A2A5D480098A105 /* error.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = error.h; sourceTree = "<group>"; }; 4CA927712A2A5D480098A105 /* error.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = error.h; sourceTree = "<group>"; };
4CA927742A2A5E2F0098A105 /* varint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = varint.h; sourceTree = "<group>"; }; 4CA927742A2A5E2F0098A105 /* varint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = varint.h; sourceTree = "<group>"; };
4CA927752A2A5E2F0098A105 /* typedefs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = typedefs.h; sourceTree = "<group>"; }; 4CA927752A2A5E2F0098A105 /* typedefs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = typedefs.h; sourceTree = "<group>"; };
4CA9275C2A28FF630098A105 /* LongformView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LongformView.swift; sourceTree = "<group>"; };
4CA9275E2A2902B20098A105 /* LongformPreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LongformPreview.swift; sourceTree = "<group>"; };
4CA927602A290E340098A105 /* EventShell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventShell.swift; sourceTree = "<group>"; };
4CA927622A290EB10098A105 /* EventTop.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventTop.swift; sourceTree = "<group>"; };
4CA927642A290F1A0098A105 /* TimeDot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeDot.swift; sourceTree = "<group>"; };
4CA927662A290F8B0098A105 /* RelativeTime.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelativeTime.swift; sourceTree = "<group>"; };
4CA927692A290FC00098A105 /* ContextButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextButton.swift; sourceTree = "<group>"; };
4CA9276B2A2910D10098A105 /* ReplyPart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplyPart.swift; sourceTree = "<group>"; };
4CAAD8AC298851D000060CEA /* AccountDeletion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountDeletion.swift; sourceTree = "<group>"; }; 4CAAD8AC298851D000060CEA /* AccountDeletion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountDeletion.swift; sourceTree = "<group>"; };
4CAAD8AF29888AD200060CEA /* RelayConfigView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayConfigView.swift; sourceTree = "<group>"; }; 4CAAD8AF29888AD200060CEA /* RelayConfigView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayConfigView.swift; sourceTree = "<group>"; };
4CACA9D4280C31E100D9BBE8 /* ReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplyView.swift; sourceTree = "<group>"; }; 4CACA9D4280C31E100D9BBE8 /* ReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplyView.swift; sourceTree = "<group>"; };
@@ -754,6 +771,7 @@
4CF0ABED29844B5500D66079 /* AnyEncodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyEncodable.swift; sourceTree = "<group>"; }; 4CF0ABED29844B5500D66079 /* AnyEncodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyEncodable.swift; sourceTree = "<group>"; };
4CF0ABEF29857E9200D66079 /* Bech32Object.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bech32Object.swift; sourceTree = "<group>"; }; 4CF0ABEF29857E9200D66079 /* Bech32Object.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bech32Object.swift; sourceTree = "<group>"; };
4CF0ABF52985CD5500D66079 /* UserSearch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSearch.swift; sourceTree = "<group>"; }; 4CF0ABF52985CD5500D66079 /* UserSearch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSearch.swift; sourceTree = "<group>"; };
4CFD502E2A2DA45800A229DB /* MediaView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaView.swift; sourceTree = "<group>"; };
4CFF8F6229CC9AD7008DB934 /* ImageContextMenuModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageContextMenuModifier.swift; sourceTree = "<group>"; }; 4CFF8F6229CC9AD7008DB934 /* ImageContextMenuModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageContextMenuModifier.swift; sourceTree = "<group>"; };
4CFF8F6629CC9E3A008DB934 /* ImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageView.swift; sourceTree = "<group>"; }; 4CFF8F6629CC9E3A008DB934 /* ImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageView.swift; sourceTree = "<group>"; };
4CFF8F6829CC9ED1008DB934 /* ImageContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageContainerView.swift; sourceTree = "<group>"; }; 4CFF8F6829CC9ED1008DB934 /* ImageContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageContainerView.swift; sourceTree = "<group>"; };
@@ -1257,6 +1275,27 @@
4C9146FB2A2A77B300DDEA40 /* NostrScript.swift */, 4C9146FB2A2A77B300DDEA40 /* NostrScript.swift */,
); );
path = nostrscript; path = nostrscript;
};
4CA9275B2A28FF570098A105 /* Longform */ = {
isa = PBXGroup;
children = (
4CA9275C2A28FF630098A105 /* LongformView.swift */,
4CA9275E2A2902B20098A105 /* LongformPreview.swift */,
);
path = Longform;
sourceTree = "<group>";
};
4CA927682A290F8F0098A105 /* Components */ = {
isa = PBXGroup;
children = (
4CA927642A290F1A0098A105 /* TimeDot.swift */,
4CA927622A290EB10098A105 /* EventTop.swift */,
4CC7AAF3297F18B400430951 /* ReplyDescription.swift */,
4CA927662A290F8B0098A105 /* RelativeTime.swift */,
4CA927692A290FC00098A105 /* ContextButton.swift */,
4CA9276B2A2910D10098A105 /* ReplyPart.swift */,
);
path = Components;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
4CA927732A2A5DCC0098A105 /* nostrscript */ = { 4CA927732A2A5DCC0098A105 /* nostrscript */ = {
@@ -1328,8 +1367,8 @@
4CC7AAEE297F11B300430951 /* Events */ = { 4CC7AAEE297F11B300430951 /* Events */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
4CA927682A290F8F0098A105 /* Components */,
4CC7AAEF297F11C700430951 /* SelectedEventView.swift */, 4CC7AAEF297F11C700430951 /* SelectedEventView.swift */,
4CC7AAF3297F18B400430951 /* ReplyDescription.swift */,
4CC7AAF5297F1A6A00430951 /* EventBody.swift */, 4CC7AAF5297F1A6A00430951 /* EventBody.swift */,
4CC7AAEA297F0AEC00430951 /* BuilderEventView.swift */, 4CC7AAEA297F0AEC00430951 /* BuilderEventView.swift */,
4CC7AAF7297F1CEE00430951 /* EventProfile.swift */, 4CC7AAF7297F1CEE00430951 /* EventProfile.swift */,
@@ -1338,6 +1377,8 @@
4C3D52B5298DB4E6001C5831 /* ZapEvent.swift */, 4C3D52B5298DB4E6001C5831 /* ZapEvent.swift */,
4C3D52B7298DB5C6001C5831 /* TextEvent.swift */, 4C3D52B7298DB5C6001C5831 /* TextEvent.swift */,
4CFF8F6C29CD022E008DB934 /* WideEventView.swift */, 4CFF8F6C29CD022E008DB934 /* WideEventView.swift */,
4CA9275B2A28FF570098A105 /* Longform */,
4CA927602A290E340098A105 /* EventShell.swift */,
); );
path = Events; path = Events;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -1557,6 +1598,7 @@
4CFF8F6629CC9E3A008DB934 /* ImageView.swift */, 4CFF8F6629CC9E3A008DB934 /* ImageView.swift */,
6439E013296790CF0020672B /* ProfilePicImageView.swift */, 6439E013296790CF0020672B /* ProfilePicImageView.swift */,
4CFF8F6829CC9ED1008DB934 /* ImageContainerView.swift */, 4CFF8F6829CC9ED1008DB934 /* ImageContainerView.swift */,
4CFD502E2A2DA45800A229DB /* MediaView.swift */,
); );
path = Images; path = Images;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -1782,6 +1824,7 @@
4C3EA64428FF558100C48A62 /* sha256.c in Sources */, 4C3EA64428FF558100C48A62 /* sha256.c in Sources */,
4CCF9AAF2A1FDBDB00E03CFB /* VideoPlayer.swift in Sources */, 4CCF9AAF2A1FDBDB00E03CFB /* VideoPlayer.swift in Sources */,
504323A72A34915F006AE6DC /* RelayModel.swift in Sources */, 504323A72A34915F006AE6DC /* RelayModel.swift in Sources */,
4CA9276A2A290FC00098A105 /* ContextButton.swift in Sources */,
4CF0ABF62985CD5500D66079 /* UserSearch.swift in Sources */, 4CF0ABF62985CD5500D66079 /* UserSearch.swift in Sources */,
4C363AA828297703006E126D /* InsertSort.swift in Sources */, 4C363AA828297703006E126D /* InsertSort.swift in Sources */,
4C285C86283892E7008A31F1 /* CreateAccountModel.swift in Sources */, 4C285C86283892E7008A31F1 /* CreateAccountModel.swift in Sources */,
@@ -1800,14 +1843,17 @@
4C3EA67728FF7A9800C48A62 /* talstr.c in Sources */, 4C3EA67728FF7A9800C48A62 /* talstr.c in Sources */,
4CE6DEE927F7A08100C66700 /* ContentView.swift in Sources */, 4CE6DEE927F7A08100C66700 /* ContentView.swift in Sources */,
4CEE2AF5280B29E600AB5EEF /* TimeAgo.swift in Sources */, 4CEE2AF5280B29E600AB5EEF /* TimeAgo.swift in Sources */,
4CA9275D2A28FF630098A105 /* LongformView.swift in Sources */,
4C75EFAD28049CFB0006080F /* PostButton.swift in Sources */, 4C75EFAD28049CFB0006080F /* PostButton.swift in Sources */,
504323A92A3495B6006AE6DC /* RelayModelCache.swift in Sources */, 504323A92A3495B6006AE6DC /* RelayModelCache.swift in Sources */,
3A8CC6CC2A2CFEF900940F5F /* StringUtil.swift in Sources */, 3A8CC6CC2A2CFEF900940F5F /* StringUtil.swift in Sources */,
4CB55EF5295E679D007FD187 /* UserRelaysView.swift in Sources */, 4CB55EF5295E679D007FD187 /* UserRelaysView.swift in Sources */,
4C363AA228296A7E006E126D /* SearchView.swift in Sources */, 4C363AA228296A7E006E126D /* SearchView.swift in Sources */,
4CC7AAED297F0B9E00430951 /* Highlight.swift in Sources */, 4CC7AAED297F0B9E00430951 /* Highlight.swift in Sources */,
4CA927652A290F1A0098A105 /* TimeDot.swift in Sources */,
4CC6193A29DC777C006A86D1 /* RelayBootstrap.swift in Sources */, 4CC6193A29DC777C006A86D1 /* RelayBootstrap.swift in Sources */,
4C285C8A2838B985008A31F1 /* ProfilePictureSelector.swift in Sources */, 4C285C8A2838B985008A31F1 /* ProfilePictureSelector.swift in Sources */,
4CFD502F2A2DA45800A229DB /* MediaView.swift in Sources */,
4C9F18E429ABDE6D008C55EC /* MaybeAnonPfpView.swift in Sources */, 4C9F18E429ABDE6D008C55EC /* MaybeAnonPfpView.swift in Sources */,
4CA5588329F33F5B00DC6A45 /* StringCodable.swift in Sources */, 4CA5588329F33F5B00DC6A45 /* StringCodable.swift in Sources */,
4C75EFB92804A2740006080F /* EventView.swift in Sources */, 4C75EFB92804A2740006080F /* EventView.swift in Sources */,
@@ -1835,6 +1881,7 @@
3A5E47C52A4A6CF400C0D090 /* Trie.swift in Sources */, 3A5E47C52A4A6CF400C0D090 /* Trie.swift in Sources */,
4C216F382871EDE300040376 /* DirectMessageModel.swift in Sources */, 4C216F382871EDE300040376 /* DirectMessageModel.swift in Sources */,
4C75EFA627FF87A20006080F /* Nostr.swift in Sources */, 4C75EFA627FF87A20006080F /* Nostr.swift in Sources */,
4CA927672A290F8B0098A105 /* RelativeTime.swift in Sources */,
4CB883A62975F83C00DC99E7 /* LNUrlPayRequest.swift in Sources */, 4CB883A62975F83C00DC99E7 /* LNUrlPayRequest.swift in Sources */,
4C7D096D2A0AEA0400943473 /* CodeScanner.swift in Sources */, 4C7D096D2A0AEA0400943473 /* CodeScanner.swift in Sources */,
4CE4F9DE2852768D00C00DD9 /* ConfigView.swift in Sources */, 4CE4F9DE2852768D00C00DD9 /* ConfigView.swift in Sources */,
@@ -1854,6 +1901,7 @@
4C363A9A28283854006E126D /* Reply.swift in Sources */, 4C363A9A28283854006E126D /* Reply.swift in Sources */,
BA693074295D649800ADDB87 /* UserSettingsStore.swift in Sources */, BA693074295D649800ADDB87 /* UserSettingsStore.swift in Sources */,
4CFF8F6729CC9E3A008DB934 /* ImageView.swift in Sources */, 4CFF8F6729CC9E3A008DB934 /* ImageView.swift in Sources */,
4CA927632A290EB10098A105 /* EventTop.swift in Sources */,
4C90BD18283A9EE5008EE7EF /* LoginView.swift in Sources */, 4C90BD18283A9EE5008EE7EF /* LoginView.swift in Sources */,
4CB8838B296F6E1E00DC99E7 /* NIP05Badge.swift in Sources */, 4CB8838B296F6E1E00DC99E7 /* NIP05Badge.swift in Sources */,
4CA3FA1029F593D000FDB3C3 /* ZapTypePicker.swift in Sources */, 4CA3FA1029F593D000FDB3C3 /* ZapTypePicker.swift in Sources */,
@@ -1938,6 +1986,7 @@
4C9146FD2A2A87C200DDEA40 /* wasm.c in Sources */, 4C9146FD2A2A87C200DDEA40 /* wasm.c in Sources */,
4C75EFAF28049D350006080F /* NostrFilter.swift in Sources */, 4C75EFAF28049D350006080F /* NostrFilter.swift in Sources */,
4C3EA64C28FF59AC00C48A62 /* bech32_util.c in Sources */, 4C3EA64C28FF59AC00C48A62 /* bech32_util.c in Sources */,
4CA9276C2A2910D10098A105 /* ReplyPart.swift in Sources */,
4CE1399029F0661A00AC6A0B /* RepostAction.swift in Sources */, 4CE1399029F0661A00AC6A0B /* RepostAction.swift in Sources */,
4CE1399229F0666100AC6A0B /* ShareActionButton.swift in Sources */, 4CE1399229F0666100AC6A0B /* ShareActionButton.swift in Sources */,
4C42812C298C848200DBF26F /* TranslateView.swift in Sources */, 4C42812C298C848200DBF26F /* TranslateView.swift in Sources */,
@@ -1981,6 +2030,7 @@
4CDA128C29EB19C40006FA5A /* LocalNotification.swift in Sources */, 4CDA128C29EB19C40006FA5A /* LocalNotification.swift in Sources */,
4C3BEFD6281D995700B3DE84 /* ActionBarModel.swift in Sources */, 4C3BEFD6281D995700B3DE84 /* ActionBarModel.swift in Sources */,
4C7D09762A0AF19E00943473 /* FillAndStroke.swift in Sources */, 4C7D09762A0AF19E00943473 /* FillAndStroke.swift in Sources */,
4CA927612A290E340098A105 /* EventShell.swift in Sources */,
4C363AA428296DEE006E126D /* SearchModel.swift in Sources */, 4C363AA428296DEE006E126D /* SearchModel.swift in Sources */,
4C8D00CC29DF92DF0036AF10 /* Hashtags.swift in Sources */, 4C8D00CC29DF92DF0036AF10 /* Hashtags.swift in Sources */,
4C7D096F2A0AEA0400943473 /* ScannerViewController.swift in Sources */, 4C7D096F2A0AEA0400943473 /* ScannerViewController.swift in Sources */,
@@ -1992,6 +2042,7 @@
4C3EA66028FF5E7700C48A62 /* node_id.c in Sources */, 4C3EA66028FF5E7700C48A62 /* node_id.c in Sources */,
4CE6DEE727F7A08100C66700 /* damusApp.swift in Sources */, 4CE6DEE727F7A08100C66700 /* damusApp.swift in Sources */,
4C363A962827096D006E126D /* PostBlock.swift in Sources */, 4C363A962827096D006E126D /* PostBlock.swift in Sources */,
4CA9275F2A2902B20098A105 /* LongformPreview.swift in Sources */,
4C5F9116283D855D0052CD1C /* EventsModel.swift in Sources */, 4C5F9116283D855D0052CD1C /* EventsModel.swift in Sources */,
4CEE2AED2805B22500AB5EEF /* NostrRequest.swift in Sources */, 4CEE2AED2805B22500AB5EEF /* NostrRequest.swift in Sources */,
4C06670E28FDEAA000038D2A /* utf8.c in Sources */, 4C06670E28FDEAA000038D2A /* utf8.c in Sources */,
+2 -2
View File
@@ -10,7 +10,7 @@ import NaturalLanguage
struct Translated: Equatable { struct Translated: Equatable {
let artifacts: NoteArtifacts let artifacts: NoteArtifactsSeparated
let language: String let language: String
} }
@@ -42,7 +42,7 @@ struct TranslateView: View {
.translate_button_style() .translate_button_style()
} }
func TranslatedView(lang: String?, artifacts: NoteArtifacts) -> some View { func TranslatedView(lang: String?, artifacts: NoteArtifactsSeparated) -> 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") let translatedFromLanguageString = String(format: NSLocalizedString("Translated from %@", comment: "Button to indicate that the note has been translated from a different language."), lang ?? "ja")
Text(translatedFromLanguageString) Text(translatedFromLanguageString)
+30 -10
View File
@@ -106,6 +106,7 @@ class HomeModel {
switch kind { switch kind {
case .chat: fallthrough case .chat: fallthrough
case .longform: fallthrough
case .text: case .text:
handle_text_event(sub_id: sub_id, ev) handle_text_event(sub_id: sub_id, ev)
case .contacts: case .contacts:
@@ -406,8 +407,7 @@ class HomeModel {
// TODO: separate likes? // TODO: separate likes?
var home_filter_kinds: [NostrKind] = [ var home_filter_kinds: [NostrKind] = [
.text, .text, .longform, .boost
.boost
] ]
if !damus_state.settings.onlyzaps_mode { if !damus_state.settings.onlyzaps_mode {
home_filter_kinds.append(.like) home_filter_kinds.append(.like)
@@ -1147,6 +1147,27 @@ func create_in_app_event_zap_notification(profiles: Profiles, zap: Zap, locale:
} }
} }
func render_notification_content_preview(cache: EventCache, ev: NostrEvent, profiles: Profiles, privkey: String?) -> String {
let prefix_len = 50
let artifacts = cache.get_cache_data(ev.id).artifacts.artifacts ?? render_note_content(ev: ev, profiles: profiles, privkey: privkey)
// special case for longform events
if ev.known_kind == .longform {
let longform = LongformEvent(event: ev)
return longform.title ?? longform.summary ?? "Longform Event"
}
switch artifacts {
case .parts:
// we should never hit this until we have more note types built out of parts
// since we handle this case above in known_kind == .longform
return String(ev.content.prefix(prefix_len))
case .separated(let artifacts):
return String(NSAttributedString(artifacts.content.attributed).string.prefix(prefix_len))
}
}
func process_local_notification(damus_state: DamusState, event ev: NostrEvent) { func process_local_notification(damus_state: DamusState, event ev: NostrEvent) {
guard let type = ev.known_kind else { guard let type = ev.known_kind else {
@@ -1170,23 +1191,22 @@ func process_local_notification(damus_state: DamusState, event ev: NostrEvent) {
} }
if type == .text && damus_state.settings.mention_notification { if type == .text && damus_state.settings.mention_notification {
let blocks = ev.blocks(damus_state.keypair.privkey) let blocks = ev.blocks(damus_state.keypair.privkey).blocks
for case .mention(let mention) in blocks where mention.ref.ref_id == damus_state.keypair.pubkey { for case .mention(let mention) in blocks where mention.ref.ref_id == damus_state.keypair.pubkey {
let content = NSAttributedString(render_note_content(ev: ev, profiles: damus_state.profiles, privkey: damus_state.keypair.privkey).content.attributed).string let content_preview = render_notification_content_preview(cache: damus_state.events, ev: ev, profiles: damus_state.profiles, privkey: damus_state.keypair.privkey)
let notify = LocalNotification(type: .mention, event: ev, target: ev, content: content_preview)
let notify = LocalNotification(type: .mention, event: ev, target: ev, content: content)
create_local_notification(profiles: damus_state.profiles, notify: notify ) create_local_notification(profiles: damus_state.profiles, notify: notify )
} }
} else if type == .boost && damus_state.settings.repost_notification, let inner_ev = ev.get_inner_event(cache: damus_state.events) { } else if type == .boost && damus_state.settings.repost_notification, let inner_ev = ev.get_inner_event(cache: damus_state.events) {
let content = NSAttributedString(render_note_content(ev: inner_ev, profiles: damus_state.profiles, privkey: damus_state.keypair.privkey).content.attributed).string let content_preview = render_notification_content_preview(cache: damus_state.events, ev: inner_ev, profiles: damus_state.profiles, privkey: damus_state.keypair.privkey)
let notify = LocalNotification(type: .repost, event: ev, target: inner_ev, content: content) let notify = LocalNotification(type: .repost, event: ev, target: inner_ev, content: content_preview)
create_local_notification(profiles: damus_state.profiles, notify: notify) create_local_notification(profiles: damus_state.profiles, notify: notify)
} else if type == .like && damus_state.settings.like_notification, } else if type == .like && damus_state.settings.like_notification,
let evid = ev.referenced_ids.last?.ref_id, let evid = ev.referenced_ids.last?.ref_id,
let liked_event = damus_state.events.lookup(evid) let liked_event = damus_state.events.lookup(evid)
{ {
let content = NSAttributedString(render_note_content(ev: liked_event, profiles: damus_state.profiles, privkey: damus_state.keypair.privkey).content.attributed).string let content_preview = render_notification_content_preview(cache: damus_state.events, ev: liked_event, profiles: damus_state.profiles, privkey: damus_state.keypair.privkey)
let notify = LocalNotification(type: .like, event: ev, target: liked_event, content: content) let notify = LocalNotification(type: .like, event: ev, target: liked_event, content: content_preview)
create_local_notification(profiles: damus_state.profiles, notify: notify) create_local_notification(profiles: damus_state.profiles, notify: notify)
} }
+8 -2
View File
@@ -150,7 +150,12 @@ func render_blocks(blocks: [Block]) -> String {
} }
} }
func parse_mentions(content: String, tags: [[String]]) -> [Block] { struct Blocks {
let words: Int
let blocks: [Block]
}
func parse_mentions(content: String, tags: [[String]]) -> Blocks {
var out: [Block] = [] var out: [Block] = []
var bs = note_blocks() var bs = note_blocks()
@@ -174,9 +179,10 @@ func parse_mentions(content: String, tags: [[String]]) -> [Block] {
i += 1 i += 1
} }
let words = Int(bs.words)
blocks_free(&bs) blocks_free(&bs)
return out return Blocks(words: words, blocks: out)
} }
func strblock_to_string(_ s: str_block_t) -> String? { func strblock_to_string(_ s: str_block_t) -> String? {
+1 -2
View File
@@ -69,8 +69,7 @@ class ProfileModel: ObservableObject, Equatable {
} }
func subscribe() { func subscribe() {
var text_filter = NostrFilter(kinds: [.text, .chat]) var text_filter = NostrFilter(kinds: [.text, .longform])
var profile_filter = NostrFilter(kinds: [.contacts, .metadata, .boost]) var profile_filter = NostrFilter(kinds: [.contacts, .metadata, .boost])
profile_filter.authors = [pubkey] profile_filter.authors = [pubkey]
+7 -7
View File
@@ -68,7 +68,7 @@ class NostrEvent: Codable, Identifiable, CustomStringConvertible, Equatable, Has
let content: String let content: String
var is_textlike: Bool { var is_textlike: Bool {
return kind == 1 || kind == 42 return kind == 1 || kind == 42 || kind == 30023
} }
var too_big: Bool { var too_big: Bool {
@@ -83,8 +83,8 @@ class NostrEvent: Codable, Identifiable, CustomStringConvertible, Equatable, Has
return calculate_event_id(ev: self) == self.id return calculate_event_id(ev: self) == self.id
} }
private var _blocks: [Block]? = nil private var _blocks: Blocks? = nil
func blocks(_ privkey: String?) -> [Block] { func blocks(_ privkey: String?) -> Blocks {
if let bs = _blocks { if let bs = _blocks {
return bs return bs
} }
@@ -93,7 +93,7 @@ class NostrEvent: Codable, Identifiable, CustomStringConvertible, Equatable, Has
return blocks return blocks
} }
func get_blocks(content: String) -> [Block] { func get_blocks(content: String) -> Blocks {
return parse_mentions(content: content, tags: self.tags) return parse_mentions(content: content, tags: self.tags)
} }
@@ -118,7 +118,7 @@ class NostrEvent: Codable, Identifiable, CustomStringConvertible, Equatable, Has
if let rs = _event_refs { if let rs = _event_refs {
return rs return rs
} }
let refs = interpret_event_refs(blocks: self.blocks(privkey), tags: self.tags) let refs = interpret_event_refs(blocks: self.blocks(privkey).blocks, tags: self.tags)
self._event_refs = refs self._event_refs = refs
return refs return refs
} }
@@ -232,7 +232,7 @@ class NostrEvent: Codable, Identifiable, CustomStringConvertible, Equatable, Has
func note_language(_ privkey: String?) -> String? { func note_language(_ privkey: String?) -> String? {
// Rely on Apple's NLLanguageRecognizer to tell us which language it thinks the note is in // Rely on Apple's NLLanguageRecognizer to tell us which language it thinks the note is in
// and filter on only the text portions of the content as URLs and hashtags confuse the language recognizer. // and filter on only the text portions of the content as URLs and hashtags confuse the language recognizer.
let originalBlocks = blocks(privkey) let originalBlocks = blocks(privkey).blocks
let originalOnlyText = originalBlocks.compactMap { $0.is_text }.joined(separator: " ") let originalOnlyText = originalBlocks.compactMap { $0.is_text }.joined(separator: " ")
// Only accept language recognition hypothesis if there's at least a 50% probability that it's accurate. // Only accept language recognition hypothesis if there's at least a 50% probability that it's accurate.
@@ -942,7 +942,7 @@ func last_etag(tags: [[String]]) -> String? {
} }
func first_eref_mention(ev: NostrEvent, privkey: String?) -> Mention? { func first_eref_mention(ev: NostrEvent, privkey: String?) -> Mention? {
let blocks = ev.blocks(privkey).filter { block in let blocks = ev.blocks(privkey).blocks.filter { block in
guard case .mention(let mention) = block else { guard case .mention(let mention) = block else {
return false return false
} }
+1
View File
@@ -20,6 +20,7 @@ enum NostrKind: Int, Codable {
case channel_meta = 41 case channel_meta = 41
case chat = 42 case chat = 42
case list = 30000 case list = 30000
case longform = 30023
case zap = 9735 case zap = 9735
case zap_request = 9734 case zap_request = 9734
case nwc_request = 23194 case nwc_request = 23194
+12 -8
View File
@@ -344,7 +344,7 @@ struct PreloadPlan {
let load_preview: Bool let load_preview: Bool
} }
func load_preview(artifacts: NoteArtifacts) async -> Preview? { func load_preview(artifacts: NoteArtifactsSeparated) async -> Preview? {
guard let link = artifacts.links.first else { guard let link = artifacts.links.first else {
return nil return nil
} }
@@ -442,14 +442,18 @@ func preload_event(plan: PreloadPlan, state: DamusState) async {
} }
} }
if plan.load_preview { if plan.load_preview, note_artifact_is_separated(kind: plan.event.known_kind) {
let arts = artifacts ?? render_note_content(ev: plan.event, profiles: profiles, privkey: our_keypair.privkey) let arts = artifacts ?? render_note_content(ev: plan.event, profiles: profiles, privkey: our_keypair.privkey)
let preview = await load_preview(artifacts: arts)
DispatchQueue.main.async { // only separated artifacts have previews
if let preview { if case .separated(let sep) = arts {
plan.data.preview_model.state = .loaded(preview) let preview = await load_preview(artifacts: sep)
} else { DispatchQueue.main.async {
plan.data.preview_model.state = .loaded(.failed) if let preview {
plan.data.preview_model.state = .loaded(preview)
} else {
plan.data.preview_model.state = .loaded(.failed)
}
} }
} }
} }
+38
View File
@@ -6,6 +6,32 @@
// //
import Foundation import Foundation
import SwiftUI
func count_leading_hashes(_ str: String) -> Int {
var count = 0
for c in str {
if c == "#" {
count += 1
} else {
break
}
}
return count
}
func get_heading_title_size(count: Int) -> SwiftUI.Font {
if count >= 3 {
return Font.title3
} else if count >= 2 {
return Font.title2
} else if count >= 1 {
return Font.title
}
return Font.body
}
public struct Markdown { public struct Markdown {
private var detector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue) private var detector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
@@ -20,6 +46,18 @@ public struct Markdown {
let md_opts: AttributedString.MarkdownParsingOptions = let md_opts: AttributedString.MarkdownParsingOptions =
.init(interpretedSyntax: .inlineOnlyPreservingWhitespace) .init(interpretedSyntax: .inlineOnlyPreservingWhitespace)
guard content.utf8.count > 0 else {
return AttributedString(stringLiteral: "")
}
let leading_hashes = count_leading_hashes(content)
if leading_hashes > 0 {
if var str = try? AttributedString(markdown: content) {
str.font = get_heading_title_size(count: leading_hashes)
return str
}
}
// TODO: escape unintentional markdown // TODO: escape unintentional markdown
let escaped = content.replacingOccurrences(of: "\\_", with: "\\\\\\_") let escaped = content.replacingOccurrences(of: "\\_", with: "\\\\\\_")
if let txt = try? AttributedString(markdown: escaped, options: md_opts) { if let txt = try? AttributedString(markdown: escaped, options: md_opts) {
+7
View File
@@ -12,6 +12,7 @@ enum EventViewKind {
case small case small
case normal case normal
case selected case selected
case title
case subheadline case subheadline
} }
@@ -42,6 +43,8 @@ struct EventView: View {
} else { } else {
EmptyView() EmptyView()
} }
} else if event.known_kind == .longform {
LongformPreview(state: damus, ev: event)
} else { } else {
TextEvent(damus: damus, event: event, pubkey: pubkey, options: options) TextEvent(damus: damus, event: event, pubkey: pubkey, options: options)
//.padding([.top], 6) //.padding([.top], 6)
@@ -107,6 +110,8 @@ func eventviewsize_to_font(_ size: EventViewKind) -> Font {
return .body return .body
case .selected: case .selected:
return .custom("selected", size: 21.0) return .custom("selected", size: 21.0)
case .title:
return .title
case .subheadline: case .subheadline:
return .subheadline return .subheadline
} }
@@ -122,6 +127,8 @@ func eventviewsize_to_uifont(_ size: EventViewKind) -> UIFont {
return .preferredFont(forTextStyle: .title2) return .preferredFont(forTextStyle: .title2)
case .subheadline: case .subheadline:
return .preferredFont(forTextStyle: .subheadline) return .preferredFont(forTextStyle: .subheadline)
case .title:
return .preferredFont(forTextStyle: .title1)
} }
} }
@@ -0,0 +1,20 @@
//
// ContextButton.swift
// damus
//
// Created by William Casarin on 2023-06-01.
//
import SwiftUI
struct ContextButton: View {
var body: some View {
Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
}
}
struct ContextButton_Previews: PreviewProvider {
static var previews: some View {
ContextButton()
}
}
@@ -0,0 +1,44 @@
//
// EventTop.swift
// damus
//
// Created by William Casarin on 2023-06-01.
//
import SwiftUI
struct EventTop: View {
let state: DamusState
let event: NostrEvent
let is_anon: Bool
init(state: DamusState, event: NostrEvent, is_anon: Bool) {
self.state = state
self.event = event
self.is_anon = is_anon
}
func ProfileName(is_anon: Bool) -> some View {
let profile = state.profiles.lookup(id: event.pubkey)
let pk = is_anon ? "anon" : event.pubkey
return EventProfileName(pubkey: pk, profile: profile, damus: state, size: .normal)
}
var body: some View {
HStack(alignment: .center, spacing: 0) {
ProfileName(is_anon: is_anon)
TimeDot()
RelativeTime(time: state.events.get_cache_data(event.id).relative_time)
Spacer()
EventMenuContext(damus: state, event: event)
}
.lineLimit(1)
}
}
struct EventTop_Previews: PreviewProvider {
static var previews: some View {
EventTop(state: test_damus_state(), event: test_event, is_anon: false)
}
}
@@ -0,0 +1,25 @@
//
// RelativeTime.swift
// damus
//
// Created by William Casarin on 2023-06-01.
//
import SwiftUI
struct RelativeTime: View {
@ObservedObject var time: RelativeTimeModel
var body: some View {
Text(verbatim: "\(time.value)")
.font(.system(size: 16))
.foregroundColor(.gray)
}
}
struct RelativeTime_Previews: PreviewProvider {
static var previews: some View {
RelativeTime(time: RelativeTimeModel())
}
}
@@ -0,0 +1,30 @@
//
// ReplyPart.swift
// damus
//
// Created by William Casarin on 2023-06-01.
//
import SwiftUI
struct ReplyPart: View {
let event: NostrEvent
let privkey: String?
let profiles: Profiles
var body: some View {
Group {
if event_is_reply(event, privkey: privkey) {
ReplyDescription(event: event, profiles: profiles)
} else {
EmptyView()
}
}
}
}
struct ReplyPart_Previews: PreviewProvider {
static var previews: some View {
ReplyPart(event: test_event, privkey: nil, profiles: test_damus_state().profiles)
}
}
@@ -0,0 +1,22 @@
//
// TimeDot.swift
// damus
//
// Created by William Casarin on 2023-06-01.
//
import SwiftUI
struct TimeDot: View {
var body: some View {
Text(verbatim: "")
.font(.footnote)
.foregroundColor(.gray)
}
}
struct TimeDot_Previews: PreviewProvider {
static var previews: some View {
TimeDot()
}
}
+8
View File
@@ -23,6 +23,14 @@ struct EventBody: View {
} }
var body: some View { var body: some View {
if event.known_kind == .longform {
let longform = LongformEvent.parse(from: event)
Text(longform.title ?? "Untitled")
.font(.title)
.padding(.horizontal)
}
NoteContentView(damus_state: damus_state, event: event, show_images: should_show_img, size: size, options: options) NoteContentView(damus_state: damus_state, event: event, show_images: should_show_img, size: size, options: options)
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
} }
+10
View File
@@ -15,6 +15,15 @@ struct EventMenuContext: View {
let muted_threads: MutedThreadsManager let muted_threads: MutedThreadsManager
@ObservedObject var settings: UserSettingsStore @ObservedObject var settings: UserSettingsStore
init(damus: DamusState, event: NostrEvent) {
self.event = event
self.keypair = damus.keypair
self.target_pubkey = event.pubkey
self.bookmarks = damus.bookmarks
self.muted_threads = damus.muted_threads
self._settings = ObservedObject(wrappedValue: damus.settings)
}
var body: some View { var body: some View {
HStack { HStack {
Menu { Menu {
@@ -26,6 +35,7 @@ struct EventMenuContext: View {
.foregroundColor(Color.gray) .foregroundColor(Color.gray)
} }
} }
.padding([.bottom], 4)
.contentShape(Rectangle()) .contentShape(Rectangle())
.onTapGesture {} .onTapGesture {}
} }
+2
View File
@@ -15,6 +15,8 @@ func eventview_pfp_size(_ size: EventViewKind) -> CGFloat {
return PFP_SIZE return PFP_SIZE
case .selected: case .selected:
return PFP_SIZE return PFP_SIZE
case .title:
return PFP_SIZE
case .subheadline: case .subheadline:
return PFP_SIZE * 0.5 return PFP_SIZE * 0.5
} }
+72
View File
@@ -0,0 +1,72 @@
//
// EventShell.swift
// damus
//
// Created by William Casarin on 2023-06-01.
//
import SwiftUI
struct EventShell<Content: View>: View {
let state: DamusState
let event: NostrEvent
let options: EventViewOptions
let content: Content
init(state: DamusState, event: NostrEvent, options: EventViewOptions, @ViewBuilder content: () -> Content) {
self.state = state
self.event = event
self.options = options
self.content = content()
}
var has_action_bar: Bool {
!options.contains(.no_action_bar)
}
func get_mention() -> Mention? {
if self.options.contains(.nested) {
return nil
}
return first_eref_mention(ev: event, privkey: state.keypair.privkey)
}
var body: some View {
VStack(alignment: .leading) {
let is_anon = event_is_anonymous(ev: event)
HStack(spacing: 10) {
MaybeAnonPfpView(state: state, is_anon: is_anon, pubkey: event.pubkey, size: options.contains(.small_pfp) ? eventview_pfp_size(.small) : PFP_SIZE )
VStack {
EventTop(state: state, event: event, is_anon: is_anon)
ReplyPart(event: event, privkey: state.keypair.privkey, profiles: state.profiles)
}
}
.padding(.horizontal)
content
if !options.contains(.no_mentions), let mention = get_mention() {
BuilderEventView(damus: state, event_id: mention.ref.id)
.padding(.horizontal)
}
if has_action_bar {
//EmptyRect
EventActionBar(damus_state: state, event: event)
.padding(.horizontal)
}
}
}
}
struct EventShell_Previews: PreviewProvider {
static var previews: some View {
EventShell(state: test_damus_state(), event: test_event, options: [.no_action_bar]) {
Text("Hello")
}
}
}
@@ -0,0 +1,49 @@
//
// LongformPreview.swift
// damus
//
// Created by William Casarin on 2023-06-01.
//
import SwiftUI
struct LongformPreview: View {
let state: DamusState
let event: LongformEvent
@ObservedObject var artifacts: NoteArtifactsModel
init(state: DamusState, ev: NostrEvent) {
self.state = state
self.event = LongformEvent.parse(from: ev)
self._artifacts = ObservedObject(wrappedValue: state.events.get_cache_data(ev.id).artifacts_model)
}
func Words(_ words: Int) -> Text {
Text(verbatim: words.description) + Text(verbatim: " ") + Text("Words")
}
var body: some View {
EventShell(state: state, event: event.event, options: [.no_mentions]) {
VStack(alignment: .leading, spacing: 10) {
Text(event.title ?? "Untitled")
.font(.title)
Text(event.summary ?? "")
.foregroundColor(.gray)
if case .loaded(let arts) = artifacts.state,
case .parts(let parts) = arts
{
Words(parts.words).font(.footnote)
}
}
.padding()
}
}
}
struct LongformPreview_Previews: PreviewProvider {
static var previews: some View {
LongformPreview(state: test_damus_state(), ev: test_longform_event.event)
}
}
@@ -0,0 +1,87 @@
//
// LongformEvent.swift
// damus
//
// Created by William Casarin on 2023-06-01.
//
import SwiftUI
struct LongformEvent {
let event: NostrEvent
var title: String? = nil
var image: URL? = nil
var summary: String? = nil
var published_at: Date? = nil
static func parse(from ev: NostrEvent) -> LongformEvent {
var longform = LongformEvent(event: ev)
for tag in ev.tags {
guard tag.count >= 2 else { continue }
switch tag[0] {
case "title": longform.title = tag[1]
case "image": longform.image = URL(string: tag[1])
case "summary": longform.summary = tag[1]
case "published_at":
longform.published_at = Double(tag[1]).map { d in Date(timeIntervalSince1970: d) }
default:
break
}
}
return longform
}
}
struct LongformView: View {
let state: DamusState
let event: LongformEvent
@ObservedObject var artifacts: NoteArtifactsModel
init(state: DamusState, event: LongformEvent, artifacts: NoteArtifactsModel? = nil) {
self.state = state
self.event = event
self._artifacts = ObservedObject(wrappedValue: artifacts ?? state.events.get_cache_data(event.event.id).artifacts_model)
}
var options: EventViewOptions {
return [.wide, .no_mentions, .no_replying_to]
}
var body: some View {
EventShell(state: state, event: event.event, options: options) {
VStack {
SelectableText(attributedString: AttributedString(stringLiteral: event.title ?? "Untitled"), size: .title)
NoteContentView(damus_state: state, event: event.event, show_images: true, size: .selected, options: options)
}
}
}
}
let test_longform_event = LongformEvent.parse(from:
.init(content: "## Let me tell you why coffee is awesome\n**IT JUST IS**",
pubkey: "pk",
kind: NostrKind.longform.rawValue,
tags: [
["title", "Coffee is awesome"],
["summary", "Did you know coffee is awesome?"],
["published_at", "1685638715"],
["t", "coffee"],
["t", "coffeechain"],
["image", "https://cdn.jb55.com/s/038fe8f558153b52.jpg"],
])
)
struct LongformView_Previews: PreviewProvider {
static var previews: some View {
let st = test_damus_state()
let artifacts = render_note_content(ev: test_longform_event.event, profiles: st.profiles, privkey: nil)
let model = NoteArtifactsModel(state: .loaded(artifacts))
LongformView(state: st, event: test_longform_event, artifacts: model)
}
}
+1 -2
View File
@@ -35,9 +35,8 @@ struct SelectedEventView: View {
Spacer() Spacer()
EventMenuContext(event: event, keypair: damus.keypair, target_pubkey: event.pubkey, bookmarks: damus.bookmarks, muted_threads: damus.muted_threads, settings: damus.settings) EventMenuContext(damus: damus, event: event)
.padding([.bottom], 4) .padding([.bottom], 4)
} }
.padding(.horizontal) .padding(.horizontal)
.minimumScaleFactor(0.75) .minimumScaleFactor(0.75)
+5 -57
View File
@@ -19,20 +19,11 @@ struct EventViewOptions: OptionSet {
static let small_pfp = EventViewOptions(rawValue: 1 << 7) static let small_pfp = EventViewOptions(rawValue: 1 << 7)
static let nested = EventViewOptions(rawValue: 1 << 8) static let nested = EventViewOptions(rawValue: 1 << 8)
static let top_zap = EventViewOptions(rawValue: 1 << 9) static let top_zap = EventViewOptions(rawValue: 1 << 9)
static let no_mentions = EventViewOptions(rawValue: 1 << 10)
static let embedded: EventViewOptions = [.no_action_bar, .small_pfp, .wide, .truncate_content, .nested] static let embedded: EventViewOptions = [.no_action_bar, .small_pfp, .wide, .truncate_content, .nested]
} }
struct RelativeTime: View {
@ObservedObject var time: RelativeTimeModel
var body: some View {
Text(verbatim: "\(time.value)")
.font(.system(size: 16))
.foregroundColor(.gray)
}
}
struct TextEvent: View { struct TextEvent: View {
let damus: DamusState let damus: DamusState
let event: NostrEvent let event: NostrEvent
@@ -73,63 +64,20 @@ struct TextEvent: View {
func TopPart(is_anon: Bool) -> some View { func TopPart(is_anon: Bool) -> some View {
HStack(alignment: .center, spacing: 0) { HStack(alignment: .center, spacing: 0) {
ProfileName(is_anon: is_anon) ProfileName(is_anon: is_anon)
TimeDot TimeDot()
RelativeTime(time: self.evdata.relative_time) RelativeTime(time: self.evdata.relative_time)
Spacer() Spacer()
ContextButton EventMenuContext(damus: damus, event: event)
} }
.lineLimit(1) .lineLimit(1)
} }
var ReplyPart: some View {
Group {
if event_is_reply(event, privkey: damus.keypair.privkey) {
ReplyDescription(event: event, profiles: damus.profiles)
} else {
EmptyView()
}
}
}
var WideStyle: some View { var WideStyle: some View {
VStack(alignment: .leading) { EventShell(state: damus, event: event, options: options) {
let is_anon = event_is_anonymous(ev: event)
HStack(spacing: 10) {
Pfp(is_anon: is_anon)
VStack {
TopPart(is_anon: is_anon)
ReplyPart
}
}
.padding(.horizontal)
EvBody(options: self.options.union(.pad_content)) EvBody(options: self.options.union(.pad_content))
if let mention = get_mention() {
Mention(mention)
.padding(.horizontal)
}
if has_action_bar {
//EmptyRect
ActionBar
.padding(.horizontal)
}
} }
} }
var TimeDot: some View {
Text(verbatim: "")
.font(.footnote)
.foregroundColor(.gray)
}
var ContextButton: some View {
EventMenuContext(event: event, keypair: damus.keypair, target_pubkey: event.pubkey, bookmarks: damus.bookmarks, muted_threads: damus.muted_threads, settings: damus.settings)
.padding([.bottom], 4)
}
func ProfileName(is_anon: Bool) -> some View { func ProfileName(is_anon: Bool) -> some View {
let profile = damus.profiles.lookup(id: pubkey) let profile = damus.profiles.lookup(id: pubkey)
let pk = is_anon ? ANON_PUBKEY : pubkey let pk = is_anon ? ANON_PUBKEY : pubkey
@@ -183,7 +131,7 @@ struct TextEvent: View {
TopPart(is_anon: is_anon) TopPart(is_anon: is_anon)
if !options.contains(.no_replying_to) { if !options.contains(.no_replying_to) {
ReplyPart ReplyPart(event: event, privkey: damus.keypair.privkey, profiles: damus.profiles)
} }
EvBody(options: self.options) EvBody(options: self.options)
+50
View File
@@ -0,0 +1,50 @@
//
// MediaView.swift
// damus
//
// Created by William Casarin on 2023-06-05.
//
import SwiftUI
/*
struct MediaView: View {
let geo: GeometryProxy
let url: MediaUrl
let index: Int
var body: some View {
Group {
switch url {
case .image(let url):
Img(geo: geo, url: url, index: index)
.onTapGesture {
open_sheet = true
}
case .video(let url):
DamusVideoPlayer(url: url, model: video_model(url), video_size: $video_size)
.onChange(of: video_size) { size in
guard let size else { return }
let fill = ImageFill.calculate_image_fill(geo_size: geo.size, img_size: size, maxHeight: maxHeight, fillHeight: fillHeight)
print("video_size changed \(size)")
if self.image_fill == nil {
print("video_size firstImageHeight \(fill.height)")
firstImageHeight = fill.height
state.events.get_cache_data(evid).media_metadata_model.fill = fill
}
self.image_fill = fill
}
}
}
}
}
struct MediaView_Previews: PreviewProvider {
static var previews: some View {
MediaView()
}
}
*/
+206 -40
View File
@@ -33,8 +33,8 @@ struct NoteContentView: View {
@ObservedObject var artifacts_model: NoteArtifactsModel @ObservedObject var artifacts_model: NoteArtifactsModel
@ObservedObject var preview_model: PreviewModel @ObservedObject var preview_model: PreviewModel
var artifacts: NoteArtifacts { var note_artifacts: NoteArtifacts {
return self.artifacts_model.state.artifacts ?? .just_content(event.get_content(damus_state.keypair.privkey)) return self.artifacts_model.state.artifacts ?? .separated(.just_content(event.get_content(damus_state.keypair.privkey)))
} }
init(damus_state: DamusState, event: NostrEvent, show_images: Bool, size: EventViewKind, options: EventViewOptions) { init(damus_state: DamusState, event: NostrEvent, show_images: Bool, size: EventViewKind, options: EventViewOptions) {
@@ -67,27 +67,27 @@ struct NoteContentView: View {
return LinkViewRepresentable(meta: .linkmeta(cached)) return LinkViewRepresentable(meta: .linkmeta(cached))
} }
var truncatedText: some View { func truncatedText(content: CompatibleText) -> some View {
Group { Group {
if truncate { if truncate {
TruncatedText(text: artifacts.content) TruncatedText(text: content)
.font(eventviewsize_to_font(size)) .font(eventviewsize_to_font(size))
} else { } else {
artifacts.content.text content.text
.font(eventviewsize_to_font(size)) .font(eventviewsize_to_font(size))
} }
} }
} }
var invoicesView: some View { func invoicesView(invoices: [Invoice]) -> some View {
InvoicesView(our_pubkey: damus_state.keypair.pubkey, invoices: artifacts.invoices, settings: damus_state.settings) InvoicesView(our_pubkey: damus_state.keypair.pubkey, invoices: invoices, settings: damus_state.settings)
} }
var translateView: some View { var translateView: some View {
TranslateView(damus_state: damus_state, event: event, size: self.size) TranslateView(damus_state: damus_state, event: event, size: self.size)
} }
var previewView: some View { func previewView(links: [URL]) -> some View {
Group { Group {
if let preview = self.preview, show_images { if let preview = self.preview, show_images {
if let preview_height { if let preview_height {
@@ -96,14 +96,14 @@ struct NoteContentView: View {
} else { } else {
preview preview
} }
} else if let link = artifacts.links.first { } else if let link = links.first {
LinkViewRepresentable(meta: .url(link)) LinkViewRepresentable(meta: .url(link))
.frame(height: 50) .frame(height: 50)
} }
} }
} }
var MainContent: some View { func MainContent(artifacts: NoteArtifactsSeparated) -> some View {
VStack(alignment: .leading) { VStack(alignment: .leading) {
if size == .selected { if size == .selected {
if with_padding { if with_padding {
@@ -114,10 +114,10 @@ struct NoteContentView: View {
} }
} else { } else {
if with_padding { if with_padding {
truncatedText truncatedText(content: artifacts.content)
.padding(.horizontal) .padding(.horizontal)
} else { } else {
truncatedText truncatedText(content: artifacts.content)
} }
} }
@@ -143,17 +143,17 @@ struct NoteContentView: View {
if artifacts.invoices.count > 0 { if artifacts.invoices.count > 0 {
if with_padding { if with_padding {
invoicesView invoicesView(invoices: artifacts.invoices)
.padding(.horizontal) .padding(.horizontal)
} else { } else {
invoicesView invoicesView(invoices: artifacts.invoices)
} }
} }
if with_padding { if with_padding {
previewView.padding(.horizontal) previewView(links: artifacts.links).padding(.horizontal)
} else { } else {
previewView previewView(links: artifacts.links)
} }
} }
@@ -183,12 +183,42 @@ struct NoteContentView: View {
} }
} }
func artifactPartsView(_ parts: [ArtifactPart]) -> some View {
LazyVStack {
ForEach(parts.indices, id: \.self) { ind in
let part = parts[ind]
switch part {
case .text(let txt):
txt
.padding(.horizontal)
case .invoice(let inv):
InvoiceView(our_pubkey: damus_state.pubkey, invoice: inv, settings: damus_state.settings)
.padding(.horizontal)
case .media(let media):
Text("media \(media.url.absoluteString)")
}
}
}
}
var ArtifactContent: some View {
Group {
switch self.note_artifacts {
case .parts(let parts):
artifactPartsView(parts.parts)
case .separated(let separated):
MainContent(artifacts: separated)
}
}
}
var body: some View { var body: some View {
MainContent ArtifactContent
.onReceive(handle_notify(.profile_updated)) { notif in .onReceive(handle_notify(.profile_updated)) { notif in
let profile = notif.object as! ProfileUpdate let profile = notif.object as! ProfileUpdate
let blocks = event.blocks(damus_state.keypair.privkey) let blocks = event.blocks(damus_state.keypair.privkey)
for block in blocks { for block in blocks.blocks {
switch block { switch block {
case .mention(let m): case .mention(let m):
if m.type == .pubkey && m.ref.ref_id == profile.pubkey { if m.type == .pubkey && m.ref.ref_id == profile.pubkey {
@@ -255,12 +285,58 @@ struct NoteContentView_Previews: PreviewProvider {
} }
} }
struct NoteArtifacts: Equatable {
static func == (lhs: NoteArtifacts, rhs: NoteArtifacts) -> Bool { enum NoteArtifacts {
case separated(NoteArtifactsSeparated)
case parts(NoteArtifactsParts)
var images: [URL] {
switch self {
case .separated(let arts):
return arts.images
case .parts(let parts):
return parts.parts.reduce(into: [URL]()) { acc, part in
guard case .media(let m) = part,
case .image(let url) = m
else { return }
acc.append(url)
}
}
}
}
enum ArtifactPart {
case text(Text)
case media(MediaUrl)
case invoice(Invoice)
var is_text: Bool {
switch self {
case .text: return true
case .media: return false
case .invoice: return false
}
}
}
class NoteArtifactsParts {
var parts: [ArtifactPart]
var words: Int
init(parts: [ArtifactPart], words: Int) {
self.parts = parts
self.words = words
}
}
struct NoteArtifactsSeparated: Equatable {
static func == (lhs: NoteArtifactsSeparated, rhs: NoteArtifactsSeparated) -> Bool {
return lhs.content == rhs.content return lhs.content == rhs.content
} }
let content: CompatibleText let content: CompatibleText
let words: Int
let urls: [UrlType] let urls: [UrlType]
let invoices: [Invoice] let invoices: [Invoice]
@@ -276,9 +352,9 @@ struct NoteArtifacts: Equatable {
return urls.compactMap { url in url.is_link } return urls.compactMap { url in url.is_link }
} }
static func just_content(_ content: String) -> NoteArtifacts { static func just_content(_ content: String) -> NoteArtifactsSeparated {
let txt = CompatibleText(attributed: AttributedString(stringLiteral: content)) let txt = CompatibleText(attributed: AttributedString(stringLiteral: content))
return NoteArtifacts(content: txt, urls: [], invoices: []) return NoteArtifactsSeparated(content: txt, words: 0, urls: [], invoices: [])
} }
} }
@@ -307,15 +383,119 @@ enum NoteArtifactState {
} }
} }
func note_artifact_is_separated(kind: NostrKind?) -> Bool {
return kind != .longform
}
func render_note_content(ev: NostrEvent, profiles: Profiles, privkey: String?) -> NoteArtifacts { func render_note_content(ev: NostrEvent, profiles: Profiles, privkey: String?) -> NoteArtifacts {
let blocks = ev.blocks(privkey) let blocks = ev.blocks(privkey)
return render_blocks(blocks: blocks, profiles: profiles) if ev.known_kind == .longform {
return .parts(render_blocks_parted(blocks: blocks, profiles: profiles))
}
return .separated(render_blocks(blocks: blocks, profiles: profiles))
} }
func render_blocks(blocks: [Block], profiles: Profiles) -> NoteArtifacts { fileprivate func artifact_part_last_text_ind(parts: [ArtifactPart]) -> (Int, Text)? {
let ind = parts.count - 1
if ind < 0 {
return nil
}
guard case .text(let txt) = parts[safe: ind] else {
return nil
}
return (ind, txt)
}
func render_blocks_parted(blocks bs: Blocks, profiles: Profiles) -> NoteArtifactsParts {
let blocks = bs.blocks
let new_parts = NoteArtifactsParts(parts: [], words: bs.words)
return blocks.reduce(into: new_parts) { parts, block in
switch block {
case .mention(let m):
guard let (last_ind, txt) = artifact_part_last_text_ind(parts: parts.parts) else {
parts.parts.append(.text(mention_str(m, profiles: profiles).text))
return
}
parts.parts[last_ind] = .text(txt + mention_str(m, profiles: profiles).text)
case .text(let str):
guard let (last_ind, txt) = artifact_part_last_text_ind(parts: parts.parts) else {
// TODO: (jb55) md is longform specific
let md = Markdown.parse(content: str)
parts.parts.append(.text(Text(md)))
return
}
parts.parts[last_ind] = .text(txt + Text(str))
case .relay(let relay):
guard let (last_ind, txt) = artifact_part_last_text_ind(parts: parts.parts) else {
parts.parts.append(.text(Text(relay)))
return
}
parts.parts[last_ind] = .text(txt + Text(relay))
case .hashtag(let htag):
guard let (last_ind, txt) = artifact_part_last_text_ind(parts: parts.parts) else {
parts.parts.append(.text(hashtag_str(htag).text))
return
}
parts.parts[last_ind] = .text(txt + hashtag_str(htag).text)
case .invoice(let invoice):
parts.parts.append(.invoice(invoice))
return
case .url(let url):
let url_type = classify_url(url)
switch url_type {
case .media(let media_url):
parts.parts.append(.media(media_url))
case .link(let url):
guard let (last_ind, txt) = artifact_part_last_text_ind(parts: parts.parts) else {
parts.parts.append(.text(url_str(url).text))
return
}
parts.parts[last_ind] = .text(txt + url_str(url).text)
}
}
}
}
func reduce_text_block(blocks: [Block], ind: Int, txt: String, one_note_ref: Bool) -> CompatibleText {
var trimmed = txt
if let prev = blocks[safe: ind-1],
case .url(let u) = prev,
classify_url(u).is_media != nil {
trimmed = " " + trim_prefix(trimmed)
}
if let next = blocks[safe: ind+1] {
if case .url(let u) = next, classify_url(u).is_media != nil {
trimmed = trim_suffix(trimmed)
} else if case .mention(let m) = next, m.type == .event, one_note_ref {
trimmed = trim_suffix(trimmed)
}
}
return CompatibleText(stringLiteral: trimmed)
}
func render_blocks(blocks bs: Blocks, profiles: Profiles) -> NoteArtifactsSeparated {
var invoices: [Invoice] = [] var invoices: [Invoice] = []
var urls: [UrlType] = [] var urls: [UrlType] = []
let blocks = bs.blocks
let one_note_ref = blocks let one_note_ref = blocks
.filter({ $0.is_note_mention }) .filter({ $0.is_note_mention })
@@ -332,22 +512,8 @@ func render_blocks(blocks: [Block], profiles: Profiles) -> NoteArtifacts {
} }
return str + mention_str(m, profiles: profiles) return str + mention_str(m, profiles: profiles)
case .text(let txt): case .text(let txt):
var trimmed = txt return str + reduce_text_block(blocks: blocks, ind: ind, txt: txt, one_note_ref: one_note_ref)
if let prev = blocks[safe: ind-1],
case .url(let u) = prev,
classify_url(u).is_media != nil {
trimmed = " " + trim_prefix(trimmed)
}
if let next = blocks[safe: ind+1] {
if case .url(let u) = next, classify_url(u).is_media != nil {
trimmed = trim_suffix(trimmed)
} else if case .mention(let m) = next, m.type == .event, one_note_ref {
trimmed = trim_suffix(trimmed)
}
}
return str + CompatibleText(stringLiteral: trimmed)
case .relay(let relay): case .relay(let relay):
return str + CompatibleText(stringLiteral: relay) return str + CompatibleText(stringLiteral: relay)
@@ -369,7 +535,7 @@ func render_blocks(blocks: [Block], profiles: Profiles) -> NoteArtifacts {
} }
} }
return NoteArtifacts(content: txt, urls: urls, invoices: invoices) return NoteArtifactsSeparated(content: txt, words: bs.words, urls: urls, invoices: invoices)
} }
enum MediaUrl { enum MediaUrl {
+1 -1
View File
@@ -24,7 +24,7 @@ struct RepostedEvent: View {
.buttonStyle(PlainButtonStyle()) .buttonStyle(PlainButtonStyle())
//SelectedEventView(damus: damus, event: inner_ev, size: .normal) //SelectedEventView(damus: damus, event: inner_ev, size: .normal)
TextEvent(damus: damus, event: inner_ev, pubkey: inner_ev.pubkey, options: options) EventView(damus: damus, event: inner_ev, pubkey: inner_ev.pubkey, options: options)
} }
} }
} }
+6 -6
View File
@@ -20,7 +20,7 @@ final class InvoiceTests: XCTestCase {
func testParseAnyAmountInvoice() throws { func testParseAnyAmountInvoice() throws {
let invstr = "LNBC1P3MR5UJSP5G7SA48YD4JWTTPCHWMY4QYN4UWZQCJQ8NMWKD6QE3HCRVYTDLH9SPP57YM9TSA9NN4M4XU59XMJCXKR7YDV29DDP6LVQUT46ZW6CU3KE9GQDQ9V9H8JXQ8P3MYLZJCQPJRZJQF60PZDVNGGQWQDNERZSQN35L8CVQ3QG2Z5NSZYD0D3Q0JW2TL6VUZA7FYQQWKGQQYQQQQLGQQQQXJQQ9Q9QXPQYSGQ39EM4QJMQFKZGJXZVGL7QJMYNSWA8PGDTAGXXRG5Z92M7VLCGKQK2L2THDF8LM0AUKAURH7FVAWDLRNMVF38W4EYJDNVN9V4Z9CRS5CQCV465C" let invstr = "LNBC1P3MR5UJSP5G7SA48YD4JWTTPCHWMY4QYN4UWZQCJQ8NMWKD6QE3HCRVYTDLH9SPP57YM9TSA9NN4M4XU59XMJCXKR7YDV29DDP6LVQUT46ZW6CU3KE9GQDQ9V9H8JXQ8P3MYLZJCQPJRZJQF60PZDVNGGQWQDNERZSQN35L8CVQ3QG2Z5NSZYD0D3Q0JW2TL6VUZA7FYQQWKGQQYQQQQLGQQQQXJQQ9Q9QXPQYSGQ39EM4QJMQFKZGJXZVGL7QJMYNSWA8PGDTAGXXRG5Z92M7VLCGKQK2L2THDF8LM0AUKAURH7FVAWDLRNMVF38W4EYJDNVN9V4Z9CRS5CQCV465C"
let parsed = parse_mentions(content: invstr, tags: []) let parsed = parse_mentions(content: invstr, tags: []).blocks
XCTAssertNotNil(parsed) XCTAssertNotNil(parsed)
XCTAssertEqual(parsed.count, 1) XCTAssertEqual(parsed.count, 1)
@@ -38,7 +38,7 @@ final class InvoiceTests: XCTestCase {
let invstr = """ let invstr = """
LNBC1P3MR5UJSP5G7SA48YD4JWTTPCHWMY4QYN4UWZQCJQ8NMWKD6QE3HCRVYTDLH9SPP57YM9TSA9NN4M4XU59XMJCXKR7YDV29DDP6LVQUT46ZW6CU3KE9GQDQ9V9H8JXQ8P3MYLZJCQPJRZJQF60PZDVNGGQWQDNERZSQN35L8CVQ3QG2Z5NSZYD0D3Q0JW2TL6VUZA7FYQQWKGQQYQQQQLGQQQQXJQQ9Q9QXPQYSGQ39EM4QJMQFKZGJXZVGL7QJMYNSWA8PGDTAGXXRG5Z92M7VLCGKQK2L2THDF8LM0AUKAURH7FVAWDLRNMVF38W4EYJDNVN9V4Z9CRS5CQCV465C hi there LNBC1P3MR5UJSP5G7SA48YD4JWTTPCHWMY4QYN4UWZQCJQ8NMWKD6QE3HCRVYTDLH9SPP57YM9TSA9NN4M4XU59XMJCXKR7YDV29DDP6LVQUT46ZW6CU3KE9GQDQ9V9H8JXQ8P3MYLZJCQPJRZJQF60PZDVNGGQWQDNERZSQN35L8CVQ3QG2Z5NSZYD0D3Q0JW2TL6VUZA7FYQQWKGQQYQQQQLGQQQQXJQQ9Q9QXPQYSGQ39EM4QJMQFKZGJXZVGL7QJMYNSWA8PGDTAGXXRG5Z92M7VLCGKQK2L2THDF8LM0AUKAURH7FVAWDLRNMVF38W4EYJDNVN9V4Z9CRS5CQCV465C hi there
""" """
let parsed = parse_mentions(content: invstr, tags: []) let parsed = parse_mentions(content: invstr, tags: []).blocks
XCTAssertNotNil(parsed) XCTAssertNotNil(parsed)
XCTAssertEqual(parsed.count, 2) XCTAssertEqual(parsed.count, 2)
@@ -54,7 +54,7 @@ LNBC1P3MR5UJSP5G7SA48YD4JWTTPCHWMY4QYN4UWZQCJQ8NMWKD6QE3HCRVYTDLH9SPP57YM9TSA9NN
func testParseInvoiceUpper() throws { func testParseInvoiceUpper() throws {
let invstr = "LNBC100N1P357SL0SP5T9N56WDZTUN39LGDQLR30XQWKSG3K69Q4Q2RKR52APLUJW0ESN0QPP5MRQGLJK62Z20Q4NVGR6LZCYN6FHYLZCCWDVU4K77APG3ZMRKUJJQDPZW35XJUEQD9EJQCFQV3JHXCMJD9C8G6T0DCXQYJW5QCQPJRZJQT56H4GVP5YX36U2UZQA6QWCSK3E2DUUNFXPPZJ9VHYPC3WFE2WSWZ607UQQ3XQQQSQQQQQQQQQQQLQQYG9QYYSGQAGX5H20AEULJ3GDWX3KXS8U9F4MCAKDKWUAKASAMM9562FFYR9EN8YG20LG0YGNR9ZPWP68524KMDA0T5XP2WYTEX35PU8HAPYJAJXQPSQL29R" let invstr = "LNBC100N1P357SL0SP5T9N56WDZTUN39LGDQLR30XQWKSG3K69Q4Q2RKR52APLUJW0ESN0QPP5MRQGLJK62Z20Q4NVGR6LZCYN6FHYLZCCWDVU4K77APG3ZMRKUJJQDPZW35XJUEQD9EJQCFQV3JHXCMJD9C8G6T0DCXQYJW5QCQPJRZJQT56H4GVP5YX36U2UZQA6QWCSK3E2DUUNFXPPZJ9VHYPC3WFE2WSWZ607UQQ3XQQQSQQQQQQQQQQQLQQYG9QYYSGQAGX5H20AEULJ3GDWX3KXS8U9F4MCAKDKWUAKASAMM9562FFYR9EN8YG20LG0YGNR9ZPWP68524KMDA0T5XP2WYTEX35PU8HAPYJAJXQPSQL29R"
let parsed = parse_mentions(content: invstr, tags: []) let parsed = parse_mentions(content: invstr, tags: []).blocks
XCTAssertNotNil(parsed) XCTAssertNotNil(parsed)
XCTAssertEqual(parsed.count, 1) XCTAssertEqual(parsed.count, 1)
@@ -70,7 +70,7 @@ LNBC1P3MR5UJSP5G7SA48YD4JWTTPCHWMY4QYN4UWZQCJQ8NMWKD6QE3HCRVYTDLH9SPP57YM9TSA9NN
func testParseInvoiceWithPrefix() throws { func testParseInvoiceWithPrefix() throws {
let invstr = "lightning:lnbc100n1p357sl0sp5t9n56wdztun39lgdqlr30xqwksg3k69q4q2rkr52aplujw0esn0qpp5mrqgljk62z20q4nvgr6lzcyn6fhylzccwdvu4k77apg3zmrkujjqdpzw35xjueqd9ejqcfqv3jhxcmjd9c8g6t0dcxqyjw5qcqpjrzjqt56h4gvp5yx36u2uzqa6qwcsk3e2duunfxppzj9vhypc3wfe2wswz607uqq3xqqqsqqqqqqqqqqqlqqyg9qyysgqagx5h20aeulj3gdwx3kxs8u9f4mcakdkwuakasamm9562ffyr9en8yg20lg0ygnr9zpwp68524kmda0t5xp2wytex35pu8hapyjajxqpsql29r" let invstr = "lightning:lnbc100n1p357sl0sp5t9n56wdztun39lgdqlr30xqwksg3k69q4q2rkr52aplujw0esn0qpp5mrqgljk62z20q4nvgr6lzcyn6fhylzccwdvu4k77apg3zmrkujjqdpzw35xjueqd9ejqcfqv3jhxcmjd9c8g6t0dcxqyjw5qcqpjrzjqt56h4gvp5yx36u2uzqa6qwcsk3e2duunfxppzj9vhypc3wfe2wswz607uqq3xqqqsqqqqqqqqqqqlqqyg9qyysgqagx5h20aeulj3gdwx3kxs8u9f4mcakdkwuakasamm9562ffyr9en8yg20lg0ygnr9zpwp68524kmda0t5xp2wytex35pu8hapyjajxqpsql29r"
let parsed = parse_mentions(content: invstr, tags: []) let parsed = parse_mentions(content: invstr, tags: []).blocks
XCTAssertNotNil(parsed) XCTAssertNotNil(parsed)
XCTAssertEqual(parsed.count, 1) XCTAssertEqual(parsed.count, 1)
@@ -79,7 +79,7 @@ LNBC1P3MR5UJSP5G7SA48YD4JWTTPCHWMY4QYN4UWZQCJQ8NMWKD6QE3HCRVYTDLH9SPP57YM9TSA9NN
func testParseInvoiceWithPrefixCapitalized() throws { func testParseInvoiceWithPrefixCapitalized() throws {
let invstr = "LIGHTNING:LNBC100N1P357SL0SP5T9N56WDZTUN39LGDQLR30XQWKSG3K69Q4Q2RKR52APLUJW0ESN0QPP5MRQGLJK62Z20Q4NVGR6LZCYN6FHYLZCCWDVU4K77APG3ZMRKUJJQDPZW35XJUEQD9EJQCFQV3JHXCMJD9C8G6T0DCXQYJW5QCQPJRZJQT56H4GVP5YX36U2UZQA6QWCSK3E2DUUNFXPPZJ9VHYPC3WFE2WSWZ607UQQ3XQQQSQQQQQQQQQQQLQQYG9QYYSGQAGX5H20AEULJ3GDWX3KXS8U9F4MCAKDKWUAKASAMM9562FFYR9EN8YG20LG0YGNR9ZPWP68524KMDA0T5XP2WYTEX35PU8HAPYJAJXQPSQL29R" let invstr = "LIGHTNING:LNBC100N1P357SL0SP5T9N56WDZTUN39LGDQLR30XQWKSG3K69Q4Q2RKR52APLUJW0ESN0QPP5MRQGLJK62Z20Q4NVGR6LZCYN6FHYLZCCWDVU4K77APG3ZMRKUJJQDPZW35XJUEQD9EJQCFQV3JHXCMJD9C8G6T0DCXQYJW5QCQPJRZJQT56H4GVP5YX36U2UZQA6QWCSK3E2DUUNFXPPZJ9VHYPC3WFE2WSWZ607UQQ3XQQQSQQQQQQQQQQQLQQYG9QYYSGQAGX5H20AEULJ3GDWX3KXS8U9F4MCAKDKWUAKASAMM9562FFYR9EN8YG20LG0YGNR9ZPWP68524KMDA0T5XP2WYTEX35PU8HAPYJAJXQPSQL29R"
let parsed = parse_mentions(content: invstr, tags: []) let parsed = parse_mentions(content: invstr, tags: []).blocks
XCTAssertNotNil(parsed) XCTAssertNotNil(parsed)
XCTAssertEqual(parsed.count, 1) XCTAssertEqual(parsed.count, 1)
@@ -88,7 +88,7 @@ LNBC1P3MR5UJSP5G7SA48YD4JWTTPCHWMY4QYN4UWZQCJQ8NMWKD6QE3HCRVYTDLH9SPP57YM9TSA9NN
func testParseInvoice() throws { func testParseInvoice() throws {
let invstr = "lnbc100n1p357sl0sp5t9n56wdztun39lgdqlr30xqwksg3k69q4q2rkr52aplujw0esn0qpp5mrqgljk62z20q4nvgr6lzcyn6fhylzccwdvu4k77apg3zmrkujjqdpzw35xjueqd9ejqcfqv3jhxcmjd9c8g6t0dcxqyjw5qcqpjrzjqt56h4gvp5yx36u2uzqa6qwcsk3e2duunfxppzj9vhypc3wfe2wswz607uqq3xqqqsqqqqqqqqqqqlqqyg9qyysgqagx5h20aeulj3gdwx3kxs8u9f4mcakdkwuakasamm9562ffyr9en8yg20lg0ygnr9zpwp68524kmda0t5xp2wytex35pu8hapyjajxqpsql29r" let invstr = "lnbc100n1p357sl0sp5t9n56wdztun39lgdqlr30xqwksg3k69q4q2rkr52aplujw0esn0qpp5mrqgljk62z20q4nvgr6lzcyn6fhylzccwdvu4k77apg3zmrkujjqdpzw35xjueqd9ejqcfqv3jhxcmjd9c8g6t0dcxqyjw5qcqpjrzjqt56h4gvp5yx36u2uzqa6qwcsk3e2duunfxppzj9vhypc3wfe2wswz607uqq3xqqqsqqqqqqqqqqqlqqyg9qyysgqagx5h20aeulj3gdwx3kxs8u9f4mcakdkwuakasamm9562ffyr9en8yg20lg0ygnr9zpwp68524kmda0t5xp2wytex35pu8hapyjajxqpsql29r"
let parsed = parse_mentions(content: invstr, tags: []) let parsed = parse_mentions(content: invstr, tags: []).blocks
XCTAssertNotNil(parsed) XCTAssertNotNil(parsed)
XCTAssertEqual(parsed.count, 1) XCTAssertEqual(parsed.count, 1)
+12
View File
@@ -34,6 +34,18 @@ class MarkdownTests: XCTestCase {
XCTAssertEqual(md, expected) XCTAssertEqual(md, expected)
} }
func test_longform_rendering() throws {
let st = test_damus_state()
let artifacts = render_note_content(ev: test_longform_event.event, profiles: st.profiles, privkey: st.keypair.privkey)
switch artifacts {
case .separated:
XCTAssert(false)
case .parts(let parts):
XCTAssertEqual(parts.parts.count, 1)
}
}
func test_convert_links() throws { func test_convert_links() throws {
let helper = Markdown() let helper = Markdown()
let md = helper.process("prologue damus.io https://nostr.build epilogue") let md = helper.process("prologue damus.io https://nostr.build epilogue")
+4 -4
View File
@@ -19,7 +19,7 @@ final class NIP19Tests: XCTestCase {
} }
func test_parse_nprofile() throws { func test_parse_nprofile() throws {
let res = parse_mentions(content: "nostr:nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpp4mhxue69uhhytnc9e3k7mgpz4mhxue69uhkg6nzv9ejuumpv34kytnrdaksjlyr9p", tags: []) let res = parse_mentions(content: "nostr:nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpp4mhxue69uhhytnc9e3k7mgpz4mhxue69uhkg6nzv9ejuumpv34kytnrdaksjlyr9p", tags: []).blocks
XCTAssertEqual(res.count, 1) XCTAssertEqual(res.count, 1)
let expected_ref = ReferencedId(ref_id: "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d", relay_id: "wss://r.x.com", key: "p") let expected_ref = ReferencedId(ref_id: "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d", relay_id: "wss://r.x.com", key: "p")
let expected_mention = Mention(index: nil, type: .pubkey, ref: expected_ref) let expected_mention = Mention(index: nil, type: .pubkey, ref: expected_ref)
@@ -27,7 +27,7 @@ final class NIP19Tests: XCTestCase {
} }
func test_parse_npub() throws { func test_parse_npub() throws {
let res = parse_mentions(content: "nostr:npub10elfcs4fr0l0r8af98jlmgdh9c8tcxjvz9qkw038js35mp4dma8qzvjptg ", tags: []) let res = parse_mentions(content: "nostr:npub10elfcs4fr0l0r8af98jlmgdh9c8tcxjvz9qkw038js35mp4dma8qzvjptg ", tags: []).blocks
XCTAssertEqual(res.count, 2) XCTAssertEqual(res.count, 2)
let expected_ref = ReferencedId(ref_id: "7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e", relay_id: nil, key: "p") let expected_ref = ReferencedId(ref_id: "7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e", relay_id: nil, key: "p")
let expected_mention = Mention(index: nil, type: .pubkey, ref: expected_ref) let expected_mention = Mention(index: nil, type: .pubkey, ref: expected_ref)
@@ -35,7 +35,7 @@ final class NIP19Tests: XCTestCase {
} }
func test_parse_note() throws { func test_parse_note() throws {
let res = parse_mentions(content: " nostr:note1s4p70596lv50x0zftuses32t6ck8x6wgd4edwacyetfxwns2jtysux7vep", tags: []) let res = parse_mentions(content: " nostr:note1s4p70596lv50x0zftuses32t6ck8x6wgd4edwacyetfxwns2jtysux7vep", tags: []).blocks
XCTAssertEqual(res.count, 2) XCTAssertEqual(res.count, 2)
let expected_ref = ReferencedId(ref_id: "8543e7d0bafb28f33c495f2198454bd62c7369c86d72d77704cad2674e0a92c9", relay_id: nil, key: "e") let expected_ref = ReferencedId(ref_id: "8543e7d0bafb28f33c495f2198454bd62c7369c86d72d77704cad2674e0a92c9", relay_id: nil, key: "e")
let expected_mention = Mention(index: nil, type: .event, ref: expected_ref) let expected_mention = Mention(index: nil, type: .event, ref: expected_ref)
@@ -43,7 +43,7 @@ final class NIP19Tests: XCTestCase {
} }
func test_mention_with_adjacent() throws { func test_mention_with_adjacent() throws {
let res = parse_mentions(content: " nostr:note1s4p70596lv50x0zftuses32t6ck8x6wgd4edwacyetfxwns2jtysux7vep?", tags: []) let res = parse_mentions(content: " nostr:note1s4p70596lv50x0zftuses32t6ck8x6wgd4edwacyetfxwns2jtysux7vep?", tags: []).blocks
XCTAssertEqual(res.count, 3) XCTAssertEqual(res.count, 3)
let expected_ref = ReferencedId(ref_id: "8543e7d0bafb28f33c495f2198454bd62c7369c86d72d77704cad2674e0a92c9", relay_id: nil, key: "e") let expected_ref = ReferencedId(ref_id: "8543e7d0bafb28f33c495f2198454bd62c7369c86d72d77704cad2674e0a92c9", relay_id: nil, key: "e")
let expected_mention = Mention(index: nil, type: .event, ref: expected_ref) let expected_mention = Mention(index: nil, type: .event, ref: expected_ref)
+9 -9
View File
@@ -21,7 +21,7 @@ class ReplyTests: XCTestCase {
func testMentionIsntReply() throws { func testMentionIsntReply() throws {
let content = "this is #[0] a mention" let content = "this is #[0] a mention"
let tags = [["e", "event_id"]] let tags = [["e", "event_id"]]
let blocks = parse_mentions(content: content, tags: tags) let blocks = parse_mentions(content: content, tags: tags).blocks
let event_refs = interpret_event_refs(blocks: blocks, tags: tags) let event_refs = interpret_event_refs(blocks: blocks, tags: tags)
XCTAssertEqual(event_refs.count, 1) XCTAssertEqual(event_refs.count, 1)
@@ -96,7 +96,7 @@ class ReplyTests: XCTestCase {
func testRootReplyWithMention() throws { func testRootReplyWithMention() throws {
let content = "this is #[1] a mention" let content = "this is #[1] a mention"
let tags = [["e", "thread_id"], ["e", "mentioned_id"]] let tags = [["e", "thread_id"], ["e", "mentioned_id"]]
let blocks = parse_mentions(content: content, tags: tags) let blocks = parse_mentions(content: content, tags: tags).blocks
let event_refs = interpret_event_refs(blocks: blocks, tags: tags) let event_refs = interpret_event_refs(blocks: blocks, tags: tags)
XCTAssertEqual(event_refs.count, 2) XCTAssertEqual(event_refs.count, 2)
@@ -114,7 +114,7 @@ class ReplyTests: XCTestCase {
func testEmptyMention() throws { func testEmptyMention() throws {
let content = "this is some & content" let content = "this is some & content"
let tags: [[String]] = [] let tags: [[String]] = []
let blocks = parse_mentions(content: content, tags: tags) let blocks = parse_mentions(content: content, tags: tags).blocks
let post_blocks = parse_post_blocks(content: content) let post_blocks = parse_post_blocks(content: content)
let post_tags = make_post_tags(post_blocks: post_blocks, tags: tags, silent_mentions: false) let post_tags = make_post_tags(post_blocks: post_blocks, tags: tags, silent_mentions: false)
let event_refs = interpret_event_refs(blocks: blocks, tags: tags) let event_refs = interpret_event_refs(blocks: blocks, tags: tags)
@@ -148,7 +148,7 @@ class ReplyTests: XCTestCase {
func testManyMentions() throws { func testManyMentions() throws {
let content = "#[10]" let content = "#[10]"
let tags: [[String]] = [[],[],[],[],[],[],[],[],[],[],["p", "3e999f94e2cb34ef44a64b351141ac4e51b5121b2d31aed4a6c84602a1144692"]] let tags: [[String]] = [[],[],[],[],[],[],[],[],[],[],["p", "3e999f94e2cb34ef44a64b351141ac4e51b5121b2d31aed4a6c84602a1144692"]]
let blocks = parse_mentions(content: content, tags: tags) let blocks = parse_mentions(content: content, tags: tags).blocks
let mentions = blocks.filter { $0.is_mention } let mentions = blocks.filter { $0.is_mention }
XCTAssertEqual(mentions.count, 1) XCTAssertEqual(mentions.count, 1)
} }
@@ -156,7 +156,7 @@ class ReplyTests: XCTestCase {
func testThreadedReply() throws { func testThreadedReply() throws {
let content = "this is some content" let content = "this is some content"
let tags = [["e", "thread_id"], ["e", "reply_id"]] let tags = [["e", "thread_id"], ["e", "reply_id"]]
let blocks = parse_mentions(content: content, tags: tags) let blocks = parse_mentions(content: content, tags: tags).blocks
let event_refs = interpret_event_refs(blocks: blocks, tags: tags) let event_refs = interpret_event_refs(blocks: blocks, tags: tags)
XCTAssertEqual(event_refs.count, 2) XCTAssertEqual(event_refs.count, 2)
@@ -172,7 +172,7 @@ class ReplyTests: XCTestCase {
func testRootReply() throws { func testRootReply() throws {
let content = "this is a reply" let content = "this is a reply"
let tags = [["e", "thread_id"]] let tags = [["e", "thread_id"]]
let blocks = parse_mentions(content: content, tags: tags) let blocks = parse_mentions(content: content, tags: tags).blocks
let event_refs = interpret_event_refs(blocks: blocks, tags: tags) let event_refs = interpret_event_refs(blocks: blocks, tags: tags)
XCTAssertEqual(event_refs.count, 1) XCTAssertEqual(event_refs.count, 1)
@@ -186,14 +186,14 @@ class ReplyTests: XCTestCase {
func testNoReply() throws { func testNoReply() throws {
let content = "this is a #[0] reply" let content = "this is a #[0] reply"
let blocks = parse_mentions(content: content, tags: []) let blocks = parse_mentions(content: content, tags: []).blocks
let event_refs = interpret_event_refs(blocks: blocks, tags: []) let event_refs = interpret_event_refs(blocks: blocks, tags: [])
XCTAssertEqual(event_refs.count, 0) XCTAssertEqual(event_refs.count, 0)
} }
func testParseMention() throws { func testParseMention() throws {
let parsed = parse_mentions(content: "this is #[0] a mention", tags: [["e", "event_id"]]) let parsed = parse_mentions(content: "this is #[0] a mention", tags: [["e", "event_id"]]).blocks
XCTAssertNotNil(parsed) XCTAssertNotNil(parsed)
XCTAssertEqual(parsed.count, 3) XCTAssertEqual(parsed.count, 3)
@@ -522,7 +522,7 @@ class ReplyTests: XCTestCase {
} }
func testParseInvalidMention() throws { func testParseInvalidMention() throws {
let parsed = parse_mentions(content: "this is #[0] a mention", tags: []) let parsed = parse_mentions(content: "this is #[0] a mention", tags: []).blocks
XCTAssertNotNil(parsed) XCTAssertNotNil(parsed)
XCTAssertEqual(parsed.count, 3) XCTAssertEqual(parsed.count, 3)
+13 -13
View File
@@ -71,7 +71,7 @@ class damusTests: XCTestCase {
[my website](https://jb55.com) [my website](https://jb55.com)
""" """
let parsed = parse_mentions(content: md, tags: []) let parsed = parse_mentions(content: md, tags: []).blocks
XCTAssertNotNil(parsed) XCTAssertNotNil(parsed)
XCTAssertEqual(parsed.count, 1) XCTAssertEqual(parsed.count, 1)
@@ -79,7 +79,7 @@ class damusTests: XCTestCase {
} }
func testParseUrlUpper() { func testParseUrlUpper() {
let parsed = parse_mentions(content: "a HTTPS://jb55.COM b", tags: []) let parsed = parse_mentions(content: "a HTTPS://jb55.COM b", tags: []).blocks
XCTAssertNotNil(parsed) XCTAssertNotNil(parsed)
XCTAssertEqual(parsed.count, 3) XCTAssertEqual(parsed.count, 3)
@@ -114,7 +114,7 @@ class damusTests: XCTestCase {
} }
func testParseUrl() { func testParseUrl() {
let parsed = parse_mentions(content: "a https://jb55.com b", tags: []) let parsed = parse_mentions(content: "a https://jb55.com b", tags: []).blocks
XCTAssertNotNil(parsed) XCTAssertNotNil(parsed)
XCTAssertEqual(parsed.count, 3) XCTAssertEqual(parsed.count, 3)
@@ -122,7 +122,7 @@ class damusTests: XCTestCase {
} }
func testParseUrlEnd() { func testParseUrlEnd() {
let parsed = parse_mentions(content: "a https://jb55.com", tags: []) let parsed = parse_mentions(content: "a https://jb55.com", tags: []).blocks
XCTAssertNotNil(parsed) XCTAssertNotNil(parsed)
XCTAssertEqual(parsed.count, 2) XCTAssertEqual(parsed.count, 2)
@@ -131,7 +131,7 @@ class damusTests: XCTestCase {
} }
func testParseUrlStart() { func testParseUrlStart() {
let parsed = parse_mentions(content: "https://jb55.com br", tags: []) let parsed = parse_mentions(content: "https://jb55.com br", tags: []).blocks
XCTAssertNotNil(parsed) XCTAssertNotNil(parsed)
XCTAssertEqual(parsed.count, 2) XCTAssertEqual(parsed.count, 2)
@@ -141,7 +141,7 @@ class damusTests: XCTestCase {
func testNoParseUrlWithOnlyWhitespace() { func testNoParseUrlWithOnlyWhitespace() {
let testString = "https:// " let testString = "https:// "
let parsed = parse_mentions(content: testString, tags: []) let parsed = parse_mentions(content: testString, tags: []).blocks
XCTAssertNotNil(parsed) XCTAssertNotNil(parsed)
XCTAssertEqual(parsed[0].is_text, testString) XCTAssertEqual(parsed[0].is_text, testString)
@@ -149,14 +149,14 @@ class damusTests: XCTestCase {
func testNoParseUrlTrailingCharacters() { func testNoParseUrlTrailingCharacters() {
let testString = "https://foo.bar, " let testString = "https://foo.bar, "
let parsed = parse_mentions(content: testString, tags: []) let parsed = parse_mentions(content: testString, tags: []).blocks
XCTAssertNotNil(parsed) XCTAssertNotNil(parsed)
XCTAssertEqual(parsed[0].is_url?.absoluteString, "https://foo.bar") XCTAssertEqual(parsed[0].is_url?.absoluteString, "https://foo.bar")
} }
func testParseMentionBlank() { func testParseMentionBlank() {
let parsed = parse_mentions(content: "", tags: [["e", "event_id"]]) let parsed = parse_mentions(content: "", tags: [["e", "event_id"]]).blocks
XCTAssertNotNil(parsed) XCTAssertNotNil(parsed)
XCTAssertEqual(parsed.count, 0) XCTAssertEqual(parsed.count, 0)
@@ -178,7 +178,7 @@ class damusTests: XCTestCase {
} }
func testParseHashtag() { func testParseHashtag() {
let parsed = parse_mentions(content: "some hashtag #bitcoin derp", tags: []) let parsed = parse_mentions(content: "some hashtag #bitcoin derp", tags: []).blocks
XCTAssertNotNil(parsed) XCTAssertNotNil(parsed)
XCTAssertEqual(parsed.count, 3) XCTAssertEqual(parsed.count, 3)
@@ -188,7 +188,7 @@ class damusTests: XCTestCase {
} }
func testHashtagWithComma() { func testHashtagWithComma() {
let parsed = parse_mentions(content: "some hashtag #bitcoin, cool", tags: []) let parsed = parse_mentions(content: "some hashtag #bitcoin, cool", tags: []).blocks
XCTAssertNotNil(parsed) XCTAssertNotNil(parsed)
XCTAssertEqual(parsed.count, 3) XCTAssertEqual(parsed.count, 3)
@@ -198,7 +198,7 @@ class damusTests: XCTestCase {
} }
func testHashtagWithEmoji() { func testHashtagWithEmoji() {
let parsed = parse_mentions(content: "some hashtag #bitcoin☕️ cool", tags: []) let parsed = parse_mentions(content: "some hashtag #bitcoin☕️ cool", tags: []).blocks
XCTAssertNotNil(parsed) XCTAssertNotNil(parsed)
XCTAssertEqual(parsed.count, 3) XCTAssertEqual(parsed.count, 3)
@@ -208,7 +208,7 @@ class damusTests: XCTestCase {
} }
func testParseHashtagEnd() { func testParseHashtagEnd() {
let parsed = parse_mentions(content: "some hashtag #bitcoin", tags: []) let parsed = parse_mentions(content: "some hashtag #bitcoin", tags: []).blocks
XCTAssertNotNil(parsed) XCTAssertNotNil(parsed)
XCTAssertEqual(parsed.count, 2) XCTAssertEqual(parsed.count, 2)
@@ -217,7 +217,7 @@ class damusTests: XCTestCase {
} }
func testParseMentionOnlyText() { func testParseMentionOnlyText() {
let parsed = parse_mentions(content: "there is no mention here", tags: [["e", "event_id"]]) let parsed = parse_mentions(content: "there is no mention here", tags: [["e", "event_id"]]).blocks
XCTAssertNotNil(parsed) XCTAssertNotNil(parsed)
XCTAssertEqual(parsed.count, 1) XCTAssertEqual(parsed.count, 1)