/**
* @typedef {Function} TransformTopicViewModelCallback
* @param {TopicViewModel} viewModel
* @param {Object} topic
* @return {Object} - new view model
*/
/**
* @typedef {Object} TopicViewModel
* @property {Number} id Topic id
* @property {String} title Topic title
* @property {String} shortContent Topic short content
* @property {Object} author Details about topic creator
* @property {String} replyCount Number of replies in the topic
* @property {String} voiceCount Number of members replied in the topic
* @property {String} date Date created
* @property {Boolean} canSeeReplies Returns `true` if logged in user can see replies
* @property {Boolean} canReply Returns `true` if logged in user can reply
* @property {String} content HTML string of topic content
* @property {Array} mediaItems Media items in the topic
* @property {String} lastActive Date of the topic's last activity
* @property {String} forumTitle Forum title of the topic's parent
* @property {Function} navigateToForum Function to navigate to topic's forum
* @property {Function} navigateToProfile Function to navigate to topic author's profile
* @property {Function} newReply Function to open new reply screen
* @property {Function} favourite Function to toggle topic as favourite
*/
/**
* @typedef {Object} TopicItemProps
* @property {Boolean} firstItem Returns `true` if data is the first item in the list
* @property {TopicViewModel} topic
* @property {FormatDateFunction} formatDateFunc Function to help format date
* @property {Object} styles App global style and app colors
* @property {Object} actionButtons Topic functions from redux
* @property {TranslationFunction} t
*/
/**
* @typedef {Function} TransformTopicSubFiltersFilterCallback
* @param {Array<string>} filters Available filters: `activity`, `date`, `title`, `popular`
* @return {Array<string>}
*/
/**
* @typedef {Function} TransformTopicsParams
* @param {FetchTopicsParams}
* @return {Object}
*/
/**
* @typedef {Object} FetchTopicsParams
* @see {@link https://www.buddyboss.com/resources/api/#api-Forum_Topics-GetBBPTopics}
* @property {Number} per_page Maximum number of items to be returned in result set.
* @property {Number} page Current page of the collection.
* @property {String} search Limit results to those matching a string.
* @property {String} orderby Sort retrieved topics by parameter. Allowed values: `meta_value`, `date`, `ID`, `author`, `title`, `modified`, `parent`, `rand`, `popular`, `activity`
* @property {String} order Designates ascending or descending order of topics. Allowed values: `asc`, `desc`
* @property {Number} parent Forum ID to retrieve all the topics.
*
*/
/**
* @class
* Topic/Discussion Hooks.
* Instance name: topicsApi
You can use this to customize the default Topic/Discussion option such as rendering a custom topic item in the list and other options.
* @example
* externalCodeSetup.topicsApi.METHOD_NAME
*/
export class TopicsApi {
topicToViewModelFilter = (viewModel, topic) => viewModel;
/**
* You can use it to set the callback function that can change an existing topic's view model object.
* @method
* @param {TransformTopicViewModelCallback} topicToViewModelFilter
* @example
* externalCodeSetup.topicsApi.setTopicToViewModelFilter((props, topic) => {
* return {
* ...props,
* topicUpvotes: 30
* };
* })
*/
setTopicToViewModelFilter = topicToViewModelFilter =>
(this.topicToViewModelFilter = topicToViewModelFilter);
TopicItemComponent = null;
/**
* It renders a custom topic item component in the topic list.
* @method
* @param {?React.ComponentType<TopicItemProps>} TopicItemComponent
* @example <caption> Add more details to Discussion/Topic item </caption>
*
* //In custom_code/components/TopicItem.js
*
* import React from 'react';
* import { View, Text, StyleSheet, Animated } from 'react-native';
* import AppTouchableOpacity from "@src/components/AppTouchableOpacity";
* import {getAvatar} from "@src/utils";
* import AppAvatar from "@src/components/AppAvatar";
* import AuthWrapper from "@src/components/AuthWrapper";
* import ActionSheetButton from "@src/components/ActionButtons/ActionSheetButton";
* import {GUTTER} from "@src/styles/global";
*
* const TopicItem = (props) => {
*
* const {topic, styles, actionButtons, formatDateFunc, t} = props;
*
* const global = styles.global;
* const colors = styles.colors;
*
* let rootStyle;
*
* if (topic.actionStates.sticky) rootStyle = [global.itemSticky];
*
* if (!topic.actionStates.open) rootStyle = [global.itemClosed];
*
*
* const Item = <AppTouchableOpacity onPress={topic.toSingle} style={[rootStyle]}>
* <Animated.View
* style={{
* ...StyleSheet.absoluteFillObject
* }}
* />
* <View
* style={{
* ...global.row,
* flex: 1,
* marginHorizontal: GUTTER
* }}
* >
* <AppAvatar
* size={42}
* name={topic.author.name}
* source={{
* uri: getAvatar(topic.author.avatar, 96)
* }}
* style={{ marginTop: 15, alignSelf: "flex-start" }}
* />
* <View
* style={{
* ...global.bottomBorder,
* ...global.row,
* flex: 1,
* marginLeft: 10
* }}
* >
* <View
* style={[
* {
* flex: 1,
* paddingTop: 15,
* paddingBottom: 14,
* paddingLeft: 0,
* paddingRight: 0
* }
* ]}
* >
* <Text
* style={{
* ...global.itemTitle,
* paddingRight: 40,
* marginBottom: 3
* }}
* numberOfLines={2}
* ellipsizeMode={"tail"}
* >
* {topic.title}
* </Text>
* <Text numberOfLines={1} ellipsizeMode={"tail"}>{topic.shortContent}</Text>
* <View style={{ ...global.row, marginBottom: 5 }}>
* <Text style={global.itemMeta}>{topic.voiceCount}</Text>
* <View style={global.dotSep} />
* <Text style={global.itemMeta}>{topic.replyCount}</Text>
* </View>
* <Text style={{ ...global.textAlt, color: colors.descTextColor }}>
* {t("topics:lastActive", {
* date: formatDateFunc(topic.lastActive)
* })}
* </Text>
* </View>
* <AuthWrapper actionOnGuestLogin={"hide"}>
* <ActionSheetButton
* color={colors.textIconColor}
* object={topic}
* colors={colors}
* actionButtons={actionButtons}
* headerProps={{
* onClick: topic.toSingle,
* title: topic.title,
* description: t("topics:lastActive", {
* date: formatDateFunc(topic.lastActive)
* }),
* avatarSource: {
* uri: getAvatar(topic.author.avatar, 96)
* }
* }}
* global={global}
* t={t}
* />
* </AuthWrapper>
* </View>
* </View>
* </AppTouchableOpacity>
*
* return Item;
* }
*
* export default TopicItem;
*
* //In custom_code/index.js...
*
* ...
*
* import TopicItem from "./components/TopicItem";
* export const applyCustomCode = externalCodeSetup => {
* externalCodeSetup.topicsApi.setTopicItemComponent(props => {
* return <TopicItem {...props} />;
* })
* }
*/
setTopicItemComponent = TopicItemComponent => {
this.TopicItemComponent = TopicItemComponent;
};
subFiltersFilter = filters => filters;
/**
* You can use this to set the subfilter function to rearrange the order of the filters in the Discussion screen.
* For example, you can rearrange the labels such as "Date created", "Last Active" and more on the discussions screen under the search bar.
* @method
* @param {TransformTopicSubFiltersFilterCallback} subFiltersFilter
* @example <caption>User would like to use "Date Created" filter only</caption>
* externalCodeSetup.topicsApi.setSubFiltersFilter((filters) => {
* return ["date"]; //available filters include "activity", "date", "title", "popular"
* })
*/
setSubFiltersFilter = subFiltersFilter => {
this.subFiltersFilter = subFiltersFilter;
};
TopicItemHeader = null;
/**
* You can use this to customize the author name and date in the topic single screen.
* @deprecated Please use the equivalent hook: topicSingleApi.setTopicItemHeader()
* @method
* @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.topicsApi.setTopicItemHeader( (props) => <TopicItemHeader {...props} />);
* }
*/
setTopicItemHeader = TopicItemHeader => {
this.TopicItemHeader = TopicItemHeader;
};
fetchParamsFilter = params => params;
/**
* It overrides the parameters that are used to fetch topics in the Topics screen so that you can make it as customizable as possible when calling its API.
* @method
* @param {TransformTopicsParams} fetchParamsFilter
*
* @example <caption> Create a custom filter in topics screen </caption>
*
* //In custom_code/components/TopicsFiltersCustom.js...
*
* import React, { useState } from "react";
* import { TextInput, View, Button, Text, Switch } from 'react-native'
* import { useDispatch } from "react-redux";
* import { topicsLoadRequest } from "@src/actions/topics";
* import { getExternalCodeSetup } from "@src/externalCode/externalRepo";
* import withGlobalStyles from "@src/components/hocs/withGlobalStyles";
*
* const hook = getExternalCodeSetup().topicsApi;
* const screenName = "topics";
*
* getExternalCodeSetup().indexScreenApiHooks.setHeaderHeight((defaultHeaderHeight, filterType, navigation) => {
*
* if (filterType === "topics"){
* return 300
* }
*
* return defaultHeaderHeight;
*
* });
*
* const filter = "all";
* const subfilters = {
* orderby: "activity", //"meta_value", "date", "ID", "author", "title", "modified", "parent", "rand", "popular", "activity"
* order: "desc"
* }
*
* const refresh = true; //Set to true to refresh list
* const searchTerm = ""
*
* const TopicsFiltersCustom = (props) => {
*
* const { navigation, route, colors } = props;
*
* const dispatch = useDispatch();
*
* //If showing the matched screen, show custom filter before displaying list component
* if (route?.params?.item?.object === screenName) {
*
* const [tag, setTag] = useState(false);
*
* const handleSubmit = () => {
*
* //Set custom parameters before fetching
* hook.setFetchParamsFilter((props) => {
*
* //You can add more parameters such as "subject", "keyword" etc...
* return {
* ...props,
* tag
* }
* })
*
* //Dispatch redux action to call api using customized filters
* dispatch(topicsLoadRequest(filter, subfilters, refresh, searchTerm));
*
* }
*
* return <View style={{ backgroundColor: colors.whiteColor, alignItems: "center", justifyContent: "center" }}>
*
* <TextInput
* style={{paddingHorizontal: 20, marginTop: 10, fontSize: 20}}
* autoFocus
* value={tag}
* onChangeText={tag => setTag(tag)}
* placeholder="Search for tag..."
* />
* <Button
* onPress={() => handleSubmit()}
* title="Filter"
* />
* </View>
* }
*
* return null;
*
* }
*
* export default withGlobalStyles(TopicsFiltersCustom);
*
* //In custom_code/index.js...
*
* ...
*
* import TopicsFiltersCustom from "./components/TopicsFiltersCustom";
* export const applyCustomCode = externalCodeSetup => {
* externalCodeSetup.filterScreenApiHooks.setAfterFilterComponent(TopicsFiltersCustom);
* }
*/
setFetchParamsFilter = fetchParamsFilter => {
this.fetchParamsFilter = fetchParamsFilter;
};
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.
* @deprecated Please use the equivalent hook: topicSingleApi.setReplyItemAvatar()
* @method
* @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.topicsApi.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.
* @deprecated Please use the equivalent hook: topicSingleApi.setReplyItemHeader()
* @method
* @example <caption> Change display date format </caption>
*
* ...
* export const applyCustomCode = externalCodeSetup => {
* externalCodeSetup.topicsApi.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.
* @deprecated Please use the equivalent hook: topicSingleApi.setReplyItemContent()
* @method
* @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";
*
* import {
* documentToViewModel
* } from "@src/utils";
*
* const ReplyItemContent = ({
* content,
* readMoreContentSize,
* attachmentMarginTop,
* formatTextForDisplay,
* filterContentCss,
* reply,
* colors,
* t,
* global,
* tagsStyles,
* imagesInitialDimensions,
* computedWidth,
* referer,
* alterChildrenHTML,
* attemptDeepLink,
* aTagRenderer,
* iframeRender
* }) => (
* <View style={{flex: 1, marginTop: 6}}>
* <ReadMore
* content={content}
* size={readMoreContentSize}
* colors={colors}
* t={t}
* global={global}
* >
* {content => (
* <HTML
* tagsStyles={{
* ...tagsStyles,
* div: {
* ...tagsStyles.p,
* },
* 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: attachmentMarginTop}}
* colors={colors}
* global={global}
* t={t}
* toUserBasedOnSettings={() => reply.navigateToProfile()}
* showActionButtons={false}
* />
* )}
* {!!reply.videos && (
* <ImageCollection
* item={reply}
* containerStyle={{marginTop: attachmentMarginTop}}
* colors={colors}
* global={global}
* t={t}
* toUserBasedOnSettings={() => reply.navigateToProfile()}
* showActionButtons={false}
* />
* )}
* {!!reply.gif?.preview_url ? (
* <View style={{marginTop: attachmentMarginTop}}>
* <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 = 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.topicsApi.setReplyItemContent(props => <ReplyItemContent {...props} />)
* }
*
*/
setReplyItemContent = ReplyItemContent => {
this.ReplyItemContent = ReplyItemContent;
};
}
Source