src/views/article/article__details.tsx (253 lines of code) (raw):

import React, {useState} from 'react'; import {View, Text, TouchableOpacity, FlatList} from 'react-native'; import ArticleContent from './article__details-content'; import ArticleWithChildren from 'components/articles/article-item-with-children'; import AttachmentsRow from 'components/attachments-row/attachments-row'; import Header from 'components/header/header'; import ModalPortal from 'components/modal-view/modal-portal'; import Router from 'components/router/router'; import Select from 'components/select/select'; import Separator from 'components/separator/separator'; import usage from 'components/usage/usage'; import {ANALYTICS_ARTICLE_PAGE} from 'components/analytics/analytics-ids'; import {i18n, i18nPlural} from 'components/i18n/i18n'; import { IconAdd, IconAngleRight, IconBack, IconClose, } from 'components/icon/icon'; import {logEvent} from 'components/log/log-helper'; import {routeMap} from 'app-routes'; import {SkeletonIssueContent} from 'components/skeleton/skeleton'; import styles from './article.styles'; import type {Article} from 'types/Article'; import type {Attachment} from 'types/CustomFields'; import type {CustomError} from 'types/Error'; import type {UITheme} from 'types/Theme'; import {HIT_SLOP} from 'components/common-styles'; type Props = { article: Article; error: CustomError | null; isLoading: boolean; onRemoveAttach?: (attachment: Attachment) => any; onCreateArticle?: () => any; uiTheme: UITheme; scrollData: Record<string, any>; onCheckboxUpdate?: (checked: boolean, position: number, articleContent: string) => void; isSplitView: boolean; }; type ModalStackData = { id: string; children: any; onHide: () => any; }; const ArticleDetails = (props: Props) => { const [modalStack, updateModalStack] = useState<ModalStackData[]>([]); function navigateToSubArticlePage(article: Article) { const update = () => { updateModalStack((prev: ModalStackData[]) => { const onHide = () => updateModalStack((prev: ModalStackData[]) => prev.filter(it => it.id !== article.id)); prev.push({ id: article.id, onHide, children: renderSubArticles( article, onHide, prev.length === 0 ? ( <IconClose size={21} color={styles.link.color} /> ) : null, ), }); return prev.slice(); }, ); }; if (props.isSplitView) { update(); } else { Router.Page({ children: renderSubArticles(article), }); } } function renderSubArticles( article: Article, onHide: () => any = () => Router.pop(), backIcon?: any, ) { const renderArticle = ({item}: {item: Article}) => { return ( <ArticleWithChildren style={styles.subArticleItem} article={item} onArticlePress={(article: Article) => { if (props.isSplitView) { Router.KnowledgeBase({ lastVisitedArticle: article, preventReload: true, }); } else { Router.Article({ articlePlaceholder: article, storePrevArticle: true, store: true, storeRouteName: routeMap.ArticleSingle, }); } }} onShowSubArticles={(childArticle: Article) => navigateToSubArticlePage(childArticle) } /> ); }; return ( <> <Header style={styles.subArticlesHeader} leftButton={backIcon || <IconBack color={styles.link.color} />} onBack={onHide} > <Text numberOfLines={2} style={styles.articlesHeaderText}> {article.summary} </Text> </Header> <FlatList data={article.childArticles} keyExtractor={(it: Article) => it.id} getItemLayout={Select.getItemLayout} renderItem={renderArticle} ItemSeparatorComponent={Select.renderSeparator} /> </> ); } const renderSubArticlesButton = () => { const hasSubArticles: boolean = article?.childArticles?.length > 0; if (!!onCreateArticle || hasSubArticles) { return ( <TouchableOpacity disabled={!hasSubArticles} onPress={() => navigateToSubArticlePage(props.article)} style={styles.subArticles} > <View style={styles.subArticlesCreate}> <Text style={styles.subArticlesTitle}>{i18n('Sub-articles')}</Text> {!!onCreateArticle && ( <TouchableOpacity hitSlop={HIT_SLOP} onPress={onCreateArticle} style={styles.subArticlesCreateIcon} > <IconAdd size={20} color={styles.subArticlesCreateIcon.color} /> </TouchableOpacity> )} </View> {hasSubArticles && ( <View style={styles.subArticlesContent}> <Text style={styles.subArticleItemText}> {i18nPlural( article?.childArticles?.length, '{{amount}} article', '{{amount}} articles', { amount: article?.childArticles?.length, }, )} </Text> <IconAngleRight size={18} color={styles.subArticlesNavigateIcon.color} style={styles.subArticlesNavigateIcon} /> </View> )} </TouchableOpacity> ); } }; const { article, isLoading, error, uiTheme, onRemoveAttach, onCreateArticle, scrollData, onCheckboxUpdate, } = props; if (!article) { return null; } return ( <> {!!article.summary && ( <Text style={styles.summaryText}>{article.summary}</Text> )} {isLoading && !error && !article?.content && <SkeletonIssueContent />} {renderSubArticlesButton()} <ArticleContent scrollData={scrollData} attachments={article?.attachments} mentions={{ articles: article?.mentionedArticles, issues: article?.mentionedIssues, users: article?.mentionedUsers, }} articleContent={article?.content} onCheckboxUpdate={( checked: boolean, position: number, articleContent: string, ) => { onCheckboxUpdate?.(checked, position, articleContent); }} /> {article?.attachments?.length > 0 && ( <> <Separator fitWindow indent /> <View style={styles.articleDetailsHeader}> <AttachmentsRow attachments={article.attachments} attachingImage={null} onImageLoadingError={(err: Record<string, any>) => logEvent({ message: err.nativeEvent, isError: true, }) } canRemoveAttachment={!!onRemoveAttach} onRemoveImage={onRemoveAttach || undefined} onOpenAttachment={type => usage.trackEvent( ANALYTICS_ARTICLE_PAGE, type === 'image' ? 'Showing image' : 'Open attachment by URL', ) } uiTheme={uiTheme} /> </View> </> )} {props.isSplitView && ( <ModalPortal onHide={ modalStack.length > 0 ? modalStack[modalStack.length - 1].onHide : () => {} } > {modalStack.length > 0 ? modalStack[modalStack.length - 1].children : null} </ModalPortal> )} </> ); }; export default React.memo<Props>(ArticleDetails);