Compare commits
28 Commits
hide-last-
...
render-blo
| Author | SHA1 | Date | |
|---|---|---|---|
|
3a331f6a05
|
|||
|
|
aef516ae9f | ||
|
|
eb4e3b692b | ||
|
|
fe52381d63 | ||
|
|
ab8d52e685 | ||
|
|
1d32200ae3 | ||
|
|
309b00380d | ||
|
|
7fa2118480 | ||
|
|
1a6c17e308 | ||
|
|
82a6046620 | ||
|
|
241755c8c4 | ||
|
|
b26f66f15c | ||
|
|
28bd0c81e8 | ||
| 0bd1814877 | |||
| ee94f67b94 | |||
|
|
3a25075473 | ||
|
|
d16ff8f78f | ||
|
|
38dc90cb33 | ||
|
|
52bbc698b2 | ||
|
|
496a11f597 | ||
| 4a8a0ea1bd | |||
|
|
c424d4da99 | ||
|
|
69d5fc1553 | ||
| bcb59896db | |||
| e1e6d9eb3d | |||
| f1fdae5957 | |||
|
|
f96647fa40 | ||
|
|
5ea522d306 |
@@ -1479,6 +1479,9 @@
|
||||
D74AAFD22B155E78006CF0F4 /* WalletConnect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7D09612A098D0E00943473 /* WalletConnect.swift */; };
|
||||
D74AAFD42B155ECB006CF0F4 /* Zaps+.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74AAFD32B155ECB006CF0F4 /* Zaps+.swift */; };
|
||||
D74AAFD62B155F0C006CF0F4 /* WalletConnect+.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74AAFD52B155F0C006CF0F4 /* WalletConnect+.swift */; };
|
||||
D74E64132DC95CC7004C7892 /* HumanReadableErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74E64112DC95CBE004C7892 /* HumanReadableErrors.swift */; };
|
||||
D74E64142DC95CC7004C7892 /* HumanReadableErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74E64112DC95CBE004C7892 /* HumanReadableErrors.swift */; };
|
||||
D74E64152DC95CC7004C7892 /* HumanReadableErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74E64112DC95CBE004C7892 /* HumanReadableErrors.swift */; };
|
||||
D74EA08A2D2BF2A7002290DD /* URLHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D767066E2C8BB3CE00F09726 /* URLHandler.swift */; };
|
||||
D74EA08E2D2E271E002290DD /* ErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74EA08D2D2E271E002290DD /* ErrorView.swift */; };
|
||||
D74EA08F2D2E271E002290DD /* ErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74EA08D2D2E271E002290DD /* ErrorView.swift */; };
|
||||
@@ -1703,6 +1706,9 @@
|
||||
D7F360262CEBBD8B009D34DA /* PresentFullScreenItemNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7EB00AF2CD59C8300660C07 /* PresentFullScreenItemNotify.swift */; };
|
||||
D7F360272CEBBDC0009D34DA /* DamusVideoControlsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7EFBA362CC322F300F45588 /* DamusVideoControlsView.swift */; };
|
||||
D7F360292CEBBE34009D34DA /* CodeScanner in Frameworks */ = {isa = PBXBuildFile; productRef = D7F360282CEBBE34009D34DA /* CodeScanner */; };
|
||||
D7FA46E52DBDAA7E002C9BB0 /* ImageCacheMigrations.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7FA46E42DBDAA75002C9BB0 /* ImageCacheMigrations.swift */; };
|
||||
D7FA46E62DBDAA7E002C9BB0 /* ImageCacheMigrations.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7FA46E42DBDAA75002C9BB0 /* ImageCacheMigrations.swift */; };
|
||||
D7FA46E72DBDAA7E002C9BB0 /* ImageCacheMigrations.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7FA46E42DBDAA75002C9BB0 /* ImageCacheMigrations.swift */; };
|
||||
D7FB10A72B0C371A00FA8D42 /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2B10272A7B0F5C008AA43E /* Log.swift */; };
|
||||
D7FB14222BE5970000398331 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = D7FB14212BE5970000398331 /* PrivacyInfo.xcprivacy */; };
|
||||
D7FB14252BE5A9A800398331 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = D7FB14242BE5A9A800398331 /* PrivacyInfo.xcprivacy */; };
|
||||
@@ -2514,6 +2520,7 @@
|
||||
D74AAFCE2B155D8C006CF0F4 /* ZapDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZapDataModel.swift; sourceTree = "<group>"; };
|
||||
D74AAFD32B155ECB006CF0F4 /* Zaps+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Zaps+.swift"; sourceTree = "<group>"; };
|
||||
D74AAFD52B155F0C006CF0F4 /* WalletConnect+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WalletConnect+.swift"; sourceTree = "<group>"; };
|
||||
D74E64112DC95CBE004C7892 /* HumanReadableErrors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HumanReadableErrors.swift; sourceTree = "<group>"; };
|
||||
D74EA08D2D2E271E002290DD /* ErrorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorView.swift; sourceTree = "<group>"; };
|
||||
D74EA0922D2E77B9002290DD /* LoadableNostrEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadableNostrEventView.swift; sourceTree = "<group>"; };
|
||||
D74F43092B23F0BE00425B75 /* DamusPurple.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusPurple.swift; sourceTree = "<group>"; };
|
||||
@@ -2574,6 +2581,7 @@
|
||||
D7EDED2D2B128E8A0018B19C /* CollectionExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionExtension.swift; sourceTree = "<group>"; };
|
||||
D7EDED322B12ACAE0018B19C /* DamusUserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusUserDefaults.swift; sourceTree = "<group>"; };
|
||||
D7EFBA362CC322F300F45588 /* DamusVideoControlsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusVideoControlsView.swift; sourceTree = "<group>"; };
|
||||
D7FA46E42DBDAA75002C9BB0 /* ImageCacheMigrations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCacheMigrations.swift; sourceTree = "<group>"; };
|
||||
D7FB14212BE5970000398331 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
|
||||
D7FB14242BE5A9A800398331 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
|
||||
D7FD12252BD345A700CF195B /* FirstAidSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirstAidSettingsView.swift; sourceTree = "<group>"; };
|
||||
@@ -3312,6 +3320,7 @@
|
||||
4C7FF7D628233637009601DB /* Util */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D7FA46E42DBDAA75002C9BB0 /* ImageCacheMigrations.swift */,
|
||||
D73B74E02D8365B40067BDBC /* ExtraFonts.swift */,
|
||||
D7DB93042D66A43B00DA1EE5 /* Undistractor.swift */,
|
||||
D73E5F7E2C6AA066007EB227 /* DamusAliases.swift */,
|
||||
@@ -4035,6 +4044,7 @@
|
||||
D78F080A2D7F78B000FC6C75 /* WalletConnect */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D74E64112DC95CBE004C7892 /* HumanReadableErrors.swift */,
|
||||
D78F08102D7F78F600FC6C75 /* Response.swift */,
|
||||
D78F080B2D7F78EB00FC6C75 /* Request.swift */,
|
||||
4C7D09612A098D0E00943473 /* WalletConnect.swift */,
|
||||
@@ -4675,6 +4685,7 @@
|
||||
4CCEB7AE29B53D260078AA28 /* SearchingEventView.swift in Sources */,
|
||||
4CF0ABE929844AF100D66079 /* AnyCodable.swift in Sources */,
|
||||
BA3759932ABCCEBA0018D73B /* CameraModel.swift in Sources */,
|
||||
D7FA46E72DBDAA7E002C9BB0 /* ImageCacheMigrations.swift in Sources */,
|
||||
D7100C5A2B76FD5100C59298 /* LogoView.swift in Sources */,
|
||||
4C0A3F8F280F640A000448DE /* ThreadModel.swift in Sources */,
|
||||
4C3AC79F2833115300E1F516 /* FollowButtonView.swift in Sources */,
|
||||
@@ -4688,6 +4699,7 @@
|
||||
D7315A2A2ACDF3B70036E30A /* DamusCacheManager.swift in Sources */,
|
||||
D7373BA82B68974500F7783D /* DamusPurpleNewUserOnboardingView.swift in Sources */,
|
||||
4C7D09682A0AE9B200943473 /* NWCScannerView.swift in Sources */,
|
||||
D74E64152DC95CC7004C7892 /* HumanReadableErrors.swift in Sources */,
|
||||
D7CB5D452B116FE800AD4105 /* Contacts+.swift in Sources */,
|
||||
4CA352A42A76AFF3003BB08B /* UpdateStatsNotify.swift in Sources */,
|
||||
D798D21E2B0858BB00234419 /* MigratedTypes.swift in Sources */,
|
||||
@@ -5125,6 +5137,7 @@
|
||||
82D6FAED2CD99F7900C925F4 /* PostNotify.swift in Sources */,
|
||||
82D6FAEE2CD99F7900C925F4 /* PresentSheetNotify.swift in Sources */,
|
||||
D74EA0932D2E77B9002290DD /* LoadableNostrEventView.swift in Sources */,
|
||||
D74E64142DC95CC7004C7892 /* HumanReadableErrors.swift in Sources */,
|
||||
82D6FAEF2CD99F7900C925F4 /* ProfileUpdatedNotify.swift in Sources */,
|
||||
82D6FAF02CD99F7900C925F4 /* ReportNotify.swift in Sources */,
|
||||
82D6FAF12CD99F7900C925F4 /* ScrollToTopNotify.swift in Sources */,
|
||||
@@ -5217,6 +5230,7 @@
|
||||
82D6FB432CD99F7900C925F4 /* KeychainStorage.swift in Sources */,
|
||||
82D6FB442CD99F7900C925F4 /* Bech32.swift in Sources */,
|
||||
82D6FB452CD99F7900C925F4 /* InputDismissKeyboard.swift in Sources */,
|
||||
D7FA46E62DBDAA7E002C9BB0 /* ImageCacheMigrations.swift in Sources */,
|
||||
82D6FB462CD99F7900C925F4 /* Constants.swift in Sources */,
|
||||
82D6FB472CD99F7900C925F4 /* LinkView.swift in Sources */,
|
||||
D7DB1FDF2D5A78CE00CF06DA /* NIP44.swift in Sources */,
|
||||
@@ -5681,6 +5695,7 @@
|
||||
D73E5E9F2C6A97F4007EB227 /* CreateAccountModel.swift in Sources */,
|
||||
D73E5EA12C6A97F4007EB227 /* SignalModel.swift in Sources */,
|
||||
5CB017272D42C5C400A9ED05 /* TransactionsView.swift in Sources */,
|
||||
D7FA46E52DBDAA7E002C9BB0 /* ImageCacheMigrations.swift in Sources */,
|
||||
D73E5EA22C6A97F4007EB227 /* FollowTarget.swift in Sources */,
|
||||
D73E5EA32C6A97F4007EB227 /* BookmarksManager.swift in Sources */,
|
||||
D73E5EA42C6A97F4007EB227 /* EventsModel.swift in Sources */,
|
||||
@@ -5731,6 +5746,7 @@
|
||||
D73E5ECC2C6A97F4007EB227 /* SuggestedUsersViewModel.swift in Sources */,
|
||||
D73E5ED22C6A97F4007EB227 /* WalletView.swift in Sources */,
|
||||
D73E5ED32C6A97F4007EB227 /* NWCScannerView.swift in Sources */,
|
||||
D74E64132DC95CC7004C7892 /* HumanReadableErrors.swift in Sources */,
|
||||
D73E5ED42C6A97F4007EB227 /* FriendsButton.swift in Sources */,
|
||||
D73E5ED52C6A97F4007EB227 /* GradientFollowButton.swift in Sources */,
|
||||
D73E5ED62C6A97F4007EB227 /* AlbyButton.swift in Sources */,
|
||||
|
||||
@@ -94,26 +94,30 @@ enum OpenWalletError: Error {
|
||||
}
|
||||
|
||||
func open_with_wallet(wallet: Wallet.Model, invoice: String) throws {
|
||||
let url = try getUrlToOpen(invoice: invoice, with: wallet)
|
||||
this_app.open(url)
|
||||
}
|
||||
|
||||
func getUrlToOpen(invoice: String, with wallet: Wallet.Model) throws(OpenWalletError) -> URL {
|
||||
if let url = URL(string: "\(wallet.link)\(invoice)"), this_app.canOpenURL(url) {
|
||||
this_app.open(url)
|
||||
return url
|
||||
} else {
|
||||
guard let store_link = wallet.appStoreLink else {
|
||||
throw OpenWalletError.no_wallet_to_open
|
||||
throw .no_wallet_to_open
|
||||
}
|
||||
|
||||
guard let url = URL(string: store_link) else {
|
||||
throw OpenWalletError.store_link_invalid
|
||||
throw .store_link_invalid
|
||||
}
|
||||
|
||||
guard this_app.canOpenURL(url) else {
|
||||
throw OpenWalletError.system_cannot_open_store_link
|
||||
throw .system_cannot_open_store_link
|
||||
}
|
||||
|
||||
this_app.open(url)
|
||||
return url
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let test_invoice = Invoice(description: .description("this is a description"), amount: .specific(10000), string: "lnbc100n1p357sl0sp5t9n56wdztun39lgdqlr30xqwksg3k69q4q2rkr52aplujw0esn0qpp5mrqgljk62z20q4nvgr6lzcyn6fhylzccwdvu4k77apg3zmrkujjqdpzw35xjueqd9ejqcfqv3jhxcmjd9c8g6t0dcxqyjw5qcqpjrzjqt56h4gvp5yx36u2uzqa6qwcsk3e2duunfxppzj9vhypc3wfe2wswz607uqq3xqqqsqqqqqqqqqqqlqqyg9qyysgqagx5h20aeulj3gdwx3kxs8u9f4mcakdkwuakasamm9562ffyr9en8yg20lg0ygnr9zpwp68524kmda0t5xp2wytex35pu8hapyjajxqpsql29r", expiry: 604800, payment_hash: Data(), created_at: 1666139119)
|
||||
|
||||
struct InvoiceView_Previews: PreviewProvider {
|
||||
|
||||
@@ -240,7 +240,7 @@ func send_zap(damus_state: DamusState, target: ZapTarget, lnurl: String, is_cust
|
||||
// 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 = WalletConnect.pay(url: nwc_state.url, pool: damus_state.nostrNetwork.pool, post: damus_state.nostrNetwork.postbox, invoice: inv, delay: delay, on_flush: flusher)
|
||||
let nwc_req = WalletConnect.pay(url: nwc_state.url, pool: damus_state.nostrNetwork.pool, post: damus_state.nostrNetwork.postbox, invoice: inv, zap_request: zapreq, 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)")
|
||||
|
||||
@@ -160,7 +160,7 @@ func translate_note(profiles: Profiles, keypair: Keypair, event: NostrEvent, set
|
||||
|
||||
// Render translated note
|
||||
let translated_blocks = parse_note_content(content: .content(translated_note, event.tags))
|
||||
let artifacts = render_blocks(blocks: translated_blocks, profiles: profiles)
|
||||
let artifacts = render_blocks(blocks: translated_blocks, profiles: profiles, can_hide_last_previewable_refs: true)
|
||||
|
||||
// and cache it
|
||||
return .translated(Translated(artifacts: artifacts, language: note_lang))
|
||||
|
||||
@@ -742,6 +742,8 @@ struct ContentView: View {
|
||||
case route(Route)
|
||||
/// Open a sheet
|
||||
case sheet(Sheets)
|
||||
/// Open an external URL
|
||||
case external_url(URL)
|
||||
/// Do nothing.
|
||||
///
|
||||
/// ## Implementation notes
|
||||
@@ -758,6 +760,8 @@ struct ContentView: View {
|
||||
navigationCoordinator.push(route: route)
|
||||
case .sheet(let sheet):
|
||||
self.active_sheet = sheet
|
||||
case .external_url(let url):
|
||||
this_app.open(url)
|
||||
case .no_action:
|
||||
return
|
||||
}
|
||||
|
||||
@@ -261,22 +261,41 @@ class HomeModel: ContactsDelegate {
|
||||
Task { @MainActor in
|
||||
// TODO: Adapt KeychainStorage to StringCodable and instead of parsing to WalletConnectURL every time
|
||||
guard let nwc_str = damus_state.settings.nostr_wallet_connect,
|
||||
let nwc = WalletConnectURL(str: nwc_str),
|
||||
let resp = await WalletConnect.FullWalletResponse(from: ev, nwc: nwc) else {
|
||||
let nwc = WalletConnectURL(str: nwc_str) else {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
guard nwc.relay == relay else { return } // Don't process NWC responses coming from relays other than our designated one
|
||||
guard ev.referenced_pubkeys.first == nwc.keypair.pubkey else {
|
||||
return // This message is not for us. Ignore it.
|
||||
}
|
||||
|
||||
var resp: WalletConnect.FullWalletResponse? = nil
|
||||
do {
|
||||
resp = try await WalletConnect.FullWalletResponse(from: ev, nwc: nwc)
|
||||
} catch {
|
||||
Log.error("HomeModel: Error on NWC wallet response handling: %s", for: .nwc, error.localizedDescription)
|
||||
if let initError = error as? WalletConnect.FullWalletResponse.InitializationError,
|
||||
let humanReadableError = initError.humanReadableError {
|
||||
present_sheet(.error(humanReadableError))
|
||||
}
|
||||
}
|
||||
guard let resp else { return }
|
||||
|
||||
// since command results are not returned for ephemeral events,
|
||||
// remove the request from the postbox which is likely failing over and over
|
||||
if damus_state.nostrNetwork.postbox.remove_relayer(relay_id: nwc.relay, event_id: resp.req_id) {
|
||||
print("nwc: got response, removed \(resp.req_id) from the postbox [\(relay)]")
|
||||
Log.debug("HomeModel: got NWC response, removed %s from the postbox [%s]", for: .nwc, resp.req_id.hex(), relay.absoluteString)
|
||||
} else {
|
||||
print("nwc: \(resp.req_id) not found in the postbox, nothing to remove [\(relay)]")
|
||||
Log.debug("HomeModel: got NWC response, %s not found in the postbox, nothing to remove [%s]", for: .nwc, resp.req_id.hex(), relay.absoluteString)
|
||||
}
|
||||
|
||||
guard resp.response.error == nil else {
|
||||
print("nwc error: \(resp.response)")
|
||||
Log.error("HomeModel: NWC wallet raised an error: %s", for: .nwc, String(describing: resp.response))
|
||||
WalletConnect.handle_error(zapcache: self.damus_state.zaps, evcache: self.damus_state.events, resp: resp)
|
||||
if let humanReadableError = resp.response.error?.humanReadableError {
|
||||
present_sheet(.error(humanReadableError))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -73,85 +73,143 @@ func render_note_content(ev: NostrEvent, profiles: Profiles, keypair: Keypair) -
|
||||
return .longform(LongformContent(ev.content))
|
||||
}
|
||||
|
||||
return .separated(render_blocks(blocks: blocks, profiles: profiles))
|
||||
return .separated(render_blocks(blocks: blocks, profiles: profiles, can_hide_last_previewable_refs: true))
|
||||
}
|
||||
|
||||
func render_blocks(blocks bs: Blocks, profiles: Profiles) -> NoteArtifactsSeparated {
|
||||
func render_blocks(blocks bs: Blocks, profiles: Profiles, can_hide_last_previewable_refs: Bool = false) -> NoteArtifactsSeparated {
|
||||
var invoices: [Invoice] = []
|
||||
var urls: [UrlType] = []
|
||||
let blocks = bs.blocks
|
||||
|
||||
let one_note_ref = blocks
|
||||
.filter({
|
||||
if case .mention(let mention) = $0,
|
||||
case .note = mention.ref {
|
||||
return true
|
||||
|
||||
var end_mention_count = 0
|
||||
var end_url_count = 0
|
||||
|
||||
// Search backwards until we find the beginning index of the chain of previewables that reach the end of the content.
|
||||
var hide_text_index = blocks.endIndex
|
||||
if can_hide_last_previewable_refs {
|
||||
outerLoop: for (i, block) in blocks.enumerated().reversed() {
|
||||
if block.is_previewable {
|
||||
switch block {
|
||||
case .mention:
|
||||
end_mention_count += 1
|
||||
|
||||
// If there is more than one previewable mention,
|
||||
// do not hide anything because we allow rich rendering of only one mention currently.
|
||||
// This should be fixed in the future to show events inline instead.
|
||||
if end_mention_count > 1 {
|
||||
hide_text_index = blocks.endIndex
|
||||
break outerLoop
|
||||
}
|
||||
case .url(let url):
|
||||
let url_type = classify_url(url)
|
||||
if case .link = url_type {
|
||||
end_url_count += 1
|
||||
|
||||
// If there is more than one link, do not hide anything because we allow rich rendering of only
|
||||
// one link.
|
||||
if end_url_count > 1 {
|
||||
hide_text_index = blocks.endIndex
|
||||
break outerLoop
|
||||
}
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
hide_text_index = i
|
||||
} else if case .text(let txt) = block, txt.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
||||
// We should hide whitespace at the end sequence.
|
||||
hide_text_index = i
|
||||
} else if case .hashtag = block {
|
||||
// We should keep hashtags at the end sequence but hide all the other previewables around it.
|
||||
hide_text_index = i
|
||||
} else {
|
||||
break
|
||||
}
|
||||
else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
.count == 1
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
var ind: Int = -1
|
||||
let txt: CompatibleText = blocks.reduce(CompatibleText()) { str, block in
|
||||
ind = ind + 1
|
||||
|
||||
|
||||
// Add the rendered previewable blocks to their type-specific lists.
|
||||
switch block {
|
||||
case .mention(let m):
|
||||
if case .note = m.ref, one_note_ref {
|
||||
case .invoice(let invoice):
|
||||
invoices.append(invoice)
|
||||
case .url(let url):
|
||||
let url_type = classify_url(url)
|
||||
urls.append(url_type)
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
if can_hide_last_previewable_refs {
|
||||
// If there are previewable blocks that occur before the consecutive sequence of them at the end of the content,
|
||||
// we should not hide the text representation of any previewable block to avoid altering the format of the note.
|
||||
if ind < hide_text_index && block.is_previewable {
|
||||
hide_text_index = blocks.endIndex
|
||||
}
|
||||
|
||||
// No need to show the text representation of the block if the only previewables are the sequence of them
|
||||
// found at the end of the content.
|
||||
// This is to save unnecessary use of screen space.
|
||||
// The only exception is that if there are hashtags embedded in the end sequence, which is not uncommon,
|
||||
// then we still want to show those hashtags but hide everything else that is previewable in the end sequence.
|
||||
if ind >= hide_text_index {
|
||||
if case .text(let txt) = block, txt.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
||||
if case .hashtag = blocks[safe: ind+1] {
|
||||
return str + CompatibleText(stringLiteral: reduce_text_block(ind: ind, hide_text_index: -1, txt: txt))
|
||||
}
|
||||
} else if case .hashtag(let htag) = block {
|
||||
return str + hashtag_str(htag)
|
||||
}
|
||||
return str
|
||||
}
|
||||
}
|
||||
|
||||
switch block {
|
||||
case .mention(let m):
|
||||
return str + mention_str(m, profiles: profiles)
|
||||
case .text(let txt):
|
||||
return str + CompatibleText(stringLiteral: reduce_text_block(blocks: blocks, ind: ind, txt: txt, one_note_ref: one_note_ref))
|
||||
|
||||
return str + CompatibleText(stringLiteral: reduce_text_block(ind: ind, hide_text_index: hide_text_index, txt: txt))
|
||||
case .relay(let relay):
|
||||
return str + CompatibleText(stringLiteral: relay)
|
||||
|
||||
case .hashtag(let htag):
|
||||
return str + hashtag_str(htag)
|
||||
case .invoice(let invoice):
|
||||
invoices.append(invoice)
|
||||
return str
|
||||
return str + invoice_str(invoice)
|
||||
case .url(let url):
|
||||
let url_type = classify_url(url)
|
||||
switch url_type {
|
||||
case .media:
|
||||
urls.append(url_type)
|
||||
return str
|
||||
case .link(let url):
|
||||
urls.append(url_type)
|
||||
return str + url_str(url)
|
||||
}
|
||||
return str + url_str(url)
|
||||
}
|
||||
}
|
||||
|
||||
return NoteArtifactsSeparated(content: txt, words: bs.words, urls: urls, invoices: invoices)
|
||||
}
|
||||
|
||||
func reduce_text_block(blocks: [Block], ind: Int, txt: String, one_note_ref: Bool) -> String {
|
||||
func reduce_text_block(ind: Int, hide_text_index: Int, txt: String) -> String {
|
||||
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)
|
||||
|
||||
// Trim leading whitespaces.
|
||||
if ind == 0 {
|
||||
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,
|
||||
case .note = m.ref,
|
||||
one_note_ref {
|
||||
trimmed = trim_suffix(trimmed)
|
||||
}
|
||||
|
||||
// Trim trailing whitespaces if the following blocks will be hidden or if this is the last block.
|
||||
if ind == hide_text_index - 1 {
|
||||
trimmed = trim_suffix(trimmed)
|
||||
}
|
||||
|
||||
|
||||
return trimmed
|
||||
}
|
||||
|
||||
func invoice_str(_ invoice: Invoice) -> CompatibleText {
|
||||
var attributedString = AttributedString(stringLiteral: abbrev_identifier(invoice.string))
|
||||
attributedString.link = URL(string: "damus:lightning:\(invoice.string)")
|
||||
attributedString.foregroundColor = DamusColors.purple
|
||||
|
||||
return CompatibleText(attributed: attributedString)
|
||||
}
|
||||
|
||||
func url_str(_ url: URL) -> CompatibleText {
|
||||
var attributedString = AttributedString(stringLiteral: url.absoluteString)
|
||||
attributedString.link = url
|
||||
@@ -161,17 +219,16 @@ func url_str(_ url: URL) -> CompatibleText {
|
||||
}
|
||||
|
||||
func classify_url(_ url: URL) -> UrlType {
|
||||
let str = url.lastPathComponent.lowercased()
|
||||
|
||||
if str.hasSuffix(".png") || str.hasSuffix(".jpg") || str.hasSuffix(".jpeg") || str.hasSuffix(".gif") || str.hasSuffix(".webp") {
|
||||
let fileExtension = url.lastPathComponent.lowercased().components(separatedBy: ".").last
|
||||
|
||||
switch fileExtension {
|
||||
case "png", "jpg", "jpeg", "gif", "webp":
|
||||
return .media(.image(url))
|
||||
}
|
||||
|
||||
if str.hasSuffix(".mp4") || str.hasSuffix(".mov") || str.hasSuffix(".m3u8") {
|
||||
case "mp4", "mov", "m3u8":
|
||||
return .media(.video(url))
|
||||
default:
|
||||
return .link(url)
|
||||
}
|
||||
|
||||
return .link(url)
|
||||
}
|
||||
|
||||
func attributed_string_attach_icon(_ astr: inout AttributedString, img: UIImage) {
|
||||
@@ -194,11 +251,11 @@ func mention_str(_ m: Mention<MentionRef>, profiles: Profiles) -> CompatibleText
|
||||
let display_str: String = {
|
||||
switch m.ref {
|
||||
case .pubkey(let pk): return getDisplayName(pk: pk, profiles: profiles)
|
||||
case .note: return abbrev_pubkey(bech32String)
|
||||
case .nevent: return abbrev_pubkey(bech32String)
|
||||
case .note: return abbrev_identifier(bech32String)
|
||||
case .nevent: return abbrev_identifier(bech32String)
|
||||
case .nprofile(let nprofile): return getDisplayName(pk: nprofile.author, profiles: profiles)
|
||||
case .nrelay(let url): return url
|
||||
case .naddr: return abbrev_pubkey(bech32String)
|
||||
case .naddr: return abbrev_identifier(bech32String)
|
||||
}
|
||||
}()
|
||||
|
||||
|
||||
@@ -43,6 +43,15 @@ struct DamusURLHandler {
|
||||
return .route(.Script(script: model))
|
||||
case .purple(let purple_url):
|
||||
return await damus_state.purple.handle(purple_url: purple_url)
|
||||
case .invoice(let invoice):
|
||||
if damus_state.settings.show_wallet_selector {
|
||||
return .sheet(.select_wallet(invoice: invoice.string))
|
||||
} else {
|
||||
guard let url = try? getUrlToOpen(invoice: invoice.string, with: damus_state.settings.default_wallet.model) else {
|
||||
return .sheet(.select_wallet(invoice: invoice.string))
|
||||
}
|
||||
return .external_url(url)
|
||||
}
|
||||
case nil:
|
||||
break
|
||||
}
|
||||
@@ -91,6 +100,11 @@ struct DamusURLHandler {
|
||||
return .filter(filt)
|
||||
case .script(let script):
|
||||
return .script(script)
|
||||
case .invoice(let bolt11):
|
||||
if let invoice = decode_bolt11(bolt11) {
|
||||
return .invoice(invoice)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -103,5 +117,6 @@ struct DamusURLHandler {
|
||||
case wallet_connect(WalletConnectURL)
|
||||
case script([UInt8])
|
||||
case purple(DamusPurpleURL)
|
||||
case invoice(Invoice)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,6 +113,9 @@ class UserSettingsStore: ObservableObject {
|
||||
@Setting(key: "show_wallet_selector", default_value: false)
|
||||
var show_wallet_selector: Bool
|
||||
|
||||
@Setting(key: "dismiss_wallet_high_balance_warning", default_value: false)
|
||||
var dismiss_wallet_high_balance_warning: Bool
|
||||
|
||||
@Setting(key: "left_handed", default_value: false)
|
||||
var left_handed: Bool
|
||||
|
||||
@@ -174,8 +177,12 @@ class UserSettingsStore: ObservableObject {
|
||||
var truncate_timeline_text: Bool
|
||||
|
||||
/// Nozaps mode gimps note zapping to fit into apple's content-tipping guidelines. It can not be configurable to end-users on the app store
|
||||
@Setting(key: "nozaps", default_value: true)
|
||||
var nozaps: Bool
|
||||
///
|
||||
/// Update 2025-05-12: This can be re-enabled 🥳. See https://github.com/damus-io/damus/issues/3016
|
||||
// @Setting(key: "nozaps", default_value: true)
|
||||
var nozaps: Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
@Setting(key: "truncate_mention_text", default_value: true)
|
||||
var truncate_mention_text: Bool
|
||||
|
||||
@@ -52,4 +52,28 @@ extension NIP04 {
|
||||
|
||||
return create_encrypted_event(message, to_pk: to_pk, tags: tags, keypair: keypair, created_at: created, kind: 4)
|
||||
}
|
||||
|
||||
/// Decrypts string content
|
||||
static func decryptContent(recipientPrivateKey: Privkey, senderPubkey: Pubkey, content: String, encoding: EncEncoding) throws(NIP04DecryptionError) -> String {
|
||||
guard let shared_sec = get_shared_secret(privkey: recipientPrivateKey, pubkey: senderPubkey) else {
|
||||
throw .failedToComputeSharedSecret
|
||||
}
|
||||
guard let dat = (encoding == .base64 ? decode_dm_base64(content) : decode_dm_bech32(content)) else {
|
||||
throw .failedToDecodeEncryptedContent
|
||||
}
|
||||
guard let dat = aes_decrypt(data: dat.content, iv: dat.iv, shared_sec: shared_sec) else {
|
||||
throw .failedToDecryptAES
|
||||
}
|
||||
guard let decryptedString = String(data: dat, encoding: .utf8) else {
|
||||
throw .utf8DecodingFailedOnDecryptedPayload
|
||||
}
|
||||
return decryptedString
|
||||
}
|
||||
|
||||
enum NIP04DecryptionError: Error {
|
||||
case failedToComputeSharedSecret
|
||||
case failedToDecodeEncryptedContent
|
||||
case failedToDecryptAES
|
||||
case utf8DecodingFailedOnDecryptedPayload
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,8 +143,16 @@ struct ReplaceableParam: TagConvertible {
|
||||
var keychar: AsciiCharacter { "d" }
|
||||
}
|
||||
|
||||
struct Signature: Hashable, Equatable {
|
||||
struct Signature: Codable, Hashable, Equatable {
|
||||
let data: Data
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
self.init(try hex_decoder(decoder, expected_len: 64))
|
||||
}
|
||||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
try hex_encoder(to: encoder, data: self.data)
|
||||
}
|
||||
|
||||
init(_ p: Data) {
|
||||
self.data = p
|
||||
|
||||
@@ -379,6 +379,10 @@ func decode_json<T: Decodable>(_ val: String) -> T? {
|
||||
return try? JSONDecoder().decode(T.self, from: Data(val.utf8))
|
||||
}
|
||||
|
||||
func decode_json_throwing<T: Decodable>(_ val: String) throws -> T {
|
||||
return try JSONDecoder().decode(T.self, from: Data(val.utf8))
|
||||
}
|
||||
|
||||
func decode_data<T: Decodable>(_ data: Data) -> T? {
|
||||
let decoder = JSONDecoder()
|
||||
do {
|
||||
@@ -539,6 +543,7 @@ func event_to_json(ev: NostrEvent) -> String {
|
||||
return str
|
||||
}
|
||||
|
||||
@available(*, deprecated, renamed: "NIP04.decryptContent", message: "Deprecated, please use NIP04.decryptContent instead")
|
||||
func decrypt_dm(_ privkey: Privkey?, pubkey: Pubkey, content: String, encoding: EncEncoding) -> String? {
|
||||
guard let privkey = privkey else {
|
||||
return nil
|
||||
|
||||
@@ -12,6 +12,7 @@ enum NostrLink: Equatable {
|
||||
case ref(RefId)
|
||||
case filter(NostrFilter)
|
||||
case script([UInt8])
|
||||
case invoice(String)
|
||||
}
|
||||
|
||||
func encode_pubkey_uri(_ pubkey: Pubkey) -> String {
|
||||
@@ -93,8 +94,15 @@ func decode_nostr_uri(_ s: String) -> NostrLink? {
|
||||
return
|
||||
}
|
||||
|
||||
if parts.count >= 2 && parts[0] == "t" {
|
||||
return .filter(NostrFilter(hashtag: [parts[1].lowercased()]))
|
||||
if parts.count >= 2 {
|
||||
switch parts[0] {
|
||||
case "t":
|
||||
return .filter(NostrFilter(hashtag: [parts[1].lowercased()]))
|
||||
case "lightning":
|
||||
return .invoice(parts[1])
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
guard parts.count == 1 else {
|
||||
|
||||
@@ -37,7 +37,23 @@ enum Block: Equatable {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var is_previewable: Bool {
|
||||
switch self {
|
||||
case .mention(let m):
|
||||
switch m.ref {
|
||||
case .note, .nevent: return true
|
||||
default: return false
|
||||
}
|
||||
case .invoice:
|
||||
return true
|
||||
case .url:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
case text(String)
|
||||
case mention(Mention<MentionRef>)
|
||||
case hashtag(String)
|
||||
|
||||
@@ -80,9 +80,9 @@ func parse_display_name(name: String?, display_name: String?, pubkey: Pubkey) ->
|
||||
}
|
||||
|
||||
func abbrev_bech32_pubkey(pubkey: Pubkey) -> String {
|
||||
return abbrev_pubkey(String(pubkey.npub.dropFirst(4)))
|
||||
return abbrev_identifier(String(pubkey.npub.dropFirst(4)))
|
||||
}
|
||||
|
||||
func abbrev_pubkey(_ pubkey: String, amount: Int = 8) -> String {
|
||||
func abbrev_identifier(_ pubkey: String, amount: Int = 8) -> String {
|
||||
return pubkey.prefix(amount) + ":" + pubkey.suffix(amount)
|
||||
}
|
||||
|
||||
79
damus/Util/ImageCacheMigrations.swift
Normal file
79
damus/Util/ImageCacheMigrations.swift
Normal file
@@ -0,0 +1,79 @@
|
||||
//
|
||||
// ImageCacheMigrations.swift
|
||||
// damus
|
||||
//
|
||||
// Created by Daniel D’Aquino on 2025-04-26.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Kingfisher
|
||||
|
||||
struct ImageCacheMigrations {
|
||||
static func migrateKingfisherCacheIfNeeded() {
|
||||
let fileManager = FileManager.default
|
||||
let defaults = UserDefaults.standard
|
||||
let migration1Key = "KingfisherCacheMigrated" // Never ever changes
|
||||
let migration2Key = "KingfisherCacheMigratedV2" // Never ever changes
|
||||
|
||||
let migration1Done = defaults.bool(forKey: migration1Key)
|
||||
let migration2Done = defaults.bool(forKey: migration2Key)
|
||||
|
||||
guard !migration1Done || !migration2Done else {
|
||||
// All migrations are already done. Skip.
|
||||
return
|
||||
}
|
||||
|
||||
let oldCachePath = migration1Done ? migration1KingfisherCachePath() : migration0KingfisherCachePath()
|
||||
|
||||
// New shared cache location
|
||||
let newCachePath = kingfisherCachePath().path
|
||||
|
||||
if fileManager.fileExists(atPath: oldCachePath) {
|
||||
do {
|
||||
// Move the old cache to the new location
|
||||
try fileManager.moveItem(atPath: oldCachePath, toPath: newCachePath)
|
||||
Log.info("Successfully migrated Kingfisher cache to %s", for: .storage, newCachePath)
|
||||
} catch {
|
||||
do {
|
||||
// Cache data is not essential, fallback to deleting the cache and starting all over
|
||||
// It's better than leaving significant garbage data stuck indefinitely on the user's phone
|
||||
try fileManager.removeItem(atPath: newCachePath)
|
||||
try fileManager.removeItem(atPath: oldCachePath)
|
||||
}
|
||||
catch {
|
||||
Log.error("Failed to migrate cache: %s", for: .storage, error.localizedDescription)
|
||||
return // Do not mark them as complete, we can try again next time the user reloads the app
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mark migrations as complete
|
||||
defaults.set(true, forKey: migration1Key)
|
||||
defaults.set(true, forKey: migration2Key)
|
||||
}
|
||||
|
||||
static private func migration0KingfisherCachePath() -> String {
|
||||
// Implementation note: These are old, so they should not be changed
|
||||
let defaultCache = ImageCache.default
|
||||
return defaultCache.diskStorage.directoryURL.path
|
||||
}
|
||||
|
||||
static private func migration1KingfisherCachePath() -> String {
|
||||
// Implementation note: These are old, so they are hard-coded on purpose, because we can't change these values from the past.
|
||||
let groupURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.damus")!
|
||||
return groupURL.appendingPathComponent("ImageCache").path
|
||||
}
|
||||
|
||||
/// The latest path for kingfisher to store cached images on.
|
||||
///
|
||||
/// Documentation references:
|
||||
/// - https://developer.apple.com/documentation/foundation/filemanager/containerurl(forsecurityapplicationgroupidentifier:)#:~:text=The%20system%20creates%20only%20the%20Library/Caches%20subdirectory%20automatically
|
||||
/// - https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html#:~:text=Put%20data%20cache,files%20as%20needed.
|
||||
static func kingfisherCachePath() -> URL {
|
||||
let groupURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: Constants.DAMUS_APP_GROUP_IDENTIFIER)!
|
||||
return groupURL
|
||||
.appendingPathComponent("Library")
|
||||
.appendingPathComponent("Caches")
|
||||
.appendingPathComponent(Constants.IMAGE_CACHE_DIRNAME)
|
||||
}
|
||||
}
|
||||
97
damus/Util/WalletConnect/HumanReadableErrors.swift
Normal file
97
damus/Util/WalletConnect/HumanReadableErrors.swift
Normal file
@@ -0,0 +1,97 @@
|
||||
//
|
||||
// HumanReadableErrors.swift
|
||||
// damus
|
||||
//
|
||||
// Created by Daniel D’Aquino on 2025-05-05.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension WalletConnect.FullWalletResponse.InitializationError {
|
||||
var humanReadableError: ErrorView.UserPresentableError? {
|
||||
switch self {
|
||||
case .incorrectAuthorPubkey:
|
||||
nil // Anyone can send a response event with an incorrect author pubkey, it is not really an "error". We should silently ignore it.
|
||||
case .missingRequestIdReference:
|
||||
.init(
|
||||
user_visible_description: NSLocalizedString("Wallet provider returned an invalid response.", comment: "Error description shown to the user when a response from the wallet provider is invalid"),
|
||||
tip: NSLocalizedString("Please copy the technical info and send it to our support team.", comment: "Tip on how to resolve issue when wallet returns an invalid response"),
|
||||
technical_info: "Wallet response does not make a reference to any request; No request ID `e` tag was found."
|
||||
)
|
||||
case .failedToDecodeJSON(let error):
|
||||
.init(
|
||||
user_visible_description: NSLocalizedString("Wallet provider returned a response that we do not understand.", comment: "Error description shown to the user when a response from the wallet provider contains data the app does not understand"),
|
||||
tip: NSLocalizedString("Please copy the technical info and send it to our support team.", comment: "Tip on how to resolve issue when wallet returns an invalid response"),
|
||||
technical_info: "Failed to decode NWC Wallet response JSON. Error: \(error)"
|
||||
)
|
||||
case .failedToDecrypt(let error):
|
||||
.init(
|
||||
user_visible_description: NSLocalizedString("Wallet provider returned a response that we could not decrypt.", comment: "Error description shown to the user when a response from the wallet provider contains data the app could not decrypt."),
|
||||
tip: NSLocalizedString("Please copy the technical info and send it to our support team.", comment: "Tip on how to resolve issue when wallet returns an invalid response"),
|
||||
technical_info: "Failed to decrypt NWC Wallet response. Error: \(error)"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension WalletConnect.WalletResponseErr {
|
||||
var humanReadableError: ErrorView.UserPresentableError? {
|
||||
guard let code = self.code else {
|
||||
return .init(
|
||||
user_visible_description: String(format: NSLocalizedString("Your connected wallet raised an unknown error. Message: %s", comment: "Human readable error description for unknown error"), self.message ?? NSLocalizedString("Empty error message", comment: "A human readable placeholder to indicate that the error message is empty")),
|
||||
tip: NSLocalizedString("Please contact the developer of your wallet provider for help.", comment: "Human readable error description for an unknown error raised by a wallet provider."),
|
||||
technical_info: "NWC wallet provider returned an error response without a valid reason code. Message: \(self.message ?? "Empty error message")"
|
||||
)
|
||||
}
|
||||
switch code {
|
||||
case .rateLimited:
|
||||
return .init(
|
||||
user_visible_description: NSLocalizedString("Your wallet is temporarily being rate limited.", comment: "Error description for rate limit error"),
|
||||
tip: NSLocalizedString("Wait a few moments, and then try again.", comment: "Tip for rate limit error"),
|
||||
technical_info: "Wallet returned a rate limit error with message: \(self.message ?? "No further details provided")"
|
||||
)
|
||||
case .notImplemented:
|
||||
return .init(
|
||||
user_visible_description: NSLocalizedString("This feature is not implemented by your wallet.", comment: "Error description for not implemented feature"),
|
||||
tip: NSLocalizedString("Please check for updates or contact your wallet provider.", comment: "Tip for not implemented error"),
|
||||
technical_info: "Wallet reported a not implemented error. Message: \(self.message ?? "No further details provided")"
|
||||
)
|
||||
case .insufficientBalance:
|
||||
return .init(
|
||||
user_visible_description: NSLocalizedString("Your wallet does not have sufficient balance for this transaction.", comment: "Error description for insufficient balance"),
|
||||
tip: NSLocalizedString("Please deposit more funds and try again.", comment: "Tip for insufficient balance errors"),
|
||||
technical_info: "Wallet returned an insufficient balance error. Message: \(self.message ?? "No further details provided")"
|
||||
)
|
||||
case .quotaExceeded:
|
||||
return .init(
|
||||
user_visible_description: NSLocalizedString("Your transaction quota has been exceeded.", comment: "Error description for quota exceeded"),
|
||||
tip: NSLocalizedString("Wait for the quota to reset, or configure your wallet provider to allow a higher limit.", comment: "Tip for quota exceeded"),
|
||||
technical_info: "Wallet reported a quota exceeded error. Message: \(self.message ?? "No further details provided")"
|
||||
)
|
||||
case .restricted:
|
||||
return .init(
|
||||
user_visible_description: NSLocalizedString("This operation is restricted by your wallet.", comment: "Error description for restricted operation"),
|
||||
tip: NSLocalizedString("Check your account permissions or contact support.", comment: "Tip for restricted operation"),
|
||||
technical_info: "Wallet returned a restricted error. Message: \(self.message ?? "No further details provided")"
|
||||
)
|
||||
case .unauthorized:
|
||||
return .init(
|
||||
user_visible_description: NSLocalizedString("You are not authorized to perform this action with your wallet.", comment: "Error description for unauthorized access"),
|
||||
tip: NSLocalizedString("Please verify your credentials or permissions.", comment: "Tip for unauthorized access"),
|
||||
technical_info: "Wallet returned an unauthorized error. Message: \(self.message ?? "No further details provided")"
|
||||
)
|
||||
case .internalError:
|
||||
return .init(
|
||||
user_visible_description: NSLocalizedString("An internal error occurred in your wallet.", comment: "Error description for an internal error"),
|
||||
tip: NSLocalizedString("Try restarting your wallet or contacting support if the problem persists.", comment: "Tip for internal error"),
|
||||
technical_info: "Wallet reported an internal error. Message: \(self.message ?? "No further details provided")"
|
||||
)
|
||||
case .other:
|
||||
return .init(
|
||||
user_visible_description: NSLocalizedString("An unspecified error occurred in your wallet.", comment: "Error description for an unspecified error"),
|
||||
tip: NSLocalizedString("Please try again or contact your wallet provider for further assistance.", comment: "Tip for unspecified error"),
|
||||
technical_info: "Wallet returned an error: \(self.message ?? "No further details provided")"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,11 @@ extension WalletConnect {
|
||||
/// Pay an invoice
|
||||
case payInvoice(
|
||||
/// bolt-11 invoice string
|
||||
invoice: String
|
||||
invoice: String,
|
||||
/// The full description of the invoice (If description does not fit in the BOLT-11 invoice, this is the pre-image of the description hash)
|
||||
description: String?,
|
||||
/// Optional metadata object containing more information
|
||||
metadata: Metadata?
|
||||
)
|
||||
/// Get the current wallet balance
|
||||
case getBalance
|
||||
@@ -33,6 +37,38 @@ extension WalletConnect {
|
||||
type: String?
|
||||
)
|
||||
|
||||
static func payZapRequest(invoice: String, zapRequest: NostrEvent?) -> Self {
|
||||
guard let zapRequest, let zapRequestEncoded = encode_json(zapRequest) else {
|
||||
return WalletConnect.Request.payInvoice(
|
||||
invoice: invoice,
|
||||
description: nil,
|
||||
metadata: nil
|
||||
)
|
||||
}
|
||||
return WalletConnect.Request.payInvoice(
|
||||
invoice: invoice,
|
||||
description: zapRequestEncoded,
|
||||
metadata: .init(nostr: zapRequest)
|
||||
)
|
||||
}
|
||||
|
||||
struct Metadata: Codable, Equatable, Hashable {
|
||||
/// NIP-57-compliant `kind:9734` zap request event
|
||||
let nostr: NostrEvent?
|
||||
|
||||
init(nostr: NostrEvent?) {
|
||||
self.nostr = nostr
|
||||
}
|
||||
|
||||
init(from decoder: any Decoder) throws {
|
||||
let container: KeyedDecodingContainer<WalletConnect.Request.Metadata.CodingKeys> = try decoder.container(keyedBy: WalletConnect.Request.Metadata.CodingKeys.self)
|
||||
guard let decodedZapRequest = try? container.decodeIfPresent(NostrEvent.self, forKey: WalletConnect.Request.Metadata.CodingKeys.nostr) else {
|
||||
self.nostr = nil // Be lenient and fallback to nil if the NWC provider provided something invalid, since metadata is not strictly spec'd yet.
|
||||
return
|
||||
}
|
||||
self.nostr = decodedZapRequest
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Interface
|
||||
|
||||
@@ -61,7 +97,7 @@ extension WalletConnect {
|
||||
|
||||
/// Keys for the JSON inside the "params" object
|
||||
private enum ParamKeys: String, CodingKey {
|
||||
case invoice
|
||||
case invoice, description, metadata
|
||||
case from, until, limit, offset, unpaid, type
|
||||
}
|
||||
|
||||
@@ -82,7 +118,9 @@ extension WalletConnect {
|
||||
case Method.payInvoice.rawValue:
|
||||
let paramsContainer = try container.nestedContainer(keyedBy: ParamKeys.self, forKey: .params)
|
||||
let invoice = try paramsContainer.decode(String.self, forKey: .invoice)
|
||||
self = .payInvoice(invoice: invoice)
|
||||
let description: String? = try paramsContainer.decodeIfPresent(String.self, forKey: .description)
|
||||
let metadata: Metadata? = try paramsContainer.decodeIfPresent(Metadata.self, forKey: .metadata)
|
||||
self = .payInvoice(invoice: invoice, description: description, metadata: metadata)
|
||||
|
||||
case Method.getBalance.rawValue:
|
||||
// No params to decode
|
||||
@@ -112,10 +150,12 @@ extension WalletConnect {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
switch self {
|
||||
case .payInvoice(let invoice):
|
||||
case .payInvoice(let invoice, let description, let metadata):
|
||||
try container.encode(Method.payInvoice.rawValue, forKey: .method)
|
||||
var paramsContainer = container.nestedContainer(keyedBy: ParamKeys.self, forKey: .params)
|
||||
try paramsContainer.encode(invoice, forKey: .invoice)
|
||||
try paramsContainer.encodeIfPresent(description, forKey: .description)
|
||||
try paramsContainer.encodeIfPresent(metadata, forKey: .metadata)
|
||||
|
||||
case .getBalance:
|
||||
try container.encode(Method.getBalance.rawValue, forKey: .method)
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
// Created by Daniel D’Aquino on 2025-03-10.
|
||||
//
|
||||
|
||||
import Combine
|
||||
|
||||
extension WalletConnect {
|
||||
/// Models a response from the NWC provider
|
||||
struct Response: Decodable {
|
||||
@@ -50,35 +52,80 @@ extension WalletConnect {
|
||||
let req_id: NoteId
|
||||
let response: Response
|
||||
|
||||
init?(from: NostrEvent, nwc: WalletConnect.ConnectURL) async {
|
||||
guard let note_id = from.referenced_ids.first else {
|
||||
return nil
|
||||
}
|
||||
|
||||
self.req_id = note_id
|
||||
|
||||
let ares = Task {
|
||||
guard let json = decrypt_dm(nwc.keypair.privkey, pubkey: nwc.pubkey, content: from.content, encoding: .base64),
|
||||
let resp: WalletConnect.Response = decode_json(json)
|
||||
else {
|
||||
let resp: WalletConnect.Response? = nil
|
||||
return resp
|
||||
}
|
||||
|
||||
return resp
|
||||
}
|
||||
init(from event: NostrEvent, nwc: WalletConnect.ConnectURL) async throws(InitializationError) {
|
||||
guard event.pubkey == nwc.pubkey else { throw .incorrectAuthorPubkey }
|
||||
|
||||
guard let res = await ares.value else {
|
||||
return nil
|
||||
guard let referencedNoteId = event.referenced_ids.first else { throw .missingRequestIdReference }
|
||||
|
||||
self.req_id = referencedNoteId
|
||||
|
||||
var json = ""
|
||||
do {
|
||||
json = try NIP04.decryptContent(
|
||||
recipientPrivateKey: nwc.keypair.privkey,
|
||||
senderPubkey: nwc.pubkey,
|
||||
content: event.content,
|
||||
encoding: .base64
|
||||
)
|
||||
}
|
||||
|
||||
self.response = res
|
||||
catch { throw .failedToDecrypt(error) }
|
||||
|
||||
do {
|
||||
let response: WalletConnect.Response = try decode_json_throwing(json)
|
||||
self.response = response
|
||||
}
|
||||
catch { throw .failedToDecodeJSON(error) }
|
||||
}
|
||||
|
||||
enum InitializationError: Error {
|
||||
case incorrectAuthorPubkey
|
||||
case missingRequestIdReference
|
||||
case failedToDecodeJSON(any Error)
|
||||
case failedToDecrypt(any Error)
|
||||
}
|
||||
}
|
||||
|
||||
struct WalletResponseErr: Codable {
|
||||
let code: String?
|
||||
let code: Code?
|
||||
let message: String?
|
||||
|
||||
enum Code: String, Codable {
|
||||
/// The client is sending commands too fast. It should retry in a few seconds.
|
||||
case rateLimited = "RATE_LIMITED"
|
||||
/// The command is not known or is intentionally not implemented.
|
||||
case notImplemented = "NOT_IMPLEMENTED"
|
||||
/// The wallet does not have enough funds to cover a fee reserve or the payment amount.
|
||||
case insufficientBalance = "INSUFFICIENT_BALANCE"
|
||||
/// The wallet has exceeded its spending quota.
|
||||
case quotaExceeded = "QUOTA_EXCEEDED"
|
||||
/// This public key is not allowed to do this operation.
|
||||
case restricted = "RESTRICTED"
|
||||
/// This public key has no wallet connected.
|
||||
case unauthorized = "UNAUTHORIZED"
|
||||
/// An internal error.
|
||||
case internalError = "INTERNAL"
|
||||
/// Other error.
|
||||
case other = "OTHER"
|
||||
}
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case code, message
|
||||
}
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
// Attempt to decode the code as a String
|
||||
if let codeString = try container.decodeIfPresent(String.self, forKey: .code),
|
||||
let validCode = Code(rawValue: codeString) {
|
||||
self.code = validCode
|
||||
} else {
|
||||
// If the code is either missing or not one of the allowed cases, set it to nil
|
||||
self.code = nil
|
||||
}
|
||||
|
||||
self.message = try container.decodeIfPresent(String.self, forKey: .message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ extension WalletConnect {
|
||||
static func subscribe(url: WalletConnectURL, pool: RelayPool) {
|
||||
var filter = NostrFilter(kinds: [.nwc_response])
|
||||
filter.authors = [url.pubkey]
|
||||
filter.pubkeys = [url.keypair.pubkey]
|
||||
filter.limit = 0
|
||||
let sub = NostrSubscribe(filters: [filter], sub_id: "nwc")
|
||||
|
||||
@@ -40,8 +41,9 @@ extension WalletConnect {
|
||||
/// - on_flush: A callback to call after the event has been flushed to the network
|
||||
/// - Returns: The Nostr Event that was sent to the network, representing the request that was made
|
||||
@discardableResult
|
||||
static func pay(url: WalletConnectURL, pool: RelayPool, post: PostBox, invoice: String, delay: TimeInterval? = 5.0, on_flush: OnFlush? = nil) -> NostrEvent? {
|
||||
let req = WalletConnect.Request.payInvoice(invoice: invoice)
|
||||
static func pay(url: WalletConnectURL, pool: RelayPool, post: PostBox, invoice: String, zap_request: NostrEvent?, delay: TimeInterval? = 5.0, on_flush: OnFlush? = nil) -> NostrEvent? {
|
||||
|
||||
let req = WalletConnect.Request.payZapRequest(invoice: invoice, zapRequest: zap_request)
|
||||
guard let ev = req.to_nostr_event(to_pk: url.pubkey, keypair: url.keypair) else {
|
||||
return nil
|
||||
}
|
||||
@@ -142,7 +144,7 @@ extension WalletConnect {
|
||||
}
|
||||
|
||||
print("damus-donation donating...")
|
||||
WalletConnect.pay(url: nwc, pool: pool, post: postbox, invoice: invoice, delay: nil)
|
||||
WalletConnect.pay(url: nwc, pool: pool, post: postbox, invoice: invoice, zap_request: nil, delay: nil)
|
||||
}
|
||||
|
||||
/// Handles a received Nostr Wallet Connect error
|
||||
|
||||
@@ -86,7 +86,7 @@ extension WalletConnect {
|
||||
let created_at: UInt64 // unixtimestamp, // invoice/payment creation time
|
||||
let expires_at: UInt64? // unixtimestamp, // invoice expiration time, optional if not applicable
|
||||
let settled_at: UInt64? // unixtimestamp, // invoice/payment settlement time, optional if unpaid
|
||||
//"metadata": {} // generic metadata that can be used to add things like zap/boostagram details for a payer name/comment/etc.
|
||||
let metadata: WalletConnect.Request.Metadata? // generic metadata that can be used to add things like zap/boostagram details for a payer name/comment/etc.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +50,10 @@ struct ErrorView: View {
|
||||
.cornerRadius(10)
|
||||
.padding(.vertical, 30)
|
||||
|
||||
if let technical_info = error.technical_info {
|
||||
ErrorTechInfoCopyButton(errorInfo: technical_info)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
if let damus_state, damus_state.is_privkey_user {
|
||||
@@ -69,6 +73,39 @@ struct ErrorView: View {
|
||||
.padding(.top, 20)
|
||||
}
|
||||
|
||||
struct ErrorTechInfoCopyButton: View {
|
||||
let errorInfo: String
|
||||
@State var copied: Bool = false
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
if !copied {
|
||||
Button(action: {
|
||||
UIPasteboard.general.string = errorInfo
|
||||
copied = true
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
||||
copied = false
|
||||
}
|
||||
}, label: {
|
||||
HStack {
|
||||
Image(systemName: "square.on.square.dashed")
|
||||
Text("Copy technical information", comment: "Button label to allow user to copy technical information from an error screen (usually to provide our support team for further troubleshooting)")
|
||||
}
|
||||
})
|
||||
}
|
||||
else {
|
||||
HStack {
|
||||
Image(systemName: "checkmark.circle")
|
||||
Text("Copied!", comment: "Label indicating that the error technical information was successfully copied to the clipboard, which shows up as soon as the user clicks the copy button.")
|
||||
}
|
||||
.foregroundStyle(.damusGreen)
|
||||
}
|
||||
}
|
||||
.padding(.vertical, 20)
|
||||
}
|
||||
}
|
||||
|
||||
/// An error that is displayed to the user, and can be sent to the Developers as well.
|
||||
struct UserPresentableError {
|
||||
/// The description of the error to be shown to the user
|
||||
@@ -113,7 +150,7 @@ struct ErrorView: View {
|
||||
error: .init(
|
||||
user_visible_description: "We are still too early",
|
||||
tip: "Stay humble, keep building, stack sats",
|
||||
technical_info: nil
|
||||
technical_info: "UTXOs too small, must stack more sats"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -401,7 +401,7 @@ struct BlurOverlayView: View {
|
||||
.foregroundStyle(.white)
|
||||
.bold()
|
||||
.padding(EdgeInsets(top: 5, leading: 10, bottom: 0, trailing: 10))
|
||||
Text(NSLocalizedString("Media from someone you \n don't follow", comment: "Label on the image blur mask"))
|
||||
Text(NSLocalizedString("Media from someone you don't follow", comment: "Label on the image blur mask"))
|
||||
.multilineTextAlignment(.center)
|
||||
.foregroundStyle(Color.white)
|
||||
.font(.title2)
|
||||
|
||||
@@ -46,7 +46,7 @@ struct PubkeyView: View {
|
||||
let bech32 = pubkey.npub
|
||||
|
||||
HStack {
|
||||
Text(verbatim: "\(abbrev_pubkey(bech32, amount: sidemenu ? 12 : 16))")
|
||||
Text(verbatim: "\(abbrev_identifier(bech32, amount: sidemenu ? 12 : 16))")
|
||||
.font(sidemenu ? .system(size: 10) : .footnote)
|
||||
.foregroundColor(keyColor())
|
||||
.padding(5)
|
||||
|
||||
@@ -11,12 +11,14 @@ struct RelayView: View {
|
||||
let state: DamusState
|
||||
let relay: RelayURL
|
||||
let recommended: Bool
|
||||
/// Disables navigation link
|
||||
let disableNavLink: Bool
|
||||
@ObservedObject private var model_cache: RelayModelCache
|
||||
|
||||
@State var relay_state: Bool
|
||||
@Binding var showActionButtons: Bool
|
||||
|
||||
init(state: DamusState, relay: RelayURL, showActionButtons: Binding<Bool>, recommended: Bool) {
|
||||
init(state: DamusState, relay: RelayURL, showActionButtons: Binding<Bool>, recommended: Bool, disableNavLink: Bool = false) {
|
||||
self.state = state
|
||||
self.relay = relay
|
||||
self.recommended = recommended
|
||||
@@ -24,6 +26,7 @@ struct RelayView: View {
|
||||
_showActionButtons = showActionButtons
|
||||
let relay_state = RelayView.get_relay_state(pool: state.nostrNetwork.pool, relay: relay)
|
||||
self._relay_state = State(initialValue: relay_state)
|
||||
self.disableNavLink = disableNavLink
|
||||
}
|
||||
|
||||
static func get_relay_state(pool: RelayPool, relay: RelayURL) -> Bool {
|
||||
@@ -96,10 +99,12 @@ struct RelayView: View {
|
||||
RelayStatusView(connection: relay_connection)
|
||||
}
|
||||
|
||||
Image("chevron-large-right")
|
||||
.resizable()
|
||||
.frame(width: 15, height: 15)
|
||||
.foregroundColor(.gray)
|
||||
if !disableNavLink {
|
||||
Image("chevron-large-right")
|
||||
.resizable()
|
||||
.frame(width: 15, height: 15)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
}
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
@@ -108,7 +113,9 @@ struct RelayView: View {
|
||||
self.relay_state = RelayView.get_relay_state(pool: state.nostrNetwork.pool, relay: self.relay)
|
||||
}
|
||||
.onTapGesture {
|
||||
state.nav.push(route: Route.RelayDetail(relay: relay, metadata: model_cache.model(with_relay_id: relay)?.metadata))
|
||||
if !disableNavLink {
|
||||
state.nav.push(route: Route.RelayDetail(relay: relay, metadata: model_cache.model(with_relay_id: relay)?.metadata))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -63,6 +63,11 @@ struct ZapSettingsView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Section(NSLocalizedString("NWC wallet", comment: "Title for section in zap settings that controls general NWC wallet settings.")) {
|
||||
Toggle(NSLocalizedString("Disable high balance warning", comment: "Setting to disable high balance warnings on the user's wallet"), isOn: $settings.dismiss_wallet_high_balance_warning)
|
||||
.toggleStyle(.switch)
|
||||
}
|
||||
}
|
||||
.navigationTitle(NSLocalizedString("Zaps", comment: "Navigation title for zap settings."))
|
||||
.onReceive(handle_notify(.switched_timeline)) { _ in
|
||||
|
||||
@@ -25,10 +25,14 @@ import SwiftUI
|
||||
|
||||
/// The URL of the video
|
||||
let url: URL
|
||||
|
||||
|
||||
// MARK: Internal state
|
||||
|
||||
/// The underlying AVPlayer that we are wrapping.
|
||||
/// This is not public because we don't want any callers of this class controlling the `AVPlayer` directly, we want them to go through our interface
|
||||
/// This measure helps avoid state inconsistencies and other flakiness. DO NOT USE THIS OUTSIDE `DamusVideoPlayer`
|
||||
private let player: AVPlayer
|
||||
private var player: AVPlayer
|
||||
|
||||
|
||||
// MARK: SwiftUI-friendly interface
|
||||
@@ -100,16 +104,39 @@ import SwiftUI
|
||||
private var videoIsPlayingObserver: NSKeyValueObservation?
|
||||
|
||||
|
||||
// MARK: - Initialization
|
||||
// MARK: - Initialization, deinitialization and reinitialization
|
||||
|
||||
public init(url: URL) {
|
||||
self.url = url
|
||||
self.player = AVPlayer(playerItem: AVPlayerItem(url: url))
|
||||
self.video_size = nil
|
||||
|
||||
Task { await self.load() }
|
||||
}
|
||||
|
||||
func reinitializePlayer() {
|
||||
Log.info("DamusVideoPlayer: Reinitializing internal player…", for: .video_coordination)
|
||||
|
||||
// Tear down
|
||||
videoSizeObserver?.invalidate()
|
||||
videoDurationObserver?.invalidate()
|
||||
videoIsPlayingObserver?.invalidate()
|
||||
|
||||
// Reset player
|
||||
self.player = AVPlayer(playerItem: AVPlayerItem(url: url))
|
||||
|
||||
// Load once again
|
||||
Task {
|
||||
await load()
|
||||
}
|
||||
}
|
||||
|
||||
/// Internally loads this class
|
||||
private func load() async {
|
||||
Task {
|
||||
has_audio = await self.video_has_audio()
|
||||
is_loading = false
|
||||
}
|
||||
|
||||
player.isMuted = is_muted
|
||||
|
||||
@@ -126,6 +153,13 @@ import SwiftUI
|
||||
observeVideoIsPlaying()
|
||||
}
|
||||
|
||||
deinit {
|
||||
// These cannot be moved into their own functions due to contraints on structured concurrency
|
||||
videoSizeObserver?.invalidate()
|
||||
videoDurationObserver?.invalidate()
|
||||
videoIsPlayingObserver?.invalidate()
|
||||
}
|
||||
|
||||
// MARK: - Observers
|
||||
// Functions that allow us to observe certain variables and publish their changes for view updates
|
||||
// These are all private because they are part of the internal logic
|
||||
@@ -175,11 +209,6 @@ import SwiftUI
|
||||
|
||||
// MARK: - Other internal logic functions
|
||||
|
||||
private func load() async {
|
||||
has_audio = await self.video_has_audio()
|
||||
is_loading = false
|
||||
}
|
||||
|
||||
private func video_has_audio() async -> Bool {
|
||||
do {
|
||||
let hasAudibleTracks = ((try await player.currentItem?.asset.loadMediaSelectionGroup(for: .audible)) != nil)
|
||||
@@ -196,17 +225,16 @@ import SwiftUI
|
||||
player.play()
|
||||
}
|
||||
|
||||
// MARK: - Deinit
|
||||
|
||||
deinit {
|
||||
videoSizeObserver?.invalidate()
|
||||
videoDurationObserver?.invalidate()
|
||||
videoIsPlayingObserver?.invalidate()
|
||||
}
|
||||
|
||||
// MARK: - Convenience interface functions
|
||||
|
||||
func play() {
|
||||
switch self.player.status {
|
||||
case .failed:
|
||||
Log.error("DamusVideoPlayer: Failed to play video. Error: '%s'", for: .video_coordination, self.player.error?.localizedDescription ?? "no error")
|
||||
self.reinitializePlayer()
|
||||
default:
|
||||
break
|
||||
}
|
||||
self.is_playing = true
|
||||
}
|
||||
|
||||
@@ -236,9 +264,9 @@ extension DamusVideoPlayer {
|
||||
}
|
||||
|
||||
func updateUIViewController(_ uiViewController: AVPlayerViewController, context: Context) {
|
||||
if uiViewController.player == nil {
|
||||
uiViewController.player = player.player
|
||||
}
|
||||
/// - If `player.player` is changed (e.g. `DamusVideoPlayer` gets reinitialized), this will refresh the video player to the new working one.
|
||||
/// - If `player.player` is unchanged, this is basically a very low cost no-op (Because `AVPlayer` is a class type, this assignment only copies a pointer, not a large structure)
|
||||
uiViewController.player = player.player
|
||||
}
|
||||
|
||||
static func dismantleUIViewController(_ uiViewController: AVPlayerViewController, coordinator: ()) {
|
||||
|
||||
@@ -70,7 +70,7 @@ struct ConnectWalletView: View {
|
||||
|
||||
Spacer()
|
||||
|
||||
NWCSettings.AccountDetailsView(nwc: nwc)
|
||||
NWCSettings.AccountDetailsView(nwc: nwc, damus_state: nil)
|
||||
|
||||
Spacer()
|
||||
|
||||
@@ -152,7 +152,7 @@ struct ConnectWalletView: View {
|
||||
CoinosButton() {
|
||||
self.show_coinos_options = true
|
||||
}
|
||||
Text("Coinos is a service operated by a third-party. We have no access to your Coinos wallet.", comment: "Small caption with a disclaimer that Damus does not own or have access to Coinos wallets, Coinos is a third-party service.")
|
||||
Text("Coinos is a service operated by a third-party. The Damus team has no access to your wallet.", comment: "Small caption with a disclaimer that Damus does not own or have access to Coinos wallets, Coinos is a third-party service.")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
.multilineTextAlignment(.center)
|
||||
|
||||
@@ -134,7 +134,10 @@ struct NWCSettings: View {
|
||||
SupportDamus
|
||||
.padding(.bottom)
|
||||
|
||||
AccountDetailsView(nwc: nwc)
|
||||
AccountDetailsView(nwc: nwc, damus_state: damus_state)
|
||||
|
||||
Toggle(NSLocalizedString("Disable high balance warning", comment: "Setting to disable high balance warnings on the user's wallet"), isOn: $settings.dismiss_wallet_high_balance_warning)
|
||||
.toggleStyle(.switch)
|
||||
|
||||
Button(action: {
|
||||
self.model.disconnect()
|
||||
@@ -182,6 +185,7 @@ struct NWCSettings: View {
|
||||
|
||||
struct AccountDetailsView: View {
|
||||
let nwc: WalletConnect.ConnectURL
|
||||
let damus_state: DamusState?
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
@@ -195,11 +199,17 @@ struct NWCSettings: View {
|
||||
Text("Routing", comment: "Label indicating the routing address for Nostr Wallet Connect payments. In other words, the relay used by the NWC wallet provider")
|
||||
.font(.headline)
|
||||
|
||||
Text(nwc.relay.absoluteString)
|
||||
.font(.body)
|
||||
.fontWeight(.bold)
|
||||
.foregroundColor(.gray)
|
||||
.padding(.bottom)
|
||||
if let damus_state {
|
||||
RelayView(state: damus_state, relay: nwc.relay, showActionButtons: .constant(false), recommended: false, disableNavLink: true)
|
||||
.padding(.bottom)
|
||||
}
|
||||
else {
|
||||
Text(nwc.relay.absoluteString)
|
||||
.font(.body)
|
||||
.fontWeight(.bold)
|
||||
.foregroundColor(.gray)
|
||||
.padding(.bottom)
|
||||
}
|
||||
|
||||
if let lud16 = nwc.lud16 {
|
||||
Text("Account", comment: "Label for the user account information with the Nostr Wallet Connect wallet provider.")
|
||||
|
||||
@@ -20,13 +20,16 @@ struct TransactionView: View {
|
||||
let created_at = Date.init(timeIntervalSince1970: TimeInterval(transaction.created_at))
|
||||
let formatter = RelativeDateTimeFormatter()
|
||||
let relativeDate = formatter.localizedString(for: created_at, relativeTo: Date.now)
|
||||
let event = decode_nostr_event_json(transaction.description ?? "")
|
||||
let pubkey = (event?.pubkey ?? ANON_PUBKEY)
|
||||
let event = decode_nostr_event_json(transaction.description ?? "") ?? transaction.metadata?.nostr
|
||||
let pubkey = self.pubkeyToDisplay(for: event, isIncomingTransaction: isIncomingTransaction) ?? ANON_PUBKEY
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
HStack(alignment: .center) {
|
||||
ZStack {
|
||||
ProfilePicView(pubkey: pubkey, size: 45, highlight: .custom(.damusAdaptableBlack, 0.1), profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation)
|
||||
.onTapGesture {
|
||||
damus_state.nav.push(route: Route.ProfileByKey(pubkey: pubkey))
|
||||
}
|
||||
|
||||
Image(txType)
|
||||
.resizable()
|
||||
@@ -71,6 +74,16 @@ struct TransactionView: View {
|
||||
}
|
||||
}
|
||||
|
||||
func pubkeyToDisplay(for zapRequest: NostrEvent?, isIncomingTransaction: Bool) -> Pubkey? {
|
||||
guard let zapRequest else { return nil }
|
||||
if isIncomingTransaction {
|
||||
return zapRequest.pubkey // We want to know who sent it to us
|
||||
}
|
||||
else {
|
||||
return zapRequest.referenced_pubkeys.first // We want to know who we sent it to
|
||||
}
|
||||
}
|
||||
|
||||
func userDisplayName(pubkey: Pubkey) -> String {
|
||||
let profile_txn = damus_state.profiles.lookup(id: pubkey, txn_name: "txview-profile")
|
||||
let profile = profile_txn?.unsafeUnownedValue
|
||||
@@ -136,10 +149,10 @@ struct TransactionsView: View {
|
||||
|
||||
struct TransactionsView_Previews: PreviewProvider {
|
||||
static let tds = test_damus_state
|
||||
static let transaction1: WalletConnect.Transaction = WalletConnect.Transaction(type: "incoming", invoice: "", description: "{\"id\":\"7c0999a5870ca3ba0186a29a8650152b555cee29b53b5b8747d8a3798042d01c\",\"pubkey\":\"b8851a06dfd79d48fc325234a15e9a46a32a0982a823b54cdf82514b9b120ba1\",\"created_at\":1736383715,\"kind\":9734,\"tags\":[[\"p\",\"520830c334a3f79f88cac934580d26f91a7832c6b21fb9625690ea2ed81b5626\"],[\"amount\",\"21000\"],[\"e\",\"a25e152a4cd1b3bbc3d22e8e9315d8ea1f35c227b2f212c7cff18abff36fa208\"],[\"relays\",\"wss://nos.lol\",\"wss://nostr.wine\",\"wss://premium.primal.net\",\"wss://relay.damus.io\",\"wss://relay.nostr.band\",\"wss://relay.nostrarabia.com\"]],\"content\":\"🫡 Onward!\",\"sig\":\"e77d16822fa21b9c2e6b580b51c470588052c14aeb222f08f0e735027e366157c8742a6d5cb850780c2bf44ac63d89b048e5cc56dd47a1bfc740a3173e578f4e\"}", description_hash: "", preimage: "", payment_hash: "1234567890", amount: 21000, fees_paid: 0, created_at: 1737736866, expires_at: 0, settled_at: 0)
|
||||
static let transaction2: WalletConnect.Transaction = WalletConnect.Transaction(type: "incoming", invoice: "", description: "", description_hash: "", preimage: "", payment_hash: "123456789033", amount: 100000000, fees_paid: 0, created_at: 1737690090, expires_at: 0, settled_at: 0)
|
||||
static let transaction3: WalletConnect.Transaction = WalletConnect.Transaction(type: "outgoing", invoice: "", description: "", description_hash: "", preimage: "", payment_hash: "123456789042", amount: 303000, fees_paid: 0, created_at: 1737590101, expires_at: 0, settled_at: 0)
|
||||
static let transaction4: WalletConnect.Transaction = WalletConnect.Transaction(type: "incoming", invoice: "", description: "", description_hash: "", preimage: "", payment_hash: "1234567890662", amount: 720000, fees_paid: 0, created_at: 1737090300, expires_at: 0, settled_at: 0)
|
||||
static let transaction1: WalletConnect.Transaction = WalletConnect.Transaction(type: "incoming", invoice: "", description: "{\"id\":\"7c0999a5870ca3ba0186a29a8650152b555cee29b53b5b8747d8a3798042d01c\",\"pubkey\":\"b8851a06dfd79d48fc325234a15e9a46a32a0982a823b54cdf82514b9b120ba1\",\"created_at\":1736383715,\"kind\":9734,\"tags\":[[\"p\",\"520830c334a3f79f88cac934580d26f91a7832c6b21fb9625690ea2ed81b5626\"],[\"amount\",\"21000\"],[\"e\",\"a25e152a4cd1b3bbc3d22e8e9315d8ea1f35c227b2f212c7cff18abff36fa208\"],[\"relays\",\"wss://nos.lol\",\"wss://nostr.wine\",\"wss://premium.primal.net\",\"wss://relay.damus.io\",\"wss://relay.nostr.band\",\"wss://relay.nostrarabia.com\"]],\"content\":\"🫡 Onward!\",\"sig\":\"e77d16822fa21b9c2e6b580b51c470588052c14aeb222f08f0e735027e366157c8742a6d5cb850780c2bf44ac63d89b048e5cc56dd47a1bfc740a3173e578f4e\"}", description_hash: "", preimage: "", payment_hash: "1234567890", amount: 21000, fees_paid: 0, created_at: 1737736866, expires_at: 0, settled_at: 0, metadata: nil)
|
||||
static let transaction2: WalletConnect.Transaction = WalletConnect.Transaction(type: "incoming", invoice: "", description: "", description_hash: "", preimage: "", payment_hash: "123456789033", amount: 100000000, fees_paid: 0, created_at: 1737690090, expires_at: 0, settled_at: 0, metadata: nil)
|
||||
static let transaction3: WalletConnect.Transaction = WalletConnect.Transaction(type: "outgoing", invoice: "", description: "", description_hash: "", preimage: "", payment_hash: "123456789042", amount: 303000, fees_paid: 0, created_at: 1737590101, expires_at: 0, settled_at: 0, metadata: nil)
|
||||
static let transaction4: WalletConnect.Transaction = WalletConnect.Transaction(type: "incoming", invoice: "", description: "", description_hash: "", preimage: "", payment_hash: "1234567890662", amount: 720000, fees_paid: 0, created_at: 1737090300, expires_at: 0, settled_at: 0, metadata: nil)
|
||||
static var test_transactions: [WalletConnect.Transaction] = [transaction1, transaction2, transaction3, transaction4]
|
||||
|
||||
static var previews: some View {
|
||||
|
||||
@@ -24,7 +24,7 @@ struct WalletView: View {
|
||||
func MainWalletView(nwc: WalletConnectURL) -> some View {
|
||||
ScrollView {
|
||||
VStack(spacing: 35) {
|
||||
if let balance = model.balance, balance > WALLET_WARNING_THRESHOLD {
|
||||
if let balance = model.balance, balance > WALLET_WARNING_THRESHOLD && !settings.dismiss_wallet_high_balance_warning {
|
||||
VStack(spacing: 10) {
|
||||
HStack {
|
||||
Image(systemName: "exclamationmark.circle")
|
||||
@@ -36,7 +36,16 @@ struct WalletView: View {
|
||||
|
||||
Text("If your wallet balance is getting high, it's important to understand how to keep your funds secure. Please consider learning the best practices to ensure your assets remain safe. [Click here](https://damus.io/docs/wallet/high-balance-safety-reminder/) to learn more.", comment: "Text reminding the user has a high balance, recommending them to learn about self-custody")
|
||||
.foregroundStyle(.damusWarningSecondary)
|
||||
.accentColor(.damusWarningTertiary)
|
||||
.opacity(0.8)
|
||||
|
||||
Button(action: {
|
||||
settings.dismiss_wallet_high_balance_warning = true
|
||||
}, label: {
|
||||
Text("Dismiss", comment: "Button label to dismiss the safety reminder that the user's wallet has a high balance")
|
||||
})
|
||||
.bold()
|
||||
.foregroundStyle(.damusWarningTertiary)
|
||||
}
|
||||
.padding()
|
||||
.overlay(
|
||||
|
||||
@@ -80,7 +80,7 @@ class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDele
|
||||
UNUserNotificationCenter.current().delegate = self
|
||||
SKPaymentQueue.default().add(StoreObserver.standard)
|
||||
registerNotificationCategories()
|
||||
migrateKingfisherCacheIfNeeded()
|
||||
ImageCacheMigrations.migrateKingfisherCacheIfNeeded()
|
||||
configureKingfisherCache()
|
||||
return true
|
||||
}
|
||||
@@ -113,50 +113,8 @@ class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDele
|
||||
completionHandler()
|
||||
}
|
||||
|
||||
private func migrateKingfisherCacheIfNeeded() {
|
||||
let fileManager = FileManager.default
|
||||
let defaults = UserDefaults.standard
|
||||
let migrationKey = "KingfisherCacheMigrated"
|
||||
|
||||
// Check if migration has already been done
|
||||
guard !defaults.bool(forKey: migrationKey) else { return }
|
||||
|
||||
// Get the default Kingfisher cache (before we override it)
|
||||
let defaultCache = ImageCache.default
|
||||
let oldCachePath = defaultCache.diskStorage.directoryURL.path
|
||||
|
||||
// New shared cache location
|
||||
guard let groupURL = fileManager.containerURL(forSecurityApplicationGroupIdentifier: Constants.DAMUS_APP_GROUP_IDENTIFIER) else { return }
|
||||
let newCachePath = groupURL.appendingPathComponent(Constants.IMAGE_CACHE_DIRNAME).path
|
||||
|
||||
// Check if the old cache exists
|
||||
if fileManager.fileExists(atPath: oldCachePath) {
|
||||
do {
|
||||
// Move the old cache to the new location
|
||||
try fileManager.moveItem(atPath: oldCachePath, toPath: newCachePath)
|
||||
print("Successfully migrated Kingfisher cache to \(newCachePath)")
|
||||
} catch {
|
||||
print("Failed to migrate cache: \(error)")
|
||||
// Optionally, copy instead of move if you want to preserve the old cache as a fallback
|
||||
do {
|
||||
try fileManager.copyItem(atPath: oldCachePath, toPath: newCachePath)
|
||||
print("Copied cache instead due to error")
|
||||
} catch {
|
||||
print("Failed to copy cache: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mark migration as complete
|
||||
defaults.set(true, forKey: migrationKey)
|
||||
}
|
||||
|
||||
private func configureKingfisherCache() {
|
||||
guard let groupURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: Constants.DAMUS_APP_GROUP_IDENTIFIER) else {
|
||||
return
|
||||
}
|
||||
|
||||
let cachePath = groupURL.appendingPathComponent(Constants.IMAGE_CACHE_DIRNAME)
|
||||
let cachePath = ImageCacheMigrations.kingfisherCachePath()
|
||||
if let cache = try? ImageCache(name: "sharedCache", cacheDirectoryURL: cachePath) {
|
||||
KingfisherManager.shared.cache = cache
|
||||
}
|
||||
|
||||
@@ -293,6 +293,11 @@ Label for filter for all notifications.</note>
|
||||
<target>Already on Nostr?</target>
|
||||
<note>Ask the user if they already have an account on Nostr</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Also click here if you had a one-click setup before." xml:space="preserve">
|
||||
<source>Also click here if you had a one-click setup before.</source>
|
||||
<target>Also click here if you had a one-click setup before.</target>
|
||||
<note>Button description hint for users who may want to do a one-click setup.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Always show onboarding" xml:space="preserve">
|
||||
<source>Always show onboarding</source>
|
||||
<target>Always show onboarding</target>
|
||||
@@ -303,16 +308,21 @@ Label for filter for all notifications.</note>
|
||||
<target>An additional percentage of each zap will be sent to support Damus development</target>
|
||||
<note>Text indicating that they can contribute zaps to support Damus development.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="An unexpected error happened while trying to create the new contact list. Please contact support." xml:space="preserve">
|
||||
<source>An unexpected error happened while trying to create the new contact list. Please contact support.</source>
|
||||
<target>An unexpected error happened while trying to create the new contact list. Please contact support.</target>
|
||||
<note>Error message for a failed contact list reset operation</note>
|
||||
<trans-unit id="An unexpected error happened while trying to perform this action. Please contact support." xml:space="preserve">
|
||||
<source>An unexpected error happened while trying to perform this action. Please contact support.</source>
|
||||
<target>An unexpected error happened while trying to perform this action. Please contact support.</target>
|
||||
<note>Error message for a failed reset/repair operation</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="An unexpected error occurred. Please contact Damus support via [Nostr](damus:npub18m76awca3y37hkvuneavuw6pjj4525fw90necxmadrvjg0sdy6qsngq955) or [email](support@damus.io) with the error message below." xml:space="preserve">
|
||||
<source>An unexpected error occurred. Please contact Damus support via [Nostr](damus:npub18m76awca3y37hkvuneavuw6pjj4525fw90necxmadrvjg0sdy6qsngq955) or [email](support@damus.io) with the error message below.</source>
|
||||
<target>An unexpected error occurred. Please contact Damus support via [Nostr](damus:npub18m76awca3y37hkvuneavuw6pjj4525fw90necxmadrvjg0sdy6qsngq955) or [email](support@damus.io) with the error message below.</target>
|
||||
<note>Label explaining there was an error, and suggesting next steps</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="An unknown error occurred while adding a relay." xml:space="preserve">
|
||||
<source>An unknown error occurred while adding a relay.</source>
|
||||
<target>An unknown error occurred while adding a relay.</target>
|
||||
<note>Title of an unknown relay error message.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Animations" xml:space="preserve">
|
||||
<source>Animations</source>
|
||||
<target>Animations</target>
|
||||
@@ -478,7 +488,7 @@ Cancel deleting bookmarks.
|
||||
Cancel deleting the user.
|
||||
Cancel out of logging out the user.
|
||||
Cancel out of search view.
|
||||
Cancel resetting the contact list.
|
||||
Cancel the user-requested operation.
|
||||
Text for button to cancel out of connecting Nostr Wallet Connect lightning wallet.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Cancelled" xml:space="preserve">
|
||||
@@ -496,6 +506,16 @@ Text for button to cancel out of connecting Nostr Wallet Connect lightning walle
|
||||
<target>Changing this setting will cause the cache to be cleared. This will free space, but images may take longer to load again. Are you sure you want to proceed?</target>
|
||||
<note>Message explaining consequences of changing the 'enable animation' setting</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Check the address and/or the relay list." xml:space="preserve">
|
||||
<source>Check the address and/or the relay list.</source>
|
||||
<target>Check the address and/or the relay list.</target>
|
||||
<note>Human readable tip for error</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Check your internet connection and try again. If the error persists, contact support." xml:space="preserve">
|
||||
<source>Check your internet connection and try again. If the error persists, contact support.</source>
|
||||
<target>Check your internet connection and try again. If the error persists, contact support.</target>
|
||||
<note>Error tip when user tries to create the one-click Coinos wallet setup but fails for a generic reason.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Choose from Library" xml:space="preserve">
|
||||
<source>Choose from Library</source>
|
||||
<target>Choose from Library</target>
|
||||
@@ -526,12 +546,22 @@ Text for button to cancel out of connecting Nostr Wallet Connect lightning walle
|
||||
<target>Clearing Cache</target>
|
||||
<note>Loading message indicating that the cache is being cleared.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Click here if you have a Coinos username and password." xml:space="preserve">
|
||||
<source>Click here if you have a Coinos username and password.</source>
|
||||
<target>Click here if you have a Coinos username and password.</target>
|
||||
<note>Button description hint for users who may want to connect via the website.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Close" xml:space="preserve">
|
||||
<source>Close</source>
|
||||
<target>Close</target>
|
||||
<note>Button label giving the user the option to close the sheet from which they posted a highlight
|
||||
Button label giving the user the option to close the sheet from which they were trying to post a highlight</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Coinos is a service operated by a third-party. We have no access to your Coinos wallet." xml:space="preserve">
|
||||
<source>Coinos is a service operated by a third-party. We have no access to your Coinos wallet.</source>
|
||||
<target>Coinos is a service operated by a third-party. We have no access to your Coinos wallet.</target>
|
||||
<note>Small caption with a disclaimer that Damus does not own or have access to Coinos wallets, Coinos is a third-party service.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Coming soon" xml:space="preserve">
|
||||
<source>Coming soon</source>
|
||||
<target>Coming soon</target>
|
||||
@@ -568,14 +598,19 @@ Text for button to conect to Nostr Wallet Connect lightning wallet.</note>
|
||||
<target>Connect to Coinos</target>
|
||||
<note>Button to attach a Coinos Wallet, a service that provides a Lightning wallet for zapping sats. Coinos is the name of the service and should not be translated.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connect via the website" xml:space="preserve">
|
||||
<source>Connect via the website</source>
|
||||
<target>Connect via the website</target>
|
||||
<note>Button label for users who are setting up a Coinos wallet and would like to connect via the website</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connecting" xml:space="preserve">
|
||||
<source>Connecting</source>
|
||||
<target>Connecting</target>
|
||||
<note>Relay status label that indicates a relay is connecting.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Contact list (Follows + Relay list)" xml:space="preserve">
|
||||
<source>Contact list (Follows + Relay list)</source>
|
||||
<target>Contact list (Follows + Relay list)</target>
|
||||
<trans-unit id="Contact list" xml:space="preserve">
|
||||
<source>Contact list</source>
|
||||
<target>Contact list</target>
|
||||
<note>Section title for Contact list first aid tools</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Contact list has been reset" xml:space="preserve">
|
||||
@@ -604,7 +639,7 @@ Text for button to conect to Nostr Wallet Connect lightning wallet.</note>
|
||||
<note>Button to dismiss suggested users view and continue to the main app
|
||||
Continue with bookmarks.
|
||||
Continue with deleting the user.
|
||||
Continue with resetting the contact list.
|
||||
Continue with the user-requested operation.
|
||||
Prompt to user to continue</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Conversations" xml:space="preserve">
|
||||
@@ -689,6 +724,11 @@ Context menu option for copying the version of damus.</note>
|
||||
<target>Could not create your initial contact list event. This is a software bug, please contact Damus support via support@damus.io or through our Nostr account for help.</target>
|
||||
<note>Error message to the user indicating that the initial contact list failed to be created.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Could not create your initial relay list. This is a software bug, please contact Damus support via support@damus.io or through our Nostr account for help." xml:space="preserve">
|
||||
<source>Could not create your initial relay list. This is a software bug, please contact Damus support via support@damus.io or through our Nostr account for help.</source>
|
||||
<target>Could not create your initial relay list. This is a software bug, please contact Damus support via support@damus.io or through our Nostr account for help.</target>
|
||||
<note>Error message to the user indicating that the initial relay list failed to be created.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Could not find the user you're looking for" xml:space="preserve">
|
||||
<source>Could not find the user you're looking for</source>
|
||||
<target>Could not find the user you're looking for</target>
|
||||
@@ -843,11 +883,6 @@ Button to dismiss error</note>
|
||||
<note>Button to dismiss wallet selection view for paying Lightning invoice.
|
||||
Button to leave edit mode for modifying the list of relays.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Duplicate relay" xml:space="preserve">
|
||||
<source>Duplicate relay</source>
|
||||
<target>Duplicate relay</target>
|
||||
<note>Title of the duplicate relay error message.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Duration" xml:space="preserve">
|
||||
<source>Duration</source>
|
||||
<target>Duration</target>
|
||||
@@ -1222,6 +1257,16 @@ This is my first post on Damus, I am happy to meet you all 🤙. What’s up?
|
||||
<target>Hide notes with #nsfw tags</target>
|
||||
<note>Setting to hide notes with the #nsfw (not safe for work) tags</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Hide notifications that tag many profiles" xml:space="preserve">
|
||||
<source>Hide notifications that tag many profiles</source>
|
||||
<target>Hide notifications that tag many profiles</target>
|
||||
<note>Label for notification settings toggle that hides notifications that tag many people.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Highlight" xml:space="preserve">
|
||||
<source>Highlight</source>
|
||||
<target>Highlight</target>
|
||||
<note>Context menu action to highlight the selected text as context to draft a new note.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Highlighted" xml:space="preserve">
|
||||
<source>Highlighted</source>
|
||||
<target>Highlighted</target>
|
||||
@@ -1237,6 +1282,11 @@ This is my first post on Damus, I am happy to meet you all 🤙. What’s up?
|
||||
<target>Home</target>
|
||||
<note>Navigation bar title for Home view where notes and replies appear from those who the user is following.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="How would you like to connect to your Coinos wallet?" xml:space="preserve">
|
||||
<source>How would you like to connect to your Coinos wallet?</source>
|
||||
<target>How would you like to connect to your Coinos wallet?</target>
|
||||
<note>Question for the user when connecting a Coinos wallet.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Howdy! I’m a graphic designer during the day and coder at night, but I’m also trying to spend more time outdoors. Hope to meet folks who are on their own journeys to a peaceful and free life!" xml:space="preserve">
|
||||
<source>Howdy! I’m a graphic designer during the day and coder at night, but I’m also trying to spend more time outdoors.
|
||||
|
||||
@@ -1246,6 +1296,11 @@ Hope to meet folks who are on their own journeys to a peaceful and free life!</s
|
||||
Hope to meet folks who are on their own journeys to a peaceful and free life!</target>
|
||||
<note>First post example given to the user during onboarding, as a suggestion as to what they could post first</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="If your wallet balance is getting high, it's important to understand how to keep your funds secure. Please consider learning the best practices to ensure your assets remain safe. [Click here](https://damus.io/docs/wallet/high-balance-safety-reminder/) to learn more." xml:space="preserve">
|
||||
<source>If your wallet balance is getting high, it's important to understand how to keep your funds secure. Please consider learning the best practices to ensure your assets remain safe. [Click here](https://damus.io/docs/wallet/high-balance-safety-reminder/) to learn more.</source>
|
||||
<target>If your wallet balance is getting high, it's important to understand how to keep your funds secure. Please consider learning the best practices to ensure your assets remain safe. [Click here](https://damus.io/docs/wallet/high-balance-safety-reminder/) to learn more.</target>
|
||||
<note>Text reminding the user has a high balance, recommending them to learn about self-custody</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Illegal Content" xml:space="preserve">
|
||||
<source>Illegal Content</source>
|
||||
<target>Illegal Content</target>
|
||||
@@ -1280,7 +1335,7 @@ Option to enter a url</note>
|
||||
<trans-unit id="In progress…" xml:space="preserve">
|
||||
<source>In progress…</source>
|
||||
<target>In progress…</target>
|
||||
<note>Loading message indicating that a contact list reset operation is in progress.</note>
|
||||
<note>Loading message indicating that a first aid operation is in progress.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Indefinite" xml:space="preserve">
|
||||
<source>Indefinite</source>
|
||||
@@ -1317,6 +1372,11 @@ Option to enter a url</note>
|
||||
<target>Invalid lightning address</target>
|
||||
<note>Message to display when there was an error attempting to zap due to an invalid lightning address.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invalid relay address" xml:space="preserve">
|
||||
<source>Invalid relay address</source>
|
||||
<target>Invalid relay address</target>
|
||||
<note>Heading for an error when adding a relay</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="It seems that you already have a translation service configured. Would you like to switch to Damus Purple as your translator?" xml:space="preserve">
|
||||
<source>It seems that you already have a translation service configured. Would you like to switch to Damus Purple as your translator?</source>
|
||||
<target>It seems that you already have a translation service configured. Would you like to switch to Damus Purple as your translator?</target>
|
||||
@@ -1466,6 +1526,11 @@ Sidebar menu label to sign out of the account.</note>
|
||||
<target>Maybe later</target>
|
||||
<note>Text for button to disconnect from Nostr Wallet Connect lightning wallet.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Media from someone you don't follow" xml:space="preserve">
|
||||
<source>Media from someone you don't follow</source>
|
||||
<target>Media from someone you don't follow</target>
|
||||
<note>Label on the image blur mask</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Media previews" xml:space="preserve">
|
||||
<source>Media previews</source>
|
||||
<target>Media previews</target>
|
||||
@@ -1503,6 +1568,7 @@ Setting to enable Mention Local Notification</note>
|
||||
<note>Alert button to mute a user.
|
||||
Button label that allows the user to mute the user shown on-screen
|
||||
Button to mute a profile
|
||||
Context menu action to mute the selected word.
|
||||
Title for confirmation dialog to mute a profile.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Mute %@?" xml:space="preserve">
|
||||
@@ -1605,6 +1671,11 @@ User confirm No</note>
|
||||
<target>No image is currently setup</target>
|
||||
<note>Accessibility value on image control</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="No initial relay list available to update." xml:space="preserve">
|
||||
<source>No initial relay list available to update.</source>
|
||||
<target>No initial relay list available to update.</target>
|
||||
<note>Human readable error description</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="No logs to display" xml:space="preserve">
|
||||
<source>No logs to display</source>
|
||||
<target>No logs to display</target>
|
||||
@@ -1625,6 +1696,11 @@ User confirm No</note>
|
||||
<target>No profile picture is currently setup</target>
|
||||
<note>Accessibility value on profile picture image control</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="No relay list was found. You might experience issues using the app. If you suspect you have permanently lost your relay list (or if you never had one), you can fix this by resetting it" xml:space="preserve">
|
||||
<source>No relay list was found. You might experience issues using the app. If you suspect you have permanently lost your relay list (or if you never had one), you can fix this by resetting it</source>
|
||||
<target>No relay list was found. You might experience issues using the app. If you suspect you have permanently lost your relay list (or if you never had one), you can fix this by resetting it</target>
|
||||
<note>Section footer for relay list first aid tools</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="No results" xml:space="preserve">
|
||||
<source>No results</source>
|
||||
<target>No results</target>
|
||||
@@ -1760,6 +1836,11 @@ Button label to dismiss an error dialog</note>
|
||||
<target>Ok</target>
|
||||
<note>Button to dismiss the alert.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="One-click setup" xml:space="preserve">
|
||||
<source>One-click setup</source>
|
||||
<target>One-click setup</target>
|
||||
<note>Button label for users to do a one-click Coinos wallet setup.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Online" xml:space="preserve">
|
||||
<source>Online</source>
|
||||
<target>Online</target>
|
||||
@@ -1861,11 +1942,26 @@ Section title for deleting the user</note>
|
||||
<target>Plan</target>
|
||||
<note>Prompt selection of DeepL subscription plan to perform machine translations on notes</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Please check the address and try again" xml:space="preserve">
|
||||
<source>Please check the address and try again</source>
|
||||
<target>Please check the address and try again</target>
|
||||
<note>Tip for an error where the relay address being added is invalid</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Please choose relays from the list below to filter the current feed:" xml:space="preserve">
|
||||
<source>Please choose relays from the list below to filter the current feed:</source>
|
||||
<target>Please choose relays from the list below to filter the current feed:</target>
|
||||
<note>Instructions on how to filter a specific timeline feed by choosing relay servers to filter on.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Please contact support for further help." xml:space="preserve">
|
||||
<source>Please contact support for further help.</source>
|
||||
<target>Please contact support for further help.</target>
|
||||
<note>Human readable tips for what to do for a failure to find the relay list</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Please contact support." xml:space="preserve">
|
||||
<source>Please contact support.</source>
|
||||
<target>Please contact support.</target>
|
||||
<note>Tip for an unknown relay error message.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Please contact the person who provided the link, and ask for another link." xml:space="preserve">
|
||||
<source>Please contact the person who provided the link, and ask for another link.</source>
|
||||
<target>Please contact the person who provided the link, and ask for another link.</target>
|
||||
@@ -1876,6 +1972,21 @@ Section title for deleting the user</note>
|
||||
<target>Please double-check the checkout web page, or go to the Side Menu → "Purple" to check your account status. If you have already paid, but still don't see your account active, please save the URL of the checkout page where you came from, contact our support, and give us the URL to help you with this issue.</target>
|
||||
<note>User-facing tips on what to do if a Purple welcome link doesn't work</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Please go to Settings > First Aid > Repair relay list, or contact support." xml:space="preserve">
|
||||
<source>Please go to Settings > First Aid > Repair relay list, or contact support.</source>
|
||||
<target>Please go to Settings > First Aid > Repair relay list, or contact support.</target>
|
||||
<note>Human readable tip for error</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Please make sure you have logged-in with your private key." xml:space="preserve">
|
||||
<source>Please make sure you have logged-in with your private key.</source>
|
||||
<target>Please make sure you have logged-in with your private key.</target>
|
||||
<note>Human readable tip for error</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Please try again later or contact support if the issue persists." xml:space="preserve">
|
||||
<source>Please try again later or contact support if the issue persists.</source>
|
||||
<target>Please try again later or contact support if the issue persists.</target>
|
||||
<note>Human readable tip for error</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Please try again, check the URL for typos, or contact support for further help." xml:space="preserve">
|
||||
<source>Please try again, check the URL for typos, or contact support for further help.</source>
|
||||
<target>Please try again, check the URL for typos, or contact support for further help.</target>
|
||||
@@ -2047,6 +2158,16 @@ Title of emoji reactions view</note>
|
||||
<target>Relay Logs</target>
|
||||
<note>Text label indicating that the text below it are developer mode logs.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Relay list" xml:space="preserve">
|
||||
<source>Relay list</source>
|
||||
<target>Relay list</target>
|
||||
<note>Section title for Relay list first aid tools</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Relay list has been repaired" xml:space="preserve">
|
||||
<source>Relay list has been repaired</source>
|
||||
<target>Relay list has been repaired</target>
|
||||
<note>Message indicating that the relay list was successfully repaired.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Relays" xml:space="preserve">
|
||||
<source>Relays</source>
|
||||
<target>Relays</target>
|
||||
@@ -2089,6 +2210,11 @@ Title of relays view</note>
|
||||
<target>Renews on</target>
|
||||
<note>Indicating when the subscription will renew</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Repair relay list" xml:space="preserve">
|
||||
<source>Repair relay list</source>
|
||||
<target>Repair relay list</target>
|
||||
<note>Button to repair relay list.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Reply" xml:space="preserve">
|
||||
<source>Reply</source>
|
||||
<target>Reply</target>
|
||||
@@ -2210,6 +2336,11 @@ Setting to enable Repost Local Notification</note>
|
||||
<target>SOFTWARE</target>
|
||||
<note>Text label indicating which relay software is used to run this Nostr relay.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Safety Reminder" xml:space="preserve">
|
||||
<source>Safety Reminder</source>
|
||||
<target>Safety Reminder</target>
|
||||
<note>Heading for a safety reminder that appears when the user has too many funds, recommending them to learn about safeguarding their funds.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Satoshi Nakamoto" xml:space="preserve">
|
||||
<source>Satoshi Nakamoto</source>
|
||||
<target>Satoshi Nakamoto</target>
|
||||
@@ -2499,6 +2630,11 @@ Button to show more of a long profile description.</note>
|
||||
<target>Someone zapped you ⚡️</target>
|
||||
<note>Title label for a push notification where someone zapped the user</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Something went wrong when performing the one-click Coinos wallet setup." xml:space="preserve">
|
||||
<source>Something went wrong when performing the one-click Coinos wallet setup.</source>
|
||||
<target>Something went wrong when performing the one-click Coinos wallet setup.</target>
|
||||
<note>Error label when user tries the one-click Coinos wallet setup but fails for some generic reason.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Sorry, but for some reason there has been an issue while trying to crop this image. Please try again later. If the error persists, please contact [Damus support](mailto:support@damus.io)" xml:space="preserve">
|
||||
<source>Sorry, but for some reason there has been an issue while trying to crop this image. Please try again later. If the error persists, please contact [Damus support](mailto:support@damus.io)</source>
|
||||
<target>Sorry, but for some reason there has been an issue while trying to crop this image. Please try again later. If the error persists, please contact [Damus support](mailto:support@damus.io)</target>
|
||||
@@ -2590,6 +2726,11 @@ Section header for Universe/Search spam</note>
|
||||
<target>Take Photo</target>
|
||||
<note>Option to take a photo with the camera</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Tap to load" xml:space="preserve">
|
||||
<source>Tap to load</source>
|
||||
<target>Tap to load</target>
|
||||
<note>Label for button that allows user to dismiss media content warning and unblur the image</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Test (local)" xml:space="preserve">
|
||||
<source>Test (local)</source>
|
||||
<target>Test (local)</target>
|
||||
@@ -2634,23 +2775,26 @@ Enjoy!</target>
|
||||
<target>The camera was not capable of scanning the requested codes.</target>
|
||||
<note>Camera's bad output error label</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="The relay you are trying to add is already added. You're all set!" xml:space="preserve">
|
||||
<source>The relay you are trying to add is already added.
|
||||
You're all set!</source>
|
||||
<target>The relay you are trying to add is already added.
|
||||
You're all set!</target>
|
||||
<note>An error message that appears when the user attempts to add a relay that has already been added.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="The social network you control" xml:space="preserve">
|
||||
<source>The social network you control</source>
|
||||
<target>The social network you control</target>
|
||||
<note>Quick description of what Damus is</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="The specified relay that you are trying to udpate was not found in your relay list." xml:space="preserve">
|
||||
<source>The specified relay that you are trying to udpate was not found in your relay list.</source>
|
||||
<target>The specified relay that you are trying to udpate was not found in your relay list.</target>
|
||||
<note>Human readable error description</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="There has been an unexpected error with the in-app purchase. Please try again later or contact support@damus.io. Error: %@" xml:space="preserve">
|
||||
<source>There has been an unexpected error with the in-app purchase. Please try again later or contact support@damus.io. Error: %@</source>
|
||||
<target>There has been an unexpected error with the in-app purchase. Please try again later or contact support@damus.io. Error: %@</target>
|
||||
<note>In-app purchase error message for the user</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="There was a problem creating the relay list event." xml:space="preserve">
|
||||
<source>There was a problem creating the relay list event.</source>
|
||||
<target>There was a problem creating the relay list event.</target>
|
||||
<note>Human readable error description</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="There was an error loading your account. Please try again later. If problem persists, please contact us at support@damus.io" xml:space="preserve">
|
||||
<source>There was an error loading your account. Please try again later. If problem persists, please contact us at support@damus.io</source>
|
||||
<target>There was an error loading your account. Please try again later. If problem persists, please contact us at support@damus.io</target>
|
||||
@@ -2676,6 +2820,11 @@ You're all set!</target>
|
||||
<target>This is an event that has been muted according to your mute list rules. We cannot suppress this notification, but we obscured the details to respect your preferences</target>
|
||||
<note>Description for a push notification which has been muted, and explanation that we cannot suppress it</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="This is an unexpected error, please contact support." xml:space="preserve">
|
||||
<source>This is an unexpected error, please contact support.</source>
|
||||
<target>This is an unexpected error, please contact support.</target>
|
||||
<note>Human readable tip for error</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="This is my first post on Nostr 💜. I love drawing and folding Origami! Nice to meet you all! #introductions #plebchain " xml:space="preserve">
|
||||
<source>This is my first post on Nostr 💜. I love drawing and folding Origami!
|
||||
|
||||
@@ -2690,6 +2839,11 @@ Nice to meet you all! #introductions #plebchain </target>
|
||||
<target>This note contains too many items and cannot be rendered</target>
|
||||
<note>Error message indicating that a note is too big and cannot be rendered</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="This relay is already in your list." xml:space="preserve">
|
||||
<source>This relay is already in your list.</source>
|
||||
<target>This relay is already in your list.</target>
|
||||
<note>Human readable tip for error</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="This user cannot be zapped because they have not configured zaps on their account yet. Time to orange-pill?" xml:space="preserve">
|
||||
<source>This user cannot be zapped because they have not configured zaps on their account yet. Time to orange-pill?</source>
|
||||
<target>This user cannot be zapped because they have not configured zaps on their account yet. Time to orange-pill?</target>
|
||||
@@ -2935,13 +3089,22 @@ YOU WILL NO LONGER BE ABLE TO LOG INTO DAMUS USING THIS ACCOUNT KEY.
|
||||
ARE YOU SURE YOU WANT TO CONTINUE?</target>
|
||||
<note>Alert for deleting the users account.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="WARNING: This will reset your contact list, including the list of everyone you follow and the list of all relays you usually connect to. ONLY PROCEED IF YOU ARE SURE YOU HAVE LOST YOUR CONTACT LIST BEYOND RECOVERABILITY." xml:space="preserve">
|
||||
<trans-unit id="WARNING: This will attempt to repair your relay list based on other information we have. You may lose any relays you have added manually. Only proceed if you have lost your relay list beyond recoverability or if you are ok with losing any manually added relays." xml:space="preserve">
|
||||
<source>WARNING:
|
||||
|
||||
This will reset your contact list, including the list of everyone you follow and the list of all relays you usually connect to. ONLY PROCEED IF YOU ARE SURE YOU HAVE LOST YOUR CONTACT LIST BEYOND RECOVERABILITY.</source>
|
||||
This will attempt to repair your relay list based on other information we have. You may lose any relays you have added manually. Only proceed if you have lost your relay list beyond recoverability or if you are ok with losing any manually added relays.</source>
|
||||
<target>WARNING:
|
||||
|
||||
This will reset your contact list, including the list of everyone you follow and the list of all relays you usually connect to. ONLY PROCEED IF YOU ARE SURE YOU HAVE LOST YOUR CONTACT LIST BEYOND RECOVERABILITY.</target>
|
||||
This will attempt to repair your relay list based on other information we have. You may lose any relays you have added manually. Only proceed if you have lost your relay list beyond recoverability or if you are ok with losing any manually added relays.</target>
|
||||
<note>Alert for repairing the user's relay list.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="WARNING: This will reset your contact list, including the list of everyone you follow and potentially the list of all relays you usually connect to. ONLY PROCEED IF YOU ARE SURE YOU HAVE LOST YOUR CONTACT LIST BEYOND RECOVERABILITY." xml:space="preserve">
|
||||
<source>WARNING:
|
||||
|
||||
This will reset your contact list, including the list of everyone you follow and potentially the list of all relays you usually connect to. ONLY PROCEED IF YOU ARE SURE YOU HAVE LOST YOUR CONTACT LIST BEYOND RECOVERABILITY.</source>
|
||||
<target>WARNING:
|
||||
|
||||
This will reset your contact list, including the list of everyone you follow and potentially the list of all relays you usually connect to. ONLY PROCEED IF YOU ARE SURE YOU HAVE LOST YOUR CONTACT LIST BEYOND RECOVERABILITY.</target>
|
||||
<note>Alert for resetting the user's contact list.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Wallet" xml:space="preserve">
|
||||
@@ -3063,6 +3226,11 @@ User confirm Yes</note>
|
||||
<target>You clicked on a Purple welcome link, but we could not find your checkout. This is likely a bug.</target>
|
||||
<note>Error label upon continuing in the app from a Damus Purple purchase</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You do not have permission to alter this relay list." xml:space="preserve">
|
||||
<source>You do not have permission to alter this relay list.</source>
|
||||
<target>You do not have permission to alter this relay list.</target>
|
||||
<note>Human readable error description</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You drive the conversation and we want to make it easier for people to support your work beyond follows, reposts, and likes." xml:space="preserve">
|
||||
<source>You drive the conversation and we want to make it easier for people to support your work beyond follows, reposts, and likes.</source>
|
||||
<target>You drive the conversation and we want to make it easier for people to support your work beyond follows, reposts, and likes.</target>
|
||||
@@ -3073,6 +3241,11 @@ User confirm Yes</note>
|
||||
<target>You have no bookmarks yet, add them in the context menu</target>
|
||||
<note>Text indicating that there are no bookmarks to be viewed</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You must be logged in with your nsec to use this option." xml:space="preserve">
|
||||
<source>You must be logged in with your nsec to use this option.</source>
|
||||
<target>You must be logged in with your nsec to use this option.</target>
|
||||
<note>Warning text for users who cannot create a Coinos account via the one-click setup without being logged in with their nsec.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You opened an invalid link. The link you tried to open refers to "nrelay", which has been deprecated and is not supported." xml:space="preserve">
|
||||
<source>You opened an invalid link. The link you tried to open refers to "nrelay", which has been deprecated and is not supported.</source>
|
||||
<target>You opened an invalid link. The link you tried to open refers to "nrelay", which has been deprecated and is not supported.</target>
|
||||
@@ -3113,6 +3286,16 @@ User confirm Yes</note>
|
||||
<target>Your highlight is being broadcasted to the network. Please wait.</target>
|
||||
<note>Label explaining there their highlight publishing action is in progress</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Your profile will not be shared with Coinos." xml:space="preserve">
|
||||
<source>Your profile will not be shared with Coinos.</source>
|
||||
<target>Your profile will not be shared with Coinos.</target>
|
||||
<note>Label text for users to reassure them that their nsec is not shared with a third party.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Your relay list appears to be broken, so we cannot connect you to your Nostr network." xml:space="preserve">
|
||||
<source>Your relay list appears to be broken, so we cannot connect you to your Nostr network.</source>
|
||||
<target>Your relay list appears to be broken, so we cannot connect you to your Nostr network.</target>
|
||||
<note>Human readable error description for a failure to parse the relay list due to a bad relay list</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Your report will be sent to the relays you are connected to" xml:space="preserve">
|
||||
<source>Your report will be sent to the relays you are connected to</source>
|
||||
<target>Your report will be sent to the relays you are connected to</target>
|
||||
@@ -3449,6 +3632,21 @@ String indicating that a given timestamp just occurred</note>
|
||||
<target>%#@FOLLOWING@</target>
|
||||
<note/>
|
||||
</trans-unit>
|
||||
<trans-unit id="/hellthread_notifications_disabled:dict/HELLTHREAD_PROFILES:dict/one:dict/:string" xml:space="preserve">
|
||||
<source>Hide notifications that tag more than %d profile</source>
|
||||
<target>Hide notifications that tag more than %d profile</target>
|
||||
<note/>
|
||||
</trans-unit>
|
||||
<trans-unit id="/hellthread_notifications_disabled:dict/HELLTHREAD_PROFILES:dict/other:dict/:string" xml:space="preserve">
|
||||
<source>Hide notifications that tag more than %d profiles</source>
|
||||
<target>Hide notifications that tag more than %d profiles</target>
|
||||
<note/>
|
||||
</trans-unit>
|
||||
<trans-unit id="/hellthread_notifications_disabled:dict/NSStringLocalizedFormatKey:dict/:string" xml:space="preserve">
|
||||
<source>%#@HELLTHREAD_PROFILES@</source>
|
||||
<target>%#@HELLTHREAD_PROFILES@</target>
|
||||
<note/>
|
||||
</trans-unit>
|
||||
<trans-unit id="/imports_count:dict/IMPORTS:dict/one:dict/:string" xml:space="preserve">
|
||||
<source>Import</source>
|
||||
<target>Import</target>
|
||||
@@ -4074,6 +4272,11 @@ Label for filter for all notifications.</note>
|
||||
<target state="new">Already on Nostr?</target>
|
||||
<note>Ask the user if they already have an account on Nostr</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Also click here if you had a one-click setup before." xml:space="preserve">
|
||||
<source>Also click here if you had a one-click setup before.</source>
|
||||
<target state="new">Also click here if you had a one-click setup before.</target>
|
||||
<note>Button description hint for users who may want to do a one-click setup.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Always show onboarding" xml:space="preserve">
|
||||
<source>Always show onboarding</source>
|
||||
<target state="new">Always show onboarding</target>
|
||||
@@ -4084,16 +4287,21 @@ Label for filter for all notifications.</note>
|
||||
<target state="new">An additional percentage of each zap will be sent to support Damus development</target>
|
||||
<note>Text indicating that they can contribute zaps to support Damus development.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="An unexpected error happened while trying to create the new contact list. Please contact support." xml:space="preserve">
|
||||
<source>An unexpected error happened while trying to create the new contact list. Please contact support.</source>
|
||||
<target state="new">An unexpected error happened while trying to create the new contact list. Please contact support.</target>
|
||||
<note>Error message for a failed contact list reset operation</note>
|
||||
<trans-unit id="An unexpected error happened while trying to perform this action. Please contact support." xml:space="preserve">
|
||||
<source>An unexpected error happened while trying to perform this action. Please contact support.</source>
|
||||
<target state="new">An unexpected error happened while trying to perform this action. Please contact support.</target>
|
||||
<note>Error message for a failed reset/repair operation</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="An unexpected error occurred. Please contact Damus support via [Nostr](damus:npub18m76awca3y37hkvuneavuw6pjj4525fw90necxmadrvjg0sdy6qsngq955) or [email](support@damus.io) with the error message below." xml:space="preserve">
|
||||
<source>An unexpected error occurred. Please contact Damus support via [Nostr](damus:npub18m76awca3y37hkvuneavuw6pjj4525fw90necxmadrvjg0sdy6qsngq955) or [email](support@damus.io) with the error message below.</source>
|
||||
<target state="new">An unexpected error occurred. Please contact Damus support via [Nostr](damus:npub18m76awca3y37hkvuneavuw6pjj4525fw90necxmadrvjg0sdy6qsngq955) or [email](support@damus.io) with the error message below.</target>
|
||||
<note>Label explaining there was an error, and suggesting next steps</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="An unknown error occurred while adding a relay." xml:space="preserve">
|
||||
<source>An unknown error occurred while adding a relay.</source>
|
||||
<target state="new">An unknown error occurred while adding a relay.</target>
|
||||
<note>Title of an unknown relay error message.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Animations" xml:space="preserve">
|
||||
<source>Animations</source>
|
||||
<target state="new">Animations</target>
|
||||
@@ -4259,7 +4467,7 @@ Cancel deleting bookmarks.
|
||||
Cancel deleting the user.
|
||||
Cancel out of logging out the user.
|
||||
Cancel out of search view.
|
||||
Cancel resetting the contact list.
|
||||
Cancel the user-requested operation.
|
||||
Text for button to cancel out of connecting Nostr Wallet Connect lightning wallet.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Cancelled" xml:space="preserve">
|
||||
@@ -4277,6 +4485,16 @@ Text for button to cancel out of connecting Nostr Wallet Connect lightning walle
|
||||
<target state="new">Changing this setting will cause the cache to be cleared. This will free space, but images may take longer to load again. Are you sure you want to proceed?</target>
|
||||
<note>Message explaining consequences of changing the 'enable animation' setting</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Check the address and/or the relay list." xml:space="preserve">
|
||||
<source>Check the address and/or the relay list.</source>
|
||||
<target state="new">Check the address and/or the relay list.</target>
|
||||
<note>Human readable tip for error</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Check your internet connection and try again. If the error persists, contact support." xml:space="preserve">
|
||||
<source>Check your internet connection and try again. If the error persists, contact support.</source>
|
||||
<target state="new">Check your internet connection and try again. If the error persists, contact support.</target>
|
||||
<note>Error tip when user tries to create the one-click Coinos wallet setup but fails for a generic reason.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Choose from Library" xml:space="preserve">
|
||||
<source>Choose from Library</source>
|
||||
<target state="new">Choose from Library</target>
|
||||
@@ -4307,6 +4525,11 @@ Text for button to cancel out of connecting Nostr Wallet Connect lightning walle
|
||||
<target state="new">Clearing Cache</target>
|
||||
<note>Loading message indicating that the cache is being cleared.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Click here if you have a Coinos username and password." xml:space="preserve">
|
||||
<source>Click here if you have a Coinos username and password.</source>
|
||||
<target state="new">Click here if you have a Coinos username and password.</target>
|
||||
<note>Button description hint for users who may want to connect via the website.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Close" xml:space="preserve">
|
||||
<source>Close</source>
|
||||
<target state="new">Close</target>
|
||||
@@ -4316,6 +4539,11 @@ Button label giving the user the option to close the sheet from which they were
|
||||
Button label giving the user the option to close the sheet from which they were trying to share.
|
||||
Button label giving the user the option to close the view when no content is available to share</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Coinos is a service operated by a third-party. We have no access to your Coinos wallet." xml:space="preserve">
|
||||
<source>Coinos is a service operated by a third-party. We have no access to your Coinos wallet.</source>
|
||||
<target state="new">Coinos is a service operated by a third-party. We have no access to your Coinos wallet.</target>
|
||||
<note>Small caption with a disclaimer that Damus does not own or have access to Coinos wallets, Coinos is a third-party service.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Coming soon" xml:space="preserve">
|
||||
<source>Coming soon</source>
|
||||
<target state="new">Coming soon</target>
|
||||
@@ -4352,14 +4580,19 @@ Text for button to conect to Nostr Wallet Connect lightning wallet.</note>
|
||||
<target state="new">Connect to Coinos</target>
|
||||
<note>Button to attach a Coinos Wallet, a service that provides a Lightning wallet for zapping sats. Coinos is the name of the service and should not be translated.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connect via the website" xml:space="preserve">
|
||||
<source>Connect via the website</source>
|
||||
<target state="new">Connect via the website</target>
|
||||
<note>Button label for users who are setting up a Coinos wallet and would like to connect via the website</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connecting" xml:space="preserve">
|
||||
<source>Connecting</source>
|
||||
<target state="new">Connecting</target>
|
||||
<note>Relay status label that indicates a relay is connecting.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Contact list (Follows + Relay list)" xml:space="preserve">
|
||||
<source>Contact list (Follows + Relay list)</source>
|
||||
<target state="new">Contact list (Follows + Relay list)</target>
|
||||
<trans-unit id="Contact list" xml:space="preserve">
|
||||
<source>Contact list</source>
|
||||
<target state="new">Contact list</target>
|
||||
<note>Section title for Contact list first aid tools</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Contact list has been reset" xml:space="preserve">
|
||||
@@ -4388,7 +4621,7 @@ Text for button to conect to Nostr Wallet Connect lightning wallet.</note>
|
||||
<note>Button to dismiss suggested users view and continue to the main app
|
||||
Continue with bookmarks.
|
||||
Continue with deleting the user.
|
||||
Continue with resetting the contact list.
|
||||
Continue with the user-requested operation.
|
||||
Prompt to user to continue</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Conversations" xml:space="preserve">
|
||||
@@ -4473,6 +4706,11 @@ Context menu option for copying the version of damus.</note>
|
||||
<target state="new">Could not create your initial contact list event. This is a software bug, please contact Damus support via support@damus.io or through our Nostr account for help.</target>
|
||||
<note>Error message to the user indicating that the initial contact list failed to be created.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Could not create your initial relay list. This is a software bug, please contact Damus support via support@damus.io or through our Nostr account for help." xml:space="preserve">
|
||||
<source>Could not create your initial relay list. This is a software bug, please contact Damus support via support@damus.io or through our Nostr account for help.</source>
|
||||
<target state="new">Could not create your initial relay list. This is a software bug, please contact Damus support via support@damus.io or through our Nostr account for help.</target>
|
||||
<note>Error message to the user indicating that the initial relay list failed to be created.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Could not find the user you're looking for" xml:space="preserve">
|
||||
<source>Could not find the user you're looking for</source>
|
||||
<target state="new">Could not find the user you're looking for</target>
|
||||
@@ -4627,11 +4865,6 @@ Button to dismiss error</note>
|
||||
<note>Button to dismiss wallet selection view for paying Lightning invoice.
|
||||
Button to leave edit mode for modifying the list of relays.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Duplicate relay" xml:space="preserve">
|
||||
<source>Duplicate relay</source>
|
||||
<target state="new">Duplicate relay</target>
|
||||
<note>Title of the duplicate relay error message.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Duration" xml:space="preserve">
|
||||
<source>Duration</source>
|
||||
<target state="new">Duration</target>
|
||||
@@ -5006,6 +5239,16 @@ This is my first post on Damus, I am happy to meet you all 🤙. What’s up?
|
||||
<target state="new">Hide notes with #nsfw tags</target>
|
||||
<note>Setting to hide notes with the #nsfw (not safe for work) tags</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Hide notifications that tag many profiles" xml:space="preserve">
|
||||
<source>Hide notifications that tag many profiles</source>
|
||||
<target state="new">Hide notifications that tag many profiles</target>
|
||||
<note>Label for notification settings toggle that hides notifications that tag many people.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Highlight" xml:space="preserve">
|
||||
<source>Highlight</source>
|
||||
<target state="new">Highlight</target>
|
||||
<note>Context menu action to highlight the selected text as context to draft a new note.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Highlighted" xml:space="preserve">
|
||||
<source>Highlighted</source>
|
||||
<target state="new">Highlighted</target>
|
||||
@@ -5021,6 +5264,11 @@ This is my first post on Damus, I am happy to meet you all 🤙. What’s up?
|
||||
<target state="new">Home</target>
|
||||
<note>Navigation bar title for Home view where notes and replies appear from those who the user is following.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="How would you like to connect to your Coinos wallet?" xml:space="preserve">
|
||||
<source>How would you like to connect to your Coinos wallet?</source>
|
||||
<target state="new">How would you like to connect to your Coinos wallet?</target>
|
||||
<note>Question for the user when connecting a Coinos wallet.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Howdy! I’m a graphic designer during the day and coder at night, but I’m also trying to spend more time outdoors. Hope to meet folks who are on their own journeys to a peaceful and free life!" xml:space="preserve">
|
||||
<source>Howdy! I’m a graphic designer during the day and coder at night, but I’m also trying to spend more time outdoors.
|
||||
|
||||
@@ -5030,6 +5278,11 @@ Hope to meet folks who are on their own journeys to a peaceful and free life!</s
|
||||
Hope to meet folks who are on their own journeys to a peaceful and free life!</target>
|
||||
<note>First post example given to the user during onboarding, as a suggestion as to what they could post first</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="If your wallet balance is getting high, it's important to understand how to keep your funds secure. Please consider learning the best practices to ensure your assets remain safe. [Click here](https://damus.io/docs/wallet/high-balance-safety-reminder/) to learn more." xml:space="preserve">
|
||||
<source>If your wallet balance is getting high, it's important to understand how to keep your funds secure. Please consider learning the best practices to ensure your assets remain safe. [Click here](https://damus.io/docs/wallet/high-balance-safety-reminder/) to learn more.</source>
|
||||
<target state="new">If your wallet balance is getting high, it's important to understand how to keep your funds secure. Please consider learning the best practices to ensure your assets remain safe. [Click here](https://damus.io/docs/wallet/high-balance-safety-reminder/) to learn more.</target>
|
||||
<note>Text reminding the user has a high balance, recommending them to learn about self-custody</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Illegal Content" xml:space="preserve">
|
||||
<source>Illegal Content</source>
|
||||
<target state="new">Illegal Content</target>
|
||||
@@ -5064,7 +5317,7 @@ Option to enter a url</note>
|
||||
<trans-unit id="In progress…" xml:space="preserve">
|
||||
<source>In progress…</source>
|
||||
<target state="new">In progress…</target>
|
||||
<note>Loading message indicating that a contact list reset operation is in progress.</note>
|
||||
<note>Loading message indicating that a first aid operation is in progress.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Indefinite" xml:space="preserve">
|
||||
<source>Indefinite</source>
|
||||
@@ -5101,6 +5354,11 @@ Option to enter a url</note>
|
||||
<target state="new">Invalid lightning address</target>
|
||||
<note>Message to display when there was an error attempting to zap due to an invalid lightning address.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invalid relay address" xml:space="preserve">
|
||||
<source>Invalid relay address</source>
|
||||
<target state="new">Invalid relay address</target>
|
||||
<note>Heading for an error when adding a relay</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="It seems that you already have a translation service configured. Would you like to switch to Damus Purple as your translator?" xml:space="preserve">
|
||||
<source>It seems that you already have a translation service configured. Would you like to switch to Damus Purple as your translator?</source>
|
||||
<target state="new">It seems that you already have a translation service configured. Would you like to switch to Damus Purple as your translator?</target>
|
||||
@@ -5250,6 +5508,11 @@ Sidebar menu label to sign out of the account.</note>
|
||||
<target state="new">Maybe later</target>
|
||||
<note>Text for button to disconnect from Nostr Wallet Connect lightning wallet.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Media from someone you don't follow" xml:space="preserve">
|
||||
<source>Media from someone you don't follow</source>
|
||||
<target state="new">Media from someone you don't follow</target>
|
||||
<note>Label on the image blur mask</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Media previews" xml:space="preserve">
|
||||
<source>Media previews</source>
|
||||
<target state="new">Media previews</target>
|
||||
@@ -5287,6 +5550,7 @@ Setting to enable Mention Local Notification</note>
|
||||
<note>Alert button to mute a user.
|
||||
Button label that allows the user to mute the user shown on-screen
|
||||
Button to mute a profile
|
||||
Context menu action to mute the selected word.
|
||||
Title for confirmation dialog to mute a profile.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Mute %@?" xml:space="preserve">
|
||||
@@ -5389,6 +5653,11 @@ User confirm No</note>
|
||||
<target state="new">No image is currently setup</target>
|
||||
<note>Accessibility value on image control</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="No initial relay list available to update." xml:space="preserve">
|
||||
<source>No initial relay list available to update.</source>
|
||||
<target state="new">No initial relay list available to update.</target>
|
||||
<note>Human readable error description</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="No logs to display" xml:space="preserve">
|
||||
<source>No logs to display</source>
|
||||
<target state="new">No logs to display</target>
|
||||
@@ -5409,6 +5678,11 @@ User confirm No</note>
|
||||
<target state="new">No profile picture is currently setup</target>
|
||||
<note>Accessibility value on profile picture image control</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="No relay list was found. You might experience issues using the app. If you suspect you have permanently lost your relay list (or if you never had one), you can fix this by resetting it" xml:space="preserve">
|
||||
<source>No relay list was found. You might experience issues using the app. If you suspect you have permanently lost your relay list (or if you never had one), you can fix this by resetting it</source>
|
||||
<target state="new">No relay list was found. You might experience issues using the app. If you suspect you have permanently lost your relay list (or if you never had one), you can fix this by resetting it</target>
|
||||
<note>Section footer for relay list first aid tools</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="No results" xml:space="preserve">
|
||||
<source>No results</source>
|
||||
<target state="new">No results</target>
|
||||
@@ -5539,6 +5813,11 @@ Button label to dismiss an error dialog</note>
|
||||
<target state="new">Ok</target>
|
||||
<note>Button to dismiss the alert.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="One-click setup" xml:space="preserve">
|
||||
<source>One-click setup</source>
|
||||
<target state="new">One-click setup</target>
|
||||
<note>Button label for users to do a one-click Coinos wallet setup.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Online" xml:space="preserve">
|
||||
<source>Online</source>
|
||||
<target state="new">Online</target>
|
||||
@@ -5640,11 +5919,26 @@ Section title for deleting the user</note>
|
||||
<target state="new">Plan</target>
|
||||
<note>Prompt selection of DeepL subscription plan to perform machine translations on notes</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Please check the address and try again" xml:space="preserve">
|
||||
<source>Please check the address and try again</source>
|
||||
<target state="new">Please check the address and try again</target>
|
||||
<note>Tip for an error where the relay address being added is invalid</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Please choose relays from the list below to filter the current feed:" xml:space="preserve">
|
||||
<source>Please choose relays from the list below to filter the current feed:</source>
|
||||
<target state="new">Please choose relays from the list below to filter the current feed:</target>
|
||||
<note>Instructions on how to filter a specific timeline feed by choosing relay servers to filter on.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Please contact support for further help." xml:space="preserve">
|
||||
<source>Please contact support for further help.</source>
|
||||
<target state="new">Please contact support for further help.</target>
|
||||
<note>Human readable tips for what to do for a failure to find the relay list</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Please contact support." xml:space="preserve">
|
||||
<source>Please contact support.</source>
|
||||
<target state="new">Please contact support.</target>
|
||||
<note>Tip for an unknown relay error message.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Please contact the person who provided the link, and ask for another link." xml:space="preserve">
|
||||
<source>Please contact the person who provided the link, and ask for another link.</source>
|
||||
<target state="new">Please contact the person who provided the link, and ask for another link.</target>
|
||||
@@ -5655,6 +5949,21 @@ Section title for deleting the user</note>
|
||||
<target state="new">Please double-check the checkout web page, or go to the Side Menu → "Purple" to check your account status. If you have already paid, but still don't see your account active, please save the URL of the checkout page where you came from, contact our support, and give us the URL to help you with this issue.</target>
|
||||
<note>User-facing tips on what to do if a Purple welcome link doesn't work</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Please go to Settings > First Aid > Repair relay list, or contact support." xml:space="preserve">
|
||||
<source>Please go to Settings > First Aid > Repair relay list, or contact support.</source>
|
||||
<target state="new">Please go to Settings > First Aid > Repair relay list, or contact support.</target>
|
||||
<note>Human readable tip for error</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Please make sure you have logged-in with your private key." xml:space="preserve">
|
||||
<source>Please make sure you have logged-in with your private key.</source>
|
||||
<target state="new">Please make sure you have logged-in with your private key.</target>
|
||||
<note>Human readable tip for error</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Please try again later or contact support if the issue persists." xml:space="preserve">
|
||||
<source>Please try again later or contact support if the issue persists.</source>
|
||||
<target state="new">Please try again later or contact support if the issue persists.</target>
|
||||
<note>Human readable tip for error</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Please try again, check the URL for typos, or contact support for further help." xml:space="preserve">
|
||||
<source>Please try again, check the URL for typos, or contact support for further help.</source>
|
||||
<target state="new">Please try again, check the URL for typos, or contact support for further help.</target>
|
||||
@@ -5816,6 +6125,16 @@ Title of emoji reactions view</note>
|
||||
<target state="new">Relay Logs</target>
|
||||
<note>Text label indicating that the text below it are developer mode logs.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Relay list" xml:space="preserve">
|
||||
<source>Relay list</source>
|
||||
<target state="new">Relay list</target>
|
||||
<note>Section title for Relay list first aid tools</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Relay list has been repaired" xml:space="preserve">
|
||||
<source>Relay list has been repaired</source>
|
||||
<target state="new">Relay list has been repaired</target>
|
||||
<note>Message indicating that the relay list was successfully repaired.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Relays" xml:space="preserve">
|
||||
<source>Relays</source>
|
||||
<target state="new">Relays</target>
|
||||
@@ -5858,6 +6177,11 @@ Title of relays view</note>
|
||||
<target state="new">Renews on</target>
|
||||
<note>Indicating when the subscription will renew</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Repair relay list" xml:space="preserve">
|
||||
<source>Repair relay list</source>
|
||||
<target state="new">Repair relay list</target>
|
||||
<note>Button to repair relay list.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Reply" xml:space="preserve">
|
||||
<source>Reply</source>
|
||||
<target state="new">Reply</target>
|
||||
@@ -5979,6 +6303,11 @@ Setting to enable Repost Local Notification</note>
|
||||
<target state="new">SOFTWARE</target>
|
||||
<note>Text label indicating which relay software is used to run this Nostr relay.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Safety Reminder" xml:space="preserve">
|
||||
<source>Safety Reminder</source>
|
||||
<target state="new">Safety Reminder</target>
|
||||
<note>Heading for a safety reminder that appears when the user has too many funds, recommending them to learn about safeguarding their funds.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Satoshi Nakamoto" xml:space="preserve">
|
||||
<source>Satoshi Nakamoto</source>
|
||||
<target state="new">Satoshi Nakamoto</target>
|
||||
@@ -6278,6 +6607,11 @@ Button to show more of a long profile description.</note>
|
||||
<target state="new">Someone zapped you ⚡️</target>
|
||||
<note>Title label for a push notification where someone zapped the user</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Something went wrong when performing the one-click Coinos wallet setup." xml:space="preserve">
|
||||
<source>Something went wrong when performing the one-click Coinos wallet setup.</source>
|
||||
<target state="new">Something went wrong when performing the one-click Coinos wallet setup.</target>
|
||||
<note>Error label when user tries the one-click Coinos wallet setup but fails for some generic reason.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Sorry, but for some reason there has been an issue while trying to crop this image. Please try again later. If the error persists, please contact [Damus support](mailto:support@damus.io)" xml:space="preserve">
|
||||
<source>Sorry, but for some reason there has been an issue while trying to crop this image. Please try again later. If the error persists, please contact [Damus support](mailto:support@damus.io)</source>
|
||||
<target state="new">Sorry, but for some reason there has been an issue while trying to crop this image. Please try again later. If the error persists, please contact [Damus support](mailto:support@damus.io)</target>
|
||||
@@ -6369,6 +6703,11 @@ Section header for Universe/Search spam</note>
|
||||
<target state="new">Take Photo</target>
|
||||
<note>Option to take a photo with the camera</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Tap to load" xml:space="preserve">
|
||||
<source>Tap to load</source>
|
||||
<target state="new">Tap to load</target>
|
||||
<note>Label for button that allows user to dismiss media content warning and unblur the image</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Test (local)" xml:space="preserve">
|
||||
<source>Test (local)</source>
|
||||
<target state="new">Test (local)</target>
|
||||
@@ -6413,18 +6752,16 @@ Enjoy!</target>
|
||||
<target state="new">The camera was not capable of scanning the requested codes.</target>
|
||||
<note>Camera's bad output error label</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="The relay you are trying to add is already added. You're all set!" xml:space="preserve">
|
||||
<source>The relay you are trying to add is already added.
|
||||
You're all set!</source>
|
||||
<target state="new">The relay you are trying to add is already added.
|
||||
You're all set!</target>
|
||||
<note>An error message that appears when the user attempts to add a relay that has already been added.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="The social network you control" xml:space="preserve">
|
||||
<source>The social network you control</source>
|
||||
<target state="new">The social network you control</target>
|
||||
<note>Quick description of what Damus is</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="The specified relay that you are trying to udpate was not found in your relay list." xml:space="preserve">
|
||||
<source>The specified relay that you are trying to udpate was not found in your relay list.</source>
|
||||
<target state="new">The specified relay that you are trying to udpate was not found in your relay list.</target>
|
||||
<note>Human readable error description</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="There has been an unexpected error with the in-app purchase. Please try again later or contact support@damus.io. Error: %@" xml:space="preserve">
|
||||
<source>There has been an unexpected error with the in-app purchase. Please try again later or contact support@damus.io. Error: %@</source>
|
||||
<target state="new">There has been an unexpected error with the in-app purchase. Please try again later or contact support@damus.io. Error: %@</target>
|
||||
@@ -6435,6 +6772,11 @@ You're all set!</target>
|
||||
<target state="new">There is no content available to share at this time. Please close this view and try again.</target>
|
||||
<note>Label explaining that no content is available to share and instructing the user to close the view and try again.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="There was a problem creating the relay list event." xml:space="preserve">
|
||||
<source>There was a problem creating the relay list event.</source>
|
||||
<target state="new">There was a problem creating the relay list event.</target>
|
||||
<note>Human readable error description</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="There was an error loading your account. Please try again later. If problem persists, please contact us at support@damus.io" xml:space="preserve">
|
||||
<source>There was an error loading your account. Please try again later. If problem persists, please contact us at support@damus.io</source>
|
||||
<target state="new">There was an error loading your account. Please try again later. If problem persists, please contact us at support@damus.io</target>
|
||||
@@ -6455,6 +6797,11 @@ You're all set!</target>
|
||||
<target state="new">This is a public key, you will not be able to make notes or interact in any way. This is used for viewing accounts from their perspective.</target>
|
||||
<note>Warning that the inputted account key is a public key and the result of what happens because of it.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="This is an unexpected error, please contact support." xml:space="preserve">
|
||||
<source>This is an unexpected error, please contact support.</source>
|
||||
<target state="new">This is an unexpected error, please contact support.</target>
|
||||
<note>Human readable tip for error</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="This is my first post on Nostr 💜. I love drawing and folding Origami! Nice to meet you all! #introductions #plebchain " xml:space="preserve">
|
||||
<source>This is my first post on Nostr 💜. I love drawing and folding Origami!
|
||||
|
||||
@@ -6469,6 +6816,11 @@ Nice to meet you all! #introductions #plebchain </target>
|
||||
<target state="new">This note contains too many items and cannot be rendered</target>
|
||||
<note>Error message indicating that a note is too big and cannot be rendered</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="This relay is already in your list." xml:space="preserve">
|
||||
<source>This relay is already in your list.</source>
|
||||
<target state="new">This relay is already in your list.</target>
|
||||
<note>Human readable tip for error</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="This user cannot be zapped because they have not configured zaps on their account yet. Time to orange-pill?" xml:space="preserve">
|
||||
<source>This user cannot be zapped because they have not configured zaps on their account yet. Time to orange-pill?</source>
|
||||
<target state="new">This user cannot be zapped because they have not configured zaps on their account yet. Time to orange-pill?</target>
|
||||
@@ -6714,13 +7066,22 @@ YOU WILL NO LONGER BE ABLE TO LOG INTO DAMUS USING THIS ACCOUNT KEY.
|
||||
ARE YOU SURE YOU WANT TO CONTINUE?</target>
|
||||
<note>Alert for deleting the users account.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="WARNING: This will reset your contact list, including the list of everyone you follow and the list of all relays you usually connect to. ONLY PROCEED IF YOU ARE SURE YOU HAVE LOST YOUR CONTACT LIST BEYOND RECOVERABILITY." xml:space="preserve">
|
||||
<trans-unit id="WARNING: This will attempt to repair your relay list based on other information we have. You may lose any relays you have added manually. Only proceed if you have lost your relay list beyond recoverability or if you are ok with losing any manually added relays." xml:space="preserve">
|
||||
<source>WARNING:
|
||||
|
||||
This will reset your contact list, including the list of everyone you follow and the list of all relays you usually connect to. ONLY PROCEED IF YOU ARE SURE YOU HAVE LOST YOUR CONTACT LIST BEYOND RECOVERABILITY.</source>
|
||||
This will attempt to repair your relay list based on other information we have. You may lose any relays you have added manually. Only proceed if you have lost your relay list beyond recoverability or if you are ok with losing any manually added relays.</source>
|
||||
<target state="new">WARNING:
|
||||
|
||||
This will reset your contact list, including the list of everyone you follow and the list of all relays you usually connect to. ONLY PROCEED IF YOU ARE SURE YOU HAVE LOST YOUR CONTACT LIST BEYOND RECOVERABILITY.</target>
|
||||
This will attempt to repair your relay list based on other information we have. You may lose any relays you have added manually. Only proceed if you have lost your relay list beyond recoverability or if you are ok with losing any manually added relays.</target>
|
||||
<note>Alert for repairing the user's relay list.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="WARNING: This will reset your contact list, including the list of everyone you follow and potentially the list of all relays you usually connect to. ONLY PROCEED IF YOU ARE SURE YOU HAVE LOST YOUR CONTACT LIST BEYOND RECOVERABILITY." xml:space="preserve">
|
||||
<source>WARNING:
|
||||
|
||||
This will reset your contact list, including the list of everyone you follow and potentially the list of all relays you usually connect to. ONLY PROCEED IF YOU ARE SURE YOU HAVE LOST YOUR CONTACT LIST BEYOND RECOVERABILITY.</source>
|
||||
<target state="new">WARNING:
|
||||
|
||||
This will reset your contact list, including the list of everyone you follow and potentially the list of all relays you usually connect to. ONLY PROCEED IF YOU ARE SURE YOU HAVE LOST YOUR CONTACT LIST BEYOND RECOVERABILITY.</target>
|
||||
<note>Alert for resetting the user's contact list.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Wallet" xml:space="preserve">
|
||||
@@ -6837,6 +7198,11 @@ User confirm Yes</note>
|
||||
<target state="new">You clicked on a Purple welcome link, but we could not find your checkout. This is likely a bug.</target>
|
||||
<note>Error label upon continuing in the app from a Damus Purple purchase</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You do not have permission to alter this relay list." xml:space="preserve">
|
||||
<source>You do not have permission to alter this relay list.</source>
|
||||
<target state="new">You do not have permission to alter this relay list.</target>
|
||||
<note>Human readable error description</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You drive the conversation and we want to make it easier for people to support your work beyond follows, reposts, and likes." xml:space="preserve">
|
||||
<source>You drive the conversation and we want to make it easier for people to support your work beyond follows, reposts, and likes.</source>
|
||||
<target state="new">You drive the conversation and we want to make it easier for people to support your work beyond follows, reposts, and likes.</target>
|
||||
@@ -6847,6 +7213,11 @@ User confirm Yes</note>
|
||||
<target state="new">You have no bookmarks yet, add them in the context menu</target>
|
||||
<note>Text indicating that there are no bookmarks to be viewed</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You must be logged in with your nsec to use this option." xml:space="preserve">
|
||||
<source>You must be logged in with your nsec to use this option.</source>
|
||||
<target state="new">You must be logged in with your nsec to use this option.</target>
|
||||
<note>Warning text for users who cannot create a Coinos account via the one-click setup without being logged in with their nsec.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You opened an invalid link. The link you tried to open refers to "nrelay", which has been deprecated and is not supported." xml:space="preserve">
|
||||
<source>You opened an invalid link. The link you tried to open refers to "nrelay", which has been deprecated and is not supported.</source>
|
||||
<target state="new">You opened an invalid link. The link you tried to open refers to "nrelay", which has been deprecated and is not supported.</target>
|
||||
@@ -6887,6 +7258,16 @@ User confirm Yes</note>
|
||||
<target state="new">Your draft has been saved to storage.</target>
|
||||
<note>Accessibility label indicating that a user's post draft has been saved, meant to be read by screen reading technology.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Your profile will not be shared with Coinos." xml:space="preserve">
|
||||
<source>Your profile will not be shared with Coinos.</source>
|
||||
<target state="new">Your profile will not be shared with Coinos.</target>
|
||||
<note>Label text for users to reassure them that their nsec is not shared with a third party.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Your relay list appears to be broken, so we cannot connect you to your Nostr network." xml:space="preserve">
|
||||
<source>Your relay list appears to be broken, so we cannot connect you to your Nostr network.</source>
|
||||
<target state="new">Your relay list appears to be broken, so we cannot connect you to your Nostr network.</target>
|
||||
<note>Human readable error description for a failure to parse the relay list due to a bad relay list</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Your report will be sent to the relays you are connected to" xml:space="preserve">
|
||||
<source>Your report will be sent to the relays you are connected to</source>
|
||||
<target state="new">Your report will be sent to the relays you are connected to</target>
|
||||
|
||||
@@ -168,18 +168,24 @@
|
||||
"Already on Nostr?" : {
|
||||
"comment" : "Ask the user if they already have an account on Nostr"
|
||||
},
|
||||
"Also click here if you had a one-click setup before." : {
|
||||
"comment" : "Button description hint for users who may want to do a one-click setup."
|
||||
},
|
||||
"Always show onboarding" : {
|
||||
"comment" : "Developer mode setting to always show onboarding suggestions."
|
||||
},
|
||||
"An additional percentage of each zap will be sent to support Damus development" : {
|
||||
"comment" : "Text indicating that they can contribute zaps to support Damus development."
|
||||
},
|
||||
"An unexpected error happened while trying to create the new contact list. Please contact support." : {
|
||||
"comment" : "Error message for a failed contact list reset operation"
|
||||
"An unexpected error happened while trying to perform this action. Please contact support." : {
|
||||
"comment" : "Error message for a failed reset/repair operation"
|
||||
},
|
||||
"An unexpected error occurred. Please contact Damus support via [Nostr](damus:npub18m76awca3y37hkvuneavuw6pjj4525fw90necxmadrvjg0sdy6qsngq955) or [email](support@damus.io) with the error message below." : {
|
||||
"comment" : "Label explaining there was an error, and suggesting next steps"
|
||||
},
|
||||
"An unknown error occurred while adding a relay." : {
|
||||
"comment" : "Title of an unknown relay error message."
|
||||
},
|
||||
"Animations" : {
|
||||
"comment" : "Toggle to enable or disable image animation"
|
||||
},
|
||||
@@ -286,7 +292,7 @@
|
||||
"comment" : "User-visible heading for an error message indicating a note has an unknown kind or is unsupported for viewing."
|
||||
},
|
||||
"Cancel" : {
|
||||
"comment" : "Alert button to cancel out of alert for muting a user.\nButton to cancel a repost.\nButton to cancel any interaction with the QRCode link.\nButton to cancel out of alert that creates a new mutelist.\nButton to cancel out of posting a note.\nButton to cancel out of search text entry mode.\nButton to cancel the upload.\nCancel button text for dismissing profile status settings view.\nCancel button text for dismissing updating image url.\nCancel deleting bookmarks.\nCancel deleting the user.\nCancel out of logging out the user.\nCancel out of search view.\nCancel resetting the contact list.\nText for button to cancel out of connecting Nostr Wallet Connect lightning wallet."
|
||||
"comment" : "Alert button to cancel out of alert for muting a user.\nButton to cancel a repost.\nButton to cancel any interaction with the QRCode link.\nButton to cancel out of alert that creates a new mutelist.\nButton to cancel out of posting a note.\nButton to cancel out of search text entry mode.\nButton to cancel the upload.\nCancel button text for dismissing profile status settings view.\nCancel button text for dismissing updating image url.\nCancel deleting bookmarks.\nCancel deleting the user.\nCancel out of logging out the user.\nCancel out of search view.\nCancel the user-requested operation.\nText for button to cancel out of connecting Nostr Wallet Connect lightning wallet."
|
||||
},
|
||||
"Cancelled" : {
|
||||
"comment" : "Title indicating that the user has cancelled."
|
||||
@@ -294,6 +300,12 @@
|
||||
"Changing this setting will cause the cache to be cleared. This will free space, but images may take longer to load again. Are you sure you want to proceed?" : {
|
||||
"comment" : "Message explaining consequences of changing the 'enable animation' setting"
|
||||
},
|
||||
"Check the address and/or the relay list." : {
|
||||
"comment" : "Human readable tip for error"
|
||||
},
|
||||
"Check your internet connection and try again. If the error persists, contact support." : {
|
||||
"comment" : "Error tip when user tries to create the one-click Coinos wallet setup but fails for a generic reason."
|
||||
},
|
||||
"Choose from Library" : {
|
||||
"comment" : "Option to select photo from library"
|
||||
},
|
||||
@@ -312,9 +324,15 @@
|
||||
"Clearing Cache" : {
|
||||
"comment" : "Loading message indicating that the cache is being cleared."
|
||||
},
|
||||
"Click here if you have a Coinos username and password." : {
|
||||
"comment" : "Button description hint for users who may want to connect via the website."
|
||||
},
|
||||
"Close" : {
|
||||
"comment" : "Button label giving the user the option to close the sheet due to not being logged in.\nButton label giving the user the option to close the sheet from which they shared content\nButton label giving the user the option to close the sheet from which they were trying share.\nButton label giving the user the option to close the sheet from which they were trying to share.\nButton label giving the user the option to close the view when no content is available to share"
|
||||
},
|
||||
"Coinos is a service operated by a third-party. We have no access to your Coinos wallet." : {
|
||||
"comment" : "Small caption with a disclaimer that Damus does not own or have access to Coinos wallets, Coinos is a third-party service."
|
||||
},
|
||||
"Coming soon" : {
|
||||
"comment" : "Feature is still in development and will be available soon"
|
||||
},
|
||||
@@ -336,13 +354,16 @@
|
||||
"Connect to Coinos" : {
|
||||
"comment" : "Button to attach a Coinos Wallet, a service that provides a Lightning wallet for zapping sats. Coinos is the name of the service and should not be translated."
|
||||
},
|
||||
"Connect via the website" : {
|
||||
"comment" : "Button label for users who are setting up a Coinos wallet and would like to connect via the website"
|
||||
},
|
||||
"Connecting" : {
|
||||
"comment" : "Relay status label that indicates a relay is connecting."
|
||||
},
|
||||
"CONTACT" : {
|
||||
"comment" : "Text label indicating that the information below is the contact information of the admin of the Nostr relay."
|
||||
},
|
||||
"Contact list (Follows + Relay list)" : {
|
||||
"Contact list" : {
|
||||
"comment" : "Section title for Contact list first aid tools"
|
||||
},
|
||||
"Contact list has been reset" : {
|
||||
@@ -358,7 +379,7 @@
|
||||
"comment" : "Section title for content filtering/moderation configuration."
|
||||
},
|
||||
"Continue" : {
|
||||
"comment" : "Button to dismiss suggested users view and continue to the main app\nContinue with bookmarks.\nContinue with deleting the user.\nContinue with resetting the contact list.\nPrompt to user to continue"
|
||||
"comment" : "Button to dismiss suggested users view and continue to the main app\nContinue with bookmarks.\nContinue with deleting the user.\nContinue with the user-requested operation.\nPrompt to user to continue"
|
||||
},
|
||||
"Conversations" : {
|
||||
"comment" : "Label for filter for seeing notes and replies that involve conversations between the signed in user and the current profile."
|
||||
@@ -408,6 +429,9 @@
|
||||
"Could not create your initial contact list event. This is a software bug, please contact Damus support via support@damus.io or through our Nostr account for help." : {
|
||||
"comment" : "Error message to the user indicating that the initial contact list failed to be created."
|
||||
},
|
||||
"Could not create your initial relay list. This is a software bug, please contact Damus support via support@damus.io or through our Nostr account for help." : {
|
||||
"comment" : "Error message to the user indicating that the initial relay list failed to be created."
|
||||
},
|
||||
"Could not find the user you're looking for" : {
|
||||
"comment" : "Indicates that there are no users found."
|
||||
},
|
||||
@@ -495,9 +519,6 @@
|
||||
"Done" : {
|
||||
"comment" : "Button to dismiss wallet selection view for paying Lightning invoice.\nButton to leave edit mode for modifying the list of relays."
|
||||
},
|
||||
"Duplicate relay" : {
|
||||
"comment" : "Title of the duplicate relay error message."
|
||||
},
|
||||
"Duration" : {
|
||||
"comment" : "Label for profile status expiration duration picker.\nThe duration in which to mute the given item."
|
||||
},
|
||||
@@ -724,6 +745,12 @@
|
||||
"Hide notes with #nsfw tags" : {
|
||||
"comment" : "Setting to hide notes with the #nsfw (not safe for work) tags"
|
||||
},
|
||||
"Hide notifications that tag many profiles" : {
|
||||
"comment" : "Label for notification settings toggle that hides notifications that tag many people."
|
||||
},
|
||||
"Highlight" : {
|
||||
"comment" : "Context menu action to highlight the selected text as context to draft a new note."
|
||||
},
|
||||
"Highlighted" : {
|
||||
"comment" : "Label to indicate that the user is highlighting their own post."
|
||||
},
|
||||
@@ -733,12 +760,18 @@
|
||||
"Home" : {
|
||||
"comment" : "Navigation bar title for Home view where notes and replies appear from those who the user is following."
|
||||
},
|
||||
"How would you like to connect to your Coinos wallet?" : {
|
||||
"comment" : "Question for the user when connecting a Coinos wallet."
|
||||
},
|
||||
"Howdy! I’m a graphic designer during the day and coder at night, but I’m also trying to spend more time outdoors.\n\nHope to meet folks who are on their own journeys to a peaceful and free life!" : {
|
||||
"comment" : "First post example given to the user during onboarding, as a suggestion as to what they could post first"
|
||||
},
|
||||
"https://jb55.com" : {
|
||||
"comment" : "Placeholder example text for website URL for user profile."
|
||||
},
|
||||
"If your wallet balance is getting high, it's important to understand how to keep your funds secure. Please consider learning the best practices to ensure your assets remain safe. [Click here](https://damus.io/docs/wallet/high-balance-safety-reminder/) to learn more." : {
|
||||
"comment" : "Text reminding the user has a high balance, recommending them to learn about self-custody"
|
||||
},
|
||||
"Illegal Content" : {
|
||||
"comment" : "Description of report type for illegal content."
|
||||
},
|
||||
@@ -758,7 +791,7 @@
|
||||
"comment" : "Description of report type for impersonation."
|
||||
},
|
||||
"In progress…" : {
|
||||
"comment" : "Loading message indicating that a contact list reset operation is in progress."
|
||||
"comment" : "Loading message indicating that a first aid operation is in progress."
|
||||
},
|
||||
"Indefinite" : {
|
||||
"comment" : "Mute a given item indefinitly (until user unmutes it). As opposed to muting the item for a given period of time."
|
||||
@@ -775,6 +808,9 @@
|
||||
"Invalid Nostr wallet connection string" : {
|
||||
"comment" : "Error message when an invalid Nostr wallet connection string is provided."
|
||||
},
|
||||
"Invalid relay address" : {
|
||||
"comment" : "Heading for an error when adding a relay"
|
||||
},
|
||||
"Invalid Tip Address" : {
|
||||
"comment" : "Title of alerting as invalid tip address."
|
||||
},
|
||||
@@ -871,6 +907,9 @@
|
||||
"Maybe later" : {
|
||||
"comment" : "Text for button to disconnect from Nostr Wallet Connect lightning wallet."
|
||||
},
|
||||
"Media from someone you don't follow" : {
|
||||
"comment" : "Label on the image blur mask"
|
||||
},
|
||||
"Media previews" : {
|
||||
"comment" : "Setting to show media"
|
||||
},
|
||||
@@ -890,7 +929,7 @@
|
||||
"comment" : "Monthly renewal of purple subscription"
|
||||
},
|
||||
"Mute" : {
|
||||
"comment" : "Alert button to mute a user.\nButton label that allows the user to mute the user shown on-screen\nButton to mute a profile\nTitle for confirmation dialog to mute a profile."
|
||||
"comment" : "Alert button to mute a user.\nButton label that allows the user to mute the user shown on-screen\nButton to mute a profile\nContext menu action to mute the selected word.\nTitle for confirmation dialog to mute a profile."
|
||||
},
|
||||
"Mute %@?" : {
|
||||
"comment" : "Alert message prompt to ask if a user should be muted."
|
||||
@@ -949,6 +988,9 @@
|
||||
"No image is currently setup" : {
|
||||
"comment" : "Accessibility value on image control"
|
||||
},
|
||||
"No initial relay list available to update." : {
|
||||
"comment" : "Human readable error description"
|
||||
},
|
||||
"No logs to display" : {
|
||||
"comment" : "Label to indicate that there are no developer mode logs available to be displayed on the screen"
|
||||
},
|
||||
@@ -961,6 +1003,9 @@
|
||||
"No profile picture is currently setup" : {
|
||||
"comment" : "Accessibility value on profile picture image control"
|
||||
},
|
||||
"No relay list was found. You might experience issues using the app. If you suspect you have permanently lost your relay list (or if you never had one), you can fix this by resetting it" : {
|
||||
"comment" : "Section footer for relay list first aid tools"
|
||||
},
|
||||
"No results" : {
|
||||
"comment" : "A label indicating that note search resulted in no results"
|
||||
},
|
||||
@@ -1060,6 +1105,9 @@
|
||||
"OK" : {
|
||||
"comment" : "Button label indicating user wants to proceed.\nButton label to dismiss an error dialog"
|
||||
},
|
||||
"One-click setup" : {
|
||||
"comment" : "Button label for users to do a one-click Coinos wallet setup."
|
||||
},
|
||||
"Online" : {
|
||||
"comment" : "Relay status label that indicates a relay is connected."
|
||||
},
|
||||
@@ -1123,15 +1171,33 @@
|
||||
"Plan" : {
|
||||
"comment" : "Prompt selection of DeepL subscription plan to perform machine translations on notes"
|
||||
},
|
||||
"Please check the address and try again" : {
|
||||
"comment" : "Tip for an error where the relay address being added is invalid"
|
||||
},
|
||||
"Please choose relays from the list below to filter the current feed:" : {
|
||||
"comment" : "Instructions on how to filter a specific timeline feed by choosing relay servers to filter on."
|
||||
},
|
||||
"Please contact support for further help." : {
|
||||
"comment" : "Human readable tips for what to do for a failure to find the relay list"
|
||||
},
|
||||
"Please contact support." : {
|
||||
"comment" : "Tip for an unknown relay error message."
|
||||
},
|
||||
"Please contact the person who provided the link, and ask for another link." : {
|
||||
"comment" : "User-visible tip on what to do if a link contains a deprecated \"nrelay\" reference."
|
||||
},
|
||||
"Please double-check the checkout web page, or go to the Side Menu → \"Purple\" to check your account status. If you have already paid, but still don't see your account active, please save the URL of the checkout page where you came from, contact our support, and give us the URL to help you with this issue." : {
|
||||
"comment" : "User-facing tips on what to do if a Purple welcome link doesn't work"
|
||||
},
|
||||
"Please go to Settings > First Aid > Repair relay list, or contact support." : {
|
||||
"comment" : "Human readable tip for error"
|
||||
},
|
||||
"Please make sure you have logged-in with your private key." : {
|
||||
"comment" : "Human readable tip for error"
|
||||
},
|
||||
"Please try again later or contact support if the issue persists." : {
|
||||
"comment" : "Human readable tip for error"
|
||||
},
|
||||
"Please try again, check the URL for typos, or contact support for further help." : {
|
||||
"comment" : "User visible error tips"
|
||||
},
|
||||
@@ -1234,6 +1300,12 @@
|
||||
"Recommended" : {
|
||||
"comment" : "Title of the tab that shows the list of relays recommended by Damus."
|
||||
},
|
||||
"Relay list" : {
|
||||
"comment" : "Section title for Relay list first aid tools"
|
||||
},
|
||||
"Relay list has been repaired" : {
|
||||
"comment" : "Message indicating that the relay list was successfully repaired."
|
||||
},
|
||||
"Relay Logs" : {
|
||||
"comment" : "Text label indicating that the text below it are developer mode logs."
|
||||
},
|
||||
@@ -1261,6 +1333,9 @@
|
||||
"Renews on" : {
|
||||
"comment" : "Indicating when the subscription will renew"
|
||||
},
|
||||
"Repair relay list" : {
|
||||
"comment" : "Button to repair relay list."
|
||||
},
|
||||
"Reply" : {
|
||||
"comment" : "Accessibility label for reply button"
|
||||
},
|
||||
@@ -1335,6 +1410,9 @@
|
||||
"Runtime error" : {
|
||||
"comment" : "Indication that a runtime error occurred when running a NostrScript."
|
||||
},
|
||||
"Safety Reminder" : {
|
||||
"comment" : "Heading for a safety reminder that appears when the user has too many funds, recommending them to learn about safeguarding their funds."
|
||||
},
|
||||
"Satoshi Nakamoto" : {
|
||||
"comment" : "Name of Bitcoin creator(s)."
|
||||
},
|
||||
@@ -1518,6 +1596,9 @@
|
||||
"Someone zapped you ⚡️" : {
|
||||
"comment" : "Title label for a push notification where someone zapped the user"
|
||||
},
|
||||
"Something went wrong when performing the one-click Coinos wallet setup." : {
|
||||
"comment" : "Error label when user tries the one-click Coinos wallet setup but fails for some generic reason."
|
||||
},
|
||||
"Sorry, but for some reason there has been an issue while trying to crop this image. Please try again later. If the error persists, please contact [Damus support](mailto:support@damus.io)" : {
|
||||
"comment" : "Cropping error message"
|
||||
},
|
||||
@@ -1572,6 +1653,9 @@
|
||||
"Take Photo" : {
|
||||
"comment" : "Option to take a photo with the camera"
|
||||
},
|
||||
"Tap to load" : {
|
||||
"comment" : "Label for button that allows user to dismiss media content warning and unblur the image"
|
||||
},
|
||||
"Test (local)" : {
|
||||
"comment" : "Label indicating a local test environment for Damus Purple functionality (Developer feature)\nLabel indicating a local test environment for Push notification functionality (Developer feature)"
|
||||
},
|
||||
@@ -1593,18 +1677,21 @@
|
||||
"The camera was not capable of scanning the requested codes." : {
|
||||
"comment" : "Camera's bad output error label"
|
||||
},
|
||||
"The relay you are trying to add is already added.\nYou're all set!" : {
|
||||
"comment" : "An error message that appears when the user attempts to add a relay that has already been added."
|
||||
},
|
||||
"The social network you control" : {
|
||||
"comment" : "Quick description of what Damus is"
|
||||
},
|
||||
"The specified relay that you are trying to udpate was not found in your relay list." : {
|
||||
"comment" : "Human readable error description"
|
||||
},
|
||||
"There has been an unexpected error with the in-app purchase. Please try again later or contact support@damus.io. Error: %@" : {
|
||||
"comment" : "In-app purchase error message for the user"
|
||||
},
|
||||
"There is no content available to share at this time. Please close this view and try again." : {
|
||||
"comment" : "Label explaining that no content is available to share and instructing the user to close the view and try again."
|
||||
},
|
||||
"There was a problem creating the relay list event." : {
|
||||
"comment" : "Human readable error description"
|
||||
},
|
||||
"There was an error loading your account. Please try again later. If problem persists, please contact us at support@damus.io" : {
|
||||
"comment" : "Error label when Purple account information fails to load"
|
||||
},
|
||||
@@ -1617,12 +1704,18 @@
|
||||
"This is a public key, you will not be able to make notes or interact in any way. This is used for viewing accounts from their perspective." : {
|
||||
"comment" : "Warning that the inputted account key is a public key and the result of what happens because of it."
|
||||
},
|
||||
"This is an unexpected error, please contact support." : {
|
||||
"comment" : "Human readable tip for error"
|
||||
},
|
||||
"This is my first post on Nostr 💜. I love drawing and folding Origami!\n\nNice to meet you all! #introductions #plebchain " : {
|
||||
"comment" : "First post example given to the user during onboarding, as a suggestion as to what they could post first"
|
||||
},
|
||||
"This note contains too many items and cannot be rendered" : {
|
||||
"comment" : "Error message indicating that a note is too big and cannot be rendered"
|
||||
},
|
||||
"This relay is already in your list." : {
|
||||
"comment" : "Human readable tip for error"
|
||||
},
|
||||
"This user cannot be zapped because they have not configured zaps on their account yet. Time to orange-pill?" : {
|
||||
"comment" : "Comment explaining why a user cannot be zapped."
|
||||
},
|
||||
@@ -1764,7 +1857,10 @@
|
||||
"Wallet" : {
|
||||
"comment" : "Navigation title for Wallet view\nNavigation title for attaching Nostr Wallet Connect lightning wallet.\nSidebar menu label for Wallet view.\nTitle for section in zap settings that controls the Lightning wallet selection."
|
||||
},
|
||||
"WARNING:\n\nThis will reset your contact list, including the list of everyone you follow and the list of all relays you usually connect to. ONLY PROCEED IF YOU ARE SURE YOU HAVE LOST YOUR CONTACT LIST BEYOND RECOVERABILITY." : {
|
||||
"WARNING:\n\nThis will attempt to repair your relay list based on other information we have. You may lose any relays you have added manually. Only proceed if you have lost your relay list beyond recoverability or if you are ok with losing any manually added relays." : {
|
||||
"comment" : "Alert for repairing the user's relay list."
|
||||
},
|
||||
"WARNING:\n\nThis will reset your contact list, including the list of everyone you follow and potentially the list of all relays you usually connect to. ONLY PROCEED IF YOU ARE SURE YOU HAVE LOST YOUR CONTACT LIST BEYOND RECOVERABILITY." : {
|
||||
"comment" : "Alert for resetting the user's contact list."
|
||||
},
|
||||
"WARNING:\n\nTHIS WILL SIGN AN EVENT THAT DELETES THIS ACCOUNT.\n\nYOU WILL NO LONGER BE ABLE TO LOG INTO DAMUS USING THIS ACCOUNT KEY.\n\n ARE YOU SURE YOU WANT TO CONTINUE?" : {
|
||||
@@ -1839,12 +1935,18 @@
|
||||
"You clicked on a Purple welcome link, but we could not find your checkout. This is likely a bug." : {
|
||||
"comment" : "Error label upon continuing in the app from a Damus Purple purchase"
|
||||
},
|
||||
"You do not have permission to alter this relay list." : {
|
||||
"comment" : "Human readable error description"
|
||||
},
|
||||
"You drive the conversation and we want to make it easier for people to support your work beyond follows, reposts, and likes." : {
|
||||
"comment" : "Text explaining the benefit of connecting a lightning wallet for content creators."
|
||||
},
|
||||
"You have no bookmarks yet, add them in the context menu" : {
|
||||
"comment" : "Text indicating that there are no bookmarks to be viewed"
|
||||
},
|
||||
"You must be logged in with your nsec to use this option." : {
|
||||
"comment" : "Warning text for users who cannot create a Coinos account via the one-click setup without being logged in with their nsec."
|
||||
},
|
||||
"You opened an invalid link. The link you tried to open refers to \"nrelay\", which has been deprecated and is not supported." : {
|
||||
"comment" : "User-visible error description for a user who tries to open a deprecated \"nrelay\" link."
|
||||
},
|
||||
@@ -1860,6 +1962,9 @@
|
||||
"Your Name" : {
|
||||
"comment" : "Label for Your Name section of user profile form."
|
||||
},
|
||||
"Your profile will not be shared with Coinos." : {
|
||||
"comment" : "Label text for users to reassure them that their nsec is not shared with a third party."
|
||||
},
|
||||
"Your Purple subscription expires in %@ days. Renew?" : {
|
||||
"comment" : "A notification message explaining to the user that their Damus Purple Subscription is expiring soon, prompting them to renew."
|
||||
},
|
||||
@@ -1869,6 +1974,9 @@
|
||||
"Your Purple subscription has expired. Renew?" : {
|
||||
"comment" : "A notification message explaining to the user that their Damus Purple Subscription has expired, prompting them to renew."
|
||||
},
|
||||
"Your relay list appears to be broken, so we cannot connect you to your Nostr network." : {
|
||||
"comment" : "Human readable error description for a failure to parse the relay list due to a bad relay list"
|
||||
},
|
||||
"Your report will be sent to the relays you are connected to" : {
|
||||
"comment" : "Footer text to inform user what will happen when the report is submitted."
|
||||
},
|
||||
|
||||
Binary file not shown.
@@ -50,6 +50,22 @@
|
||||
<string>Following</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>hellthread_notifications_disabled</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@HELLTHREAD_PROFILES@</string>
|
||||
<key>HELLTHREAD_PROFILES</key>
|
||||
<dict>
|
||||
<key>NSStringFormatSpecTypeKey</key>
|
||||
<string>NSStringPluralRuleType</string>
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>Hide notifications that tag more than %d profile</string>
|
||||
<key>other</key>
|
||||
<string>Hide notifications that tag more than %d profiles</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>imports_count</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
@@ -82,6 +98,22 @@
|
||||
<string>%2$@ and %1$d others reposted</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>quoted_reposts_count</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@QUOTE_REPOSTS@</string>
|
||||
<key>QUOTE_REPOSTS</key>
|
||||
<dict>
|
||||
<key>NSStringFormatSpecTypeKey</key>
|
||||
<string>NSStringPluralRuleType</string>
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>Quote</string>
|
||||
<key>other</key>
|
||||
<string>Quotes</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>reacted_tagged_in_3</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
@@ -242,22 +274,6 @@
|
||||
<string>Reposts</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>quoted_reposts_count</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@QUOTE_REPOSTS@</string>
|
||||
<key>QUOTE_REPOSTS</key>
|
||||
<dict>
|
||||
<key>NSStringFormatSpecTypeKey</key>
|
||||
<string>NSStringPluralRuleType</string>
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>Quote</string>
|
||||
<key>other</key>
|
||||
<string>Quotes</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>sats</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -44,6 +44,20 @@
|
||||
<string>フォロー中</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>hellthread_notifications_disabled</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@HELLTHREAD_PROFILES@</string>
|
||||
<key>HELLTHREAD_PROFILES</key>
|
||||
<dict>
|
||||
<key>NSStringFormatSpecTypeKey</key>
|
||||
<string>NSStringPluralRuleType</string>
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>other</key>
|
||||
<string>%d以上のプロフィールをタグづけしている通知を表示しない</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>imports_count</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
@@ -72,6 +86,20 @@
|
||||
<string>%2$@と他%1$d人がリポストしました</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>quoted_reposts_count</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@QUOTE_REPOSTS@</string>
|
||||
<key>QUOTE_REPOSTS</key>
|
||||
<dict>
|
||||
<key>NSStringFormatSpecTypeKey</key>
|
||||
<string>NSStringPluralRuleType</string>
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>other</key>
|
||||
<string>引用</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>reacted_tagged_in_3</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
@@ -212,20 +240,6 @@
|
||||
<string>リポスト</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>quoted_reposts_count</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@QUOTE_REPOSTS@</string>
|
||||
<key>QUOTE_REPOSTS</key>
|
||||
<dict>
|
||||
<key>NSStringFormatSpecTypeKey</key>
|
||||
<string>NSStringPluralRuleType</string>
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>other</key>
|
||||
<string>引用</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>sats</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
|
||||
Binary file not shown.
@@ -50,6 +50,22 @@
|
||||
<string>Volgend</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>hellthread_notifications_disabled</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@HELLTHREAD_PROFILES@</string>
|
||||
<key>HELLTHREAD_PROFILES</key>
|
||||
<dict>
|
||||
<key>NSStringFormatSpecTypeKey</key>
|
||||
<string>NSStringPluralRuleType</string>
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>Onderdruk meldingen die meer dan %d profiel vermelden</string>
|
||||
<key>other</key>
|
||||
<string>Onderdruk meldingen die meer dan %d profielen vermelden</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>imports_count</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
@@ -82,6 +98,22 @@
|
||||
<string>%2$@ en %1$d anderen hebben herplaatst</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>quoted_reposts_count</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@QUOTE_REPOSTS@</string>
|
||||
<key>QUOTE_REPOSTS</key>
|
||||
<dict>
|
||||
<key>NSStringFormatSpecTypeKey</key>
|
||||
<string>NSStringPluralRuleType</string>
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>Citaat</string>
|
||||
<key>other</key>
|
||||
<string>Citaten</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>reacted_tagged_in_3</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
@@ -242,22 +274,6 @@
|
||||
<string>Herplaatsingen</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>quoted_reposts_count</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@QUOTE_REPOSTS@</string>
|
||||
<key>QUOTE_REPOSTS</key>
|
||||
<dict>
|
||||
<key>NSStringFormatSpecTypeKey</key>
|
||||
<string>NSStringPluralRuleType</string>
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>Citaat</string>
|
||||
<key>other</key>
|
||||
<string>Citaten</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>sats</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -44,6 +44,20 @@
|
||||
<string>กำลังติดตาม</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>hellthread_notifications_disabled</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@HELLTHREAD_PROFILES@</string>
|
||||
<key>HELLTHREAD_PROFILES</key>
|
||||
<dict>
|
||||
<key>NSStringFormatSpecTypeKey</key>
|
||||
<string>NSStringPluralRuleType</string>
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>other</key>
|
||||
<string>ซ่อนการแจ้งเตือนที่แท็กมากกว่า %d โปรไฟล์</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>imports_count</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
@@ -72,6 +86,20 @@
|
||||
<string>%2$@ และ %1$d ได้รีโพสต์</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>quoted_reposts_count</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@QUOTE_REPOSTS@</string>
|
||||
<key>QUOTE_REPOSTS</key>
|
||||
<dict>
|
||||
<key>NSStringFormatSpecTypeKey</key>
|
||||
<string>NSStringPluralRuleType</string>
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>other</key>
|
||||
<string>อ้างอิง</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>reacted_tagged_in_3</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
@@ -212,20 +240,6 @@
|
||||
<string>รีโพสต์</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>quoted_reposts_count</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@QUOTE_REPOSTS@</string>
|
||||
<key>QUOTE_REPOSTS</key>
|
||||
<dict>
|
||||
<key>NSStringFormatSpecTypeKey</key>
|
||||
<string>NSStringPluralRuleType</string>
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>other</key>
|
||||
<string>อ้างอิง</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>sats</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
|
||||
@@ -10,28 +10,331 @@ import SwiftUI
|
||||
@testable import damus
|
||||
|
||||
class NoteContentViewTests: XCTestCase {
|
||||
func testRenderBlocksWithNonLatinHashtags() {
|
||||
func testRenderBlocksWithNonLatinHashtags() throws {
|
||||
let content = "Damusはかっこいいです #cool #かっこいい"
|
||||
let note = NostrEvent(content: content, keypair: test_keypair, tags: [["t", "かっこいい"]])!
|
||||
let note = try XCTUnwrap(NostrEvent(content: content, keypair: test_keypair, tags: [["t", "かっこいい"]]))
|
||||
let parsed: Blocks = parse_note_content(content: .init(note: note, keypair: test_keypair))
|
||||
|
||||
let testState = test_damus_state
|
||||
|
||||
let text: NoteArtifactsSeparated = render_blocks(blocks: parsed, profiles: testState.profiles)
|
||||
let text: NoteArtifactsSeparated = render_blocks(blocks: parsed, profiles: testState.profiles, can_hide_last_previewable_refs: true)
|
||||
let attributedText: AttributedString = text.content.attributed
|
||||
|
||||
let runs: AttributedString.Runs = attributedText.runs
|
||||
let runArray: [AttributedString.Runs.Run] = Array(runs)
|
||||
print(runArray.description)
|
||||
XCTAssertEqual(runArray[1].link?.absoluteString, "damus:t:cool", "Latin-character hashtag is missing. Runs description :\(runArray.description)")
|
||||
XCTAssertEqual(runArray[3].link?.absoluteString.removingPercentEncoding!, "damus:t:かっこいい", "Non-latin-character hashtag is missing. Runs description :\(runArray.description)")
|
||||
XCTAssertEqual(runArray[3].link?.absoluteString.removingPercentEncoding, "damus:t:かっこいい", "Non-latin-character hashtag is missing. Runs description :\(runArray.description)")
|
||||
}
|
||||
|
||||
|
||||
func testRenderBlocksWithLeadingAndTrailingWhitespacesTrimmed() throws {
|
||||
let content = " \n\n Hello, \nworld! \n\n "
|
||||
let note = try XCTUnwrap(NostrEvent(content: content, keypair: test_keypair))
|
||||
let parsed: Blocks = parse_note_content(content: .init(note: note, keypair: test_keypair))
|
||||
|
||||
let testState = test_damus_state
|
||||
|
||||
let noteArtifactsSeparated: NoteArtifactsSeparated = render_blocks(blocks: parsed, profiles: testState.profiles, can_hide_last_previewable_refs: true)
|
||||
let attributedText: AttributedString = noteArtifactsSeparated.content.attributed
|
||||
let text = attributedText.description
|
||||
|
||||
let runs: AttributedString.Runs = attributedText.runs
|
||||
let runArray: [AttributedString.Runs.Run] = Array(runs)
|
||||
|
||||
XCTAssertEqual(runArray.count, 1)
|
||||
XCTAssertTrue(text.contains("Hello, \nworld!"))
|
||||
XCTAssertFalse(text.contains(content))
|
||||
}
|
||||
|
||||
func testRenderBlocksWithMediaBlockInMiddleRendered() throws {
|
||||
let content = " Check this out: https://damus.io/image.png Isn't this cool? "
|
||||
let note = try XCTUnwrap(NostrEvent(content: content, keypair: test_keypair))
|
||||
let parsed: Blocks = parse_note_content(content: .init(note: note, keypair: test_keypair))
|
||||
|
||||
let testState = test_damus_state
|
||||
|
||||
let noteArtifactsSeparated: NoteArtifactsSeparated = render_blocks(blocks: parsed, profiles: testState.profiles)
|
||||
let attributedText: AttributedString = noteArtifactsSeparated.content.attributed
|
||||
|
||||
let runs: AttributedString.Runs = attributedText.runs
|
||||
let runArray: [AttributedString.Runs.Run] = Array(runs)
|
||||
XCTAssertEqual(runArray.count, 3)
|
||||
XCTAssertTrue(runArray[0].description.contains("Check this out: "))
|
||||
XCTAssertTrue(runArray[1].description.contains("https://damus.io/image.png "))
|
||||
XCTAssertEqual(runArray[1].link?.absoluteString, "https://damus.io/image.png")
|
||||
XCTAssertTrue(runArray[2].description.contains(" Isn't this cool?"))
|
||||
|
||||
XCTAssertEqual(noteArtifactsSeparated.images.count, 1)
|
||||
XCTAssertEqual(noteArtifactsSeparated.images[0].absoluteString, "https://damus.io/image.png")
|
||||
}
|
||||
|
||||
func testRenderBlocksWithInvoiceInMiddleAbbreviated() throws {
|
||||
let invoiceString = "lnbc100n1p357sl0sp5t9n56wdztun39lgdqlr30xqwksg3k69q4q2rkr52aplujw0esn0qpp5mrqgljk62z20q4nvgr6lzcyn6fhylzccwdvu4k77apg3zmrkujjqdpzw35xjueqd9ejqcfqv3jhxcmjd9c8g6t0dcxqyjw5qcqpjrzjqt56h4gvp5yx36u2uzqa6qwcsk3e2duunfxppzj9vhypc3wfe2wswz607uqq3xqqqsqqqqqqqqqqqlqqyg9qyysgqagx5h20aeulj3gdwx3kxs8u9f4mcakdkwuakasamm9562ffyr9en8yg20lg0ygnr9zpwp68524kmda0t5xp2wytex35pu8hapyjajxqpsql29r"
|
||||
let content = " Donations appreciated: \(invoiceString) Pura Vida "
|
||||
let note = try XCTUnwrap(NostrEvent(content: content, keypair: test_keypair))
|
||||
let parsed: Blocks = parse_note_content(content: .init(note: note, keypair: test_keypair))
|
||||
|
||||
let testState = test_damus_state
|
||||
|
||||
let noteArtifactsSeparated: NoteArtifactsSeparated = render_blocks(blocks: parsed, profiles: testState.profiles)
|
||||
let attributedText: AttributedString = noteArtifactsSeparated.content.attributed
|
||||
|
||||
let runs: AttributedString.Runs = attributedText.runs
|
||||
let runArray: [AttributedString.Runs.Run] = Array(runs)
|
||||
XCTAssertEqual(runArray.count, 3)
|
||||
XCTAssertTrue(runArray[0].description.contains("Donations appreciated: "))
|
||||
XCTAssertTrue(runArray[1].description.contains("lnbc100n:qpsql29r"))
|
||||
XCTAssertEqual(runArray[1].link?.absoluteString, "damus:lightning:\(invoiceString)")
|
||||
XCTAssertTrue(runArray[2].description.contains(" Pura Vida"))
|
||||
}
|
||||
|
||||
func testRenderBlocksWithNoteIdInMiddleAreRendered() throws {
|
||||
let noteId = test_note.id.bech32
|
||||
let content = " Check this out: nostr:\(noteId) Pura Vida "
|
||||
let note = try XCTUnwrap(NostrEvent(content: content, keypair: test_keypair))
|
||||
let parsed: Blocks = parse_note_content(content: .init(note: note, keypair: test_keypair))
|
||||
|
||||
let testState = test_damus_state
|
||||
|
||||
let noteArtifactsSeparated: NoteArtifactsSeparated = render_blocks(blocks: parsed, profiles: testState.profiles)
|
||||
let attributedText: AttributedString = noteArtifactsSeparated.content.attributed
|
||||
|
||||
let runs: AttributedString.Runs = attributedText.runs
|
||||
let runArray: [AttributedString.Runs.Run] = Array(runs)
|
||||
XCTAssertEqual(runArray.count, 3)
|
||||
XCTAssertTrue(runArray[0].description.contains("Check this out: "))
|
||||
XCTAssertTrue(runArray[1].description.contains("note1qqq:qqn2l0z3"))
|
||||
XCTAssertEqual(runArray[1].link?.absoluteString, "damus:nostr:\(noteId)")
|
||||
XCTAssertTrue(runArray[2].description.contains(" Pura Vida"))
|
||||
}
|
||||
|
||||
func testRenderBlocksWithNeventInMiddleAreRendered() throws {
|
||||
let nevent = "nevent1qqstna2yrezu5wghjvswqqculvvwxsrcvu7uc0f78gan4xqhvz49d9spr3mhxue69uhkummnw3ez6un9d3shjtn4de6x2argwghx6egpr4mhxue69uhkummnw3ez6ur4vgh8wetvd3hhyer9wghxuet5nxnepm"
|
||||
let content = " Check this out: nostr:\(nevent) Pura Vida "
|
||||
let note = try XCTUnwrap(NostrEvent(content: content, keypair: test_keypair))
|
||||
let parsed: Blocks = parse_note_content(content: .init(note: note, keypair: test_keypair))
|
||||
|
||||
let testState = test_damus_state
|
||||
|
||||
let noteArtifactsSeparated: NoteArtifactsSeparated = render_blocks(blocks: parsed, profiles: testState.profiles)
|
||||
let attributedText: AttributedString = noteArtifactsSeparated.content.attributed
|
||||
|
||||
let runs: AttributedString.Runs = attributedText.runs
|
||||
let runArray: [AttributedString.Runs.Run] = Array(runs)
|
||||
XCTAssertEqual(runArray.count, 3)
|
||||
XCTAssertTrue(runArray[0].description.contains("Check this out: "))
|
||||
XCTAssertTrue(runArray[1].description.contains("nevent1q:t5nxnepm"))
|
||||
XCTAssertEqual(runArray[1].link?.absoluteString, "damus:nostr:\(nevent)")
|
||||
XCTAssertTrue(runArray[2].description.contains(" Pura Vida"))
|
||||
}
|
||||
|
||||
func testRenderBlocksWithPreviewableBlocksAtEndAreHidden() throws {
|
||||
let noteId = test_note.id.bech32
|
||||
let invoiceString = "lnbc100n1p357sl0sp5t9n56wdztun39lgdqlr30xqwksg3k69q4q2rkr52aplujw0esn0qpp5mrqgljk62z20q4nvgr6lzcyn6fhylzccwdvu4k77apg3zmrkujjqdpzw35xjueqd9ejqcfqv3jhxcmjd9c8g6t0dcxqyjw5qcqpjrzjqt56h4gvp5yx36u2uzqa6qwcsk3e2duunfxppzj9vhypc3wfe2wswz607uqq3xqqqsqqqqqqqqqqqlqqyg9qyysgqagx5h20aeulj3gdwx3kxs8u9f4mcakdkwuakasamm9562ffyr9en8yg20lg0ygnr9zpwp68524kmda0t5xp2wytex35pu8hapyjajxqpsql29r"
|
||||
let content = " Check this out. \nhttps://hidden.tld/\nhttps://damus.io/hidden1.png\n\(invoiceString)\nhttps://damus.io/hidden2.png\nnostr:\(noteId) "
|
||||
let note = try XCTUnwrap(NostrEvent(content: content, keypair: test_keypair))
|
||||
let parsed: Blocks = parse_note_content(content: .init(note: note, keypair: test_keypair))
|
||||
|
||||
let testState = test_damus_state
|
||||
|
||||
let noteArtifactsSeparated: NoteArtifactsSeparated = render_blocks(blocks: parsed, profiles: testState.profiles, can_hide_last_previewable_refs: true)
|
||||
let attributedText: AttributedString = noteArtifactsSeparated.content.attributed
|
||||
|
||||
let runs: AttributedString.Runs = attributedText.runs
|
||||
let runArray: [AttributedString.Runs.Run] = Array(runs)
|
||||
XCTAssertEqual(runArray.count, 1)
|
||||
XCTAssertTrue(runArray[0].description.contains("Check this out."))
|
||||
XCTAssertFalse(runArray[0].description.contains("https://hidden.tld/"))
|
||||
XCTAssertFalse(runArray[0].description.contains("https://damus.io/hidden1.png"))
|
||||
XCTAssertFalse(runArray[0].description.contains("lnbc100n:qpsql29r"))
|
||||
XCTAssertFalse(runArray[0].description.contains("https://damus.io/hidden2.png"))
|
||||
XCTAssertFalse(runArray[0].description.contains("note1qqq:qqn2l0z3"))
|
||||
|
||||
XCTAssertEqual(noteArtifactsSeparated.images.count, 2)
|
||||
XCTAssertEqual(noteArtifactsSeparated.images[0].absoluteString, "https://damus.io/hidden1.png")
|
||||
XCTAssertEqual(noteArtifactsSeparated.images[1].absoluteString, "https://damus.io/hidden2.png")
|
||||
|
||||
XCTAssertEqual(noteArtifactsSeparated.media.count, 2)
|
||||
XCTAssertEqual(noteArtifactsSeparated.media[0].url.absoluteString, "https://damus.io/hidden1.png")
|
||||
XCTAssertEqual(noteArtifactsSeparated.media[1].url.absoluteString, "https://damus.io/hidden2.png")
|
||||
|
||||
XCTAssertEqual(noteArtifactsSeparated.links.count, 1)
|
||||
XCTAssertEqual(noteArtifactsSeparated.links[0].absoluteString, "https://hidden.tld/")
|
||||
|
||||
XCTAssertEqual(noteArtifactsSeparated.invoices.count, 1)
|
||||
XCTAssertEqual(noteArtifactsSeparated.invoices[0].string, invoiceString)
|
||||
}
|
||||
|
||||
func testRenderBlocksWithMultipleLinksAtEndAreNotHidden() throws {
|
||||
let noteId = test_note.id.bech32
|
||||
let invoiceString = "lnbc100n1p357sl0sp5t9n56wdztun39lgdqlr30xqwksg3k69q4q2rkr52aplujw0esn0qpp5mrqgljk62z20q4nvgr6lzcyn6fhylzccwdvu4k77apg3zmrkujjqdpzw35xjueqd9ejqcfqv3jhxcmjd9c8g6t0dcxqyjw5qcqpjrzjqt56h4gvp5yx36u2uzqa6qwcsk3e2duunfxppzj9vhypc3wfe2wswz607uqq3xqqqsqqqqqqqqqqqlqqyg9qyysgqagx5h20aeulj3gdwx3kxs8u9f4mcakdkwuakasamm9562ffyr9en8yg20lg0ygnr9zpwp68524kmda0t5xp2wytex35pu8hapyjajxqpsql29r"
|
||||
let content = " Check this out. \nhttps://nothidden1.tld/\nhttps://nothidden2.tld/\nhttps://damus.io/nothidden1.png\n\(invoiceString)\nhttps://damus.io/nothidden2.png\nnostr:\(noteId) "
|
||||
let note = try XCTUnwrap(NostrEvent(content: content, keypair: test_keypair))
|
||||
let parsed: Blocks = parse_note_content(content: .init(note: note, keypair: test_keypair))
|
||||
|
||||
let testState = test_damus_state
|
||||
|
||||
let noteArtifactsSeparated: NoteArtifactsSeparated = render_blocks(blocks: parsed, profiles: testState.profiles, can_hide_last_previewable_refs: true)
|
||||
let attributedText: AttributedString = noteArtifactsSeparated.content.attributed
|
||||
|
||||
let runs: AttributedString.Runs = attributedText.runs
|
||||
let runArray: [AttributedString.Runs.Run] = Array(runs)
|
||||
XCTAssertEqual(runArray.count, 12)
|
||||
XCTAssertTrue(runArray[0].description.contains("Check this out."))
|
||||
XCTAssertTrue(runArray[1].description.contains("https://nothidden1.tld/"))
|
||||
XCTAssertTrue(runArray[3].description.contains("https://nothidden2.tld/"))
|
||||
XCTAssertTrue(runArray[5].description.contains("https://damus.io/nothidden1.png"))
|
||||
XCTAssertTrue(runArray[7].description.contains("lnbc100n:qpsql29r"))
|
||||
XCTAssertTrue(runArray[9].description.contains("https://damus.io/nothidden2.png"))
|
||||
XCTAssertTrue(runArray[11].description.contains("note1qqq:qqn2l0z3"))
|
||||
|
||||
XCTAssertEqual(noteArtifactsSeparated.images.count, 2)
|
||||
XCTAssertEqual(noteArtifactsSeparated.images[0].absoluteString, "https://damus.io/nothidden1.png")
|
||||
XCTAssertEqual(noteArtifactsSeparated.images[1].absoluteString, "https://damus.io/nothidden2.png")
|
||||
|
||||
XCTAssertEqual(noteArtifactsSeparated.media.count, 2)
|
||||
XCTAssertEqual(noteArtifactsSeparated.media[0].url.absoluteString, "https://damus.io/nothidden1.png")
|
||||
XCTAssertEqual(noteArtifactsSeparated.media[1].url.absoluteString, "https://damus.io/nothidden2.png")
|
||||
|
||||
XCTAssertEqual(noteArtifactsSeparated.links.count, 2)
|
||||
XCTAssertEqual(noteArtifactsSeparated.links[0].absoluteString, "https://nothidden1.tld/")
|
||||
XCTAssertEqual(noteArtifactsSeparated.links[1].absoluteString, "https://nothidden2.tld/")
|
||||
|
||||
XCTAssertEqual(noteArtifactsSeparated.invoices.count, 1)
|
||||
XCTAssertEqual(noteArtifactsSeparated.invoices[0].string, invoiceString)
|
||||
}
|
||||
|
||||
func testRenderBlocksWithMultipleEventsAtEndAreNotHidden() throws {
|
||||
let noteId = test_note.id.bech32
|
||||
let nevent = "nevent1qqstna2yrezu5wghjvswqqculvvwxsrcvu7uc0f78gan4xqhvz49d9spr3mhxue69uhkummnw3ez6un9d3shjtn4de6x2argwghx6egpr4mhxue69uhkummnw3ez6ur4vgh8wetvd3hhyer9wghxuet5nxnepm"
|
||||
let invoiceString = "lnbc100n1p357sl0sp5t9n56wdztun39lgdqlr30xqwksg3k69q4q2rkr52aplujw0esn0qpp5mrqgljk62z20q4nvgr6lzcyn6fhylzccwdvu4k77apg3zmrkujjqdpzw35xjueqd9ejqcfqv3jhxcmjd9c8g6t0dcxqyjw5qcqpjrzjqt56h4gvp5yx36u2uzqa6qwcsk3e2duunfxppzj9vhypc3wfe2wswz607uqq3xqqqsqqqqqqqqqqqlqqyg9qyysgqagx5h20aeulj3gdwx3kxs8u9f4mcakdkwuakasamm9562ffyr9en8yg20lg0ygnr9zpwp68524kmda0t5xp2wytex35pu8hapyjajxqpsql29r"
|
||||
let content = " Check this out. \nnostr:\(noteId)\nnostr:\(nevent)\nhttps://damus.io/nothidden1.png\n\(invoiceString)\nhttps://damus.io/nothidden2.png "
|
||||
let note = try XCTUnwrap(NostrEvent(content: content, keypair: test_keypair))
|
||||
let parsed: Blocks = parse_note_content(content: .init(note: note, keypair: test_keypair))
|
||||
|
||||
let testState = test_damus_state
|
||||
|
||||
let noteArtifactsSeparated: NoteArtifactsSeparated = render_blocks(blocks: parsed, profiles: testState.profiles, can_hide_last_previewable_refs: true)
|
||||
let attributedText: AttributedString = noteArtifactsSeparated.content.attributed
|
||||
|
||||
let runs: AttributedString.Runs = attributedText.runs
|
||||
let runArray: [AttributedString.Runs.Run] = Array(runs)
|
||||
XCTAssertEqual(runArray.count, 10)
|
||||
XCTAssertTrue(runArray[0].description.contains("Check this out."))
|
||||
XCTAssertTrue(runArray[1].description.contains("note1qqq:qqn2l0z3"))
|
||||
XCTAssertTrue(runArray[3].description.contains("nevent1q:t5nxnepm"))
|
||||
XCTAssertTrue(runArray[5].description.contains("https://damus.io/nothidden1.png"))
|
||||
XCTAssertTrue(runArray[7].description.contains("lnbc100n:qpsql29r"))
|
||||
XCTAssertTrue(runArray[9].description.contains("https://damus.io/nothidden2.png"))
|
||||
|
||||
XCTAssertEqual(noteArtifactsSeparated.images.count, 2)
|
||||
XCTAssertEqual(noteArtifactsSeparated.images[0].absoluteString, "https://damus.io/nothidden1.png")
|
||||
XCTAssertEqual(noteArtifactsSeparated.images[1].absoluteString, "https://damus.io/nothidden2.png")
|
||||
|
||||
XCTAssertEqual(noteArtifactsSeparated.media.count, 2)
|
||||
XCTAssertEqual(noteArtifactsSeparated.media[0].url.absoluteString, "https://damus.io/nothidden1.png")
|
||||
XCTAssertEqual(noteArtifactsSeparated.media[1].url.absoluteString, "https://damus.io/nothidden2.png")
|
||||
|
||||
XCTAssertEqual(noteArtifactsSeparated.links.count, 0)
|
||||
|
||||
XCTAssertEqual(noteArtifactsSeparated.invoices.count, 1)
|
||||
XCTAssertEqual(noteArtifactsSeparated.invoices[0].string, invoiceString)
|
||||
}
|
||||
|
||||
func testRenderBlocksWithPreviewableBlocksAtEndAreNotHiddenWhenMediaBlockPrecedesThem() throws {
|
||||
let content = " Check this out: https://damus.io/image.png Isn't this cool? \nhttps://damus.io/nothidden.png "
|
||||
let note = try XCTUnwrap(NostrEvent(content: content, keypair: test_keypair))
|
||||
let parsed: Blocks = parse_note_content(content: .init(note: note, keypair: test_keypair))
|
||||
|
||||
let testState = test_damus_state
|
||||
|
||||
let noteArtifactsSeparated: NoteArtifactsSeparated = render_blocks(blocks: parsed, profiles: testState.profiles, can_hide_last_previewable_refs: true)
|
||||
let attributedText: AttributedString = noteArtifactsSeparated.content.attributed
|
||||
|
||||
let runs: AttributedString.Runs = attributedText.runs
|
||||
let runArray: [AttributedString.Runs.Run] = Array(runs)
|
||||
XCTAssertEqual(runArray.count, 4)
|
||||
XCTAssertTrue(runArray[0].description.contains("Check this out: "))
|
||||
XCTAssertTrue(runArray[1].description.contains("https://damus.io/image.png "))
|
||||
XCTAssertEqual(runArray[1].link?.absoluteString, "https://damus.io/image.png")
|
||||
XCTAssertTrue(runArray[2].description.contains(" Isn't this cool?"))
|
||||
XCTAssertTrue(runArray[3].description.contains("https://damus.io/nothidden.png"))
|
||||
XCTAssertEqual(runArray[3].link?.absoluteString, "https://damus.io/nothidden.png")
|
||||
|
||||
XCTAssertEqual(noteArtifactsSeparated.images.count, 2)
|
||||
XCTAssertEqual(noteArtifactsSeparated.images[0].absoluteString, "https://damus.io/image.png")
|
||||
XCTAssertEqual(noteArtifactsSeparated.images[1].absoluteString, "https://damus.io/nothidden.png")
|
||||
}
|
||||
|
||||
func testRenderBlocksWithPreviewableBlocksAtEndAreNotHiddenWhenInvoicePrecedesThem() throws {
|
||||
let invoiceString = "lnbc100n1p357sl0sp5t9n56wdztun39lgdqlr30xqwksg3k69q4q2rkr52aplujw0esn0qpp5mrqgljk62z20q4nvgr6lzcyn6fhylzccwdvu4k77apg3zmrkujjqdpzw35xjueqd9ejqcfqv3jhxcmjd9c8g6t0dcxqyjw5qcqpjrzjqt56h4gvp5yx36u2uzqa6qwcsk3e2duunfxppzj9vhypc3wfe2wswz607uqq3xqqqsqqqqqqqqqqqlqqyg9qyysgqagx5h20aeulj3gdwx3kxs8u9f4mcakdkwuakasamm9562ffyr9en8yg20lg0ygnr9zpwp68524kmda0t5xp2wytex35pu8hapyjajxqpsql29r"
|
||||
let content = " Donations appreciated: \(invoiceString) Pura Vida \nhttps://damus.io/nothidden.png "
|
||||
let note = try XCTUnwrap(NostrEvent(content: content, keypair: test_keypair))
|
||||
let parsed: Blocks = parse_note_content(content: .init(note: note, keypair: test_keypair))
|
||||
|
||||
let testState = test_damus_state
|
||||
|
||||
let noteArtifactsSeparated: NoteArtifactsSeparated = render_blocks(blocks: parsed, profiles: testState.profiles, can_hide_last_previewable_refs: true)
|
||||
let attributedText: AttributedString = noteArtifactsSeparated.content.attributed
|
||||
|
||||
let runs: AttributedString.Runs = attributedText.runs
|
||||
let runArray: [AttributedString.Runs.Run] = Array(runs)
|
||||
XCTAssertEqual(runArray.count, 4)
|
||||
XCTAssertTrue(runArray[0].description.contains("Donations appreciated: "))
|
||||
XCTAssertTrue(runArray[1].description.contains("lnbc100n:qpsql29r"))
|
||||
XCTAssertEqual(runArray[1].link?.absoluteString, "damus:lightning:\(invoiceString)")
|
||||
XCTAssertTrue(runArray[2].description.contains(" Pura Vida"))
|
||||
XCTAssertTrue(runArray[3].description.contains("https://damus.io/nothidden.png"))
|
||||
XCTAssertEqual(runArray[3].link?.absoluteString, "https://damus.io/nothidden.png")
|
||||
|
||||
XCTAssertEqual(noteArtifactsSeparated.images.count, 1)
|
||||
XCTAssertEqual(noteArtifactsSeparated.images[0].absoluteString, "https://damus.io/nothidden.png")
|
||||
}
|
||||
|
||||
func testRenderBlocksWithPreviewableBlocksAtEndAreHiddenWhenHashtagsAreEmbedded() throws {
|
||||
let noteId = test_note.id.bech32
|
||||
let invoiceString = "lnbc100n1p357sl0sp5t9n56wdztun39lgdqlr30xqwksg3k69q4q2rkr52aplujw0esn0qpp5mrqgljk62z20q4nvgr6lzcyn6fhylzccwdvu4k77apg3zmrkujjqdpzw35xjueqd9ejqcfqv3jhxcmjd9c8g6t0dcxqyjw5qcqpjrzjqt56h4gvp5yx36u2uzqa6qwcsk3e2duunfxppzj9vhypc3wfe2wswz607uqq3xqqqsqqqqqqqqqqqlqqyg9qyysgqagx5h20aeulj3gdwx3kxs8u9f4mcakdkwuakasamm9562ffyr9en8yg20lg0ygnr9zpwp68524kmda0t5xp2wytex35pu8hapyjajxqpsql29r"
|
||||
let content = " Check this out. \nhttps://hidden.tld/\nhttps://damus.io/hidden1.png\n\(invoiceString)\nhttps://damus.io/hidden2.png\nnostr:\(noteId)#hashtag1 #hashtag2 "
|
||||
let note = try XCTUnwrap(NostrEvent(content: content, keypair: test_keypair))
|
||||
let parsed: Blocks = parse_note_content(content: .init(note: note, keypair: test_keypair))
|
||||
|
||||
let testState = test_damus_state
|
||||
|
||||
let noteArtifactsSeparated: NoteArtifactsSeparated = render_blocks(blocks: parsed, profiles: testState.profiles, can_hide_last_previewable_refs: true)
|
||||
let attributedText: AttributedString = noteArtifactsSeparated.content.attributed
|
||||
|
||||
let runs: AttributedString.Runs = attributedText.runs
|
||||
let runArray: [AttributedString.Runs.Run] = Array(runs)
|
||||
XCTAssertEqual(runArray.count, 4)
|
||||
XCTAssertTrue(runArray[0].description.contains("Check this out."))
|
||||
XCTAssertFalse(runArray[0].description.contains("https://hidden.tld/"))
|
||||
XCTAssertFalse(runArray[0].description.contains("https://damus.io/hidden1.png"))
|
||||
XCTAssertFalse(runArray[0].description.contains("lnbc100n:qpsql29r"))
|
||||
XCTAssertFalse(runArray[0].description.contains("https://damus.io/hidden2.png"))
|
||||
XCTAssertFalse(runArray[0].description.contains("note1qqq:qqn2l0z3"))
|
||||
XCTAssertTrue(runArray[1].description.contains("#hashtag1"))
|
||||
XCTAssertTrue(runArray[3].description.contains("#hashtag2"))
|
||||
|
||||
XCTAssertEqual(noteArtifactsSeparated.images.count, 2)
|
||||
XCTAssertEqual(noteArtifactsSeparated.images[0].absoluteString, "https://damus.io/hidden1.png")
|
||||
XCTAssertEqual(noteArtifactsSeparated.images[1].absoluteString, "https://damus.io/hidden2.png")
|
||||
|
||||
XCTAssertEqual(noteArtifactsSeparated.media.count, 2)
|
||||
XCTAssertEqual(noteArtifactsSeparated.media[0].url.absoluteString, "https://damus.io/hidden1.png")
|
||||
XCTAssertEqual(noteArtifactsSeparated.media[1].url.absoluteString, "https://damus.io/hidden2.png")
|
||||
|
||||
XCTAssertEqual(noteArtifactsSeparated.links.count, 1)
|
||||
XCTAssertEqual(noteArtifactsSeparated.links[0].absoluteString, "https://hidden.tld/")
|
||||
|
||||
XCTAssertEqual(noteArtifactsSeparated.invoices.count, 1)
|
||||
XCTAssertEqual(noteArtifactsSeparated.invoices[0].string, invoiceString)
|
||||
}
|
||||
|
||||
/// Based on https://github.com/damus-io/damus/issues/1468
|
||||
/// Tests whether a note content view correctly parses an image block when url in JSON content contains optional escaped slashes
|
||||
func testParseImageBlockInContentWithEscapedSlashes() {
|
||||
func testParseImageBlockInContentWithEscapedSlashes() throws {
|
||||
let testJSONWithEscapedSlashes = "{\"tags\":[],\"pubkey\":\"f8e6c64342f1e052480630e27e1016dce35fc3a614e60434fef4aa2503328ca9\",\"content\":\"https:\\/\\/cdn.nostr.build\\/i\\/5c1d3296f66c2630131bf123106486aeaf051ed8466031c0e0532d70b33cddb2.jpg\",\"created_at\":1691864981,\"kind\":1,\"sig\":\"fc0033aa3d4df50b692a5b346fa816fdded698de2045e36e0642a021391468c44ca69c2471adc7e92088131872d4aaa1e90ea6e1ad97f3cc748f4aed96dfae18\",\"id\":\"e8f6eca3b161abba034dac9a02bb6930ecde9fd2fb5d6c5f22a05526e11382cb\"}"
|
||||
let testNote = NostrEvent.owned_from_json(json: testJSONWithEscapedSlashes)!
|
||||
let testNote = try XCTUnwrap(NostrEvent.owned_from_json(json: testJSONWithEscapedSlashes))
|
||||
let parsed = parse_note_content(content: .init(note: testNote, keypair: test_keypair))
|
||||
|
||||
XCTAssertTrue((parsed.blocks[0].asURL != nil), "NoteContentView does not correctly parse an image block when url in JSON content contains optional escaped slashes.")
|
||||
@@ -69,9 +372,9 @@ class NoteContentViewTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testMentionStr_Note_ContainsFullBech32() {
|
||||
let compatableText = createCompatibleText(test_note.id.bech32)
|
||||
let compatibleText = createCompatibleText(test_note.id.bech32)
|
||||
|
||||
assertCompatibleTextHasExpectedString(compatibleText: compatableText, expected: test_note.id.bech32)
|
||||
assertCompatibleTextHasExpectedString(compatibleText: compatibleText, expected: test_note.id.bech32)
|
||||
}
|
||||
|
||||
func testMentionStr_Nevent_ContainsAbbreviated() {
|
||||
|
||||
@@ -87,7 +87,7 @@ final class WalletConnectTests: XCTestCase {
|
||||
let pool = RelayPool(ndb: .empty)
|
||||
let box = PostBox(pool: pool)
|
||||
|
||||
WalletConnect.pay(url: nwc, pool: pool, post: box, invoice: "invoice")
|
||||
WalletConnect.pay(url: nwc, pool: pool, post: box, invoice: "invoice", zap_request: nil)
|
||||
|
||||
XCTAssertEqual(pool.our_descriptors.count, 0)
|
||||
XCTAssertEqual(pool.all_descriptors.count, 1)
|
||||
@@ -99,4 +99,109 @@ final class WalletConnectTests: XCTestCase {
|
||||
XCTAssertEqual(ev.remaining.count, 1)
|
||||
XCTAssertEqual(ev.remaining[0].relay.url.absoluteString, "ws://127.0.0.1")
|
||||
}
|
||||
|
||||
let testBolt11 = "lnbc15u1p3xnhl2pp5jptserfk3zk4qy42tlucycrfwxhydvlemu9pqr93tuzlv9cc7g3sdqsvfhkcap3xyhx7un8cqzpgxqzjcsp5f8c52y2stc300gl6s4xswtjpc37hrnnr3c9wvtgjfuvqmpm35evq9qyyssqy4lgd8tj637qcjp05rdpxxykjenthxftej7a2zzmwrmrl70fyj9hvj0rewhzj7jfyuwkwcg9g2jpwtk3wkjtwnkdks84hsnu8xps5vsq4gj5hs"
|
||||
let testStringEncodedZapRequest = """
|
||||
{"content":"","created_at":1746235486,"id":"faf5192c6805dea002e50cd52c7e553e3ee66ac42f30f41f1fe62b924f68fb22","kind":9734,"pubkey":"056b5b5966f500defb3b790a14633e5ec4a0e8883ca29bc23d0030553edb084a","sig":"21076018677656a220977c77e34bfa7427e1056a49b633afd3653d1d7466846cf6b35cf3fbf5908c712ebd647119cfadb1fa47e83121a238d77b1996f0fa26ee","tags":[["p","e8361082333142fc7f483b7dbd9bb36d671f2fbcf0a28015b2304fed79365fe8"],["relays","wss://nos.lol","wss://notify.damus.io","wss://relay.damus.io"]]}
|
||||
"""
|
||||
let testDoubleStringEncodedZapRequest = """
|
||||
"{\\\"content\\\":\\\"\\\",\\\"created_at\\\":1746235486,\\\"id\\\":\\\"faf5192c6805dea002e50cd52c7e553e3ee66ac42f30f41f1fe62b924f68fb22\\\",\\\"kind\\\":9734,\\\"pubkey\\\":\\\"056b5b5966f500defb3b790a14633e5ec4a0e8883ca29bc23d0030553edb084a\\\",\\\"sig\\\":\\\"21076018677656a220977c77e34bfa7427e1056a49b633afd3653d1d7466846cf6b35cf3fbf5908c712ebd647119cfadb1fa47e83121a238d77b1996f0fa26ee\\\",\\\"tags\\\":[[\\\"p\\\",\\\"e8361082333142fc7f483b7dbd9bb36d671f2fbcf0a28015b2304fed79365fe8\\\"],[\\\"relays\\\",\\\"wss://nos.lol\\\",\\\"wss://notify.damus.io\\\",\\\"wss://relay.damus.io\\\"]]}"
|
||||
"""
|
||||
|
||||
func testEncodingPayInvoiceRequest() throws {
|
||||
let testZapRequest = decode_nostr_event_json(json: testStringEncodedZapRequest)!
|
||||
let metadata = WalletConnect.Request.Metadata(nostr: testZapRequest)
|
||||
let request = WalletConnect.Request.payInvoice(invoice: "lntest", description: testStringEncodedZapRequest, metadata: metadata)
|
||||
|
||||
let encodedData = try JSONEncoder().encode(request)
|
||||
let encodedString = String(data: encodedData, encoding: .utf8)!
|
||||
|
||||
XCTAssertTrue(encodedString.contains("\"method\":\"pay_invoice\""))
|
||||
XCTAssertTrue(encodedString.contains("\"invoice\":\"lntest\""))
|
||||
XCTAssertTrue(encodedString.contains("\"description\":\"{"))
|
||||
XCTAssertTrue(encodedString.contains("\"nostr\":{"))
|
||||
}
|
||||
|
||||
func testDecodingPayInvoiceRequest() throws {
|
||||
let testZapRequest = decode_nostr_event_json(json: testStringEncodedZapRequest)!
|
||||
|
||||
let jsonText = """
|
||||
{
|
||||
"method": "pay_invoice",
|
||||
"params": {
|
||||
"invoice": "\(testBolt11)",
|
||||
"description": \(testDoubleStringEncodedZapRequest),
|
||||
"metadata": {
|
||||
"nostr": \(testStringEncodedZapRequest)
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
let jsonData = jsonText.data(using: .utf8)!
|
||||
|
||||
let decodedRequest = try JSONDecoder().decode(WalletConnect.Request.self, from: jsonData)
|
||||
|
||||
switch decodedRequest {
|
||||
case .payInvoice(let invoice, let description, let metadata):
|
||||
XCTAssertEqual(invoice, testBolt11)
|
||||
XCTAssertEqual(description, testStringEncodedZapRequest)
|
||||
XCTAssertNotNil(metadata)
|
||||
XCTAssertEqual(metadata!.nostr, testZapRequest)
|
||||
default:
|
||||
XCTFail("Decoded to the wrong case")
|
||||
}
|
||||
}
|
||||
|
||||
func testDecodingPayInvoiceRequestWithoutMetadata() throws {
|
||||
let jsonData = """
|
||||
{
|
||||
"method": "pay_invoice",
|
||||
"params": {
|
||||
"invoice": "\(testBolt11)",
|
||||
"description": \(testDoubleStringEncodedZapRequest)
|
||||
}
|
||||
}
|
||||
""".data(using: .utf8)!
|
||||
|
||||
let decodedRequest = try JSONDecoder().decode(WalletConnect.Request.self, from: jsonData)
|
||||
|
||||
switch decodedRequest {
|
||||
case .payInvoice(let invoice, let description, let metadata):
|
||||
XCTAssertEqual(invoice, testBolt11)
|
||||
XCTAssertEqual(description, testStringEncodedZapRequest)
|
||||
XCTAssertNil(metadata)
|
||||
default:
|
||||
XCTFail("Decoded to the wrong case")
|
||||
}
|
||||
}
|
||||
|
||||
func testDecodingPayInvoiceRequestWithCrazyMetadata() throws {
|
||||
let jsonText = """
|
||||
{
|
||||
"method": "pay_invoice",
|
||||
"params": {
|
||||
"invoice": "\(testBolt11)",
|
||||
"description": \(testDoubleStringEncodedZapRequest),
|
||||
"metadata": {
|
||||
"nostr": "totally not a zap request because this metadata is crazy",
|
||||
"lorem": "ipsum"
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
let jsonData = jsonText.data(using: .utf8)!
|
||||
|
||||
let decodedRequest = try JSONDecoder().decode(WalletConnect.Request.self, from: jsonData)
|
||||
|
||||
switch decodedRequest {
|
||||
case .payInvoice(let invoice, let description, let metadata):
|
||||
XCTAssertEqual(invoice, testBolt11)
|
||||
XCTAssertEqual(description, testStringEncodedZapRequest)
|
||||
XCTAssertEqual(metadata?.nostr, nil)
|
||||
default:
|
||||
XCTFail("Decoded to the wrong case")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,10 +36,9 @@ class damusTests: XCTestCase {
|
||||
XCTAssertEqual(bytes.count, 32)
|
||||
}
|
||||
|
||||
func testTrimmingFunctions() {
|
||||
func testTrimSuffix() {
|
||||
let txt = " bobs "
|
||||
|
||||
XCTAssertEqual(trim_prefix(txt), "bobs ")
|
||||
XCTAssertEqual(trim_suffix(txt), " bobs")
|
||||
}
|
||||
|
||||
|
||||
@@ -42,9 +42,9 @@ enum NdbData {
|
||||
}
|
||||
}
|
||||
|
||||
class NdbNote: Encodable, Equatable, Hashable {
|
||||
class NdbNote: Codable, Equatable, Hashable {
|
||||
// we can have owned notes, but we can also have lmdb virtual-memory mapped notes so its optional
|
||||
let owned: Bool
|
||||
private(set) var owned: Bool
|
||||
let count: Int
|
||||
let key: NoteKey?
|
||||
let note: UnsafeMutablePointer<ndb_note>
|
||||
@@ -72,7 +72,6 @@ class NdbNote: Encodable, Equatable, Hashable {
|
||||
print("\(NdbNote.notes_created) ndb_notes, \(NdbNote.total_ndb_size) bytes")
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
func to_owned() -> NdbNote {
|
||||
@@ -86,6 +85,10 @@ class NdbNote: Encodable, Equatable, Hashable {
|
||||
|
||||
return NdbNote(note: new_note, size: self.count, owned: true, key: self.key)
|
||||
}
|
||||
|
||||
func mark_ownership_moved() {
|
||||
self.owned = false
|
||||
}
|
||||
|
||||
var content: String {
|
||||
String(cString: content_raw, encoding: .utf8) ?? ""
|
||||
@@ -161,13 +164,63 @@ class NdbNote: Encodable, Equatable, Hashable {
|
||||
try container.encode(content, forKey: .content)
|
||||
try container.encode(tags, forKey: .tags)
|
||||
}
|
||||
|
||||
required init(from decoder: any Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
let content = try container.decode(String.self, forKey: .content)
|
||||
let pubkey = try container.decode(Pubkey.self, forKey: .pubkey)
|
||||
let kind = try container.decode(UInt32.self, forKey: .kind)
|
||||
let tags = try container.decode([[String]].self, forKey: .tags)
|
||||
let createdAt = try container.decode(UInt32.self, forKey: .created_at)
|
||||
let noteId = try container.decode(NoteId.self, forKey: .id)
|
||||
let signature = try container.decode(Signature.self, forKey: .sig)
|
||||
|
||||
guard let note = NdbNote.init(content: content, author: pubkey, kind: kind, tags: tags, createdAt: createdAt, id: noteId, sig: signature) else {
|
||||
throw DecodingError.initializationFailed
|
||||
}
|
||||
|
||||
self.note = note.note
|
||||
self.owned = note.owned
|
||||
note.mark_ownership_moved() // This is done to prevent a double-free error when both `self` and `note` get deinitialized.
|
||||
self.count = note.count
|
||||
self.key = note.key
|
||||
|
||||
}
|
||||
|
||||
enum DecodingError: Error {
|
||||
case initializationFailed
|
||||
}
|
||||
|
||||
#if DEBUG_NOTE_SIZE
|
||||
static var total_ndb_size: Int = 0
|
||||
static var notes_created: Int = 0
|
||||
#endif
|
||||
|
||||
init?(content: String, keypair: Keypair, kind: UInt32 = 1, tags: [[String]] = [], createdAt: UInt32 = UInt32(Date().timeIntervalSince1970)) {
|
||||
|
||||
fileprivate enum NoteConstructionMaterial {
|
||||
case keypair(Keypair)
|
||||
case manual(Pubkey, Signature, NoteId)
|
||||
|
||||
var pubkey: Pubkey {
|
||||
switch self {
|
||||
case .keypair(let keypair):
|
||||
return keypair.pubkey
|
||||
case .manual(let pubkey, _, _):
|
||||
return pubkey
|
||||
}
|
||||
}
|
||||
|
||||
var privkey: Privkey? {
|
||||
switch self {
|
||||
case .keypair(let kp):
|
||||
return kp.privkey
|
||||
case .manual(_, _, _):
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate init?(content: String, noteConstructionMaterial: NoteConstructionMaterial, kind: UInt32 = 1, tags: [[String]] = [], createdAt: UInt32 = UInt32(Date().timeIntervalSince1970)) {
|
||||
|
||||
var builder = ndb_builder()
|
||||
let buflen = MAX_NOTE_SIZE
|
||||
@@ -175,7 +228,7 @@ class NdbNote: Encodable, Equatable, Hashable {
|
||||
|
||||
ndb_builder_init(&builder, buf, Int32(buflen))
|
||||
|
||||
var pk_raw = keypair.pubkey.bytes
|
||||
var pk_raw = noteConstructionMaterial.pubkey.bytes
|
||||
|
||||
ndb_builder_set_pubkey(&builder, &pk_raw)
|
||||
ndb_builder_set_kind(&builder, UInt32(kind))
|
||||
@@ -203,30 +256,57 @@ class NdbNote: Encodable, Equatable, Hashable {
|
||||
|
||||
var n = UnsafeMutablePointer<ndb_note>?(nil)
|
||||
|
||||
|
||||
var the_kp: ndb_keypair? = nil
|
||||
|
||||
if let sec = keypair.privkey {
|
||||
var kp = ndb_keypair()
|
||||
memcpy(&kp.secret.0, sec.id.bytes, 32);
|
||||
|
||||
if ndb_create_keypair(&kp) <= 0 {
|
||||
print("bad keypair")
|
||||
} else {
|
||||
the_kp = kp
|
||||
}
|
||||
}
|
||||
|
||||
var len: Int32 = 0
|
||||
if var the_kp {
|
||||
len = ndb_builder_finalize(&builder, &n, &the_kp)
|
||||
} else {
|
||||
len = ndb_builder_finalize(&builder, &n, nil)
|
||||
}
|
||||
|
||||
if len <= 0 {
|
||||
free(buf)
|
||||
return nil
|
||||
switch noteConstructionMaterial {
|
||||
case .keypair(let keypair):
|
||||
var the_kp: ndb_keypair? = nil
|
||||
|
||||
if let sec = noteConstructionMaterial.privkey {
|
||||
var kp = ndb_keypair()
|
||||
memcpy(&kp.secret.0, sec.id.bytes, 32);
|
||||
|
||||
if ndb_create_keypair(&kp) <= 0 {
|
||||
print("bad keypair")
|
||||
} else {
|
||||
the_kp = kp
|
||||
}
|
||||
}
|
||||
|
||||
if var the_kp {
|
||||
len = ndb_builder_finalize(&builder, &n, &the_kp)
|
||||
} else {
|
||||
len = ndb_builder_finalize(&builder, &n, nil)
|
||||
}
|
||||
|
||||
if len <= 0 {
|
||||
free(buf)
|
||||
return nil
|
||||
}
|
||||
case .manual(_, let signature, _):
|
||||
var raw_sig = signature.data.bytes
|
||||
ndb_builder_set_sig(&builder, &raw_sig)
|
||||
|
||||
do {
|
||||
// Finalize note, save length, and ensure it is higher than zero (which signals finalization has succeeded)
|
||||
len = ndb_builder_finalize(&builder, &n, nil)
|
||||
guard len > 0 else { throw InitError.generic }
|
||||
|
||||
let scratch_buf_len = MAX_NOTE_SIZE
|
||||
let scratch_buf = malloc(scratch_buf_len)
|
||||
defer { free(scratch_buf) } // Ensure we deallocate as soon as we leave this scope, regardless of the outcome
|
||||
|
||||
// Calculate the ID based on the content
|
||||
guard ndb_calculate_id(n, scratch_buf, Int32(scratch_buf_len)) == 1 else { throw InitError.generic }
|
||||
|
||||
// Verify the signature against the pubkey and the computed ID, to verify the validity of the whole note
|
||||
var ctx = secp256k1_context_create(UInt32(SECP256K1_CONTEXT_VERIFY))
|
||||
guard ndb_note_verify(&ctx, ndb_note_pubkey(n), ndb_note_id(n), ndb_note_sig(n)) == 1 else { throw InitError.generic }
|
||||
}
|
||||
catch {
|
||||
free(buf)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
//guard let n else { return nil }
|
||||
@@ -244,6 +324,14 @@ class NdbNote: Encodable, Equatable, Hashable {
|
||||
self.key = nil
|
||||
}
|
||||
|
||||
convenience init?(content: String, keypair: Keypair, kind: UInt32 = 1, tags: [[String]] = [], createdAt: UInt32 = UInt32(Date().timeIntervalSince1970)) {
|
||||
self.init(content: content, noteConstructionMaterial: .keypair(keypair), kind: kind, tags: tags, createdAt: createdAt)
|
||||
}
|
||||
|
||||
convenience init?(content: String, author: Pubkey, kind: UInt32 = 1, tags: [[String]] = [], createdAt: UInt32 = UInt32(Date().timeIntervalSince1970), id: NoteId, sig: Signature) {
|
||||
self.init(content: content, noteConstructionMaterial: .manual(author, sig, id), kind: kind, tags: tags, createdAt: createdAt)
|
||||
}
|
||||
|
||||
static func owned_from_json(json: String, bufsize: Int = 2 << 18) -> NdbNote? {
|
||||
return json.withCString { cstr in
|
||||
return NdbNote.owned_from_json_cstr(
|
||||
@@ -520,3 +608,10 @@ func hex_encode(_ data: Data) -> String {
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
extension NdbNote {
|
||||
/// A generic init error type to help make error handling code more concise
|
||||
fileprivate enum InitError: Error {
|
||||
case generic
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user