@rapidmx/web-client 0.3.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apps/admin/audit-log/index.tsx +35 -8
- package/apps/admin/branding/index.tsx +39 -404
- package/apps/admin/data-requests/index.tsx +480 -485
- package/apps/admin/distribution-lists/[uid].tsx +19 -1
- package/apps/admin/domains/[uid].tsx +92 -259
- package/apps/admin/encryption-policy/index.tsx +19 -0
- package/apps/admin/escrow-scopes/[uid].tsx +348 -237
- package/apps/admin/escrow-scopes/new/index.tsx +8 -2
- package/apps/admin/index.tsx +118 -82
- package/apps/admin/ingest-queue/index.tsx +44 -5
- package/apps/admin/mailbox-policy/index.tsx +19 -0
- package/apps/admin/mailboxes/[uid].tsx +211 -184
- package/apps/admin/mailboxes/new/index.tsx +28 -291
- package/apps/admin/plugins/index.tsx +15 -0
- package/apps/admin/quarantine/index.tsx +46 -14
- package/apps/admin/retention-policy/index.tsx +39 -132
- package/apps/admin/setup/index.tsx +15 -0
- package/apps/admin/transport-rules/[uid].tsx +30 -1
- package/apps/admin/transport-rules/_transportRuleConfig.tsx +38 -2
- package/apps/admin/transport-rules/index.tsx +24 -6
- package/apps/admin/transport-rules/new/index.tsx +30 -1
- package/apps/escrow/_layout.tsx +2 -3
- package/apps/escrow/audit-log/index.tsx +196 -168
- package/apps/escrow/matters/[uid].tsx +621 -521
- package/apps/shared/components/admin/escrowScopes/EscrowScopeKeyAndHoldersFields.tsx +22 -1
- package/apps/shared/components/admin/layout/AdminShell.tsx +251 -210
- package/apps/shared/components/admin/mailboxes/EscrowScopeCard.tsx +152 -0
- package/apps/shared/components/admin/mailboxes/ResourceSettingsCard.tsx +3 -3
- package/apps/shared/components/admin/mailboxes/ShareAccessCard.tsx +55 -11
- package/apps/shared/components/admin/settings/BrandingForm.tsx +385 -0
- package/apps/shared/components/admin/settings/DomainDnsSetup.tsx +187 -0
- package/apps/shared/components/admin/settings/EncryptionPolicyForm.tsx +112 -0
- package/apps/shared/components/admin/settings/LoadedSettingsForm.tsx +34 -0
- package/apps/shared/components/admin/settings/MailboxCreateForm.tsx +308 -0
- package/apps/shared/components/admin/settings/MailboxPolicyForm.tsx +152 -0
- package/apps/shared/components/admin/settings/PluginsManager.tsx +1235 -0
- package/apps/shared/components/admin/settings/RetentionPolicyForm.tsx +174 -0
- package/apps/shared/components/admin/setup/EscrowSetupStep.tsx +290 -0
- package/apps/shared/components/admin/setup/SetupWizard.tsx +416 -0
- package/apps/shared/components/admin/signOut.ts +79 -0
- package/apps/shared/components/admin/usePagedList.tsx +129 -0
- package/apps/shared/components/calendar/CalendarListSidebar.tsx +120 -76
- package/apps/shared/components/calendar/EventModal.tsx +717 -513
- package/apps/shared/components/calendar/MonthView.tsx +173 -139
- package/apps/shared/components/calendar/RecurrenceEditor.tsx +222 -180
- package/apps/shared/components/calendar/SplitDayView.tsx +143 -137
- package/apps/shared/components/calendar/TimeGridView.tsx +244 -198
- package/apps/shared/components/calendar/allDay.ts +124 -0
- package/apps/shared/components/calendar/layout/CalendarShell.tsx +80 -99
- package/apps/shared/components/contacts/ContactDetailPane.tsx +100 -21
- package/apps/shared/components/contacts/ContactForm.tsx +377 -314
- package/apps/shared/components/contacts/ContactsSidebar.tsx +6 -2
- package/apps/shared/components/contacts/KeyChangeReview.tsx +192 -0
- package/apps/shared/components/contacts/contactKeys.ts +78 -0
- package/apps/shared/components/escrow/layout/EscrowShell.tsx +148 -134
- package/apps/shared/components/layout/AppShell.tsx +270 -169
- package/apps/shared/components/layout/BrandingChrome.tsx +48 -10
- package/apps/shared/components/layout/KeyEnrollmentGate.tsx +150 -26
- package/apps/shared/components/layout/MailboxProvisioning.tsx +19 -3
- package/apps/shared/components/layout/RecoveryCodeUnlock.tsx +350 -0
- package/apps/shared/components/layout/UnlockPromptProvider.tsx +257 -0
- package/apps/shared/components/mail/ConversationThreadPane.tsx +216 -181
- package/apps/shared/components/mail/MessageDetailPane.tsx +799 -76
- package/apps/shared/components/mail/compose/ComposeContext.tsx +26 -16
- package/apps/shared/components/mail/compose/ComposeWindow.tsx +1652 -765
- package/apps/shared/components/mail/compose/composeFlushRegistry.ts +60 -0
- package/apps/shared/components/mail/layout/MailShell.tsx +418 -302
- package/apps/shared/components/mail/pinnedSigners.ts +124 -0
- package/apps/shared/components/mail/verificationSeals.ts +125 -0
- package/apps/shared/components/mail/writableMailboxes.ts +101 -0
- package/apps/shared/components/rules/RuleBuilder.tsx +311 -276
- package/apps/shared/components/settings/layout/SettingsShell.tsx +1 -0
- package/apps/shared/mail/findWellKnownFolderUid.ts +13 -0
- package/apps/shared/mail/listAllPages.ts +39 -0
- package/apps/shared/search/LocalIndexLifecycle.tsx +98 -0
- package/apps/shared/search/localIndexBlockCipher.ts +83 -0
- package/apps/shared/search/localIndexBuilder.ts +481 -0
- package/apps/shared/search/localIndexKey.ts +27 -0
- package/apps/shared/search/localIndexRpcClient.ts +316 -0
- package/apps/shared/search/localIndexSchema.ts +236 -0
- package/apps/shared/search/localIndexSizePreference.ts +88 -0
- package/apps/shared/search/localIndexStorage.ts +98 -0
- package/apps/shared/search/localIndexVFS.ts +266 -0
- package/apps/shared/search/localIndexWorker.ts +926 -0
- package/apps/shared/search/searchTier2.ts +79 -0
- package/apps/shared/search/wa-sqlite-shims.d.ts +44 -0
- package/apps/www/calendar/index.tsx +438 -407
- package/apps/www/contacts/[uid].tsx +21 -2
- package/apps/www/contacts/index.tsx +148 -17
- package/apps/www/index.tsx +1314 -486
- package/apps/www/messages/[uid].tsx +23 -7
- package/apps/www/settings/auto-reply/index.tsx +134 -129
- package/apps/www/settings/booking-types/[uid].tsx +306 -300
- package/apps/www/settings/encryption/index.tsx +800 -215
- package/apps/www/settings/filters/[uid].tsx +171 -155
- package/apps/www/settings/filters/new/index.tsx +8 -1
- package/apps/www/settings/focused-inbox/index.tsx +150 -148
- package/apps/www/settings/privacy/index.tsx +494 -436
- package/apps/www/settings/read-receipts/index.tsx +148 -125
- package/apps/www/settings/sharing/index.tsx +274 -0
- package/apps/www/tasks/index.tsx +638 -530
- package/dist/apps/admin/audit-log/index.js +38 -8
- package/dist/apps/admin/branding/index.js +4 -98
- package/dist/apps/admin/data-requests/index.js +52 -72
- package/dist/apps/admin/distribution-lists/[uid].js +1 -1
- package/dist/apps/admin/domains/[uid].js +5 -68
- package/dist/apps/admin/encryption-policy/index.js +8 -0
- package/dist/apps/admin/escrow-scopes/[uid].js +91 -12
- package/dist/apps/admin/escrow-scopes/new/index.js +8 -3
- package/dist/apps/admin/index.js +17 -1
- package/dist/apps/admin/ingest-queue/index.js +24 -6
- package/dist/apps/admin/mailbox-policy/index.js +8 -0
- package/dist/apps/admin/mailboxes/[uid].js +20 -4
- package/dist/apps/admin/mailboxes/new/index.js +4 -89
- package/dist/apps/admin/plugins/index.js +6 -0
- package/dist/apps/admin/quarantine/index.js +20 -11
- package/dist/apps/admin/retention-policy/index.js +3 -36
- package/dist/apps/admin/setup/index.js +6 -0
- package/dist/apps/admin/transport-rules/[uid].js +18 -2
- package/dist/apps/admin/transport-rules/_transportRuleConfig.js +19 -0
- package/dist/apps/admin/transport-rules/index.js +22 -6
- package/dist/apps/admin/transport-rules/new/index.js +18 -2
- package/dist/apps/escrow/_layout.js +3 -3
- package/dist/apps/escrow/audit-log/index.js +26 -1
- package/dist/apps/escrow/matters/[uid].js +77 -42
- package/dist/apps/shared/components/admin/escrowScopes/EscrowScopeKeyAndHoldersFields.js +11 -2
- package/dist/apps/shared/components/admin/layout/AdminShell.js +37 -4
- package/dist/apps/shared/components/admin/mailboxes/EscrowScopeCard.js +72 -0
- package/dist/apps/shared/components/admin/mailboxes/ResourceSettingsCard.js +3 -3
- package/dist/apps/shared/components/admin/mailboxes/ShareAccessCard.js +22 -4
- package/dist/apps/shared/components/admin/settings/BrandingForm.js +116 -0
- package/dist/apps/shared/components/admin/settings/DomainDnsSetup.js +83 -0
- package/dist/apps/shared/components/admin/settings/EncryptionPolicyForm.js +62 -0
- package/dist/apps/shared/components/admin/settings/LoadedSettingsForm.js +25 -0
- package/dist/apps/shared/components/admin/settings/MailboxCreateForm.js +112 -0
- package/dist/apps/shared/components/admin/settings/MailboxPolicyForm.js +74 -0
- package/dist/apps/shared/components/admin/settings/PluginsManager.js +572 -0
- package/dist/apps/shared/components/admin/settings/RetentionPolicyForm.js +73 -0
- package/dist/apps/shared/components/admin/setup/EscrowSetupStep.js +147 -0
- package/dist/apps/shared/components/admin/setup/SetupWizard.js +208 -0
- package/dist/apps/shared/components/admin/signOut.js +75 -0
- package/dist/apps/shared/components/admin/usePagedList.js +99 -0
- package/dist/apps/shared/components/calendar/CalendarListSidebar.js +24 -13
- package/dist/apps/shared/components/calendar/EventModal.js +154 -21
- package/dist/apps/shared/components/calendar/MonthView.js +12 -5
- package/dist/apps/shared/components/calendar/RecurrenceEditor.js +40 -5
- package/dist/apps/shared/components/calendar/SplitDayView.js +10 -4
- package/dist/apps/shared/components/calendar/TimeGridView.js +23 -7
- package/dist/apps/shared/components/calendar/allDay.js +111 -0
- package/dist/apps/shared/components/calendar/layout/CalendarShell.js +47 -39
- package/dist/apps/shared/components/contacts/ContactDetailPane.js +43 -5
- package/dist/apps/shared/components/contacts/ContactForm.js +45 -12
- package/dist/apps/shared/components/contacts/ContactsSidebar.js +2 -2
- package/dist/apps/shared/components/contacts/KeyChangeReview.js +63 -0
- package/dist/apps/shared/components/contacts/contactKeys.js +64 -0
- package/dist/apps/shared/components/escrow/layout/EscrowShell.js +24 -5
- package/dist/apps/shared/components/layout/AppShell.js +100 -8
- package/dist/apps/shared/components/layout/BrandingChrome.js +45 -10
- package/dist/apps/shared/components/layout/KeyEnrollmentGate.js +63 -14
- package/dist/apps/shared/components/layout/MailboxProvisioning.js +10 -2
- package/dist/apps/shared/components/layout/RecoveryCodeUnlock.js +174 -0
- package/dist/apps/shared/components/layout/UnlockPromptProvider.js +136 -0
- package/dist/apps/shared/components/mail/ConversationThreadPane.js +50 -12
- package/dist/apps/shared/components/mail/MessageDetailPane.js +464 -34
- package/dist/apps/shared/components/mail/compose/ComposeContext.js +9 -6
- package/dist/apps/shared/components/mail/compose/ComposeWindow.js +807 -116
- package/dist/apps/shared/components/mail/compose/composeFlushRegistry.js +50 -0
- package/dist/apps/shared/components/mail/layout/MailShell.js +116 -41
- package/dist/apps/shared/components/mail/pinnedSigners.js +98 -0
- package/dist/apps/shared/components/mail/verificationSeals.js +112 -0
- package/dist/apps/shared/components/mail/writableMailboxes.js +87 -0
- package/dist/apps/shared/components/rules/RuleBuilder.js +34 -6
- package/dist/apps/shared/components/settings/layout/SettingsShell.js +1 -0
- package/dist/apps/shared/mail/findWellKnownFolderUid.js +12 -0
- package/dist/apps/shared/mail/listAllPages.js +25 -0
- package/dist/apps/shared/search/LocalIndexLifecycle.js +80 -0
- package/dist/apps/shared/search/localIndexBlockCipher.js +63 -0
- package/dist/apps/shared/search/localIndexBuilder.js +408 -0
- package/dist/apps/shared/search/localIndexKey.js +25 -0
- package/dist/apps/shared/search/localIndexRpcClient.js +241 -0
- package/dist/apps/shared/search/localIndexSchema.js +185 -0
- package/dist/apps/shared/search/localIndexSizePreference.js +78 -0
- package/dist/apps/shared/search/localIndexStorage.js +86 -0
- package/dist/apps/shared/search/localIndexVFS.js +258 -0
- package/dist/apps/shared/search/localIndexWorker.js +687 -0
- package/dist/apps/shared/search/searchTier2.js +38 -0
- package/dist/apps/www/calendar/index.js +38 -18
- package/dist/apps/www/contacts/[uid].js +13 -2
- package/dist/apps/www/contacts/index.js +94 -15
- package/dist/apps/www/index.js +770 -137
- package/dist/apps/www/messages/[uid].js +23 -7
- package/dist/apps/www/settings/auto-reply/index.js +9 -4
- package/dist/apps/www/settings/booking-types/[uid].js +17 -12
- package/dist/apps/www/settings/encryption/index.js +541 -96
- package/dist/apps/www/settings/filters/[uid].js +22 -7
- package/dist/apps/www/settings/filters/new/index.js +8 -2
- package/dist/apps/www/settings/focused-inbox/index.js +3 -1
- package/dist/apps/www/settings/privacy/index.js +57 -22
- package/dist/apps/www/settings/read-receipts/index.js +10 -3
- package/dist/apps/www/settings/sharing/index.js +124 -0
- package/dist/apps/www/tasks/index.js +76 -14
- package/package.json +3 -2
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz
|
|
3
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
+
/**
|
|
6
|
+
* Tier 2 (`specs/search.md` §6) - the local encrypted index's public entry point, mirroring
|
|
7
|
+
* `@rapidmx/react-shared`'s `searchTier3.ts#searchEncryptedCandidates()` in shape: given a parsed query
|
|
8
|
+
* and this session's unlocked keys, return already-scored, already-sourced results plus how much of the
|
|
9
|
+
* mailbox the local index actually covers right now.
|
|
10
|
+
*
|
|
11
|
+
* Returns `{ results: [], coverage: undefined }` - never throws, never rejects - both when `unlocked` is
|
|
12
|
+
* absent (nothing is indexed without a master key to derive the index key from, the same "silently
|
|
13
|
+
* contributes nothing" degradation `searchTier3.ts`'s own doc comment describes for its identical case)
|
|
14
|
+
* and when anything about the local index itself fails (Worker/WASM/OPFS unsupported or unavailable in
|
|
15
|
+
* this browser, a corrupted index mid-rebuild, a query timeout). This tier's own storage engine is
|
|
16
|
+
* strictly best-effort infrastructure Tier 1 doesn't depend on - a caller `Promise.all()`-ing this
|
|
17
|
+
* alongside Tier 1/Tier 3 (`apps/www/index.tsx`'s own search orchestration) must never see the *whole
|
|
18
|
+
* search* fail just because this one, most novel piece had a bad moment.
|
|
19
|
+
*/
|
|
20
|
+
import type { UnlockedKeys } from "@rapidmx/react-shared/crypto/keySession.js";
|
|
21
|
+
import type { ParsedSearchQuery } from "@rapidmx/react-shared/search/queryGrammar.js";
|
|
22
|
+
import type { SearchResult } from "@rapidmx/react-shared/search/searchApi.js";
|
|
23
|
+
import { deriveLocalIndexKey } from "./localIndexKey.js";
|
|
24
|
+
import { getLocalCoverage, initLocalIndex, searchLocal } from "./localIndexRpcClient.js";
|
|
25
|
+
import type { Coverage } from "./localIndexWorker.js";
|
|
26
|
+
|
|
27
|
+
export interface Tier2SearchOutcome {
|
|
28
|
+
results: SearchResult[];
|
|
29
|
+
/** `undefined` when `unlocked` was absent - there's no local index to report coverage for at all in
|
|
30
|
+
* that case, distinct from a real, empty (just-built) index. */
|
|
31
|
+
coverage?: Coverage;
|
|
32
|
+
/** `true` when at least one more match exists beyond `results` - §8's composite pagination cursor's
|
|
33
|
+
* own per-tier continuation signal. Always `false` alongside the `unlocked`-absent/error
|
|
34
|
+
* degradations below - there is nothing more to page through either way. */
|
|
35
|
+
hasMore: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Ensures this mailbox's local index connection is open before it's queried - idempotent
|
|
39
|
+
* (`localIndexWorker.ts`'s own `init()` no-ops for an already-open mailbox), so callers never need to
|
|
40
|
+
* coordinate with `LocalIndexLifecycle.tsx`'s own unlock-triggered `init()` call; whichever runs first
|
|
41
|
+
* wins, and the other is a cheap no-op. */
|
|
42
|
+
async function ensureInitialized(mailboxUid: string, unlocked: UnlockedKeys): Promise<void> {
|
|
43
|
+
const indexKey = await deriveLocalIndexKey(unlocked.masterKey, mailboxUid);
|
|
44
|
+
await initLocalIndex({ mailboxUid, indexKey });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function searchLocalIndex(
|
|
48
|
+
mailboxUid: string,
|
|
49
|
+
parsed: ParsedSearchQuery,
|
|
50
|
+
unlocked: UnlockedKeys | undefined,
|
|
51
|
+
limit = 50,
|
|
52
|
+
offset = 0,
|
|
53
|
+
): Promise<Tier2SearchOutcome> {
|
|
54
|
+
if (!unlocked) {
|
|
55
|
+
return { results: [], hasMore: false };
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
await ensureInitialized(mailboxUid, unlocked);
|
|
59
|
+
const [page, coverage] = await Promise.all([searchLocal(mailboxUid, parsed, limit, offset), getLocalCoverage(mailboxUid)]);
|
|
60
|
+
const results: SearchResult[] = page.hits.map((hit) => ({
|
|
61
|
+
entityType: "message",
|
|
62
|
+
entityUid: hit.entityUid,
|
|
63
|
+
// Negated: bm25()'s own convention is "more negative is a better match" (SQLite), the
|
|
64
|
+
// opposite of every other score this codebase merges (Postgres ts_rank, OpenSearch _score,
|
|
65
|
+
// and normalizeServerScores() itself all treat "higher is better"). Negating here, once,
|
|
66
|
+
// keeps that convention uniform for the caller (apps/www/index.tsx's merge layer) - it never
|
|
67
|
+
// needs to know this tier's underlying scoring function works backwards from the others.
|
|
68
|
+
score: -hit.score,
|
|
69
|
+
snippet: hit.snippet,
|
|
70
|
+
source: "local",
|
|
71
|
+
metadataOnly: false,
|
|
72
|
+
}));
|
|
73
|
+
return { results, coverage, hasMore: page.hasMore };
|
|
74
|
+
} catch {
|
|
75
|
+
// See this function's own doc comment - a broken local index degrades to "nothing to contribute
|
|
76
|
+
// this time," never a rejected search.
|
|
77
|
+
return { results: [], hasMore: false };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz
|
|
3
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
+
/**
|
|
6
|
+
* `@journeyapps/wa-sqlite` ships real `.d.ts` files for its top-level module and most `src/examples/*`
|
|
7
|
+
* VFS implementations, but not for `src/FacadeVFS.js` (the base class for hand-written VFS
|
|
8
|
+
* implementations) or `src/examples/AccessHandlePoolVFS.js` (the OPFS SAH Pool VFS this codebase's
|
|
9
|
+
* `EncryptingVFS` wraps) - both used directly by `localIndexVFS.ts`. Declared here, minimally, covering
|
|
10
|
+
* only the members this codebase actually calls.
|
|
11
|
+
*/
|
|
12
|
+
declare module "@journeyapps/wa-sqlite/src/FacadeVFS.js" {
|
|
13
|
+
import * as VFS from "@journeyapps/wa-sqlite/src/VFS.js";
|
|
14
|
+
|
|
15
|
+
/** Convenience base class for a JavaScript VFS - see the real source's own doc comment. Subclasses
|
|
16
|
+
* override the `jXxx` methods (JS-friendly wrappers) rather than the raw `xXxx` C-callback methods. */
|
|
17
|
+
export class FacadeVFS extends VFS.Base {
|
|
18
|
+
constructor(name: string, module: unknown);
|
|
19
|
+
hasAsyncMethod(methodName: string): boolean;
|
|
20
|
+
getFilename(pFile: number): string;
|
|
21
|
+
jOpen(filename: string | null, pFile: number, flags: number, pOutFlags: DataView): number | Promise<number>;
|
|
22
|
+
jDelete(filename: string, syncDir: number): number | Promise<number>;
|
|
23
|
+
jAccess(filename: string, flags: number, pResOut: DataView): number | Promise<number>;
|
|
24
|
+
jFullPathname(filename: string, zOut: Uint8Array): number | Promise<number>;
|
|
25
|
+
jClose(pFile: number): number | Promise<number>;
|
|
26
|
+
jRead(pFile: number, pData: Uint8Array, iOffset: number): number | Promise<number>;
|
|
27
|
+
jWrite(pFile: number, pData: Uint8Array, iOffset: number): number | Promise<number>;
|
|
28
|
+
jTruncate(pFile: number, size: number): number | Promise<number>;
|
|
29
|
+
jSync(pFile: number, flags: number): number | Promise<number>;
|
|
30
|
+
jFileSize(pFile: number, pSize64: DataView): number | Promise<number>;
|
|
31
|
+
jSectorSize(pFile: number): number;
|
|
32
|
+
jDeviceCharacteristics(pFile: number): number;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
declare module "@journeyapps/wa-sqlite/src/examples/AccessHandlePoolVFS.js" {
|
|
37
|
+
import { FacadeVFS } from "@journeyapps/wa-sqlite/src/FacadeVFS.js";
|
|
38
|
+
|
|
39
|
+
/** The OPFS "SAH Pool" VFS - synchronous, works with the plain (non-Asyncify) SQLite WASM build.
|
|
40
|
+
* `create()` is the intended entry point (constructs, then awaits internal readiness). */
|
|
41
|
+
export class AccessHandlePoolVFS extends FacadeVFS {
|
|
42
|
+
static create(name: string, module: unknown): Promise<AccessHandlePoolVFS>;
|
|
43
|
+
}
|
|
44
|
+
}
|