-
Notifications
You must be signed in to change notification settings - Fork 3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature: Render markdown in thread header #24574
Changes from 75 commits
32a46ee
9b3c217
d6fafdb
0c9829c
1cb14d0
ad73440
eea1007
8111823
cc8071e
6a61a36
3bfe861
23ac696
f8fea9b
e069de0
804672f
4b2b7ce
4596197
5b2951e
555d4be
6102ac9
d8459fa
8fb1b8a
171edbd
96da94e
0744817
6d57338
6aca837
5f1b01c
727436a
55b01cf
96da21f
99eff2e
488e9d9
72209df
e27a398
90c9ead
51235e9
759ec0e
62afa9d
99fe8dd
cdfdd58
0632f7d
057a78d
b630ff0
a9cc3d7
65164ff
3543ea3
8f95128
e162f59
c9b5c9e
3543cf6
753b75c
8b12df6
ce9953b
3384654
ddde5d6
ee78205
f98485f
91903c1
b9bbccf
0ef5a6d
7767205
e868c58
fbc84da
14b82ac
8857f04
292f113
f3acd60
8eb159e
c712a20
b9aff52
c8edb0d
7068ddb
4979581
a70bd42
b09f817
7b3d922
d309f69
4780811
4f969fc
137e138
7b01ff9
db51209
808fd69
f8d8472
f34b7db
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -57,6 +57,14 @@ function BaseHTMLEngineProvider({textSelectable = false, children, enableExperim | |
mixedUAStyles: {whiteSpace: 'pre'}, | ||
contentModel: HTMLContentModel.textual, | ||
}), | ||
'thread-title': HTMLElementModel.fromCustomModel({ | ||
tagName: 'thread-title', | ||
contentModel: HTMLContentModel.textual, | ||
mixedUAStyles: {...styles.headerText, whiteSpace: 'pre'}, | ||
reactNativeProps: { | ||
text: {numberOfLines: 1}, | ||
}, | ||
}), | ||
'mention-user': HTMLElementModel.fromCustomModel({tagName: 'mention-user', contentModel: HTMLContentModel.textual}), | ||
'mention-here': HTMLElementModel.fromCustomModel({tagName: 'mention-here', contentModel: HTMLContentModel.textual}), | ||
'next-step': HTMLElementModel.fromCustomModel({ | ||
|
@@ -71,13 +79,13 @@ function BaseHTMLEngineProvider({textSelectable = false, children, enableExperim | |
contentModel: HTMLContentModel.block, | ||
}), | ||
}), | ||
[styles.colorMuted, styles.formError, styles.mb0, styles.textLabelSupporting, styles.lh16], | ||
[styles.colorMuted, styles.formError, styles.mb0, styles.textLabelSupporting, styles.lh16, styles.headerText], | ||
); | ||
/* eslint-enable @typescript-eslint/naming-convention */ | ||
|
||
// We need to memoize this prop to make it referentially stable. | ||
const defaultTextProps: TextProps = useMemo(() => ({selectable: textSelectable, allowFontScaling: false, textBreakStrategy: 'simple'}), [textSelectable]); | ||
const defaultViewProps = {style: [styles.alignItemsStart, styles.userSelectText]}; | ||
const defaultViewProps = {style: [styles.alignItemsStart, styles.userSelectText, styles.mw100]}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To make the header text truncate. |
||
return ( | ||
<TRenderEngineProvider | ||
customHTMLElementModels={customHTMLElementModels} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,7 @@ import lodashEscape from 'lodash/escape'; | |
import lodashFindLastIndex from 'lodash/findLastIndex'; | ||
import lodashIntersection from 'lodash/intersection'; | ||
import lodashIsEqual from 'lodash/isEqual'; | ||
import lodashUnescape from 'lodash/unescape'; | ||
import type {OnyxCollection, OnyxEntry, OnyxUpdate} from 'react-native-onyx'; | ||
import Onyx from 'react-native-onyx'; | ||
import type {ValueOf} from 'type-fest'; | ||
|
@@ -68,6 +69,7 @@ import * as PolicyUtils from './PolicyUtils'; | |
import type {LastVisibleMessage} from './ReportActionsUtils'; | ||
import * as ReportActionsUtils from './ReportActionsUtils'; | ||
import shouldAllowRawHTMLMessages from './shouldAllowRawHTMLMessages'; | ||
import StringUtils from './StringUtils'; | ||
import * as TransactionUtils from './TransactionUtils'; | ||
import * as Url from './Url'; | ||
import * as UserUtils from './UserUtils'; | ||
|
@@ -2546,10 +2548,54 @@ function getAdminRoomInvitedParticipants(parentReportAction: ReportAction | Reco | |
return roomName ? `${verb} ${users} ${preposition} ${roomName}` : `${verb} ${users}`; | ||
} | ||
|
||
/** | ||
* Get the formatted title in HTML for a thread based on parent message. | ||
* Only the first line of the message should display. | ||
*/ | ||
function getThreadReportNameHtml(reportActionMessageHtml: string): string { | ||
const blockTags = ['br', 'h1', 'pre', 'div', 'blockquote', 'p', 'li', 'div']; | ||
const blockTagRegExp = `(?:<\\/?(?:${blockTags.join('|')})(?:[^>]*)>|\\r\\n|\\n|\\r)`; | ||
const threadHeaderHtmlRegExp = new RegExp(`^(?:<([^>]+)>)?((?:(?!${blockTagRegExp}).)*)(${blockTagRegExp}.*)`, 'gmi'); | ||
return reportActionMessageHtml.replace(threadHeaderHtmlRegExp, (match, g1: string, g2: string) => { | ||
if (!g1 || g1 === 'h1') { | ||
return g2; | ||
} | ||
if (g1 === 'pre') { | ||
return `<code>${g2}</code>`; | ||
} | ||
const parser = new ExpensiMark(); | ||
if (parser.containsNonPairTag(g2)) { | ||
return `<${g1}>${g2}`; | ||
} | ||
return `<${g1}>${g2}</${g1}>`; | ||
}); | ||
} | ||
|
||
/** | ||
* Get the title for a thread based on parent message. | ||
* If render in html, only the first line of the message should display. | ||
*/ | ||
function getThreadReportName(parentReportAction: OnyxEntry<ReportAction> | EmptyObject = {}, shouldRenderAsHTML = false, shouldRenderFirstLineOnly = true): string { | ||
if (ReportActionsUtils.isApprovedOrSubmittedReportAction(parentReportAction)) { | ||
return ReportActionsUtils.getReportActionMessageText(parentReportAction).replace(/(\r\n|\n|\r)/gm, ' '); | ||
} | ||
if (!shouldRenderAsHTML && !shouldRenderFirstLineOnly) { | ||
return (parentReportAction?.message?.[0]?.text ?? '').replace(/(\r\n|\n|\r)/gm, ' '); | ||
} | ||
|
||
const threadReportNameHtml = getThreadReportNameHtml(parentReportAction?.message?.[0]?.html ?? ''); | ||
|
||
if (!shouldRenderAsHTML && shouldRenderFirstLineOnly) { | ||
return lodashUnescape(Str.stripHTML(threadReportNameHtml)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unescape html characters to avoid the cases of |
||
} | ||
|
||
return StringUtils.containsHtml(threadReportNameHtml) ? `<thread-title>${threadReportNameHtml}</thread-title>` : lodashUnescape(threadReportNameHtml); | ||
} | ||
|
||
/** | ||
* Get the title for a report. | ||
*/ | ||
function getReportName(report: OnyxEntry<Report>, policy: OnyxEntry<Policy> = null): string { | ||
function getReportName(report: OnyxEntry<Report>, policy: OnyxEntry<Policy> = null, shouldRenderAsHTML = false, shouldRenderFirstLineOnly = false): string { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
let formattedName: string | undefined; | ||
const parentReportAction = ReportActionsUtils.getParentReportAction(report); | ||
if (isChatThread(report)) { | ||
|
@@ -2562,11 +2608,7 @@ function getReportName(report: OnyxEntry<Report>, policy: OnyxEntry<Policy> = nu | |
} | ||
|
||
const isAttachment = ReportActionsUtils.isReportActionAttachment(!isEmptyObject(parentReportAction) ? parentReportAction : null); | ||
const parentReportActionMessage = ( | ||
ReportActionsUtils.isApprovedOrSubmittedReportAction(parentReportAction) | ||
? ReportActionsUtils.getReportActionMessageText(parentReportAction) | ||
: parentReportAction?.message?.[0]?.text ?? '' | ||
).replace(/(\r\n|\n|\r)/gm, ' '); | ||
const parentReportActionMessage = getThreadReportName(parentReportAction, shouldRenderAsHTML, shouldRenderFirstLineOnly); | ||
if (isAttachment && parentReportActionMessage) { | ||
return `[${Localize.translateLocal('common.attachment')}]`; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I cannot nest with the below
Text
because that would add a huge gap between the header title and subtitle on native devices.