Source

externalCode/topicSingle.js

/**
 * @typedef {Object} TopicProps
 * @property { Object } global App global style
 * @property { Object } colors App colors
 * @property { TranslationFunction } t
 * @property {TopicViewModel} topic
 */

/**
 * @typedef {Object} TopicTitleComponentProps
 * @property {TopicProps} topicProps
 */

/**
 * @typedef {Object} TopicContentComponentProps
 * @property {TopicProps} topicProps
 * @property {Object} tagsStyles Styles used for different HTML tags
 * @property {String} content Topic content
 * @property {Function} attemptDeepLink Helper function to attempt deep link
 * @property {Number} computedWidth Default computed width of component to render
 */

/**
 * @typedef {Object} TopicReplyButtonProps
 * @property {TopicProps} topicProps
 * @property {Boolean} topicCloseForUser Returns `true` if topic is closed for the logged-in user
 * @property {Function} openClosedDiscussionModal Helper function that shows a modal if the topic has been closed to new replies
 */

/**
 * @typedef {Object} TopicMetadataComponentProps
 * @property {TopicProps} topicProps
 */

/**
 * @typedef {Object} TopicItemHeaderProps
 * @property {TopicViewModel} item
 * @property {FormatDateFunction} formatDateFunc Function to help format date
 * @property {Object} global App global style
 * @property {String} textColor Default text color
 * @property {String} linkColor Default link color
 * @property {Boolean} light Returns `true` if text should be light colored
 * @property {String} alignItems Default alignment of items
 * @property {Number} avatarSize Default size of avatar
 * @property {Object} titleStyle Default styling for title
 * @property {Object} actionButtons Topic functions from redux
 */

/**
 * @typedef {Object} ReplyItemAvatarProps
 * @property {Object} reply Reply details
 * @property {Object} global App global style
 * @property {Boolean} isNested Returns `true` if the reply is nested inside a reply
 */

/**
 * @typedef {Object} ReplyItemHeaderProps
 * @property {Object} global App global style
 * @property {Object} headerTitleStyle Default styling applied to the component
 * @property {Object} reply Reply details
 * @property {Function} formatDateFunc Helper function which can be used to format dates
 */

/**
 * @typedef {Object} ReplyItemContentProps
 * @property {Function} formatTextForDisplay Helper function which formats a text
 * @property {Function} filterContentCss Helper function which filters css to a safe format
 * @property {Object} reply Reply details
 * @property {Object} colors App colors
 * @property {TranslationFunction} t
 * @property {Object} global App global style
 * @property {Object} tagsStyles Default styling for HTML tags
 * @property {Object} imagesInitialDimensions
 * @property {Number} computedWidth
 * @property {String} referer Used by iframeRender to add `referer` to a webview
 * @property {Function} alterChildrenHTML Helper function which cleans up HTML
 * @property {Function} attemptDeepLink Helper function to attempt deep link
 * @property {Function} aTagRenderer Helper function for rendering anchor tags
 * @property {Function} iframeRender Helper function for rendering iFrame in an HTML
 *
 */

/**
 * @class
 * Topic/Discussion Single Hooks.
 * Instance name: topicSingleApi
  
   You can use this to customize the default topic/discussion single screen components such as the ReplyItemAvatar, ReplyItemContent, and so on.
 * @example
 * externalCodeSetup.topicSingleApi.METHOD_NAME
 */
export class TopicSingleApi {
	TopicItemHeader = null;
	/**
	 * You can use this to customize the author name and date in the topic single screen.
	 * @method
	 * @param {React.ComponentType<TopicItemHeaderProps>} TopicItemHeader
	 * @example <caption> Add a "Verified" text beside the author's name </caption>
	 *
	 * //In custom_code/components/TopicItemHeader.js...
	 *
	 * import React from "react";
	 * import {View, Text, TouchableOpacity} from "react-native";
	 * import {getAvatar} from "@src/utils";
	 * import AppTouchableOpacity from "@src/components/AppTouchableOpacity";
	 * import AppAvatar from "@src/components/AppAvatar";
	 *
	 * const renderVerified = (author) => {
	 *    if(author.id === 1){
	 *        return <Text style={{fontSize: 10}}>Verified</Text>
	 *    }
	 *
	 *    return null;
	 * }
	 *
	 * const ItemHeader = ({
	 *  item,
	 *  global,
	 *  formatDateFunc,
	 *  textColor,
	 *  linkColor,
	 *  light,
	 *  alignItems,
	 *  avatarSize,
	 *  titleStyle,
	 *  actionButtons
	 * }) => {
	 *
	 * let lightStyle = {};
	 * if (light) lightStyle = {color: "#ffffff"};
	 *
	 * let alignStyle = {};
	 * if (alignItems) alignStyle = {alignItems: alignItems};
	 * return (
	 *    <View style={[global.itemHeader, alignStyle]}>
	 *      <View style={[global.itemLeft, {alignItems: "center"}]}>
	 *        <AppTouchableOpacity
	 *          onPress={item.navigateToProfile ? item.navigateToProfile : () => {}}
	 *          style={global.avatarWrap}
	 *        >
	 *      <AppAvatar
	 *        size={avatarSize}
	 *        name={item.author.name}
	 *        source={{
	 *          uri: getAvatar(item.author.avatar, 96)
	 *        }}
	 *      />
	 *      </AppTouchableOpacity>
	 *        {!!item.author.name && (
	 *          <View style={{flex: 1}}>
	 *            <Text
	 *              style={[
	 *                global.itemName,
	 *                lightStyle,
	 *                titleStyle
	 *              ]}
	 *            >
	 *              {item.author.name} {renderVerified(item.author)}
	 *            </Text>
	 *            <View style={{flexDirection: "row", flexWrap: "wrap"}}>
	 *              <Text style={[global.itemMeta, lightStyle]}>
	 *                {formatDateFunc(item.lastActive)}
	 *              </Text>
	 *            </View>
	 *          </View>
	 *         )}
	 *        </View>
	 *    </View>
	 *  );
	 * };
	 *
	 * export default ItemHeader;
	 *
	 * //In custom_code/index.js...
	 *
	 * ...
	 *
	 * import TopicItemHeader from "./components/TopicItemHeader";
	 *
	 * export const applyCustomCode = externalCodeSetup => {
	 *  externalCodeSetup.topicSingleApi.setTopicItemHeader( (props) => <TopicItemHeader {...props} />);
	 * }
	 */
	setTopicItemHeader = TopicItemHeader => {
		this.TopicItemHeader = TopicItemHeader;
	};

	ReplyItemAvatar = null;
	/**
	 * You can use this hook to customize the reply item avatar in the topic single screen.
	 * For example, you can add a "verified" icon or text beside the avatar.
	 * @method
	 * @param {React.ComponentType<ReplyItemAvatarProps>} ReplyItemAvatar
	 * @example <caption> Add a "Verified" text below the avatar </caption>
	 *
	 * ...
	 *
	 * import AppAvatar from "@src/components/AppAvatar";
	 * import AppTouchableOpacity from "@src/components/AppTouchableOpacity";
	 *
	 * export const applyCustomCode = externalCodeSetup => {
	 *
	 *   externalCodeSetup.topicSingleApi.setReplyItemAvatar(props => {
	 *
	 *     const { reply, global, isNested } = props
	 *
	 *     return <AppTouchableOpacity
	 *       onPress={reply.navigateToProfile ? reply.navigateToProfile : () => { }}
	 *       style={global.avatarWrap}
	 *     >
	 *       <AppAvatar
	 *         size={isNested ? 30 : 40}
	 *         name={reply.author.fullname}
	 *         source={{
	 *           uri: reply.author.avatarUrl
	 *         }}
	 *       />
	 *       <Text>Verified</Text>
	 *     </AppTouchableOpacity>
	 *   })
	 * }
	 */
	setReplyItemAvatar = ReplyItemAvatar => {
		this.ReplyItemAvatar = ReplyItemAvatar;
	};

	ReplyItemHeader = null;
	/**
	 * You can use this hook to customize the reply item header which by default contains the author of the reply and its date.
	 * @method
	 * @param {React.ComponentType<ReplyItemHeaderProps>} ReplyItemHeader
	 * @example <caption> Change display date format </caption>
	 *
	 * ...
	 * export const applyCustomCode = externalCodeSetup => {
	 *   externalCodeSetup.topicSingleApi.setReplyItemHeader(props => {
	 *     const { global, headerTitleStyle, reply, formatDateFunc } = props;
	 *     return <View style={global.row}>
	 *       <Text style={[global.itemName, headerTitleStyle, { marginBottom: 0 }]}>
	 *         {reply.author.fullname}
	 *       </Text>
	 *       {!reply.author.reported && (
	 *         <Text style={[global.itemLightMeta, { marginLeft: 8 }]}>
	 *           //{formatDateFunc(reply.date)}
	 *           {reply.date}
	 *         </Text>
	 *       )}
	 *     </View>
	 *
	 *   })
	 * }
	 */
	setReplyItemHeader = ReplyItemHeader => {
		this.ReplyItemHeader = ReplyItemHeader;
	};

	ReplyItemContent = null;
	/**
	 * You can use this hook to customize the reply item content.
	 * @method
	 * @param {React.ComponentType<ReplyItemContentProps>} ReplyItemContent
	 * @example <caption> Implement default BB component </caption>
	 *
	 * //In custom_code/components/ReplyItemContent.js...
	 *
	 * import React from "react";
	 * import {View} from "react-native";
	 * import HTML from "react-native-render-html";
	 * import ReadMore from "@src/components/ReadMore";
	 * import AutoSizeImage from "@src/components/AutoSizeImage";
	 * import ImageCollection from "@src/components/ImageCollection";
	 * import {GifVideoPlayer} from "@src/components/Gif";
	 * import EmbeddedDocumentItem from "@src/components/Documents/EmbeddedDocumentItem";
	 *
	 * const ReplyItemContent = ({
	 *   formatTextForDisplay,
	 *   filterContentCss,
	 *   reply,
	 *   colors,
	 *   t,
	 *   global,
	 *   tagsStyles,
	 *   imagesInitialDimensions,
	 *   computedWidth,
	 *   referer,
	 *   alterChildrenHTML,
	 *   attemptDeepLink,
	 *   aTagRenderer,
	 *   iframeRender
	 * }) => (
	 *   <View style={{flex: 1, marginTop: 6}}>
	 *     <ReadMore
	 *       content={formatTextForDisplay(filterContentCss(reply.content))}
	 *       size={300}
	 *       colors={colors}
	 *       t={t}
	 *       global={global}
	 *     >
	 *       {content => (
	 *         <HTML
	 *           tagsStyles={{
	 *             ...tagsStyles,
	 *             p: {
	 *               ...tagsStyles.p,
	 *               marginBottom: 0
	 *             },
	 *             iframe: {
	 *               marginTop: 10
	 *             }
	 *           }}
	 *           baseFontStyle={global.textHtml}
	 *           html={content}
	 *           imagesInitialDimensions={imagesInitialDimensions}
	 *           staticContentMaxWidth={computedWidth}
	 *           alterChildren={alterChildrenHTML(computedWidth)}
	 *           onLinkPress={attemptDeepLink}
	 *           renderers={{
	 *             a: aTagRenderer(computedWidth),
	 *             iframe: iframeRender(referer),
	 *             img: (htmlAttribs, children, convertedCSSStyles, passProps) => {
	 *               return (
	 *                 <AutoSizeImage
	 *                   url={htmlAttribs.src}
	 *                   wrapperStyle={{
	 *                     marginTop: convertedCSSStyles.marginTop,
	 *                     marginBottom: convertedCSSStyles.marginBottom
	 *                   }}
	 *                   style={{
	 *                     ...convertedCSSStyles,
	 *                     paddingVertical: 100
	 *                   }}
	 *                 />
	 *                );
	 *             }
	 *           }}
	 *          />
	 *       )}
	 *     </ReadMore>
	 *     {!!reply.media && (
	 *       <ImageCollection
	 *         item={reply}
	 *         containerStyle={{marginTop: 16}}
	 *         colors={colors}
	 *         global={global}
	 *         t={t}
	 *         toUserBasedOnSettings={() => reply.navigateToProfile()}
	 *         showActionButtons={false}
	 *       />
	 *     )}
	 *     {!!reply.videos && (
	 *       <ImageCollection
	 *         item={reply}
	 *         containerStyle={{marginTop: 16}}
	 *         colors={colors}
	 *         global={global}
	 *         t={t}
	 *         toUserBasedOnSettings={() => reply.navigateToProfile()}
	 *         showActionButtons={false}
	 *       />
	 *     )}
	 *     {!!reply.gif?.preview_url ? (
	 *       <View style={{marginTop: 16}}>
	 *         <GifVideoPlayer
	 *           url={reply.gif?.video_url}
	 *           poster={reply.gif?.preview_url}
	 *           width={computedWidth - 30}
	 *           containerStyle={{backgroundColor: "#F9F9F9"}}
	 *         />
	 *       </View>
	 *     ) : null}
	 *
	 *     {reply?.documents?.length > 0 &&
	 *       reply.documents.map(item => {
	 *         const viewModel: DocumentViewModel = documentToViewModel(item);
	 *
	 *         return (
	 *           <EmbeddedDocumentItem
	 *             {...{
	 *               t,
	 *               colors,
	 *               global,
	 *               token,
	 *               viewModel,
	 *               navigation
	 *             }}
	 *           />
	 *         );
	 *       })}
	 *     </View>
	 * );
	 *
	 * export default ReplyItemContent
	 *
	 * //In custom_code/index.js...
	 *
	 * import ReplyItemContent from "./components/ReplyItemContent"
	 * export const applyCustomCode = externalCodeSetup => {
	 *   externalCodeSetup.topicSingleApi.setReplyItemContent(props => <ReplyItemContent {...props} />)
	 * }
	 *
	 */
	setReplyItemContent = ReplyItemContent => {
		this.ReplyItemContent = ReplyItemContent;
	};

	TopicTitleComponent = null;
	/**
	 * You can use this hook to customize the title of the topic/discussion in the TopicSingleScreen.
	 * @method
	 * @param {TopicTitleComponentProps} TopicTitleComponent
	 * @example
	 * externalCodeSetup.topicSingleApi.setTopicTitleComponent(({
	 *   global,
	 *   topic
	 * }) => <Text style={[global.topicSingleTitle, { marginBottom: 20, color: "red" }]}>
	 *     {topic.title}
	 *   </Text>
	 * )
	 */
	setTopicTitleComponent = TopicTitleComponent => {
		this.TopicTitleComponent = TopicTitleComponent;
	};

	TopicContentComponent = null;

	/**
	 * You can use this hook to customize the content of the topic/discussion in the TopicSingleScreen.
	 * @method
	 * @param {TopicContentComponentProps} TopicContentComponent
	 * @example
	 *
	 * ...
	 *
	 * import HTML from "react-native-render-html";
	 * import ReadMore from "@src/components/ReadMore";
	 * import {
	 *     alterChildrenHTML
	 * } from "@src/utils";
	 * import { aTagRenderer } from "@src/utils/htmlRender";
	 * export const applyCustomCode = (externalCodeSetup: any) => {
	 *
	 *     externalCodeSetup.topicSingleApi.setTopicContentComponent(({
	 *         colors,
	 *         content,
	 *         global,
	 *         t,
	 *         tagsStyles,
	 *         attemptDeepLink,
	 *         computedWidth,
	 *         topic
	 *     }) => {
	 *
	 *         const newContent = `<a href="https://google.com/search?q=${topic.title}"> Press this to google the topic </a>`
	 *
	 *         return <View style={{ marginTop: -4 }}>
	 *             <ReadMore
	 *                 colors={colors}
	 *                 content={content}
	 *                 size={400}
	 *                 t={t}
	 *                 global={global}
	 *                 style={{ marginBottom: 20 }}
	 *             >
	 *                 {content => (
	 *                     <HTML
	 *                         html={content + newContent}
	 *                         tagsStyles={{
	 *                             ...tagsStyles,
	 *                             iframe: {
	 *                                 marginTop: 10,
	 *                                 marginBottom: 10
	 *                             }
	 *                         }}
	 *                         baseFontStyle={global.textHtml}
	 *                         onLinkPress={attemptDeepLink}
	 *                         staticContentMaxWidth={computedWidth}
	 *                         alterChildren={alterChildrenHTML(computedWidth)}
	 *                         renderers={{
	 *                             a: aTagRenderer(computedWidth)
	 *                         }}
	 *                     />
	 *                 )}
	 *             </ReadMore>
	 *         </View>
	 *     })
	 * }
	 */
	setTopicContentComponent = TopicContentComponent => {
		this.TopicContentComponent = TopicContentComponent;
	};

	TopicReplyButton = null;
	/**
	 * You can use this hook to customize the reply button which allows the users to reply to the main content of the topic/discussion.
	 * @method
	 * @param {TopicReplyButtonProps} TopicReplyButton
	 * @example
	 * ...
	 *
	 * import Icon from "@src/components/Icon";
	 * import AppTouchableOpacity from "@src/components/AppTouchableOpacity";
	 * import AuthWrapper from "@src/components/AuthWrapper";
	 * export const applyCustomCode = (externalCodeSetup: any) => {
	 *     externalCodeSetup.topicSingleApi.setTopicReplyButton(({
	 *         t,
	 *         colors,
	 *         global,
	 *         topic,
	 *         topicCloseForUser,
	 *         openClosedDiscussionModal
	 *     }) =>
	 *         <AuthWrapper>
	 *             <AppTouchableOpacity
	 *                 activeOpacity={topicCloseForUser ? 0.5 : 1}
	 *                 style={[global.itemFooter, { opacity: topicCloseForUser ? 0.5 : 1 }]}
	 *                 onPress={
	 *                     topicCloseForUser ? openClosedDiscussionModal : topic.newReply
	 *                 }
	 *                 hitSlop={{ top: 10, right: 20, bottom: 20, left: 20 }}
	 *             >
	 *                 <Icon
	 *                     icon={{fontIconName: "reply", weight: 400}}
	 *                     webIcon={"IconReply"}
	 *                     tintColor={colors.descLightTextColor}
	 *                     style={{
	 *                         width: 14,
	 *                         height: 12
	 *                     }}
	 *                 />
	 *                 <Text
	 *                     style={[global.itemMeta, { marginLeft: 6, color: colors.textColor }]}
	 *                 >
	 *                     {t("topic:reply")}
	 *                 </Text>
	 *             </AppTouchableOpacity>
	 *         </AuthWrapper>
	 *     )
	 * }
	 */
	setTopicReplyButton = TopicReplyButton => {
		this.TopicReplyButton = TopicReplyButton;
	};

	TopicMetadataComponent = null;

	/**
	 * You can use this hook to customize the metadeta component in the TopicSingleScreen which by default, displays the number of members and replies in the discussion.
	 * @method
	 * @param {TopicMetadataComponentProps} TopicMetadataComponent
	 * @example
	 *
	 *  externalCodeSetup.topicSingleApi.setTopicMetadataComponent(({
	 *   global,
	 *   topic
	 *  }) =>
	 *    <View style={[global.itemFooterMeta, {marginLeft: "auto"}]}>
	 *        <Text style={global.itemMeta}>
	 *            {topic.voiceCount} • {topic.replyCount}
	 *        </Text>
	 *    </View>
	 *  )
	 */
	setTopicMetadataComponent = TopicMetadataComponent => {
		this.TopicMetadataComponent = TopicMetadataComponent;
	};
}