Skip to content

Commit 6100297

Browse files
committed
Merge branch 'main' into feat/use-reanimated-modal-in-help-modal
2 parents 35a5a9f + 9a88559 commit 6100297

298 files changed

Lines changed: 4937 additions & 2177 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.eslintrc.changed.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ module.exports = {
88
'deprecation/deprecation': 'error',
99
'rulesdir/no-default-id-values': 'error',
1010
'rulesdir/provide-canBeMissing-in-useOnyx': 'error',
11+
'rulesdir/no-unstable-hook-defaults': 'error',
1112
'no-restricted-syntax': [
1213
'error',
1314
{

.eslintrc.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@ module.exports = {
219219
'es/no-optional-chaining': 'off',
220220
'deprecation/deprecation': 'off',
221221
'arrow-body-style': 'off',
222+
'no-continue': 'off',
222223

223224
// Import specific rules
224225
'import/consistent-type-specifier-style': ['error', 'prefer-top-level'],

.github/scripts/buildActions.sh

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,21 @@ for ((i=0; i < ${#GITHUB_ACTIONS[@]}; i++)); do
4949
ASYNC_BUILDS[i]=$!
5050
done
5151

52+
EXIT_CODE=0
5253
for ((i=0; i < ${#GITHUB_ACTIONS[@]}; i++)); do
5354
ACTION=${GITHUB_ACTIONS[$i]}
5455
ACTION_DIR=$(dirname "$ACTION")
56+
ACTION_NAME=$(basename "$ACTION" .ts)
5557

5658
# Wait for the background build to finish
57-
wait "${ASYNC_BUILDS[$i]}"
58-
59-
# Prepend the warning note to the top of the compiled file
60-
OUTPUT_FILE="$ACTION_DIR/index.js"
61-
echo "$NOTE_DONT_EDIT$(cat "$OUTPUT_FILE")" > "$OUTPUT_FILE"
59+
if wait "${ASYNC_BUILDS[$i]}"; then
60+
# Prepend the warning note to the top of the compiled file
61+
OUTPUT_FILE="$ACTION_DIR/index.js"
62+
echo "$NOTE_DONT_EDIT$(cat "$OUTPUT_FILE")" > "$OUTPUT_FILE"
63+
else
64+
echo "$ACTION_NAME failed to build: $ACTION_DIR/index.js" >&2
65+
EXIT_CODE=1
66+
fi
6267
done
68+
69+
exit $EXIT_CODE

.storybook/preview.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ import React from 'react';
33
import Onyx from 'react-native-onyx';
44
import {SafeAreaProvider} from 'react-native-safe-area-context';
55
import type {Parameters} from 'storybook/internal/types';
6+
import OnyxListItemProvider from '@components/OnyxListItemProvider';
67
import {SearchContextProvider} from '@components/Search/SearchContext';
78
import ComposeProviders from '@src/components/ComposeProviders';
89
import HTMLEngineProvider from '@src/components/HTMLEngineProvider';
910
import {LocaleContextProvider} from '@src/components/LocaleContextProvider';
10-
import OnyxProvider from '@src/components/OnyxProvider';
1111
import {EnvironmentProvider} from '@src/components/withEnvironment';
1212
import {KeyboardStateProvider} from '@src/components/withKeyboardState';
1313
import ONYXKEYS from '@src/ONYXKEYS';
@@ -23,7 +23,16 @@ Onyx.init({
2323
const decorators = [
2424
(Story: React.ElementType) => (
2525
<ComposeProviders
26-
components={[OnyxProvider, LocaleContextProvider, HTMLEngineProvider, SafeAreaProvider, PortalProvider, EnvironmentProvider, KeyboardStateProvider, SearchContextProvider]}
26+
components={[
27+
OnyxListItemProvider,
28+
LocaleContextProvider,
29+
HTMLEngineProvider,
30+
SafeAreaProvider,
31+
PortalProvider,
32+
EnvironmentProvider,
33+
KeyboardStateProvider,
34+
SearchContextProvider,
35+
]}
2736
>
2837
<Story />
2938
</ComposeProviders>

Mobile-Expensify

android/app/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,8 @@ android {
114114
minSdkVersion rootProject.ext.minSdkVersion
115115
targetSdkVersion rootProject.ext.targetSdkVersion
116116
multiDexEnabled rootProject.ext.multiDexEnabled
117-
versionCode 1009018103
118-
versionName "9.1.81-3"
117+
versionCode 1009018202
118+
versionName "9.1.82-2"
119119
// Supported language variants must be declared here to avoid from being removed during the compilation.
120120
// This also helps us to not include unnecessary language variants in the APK.
121121
resConfigs "en", "es"

contributingGuides/HOW_TO_BECOME_A_CONTRIBUTOR_PLUS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ C+ are contributors who are experienced at working with Expensify and have gaine
1313
- If C+ ever applies to work at Expensify, they’ve got a leg up on the competition because they're familiar with the company and processes. View job openings at https://we.are.expensify.com.
1414

1515
## Requirements to join C+
16-
- Have had 10 PRs deployed to production without regressions.
16+
- Have had 30 PRs deployed to production without regressions.
1717
- Follow our Code of Conduct, Contributing.md and README.md docs and processes.
1818
- Comment and fix bugs in a timely manner.
1919
- Clear communicator
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Mentions highlighting in Composer input and chat messages
2+
3+
## Glossary
4+
**Full mention** - called more simply `userMention` is the full email of a user. In Expensify app _every_ correct full email gets highlighted in text.
5+
When parsed via ExpensiMark into html, this mention is described with tag `<mention-user>`.
6+
#### Examples of user mentions: `@john.doe@company.org`, `@vit@expensify.com`
7+
8+
**Short mention** - a special type of mention that contains only the login part of a user email **AND** the email domain has to be the same as our email domain. Any other `@mention` will not get highlighted if domains don't match
9+
When parsed via ExpensiMark into html, it is described with tag `<mention-short>`.
10+
11+
#### Examples of short mentions:
12+
- `@vit` - **IF** my domain is `expensify.com` **AND** there exists a user with email `vit@expensify.com` - it will get highlighted ✅
13+
- `@mateusz` - **IF** my domain is `expensify.com` **AND** there is **NO** user with email `mateusz@expensify.com`, but there is for example `mateusz@company.org` - it will NOT get highlighted ❌
14+
15+
**ExpensiMark** - parser that we are using, which allows for parsing between markdown <---> html formats. Imported from `expensify-common` package.
16+
17+
## tl;dr - the most important part
18+
- there are 2 slightly different flows of handling mentions - one is inside the Composer Input and the other outside of it
19+
- both are complex and need to support both userMentions and shortMentions - See **FAQ**
20+
21+
## Parsing mentions inside Composer/Input (LiveMarkdown)
22+
Our `Composer` component uses `react-native-live-markdown` for writing and editing markdown and handling mentions. When discussing how mentions work **inside** the composer input always look for answers in this [library](https://github.com/Expensify/react-native-live-markdown).
23+
24+
### Mention parsing flow in live-markdown
25+
1. User types in some text
26+
2. `RNMarkdownTextInput` will handle the text by calling `parseExpensiMark`, which is an internal function of live-markdown: https://github.com/Expensify/react-native-live-markdown/blob/main/src/parseExpensiMark.ts
27+
3. `parseExpensiMark` will use `ExpensiMark` for parsing, then do several extra operations so that the component can work correctly
28+
4. When `ExpensiMark` parses the text, any full email will get parsed to `<mention-user>...</mention-user>` and any `@phrase` will get parsed to `<mention-short>...</mention-short>`
29+
5. `userMentions` are ready to use as they are so they require no further modification, however for `shortMentions` we need to check if they actually should get the highlighting
30+
5. We use the `parser` prop of `<MarkdownTextInput>` to pass custom parsing logic - this allows us to do some extra processing after `parseExpensiMark` runs.
31+
6. Our custom logic will go over every `<mention-short>` entry and verify if this login is someone that exists in userDetails data, then transform this into a full mention which gets highlighting
32+
33+
**NOTE:** this entire process takes part only "inside" Composer input. This is what happens between user typing in some text and user seeing the markdown/highlights in real time.
34+
35+
## Parsing mentions outside of Composer/Input
36+
When a user types in a message and wants to send it, we need to process the message text and then call the appropriate backend command.
37+
However, backend only accepts text in html format. This means that text payload sent to backend has to be parsed via ExpensiMark. In addition, api **will not** accept `<mention-short>` tag - it only accepts full user mention with email. Frontend needs to process every `shortMention` into a `userMention` or stripping it completely from text.
38+
39+
### Mention processing flow when sending a message
40+
1. After typing in some text user hits ENTER or presses the send button
41+
2. Several functions are called but ultimately `addActions(...)` is the one that will prepare backend payload and make the Api call.
42+
3. The function solely responsible for getting the correctly parsed text is `getParsedComment()` - it should return the string that is safe to send to backend.
43+
4. We **do not** have access to `parseExpensiMark` or any functions that worked in worklets, as we are outside of `live-markdown` but we need to process `shortMentions` regardless.
44+
5. The processing is done in `getParsedMessageWithShortMentions`: we parse via `ExpensiMark` with options very similar to what happens inside `parseExpensiMark` in `live-markdown`. (this is similar to Step 5. from previous flow).
45+
6. We then find every `<mention-short>...</mention-short>` and try to see if the specific mention exists in userDetails data.
46+
47+
## FAQ
48+
### Q: Why can't we simply use `parseExpensiMark` in both cases?!
49+
We cannot call `parseExpensiMark` in both cases, because `parseExpensiMark` returns a special data structure, called `MarkdownRange` which is both created and consumed by `react-native-live-markdown`.
50+
51+
Expensify API only accepts HTML and not markdown range.
52+
53+
Useful graph:
54+
```
55+
ExpensiMark: (raw text with markdown markers) ----> (HTML)
56+
parseExpensiMark: (raw text with markdown markers) ----> MarkdownRange[] structure
57+
```
58+
```
59+
<MarkdownTextInput> accepts MarkdownRange[]
60+
Expensify Api call accepts HTML
61+
```

cspell.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -688,7 +688,8 @@
688688
"zxcv",
689689
"zxldvw",
690690
"مثال",
691-
"pnrs"
691+
"pnrs",
692+
"POLICYCHANGELOG_ADD_EMPLOYEE"
692693
],
693694
"ignorePaths": [
694695
"src/languages/de.ts",

docs/articles/expensify-classic/expensify-billing/Billing-Overview.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ Collect is perfect for small teams that need an easy way to track receipts and g
3838

3939
## 🌍 Localized Pricing
4040

41-
- Equivalent pricing available in **GBP**, **AUD**, and **NZD**
41+
- Equivalent pricing available in **EUR**, **GBP**, **AUD**, and **NZD**
4242

4343
## 🔄 Flexible Scaling
4444

0 commit comments

Comments
 (0)