functions.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. import { Platform } from 'react-native'
  2. import { fetcher } from 'app/lib/fetcher'
  3. import { updateRouteDataForConnection, appSetting } from 'app/lib/util'
  4. import { normalizeTiers, responsiveClasses } from 'app/lib/responsive-classes'
  5. import { FLUSH_LIST_MODULES } from 'app/components/units/flush-list'
  6. import { components } from 'app/components/registry';
  7. import { settingsLayout } from 'app/settings/layout'
  8. import i18n from 'i18next'
  9. export function ProfileDisplayName(title) {
  10. return title;
  11. }
  12. export function CaptionForFileInput(props) {
  13. return 'Drag & Drop or browse files...';
  14. }
  15. export function ParseHtmlClasses(className, tag) {
  16. return className;
  17. }
  18. export function getFriendsCounter(currentUser) {
  19. return currentUser?.counters?.bx_persons_friend_requests
  20. }
  21. export function getModalPostTitle(authorData, t) {
  22. return !!authorData?.content?.[0]?.data?.author_data?.display_name ? `${authorData?.content?.[0]?.data?.author_data?.display_name}'s ${t('item_' + authorData?.module)}` : ' '
  23. }
  24. export function layoutForList(endpoint, unitMode = '') {
  25. const moduleName = typeof endpoint === 'string' ? endpoint : endpoint?.module;
  26. const requestUrl = typeof endpoint === 'string' ? endpoint : endpoint?.request_url;
  27. const paramsRequestUrl = endpoint?.params?.request_url;
  28. const unitName = typeof endpoint === 'string' ? '' : endpoint?.unit;
  29. if (unitMode == 'search'){
  30. if (moduleName == 'bx_timeline' || requestUrl?.includes('_cmts')){
  31. return 'w-full @list-lg/list:w-1/2 ';
  32. }
  33. }
  34. if (!endpoint || requestUrl?.includes('bx_timeline') || paramsRequestUrl?.includes('bx_timeline') || unitName == "notifications")
  35. return 'w-full';
  36. // Forum rows: 2px apart on phones; 16px apart in a narrow list on wider screens (a side
  37. // column); from 40rem each card pads 8px instead.
  38. if (moduleName == 'bx_forum' || requestUrl?.includes('r=bx_forum'))
  39. return 'w-full mb-0.5 sm:mb-4 @list-sm/list:p-2 @list-sm/list:m-0';
  40. if (moduleName == 'bx_groups' || requestUrl?.includes('r=bx_groups'))
  41. return 'w-full @list-sm/list:w-1/2 @list-md/list:w-1/3 @list-lg/list:w-1/4 p-1.5';
  42. if (moduleName == 'bx_videos')
  43. return 'w-full @list-sm/list:w-1/2 @list-lg/list:w-1/3 @list-xl/list:w-1/4 px-3 pt-3 @list-sm/list:p-2 ';
  44. if (moduleName == 'bx_posts')
  45. return ' w-full px-3 pt-3 @list-sm/list:w-1/2 @list-md/list:w-1/3 @list-lg/list:w-1/4 @list-xl/list:w-1/5 @list-sm/list:p-2 @list-sm/list:m-0 ';
  46. if (moduleName == 'bx_persons')
  47. return 'w-full @list-sm/list:w-1/2 @list-md/list:w-1/3 @list-lg/list:w-1/4 @list-xl/list:w-1/5 @list-sm/list:p-2 @list-md/list:p-2 @list-sm/list:m-0 ';
  48. return 'w-full @list-sm/list:w-1/2 @list-md/list:w-1/3 @list-lg/list:w-1/4 @list-xl/list:w-1/4 @list-sm/list:p-2 @list-md/list:p-2 ';
  49. }
  50. /**
  51. * Card chrome for a browse list, from its block's App config in UNA, e.g.
  52. * {"designbox": {"bg": ["tablet", "desktop"]}}: the theme's block background,
  53. * radius (`rounded` tiers) and space around the card, on the tiers listed.
  54. * Web only: a native list scrolls under the overlaid page header and pads for
  55. * it inside, so a card on its content would reach up under the header.
  56. */
  57. function listCardClasses(designbox) {
  58. if (Platform.OS !== 'web') return '';
  59. const bg = normalizeTiers(designbox?.bg);
  60. if (!bg) return '';
  61. const rounded = responsiveClasses('rounded', designbox?.rounded);
  62. if (bg === true) return [appSetting('theme', 'blocks', 'u-block-bg'), rounded, 'my-4'].join(' ');
  63. return [responsiveClasses('bg', bg), rounded, responsiveClasses('card-my', bg)].join(' ');
  64. }
  65. export function paddingForList(endpoint) {
  66. const card = listCardClasses(endpoint?.designbox);
  67. // Timeline uses no padding (full width items)
  68. if (!endpoint || endpoint?.request_url?.includes('bx_timeline') || endpoint?.params?.request_url?.includes('bx_timeline'))
  69. return card;
  70. // Notification rows: on phones edge-to-edge card rows, each 1px below the
  71. // one above (the first below the header band). From sm they inset themselves 8px on the sides and sit 2px apart:
  72. // 6px more at the top and bottom puts the first and last rows 8px from the
  73. // list's edges too (below the subtabs, and inside the card where it shows).
  74. if (endpoint?.unit == "notifications")
  75. return ['sm:py-1.5', card].join(' ').trim();
  76. // Groups and grid layouts get padding for better spacing
  77. if (endpoint?.module == 'bx_groups' || endpoint?.request_url?.includes('r=bx_groups'))
  78. return ['m-1.5 @list-md/list:m-1.5', card].join(' ').trim();
  79. // Profile lists run flush rows on mobile: edge-to-edge card rows, each 1px
  80. // below the one above, like the Notifications and Messages rows. The
  81. // sm+ grid spaces its cards (`padding_content`: container queries from
  82. // list-sm, 40rem, so never on a phone).
  83. if (FLUSH_LIST_MODULES.has(endpoint?.module))
  84. return [settingsLayout.layout.padding_content, card].join(' ').trim();
  85. // Default padding for grid-based content lists
  86. return [settingsLayout.layout.padding_content, card].join(' ').trim();
  87. }
  88. export function getSkeletonByEndPoint(currentRoute) {
  89. return null;
  90. }
  91. export function updateRouteDataForConnections(
  92. currentRoute,
  93. layoutData,
  94. routes,
  95. index,
  96. setRoutes
  97. ) {
  98. updateRouteDataForConnection(
  99. 'system/browse_subscriptions',
  100. ['remove'],
  101. 'sys_profiles_subscriptions',
  102. currentRoute,
  103. layoutData,
  104. routes,
  105. index,
  106. setRoutes
  107. )
  108. updateRouteDataForConnection(
  109. 'system/browse_friends',
  110. ['remove'],
  111. 'sys_profiles_friends',
  112. currentRoute,
  113. layoutData,
  114. routes,
  115. index,
  116. setRoutes
  117. )
  118. updateRouteDataForConnection(
  119. 'system/browse_friend_requested',
  120. ['remove'],
  121. 'sys_profiles_friends',
  122. currentRoute,
  123. layoutData,
  124. routes,
  125. index,
  126. setRoutes
  127. )
  128. updateRouteDataForConnection(
  129. 'system/browse_friend_requests',
  130. ['add'],
  131. 'sys_profiles_friends',
  132. currentRoute,
  133. layoutData,
  134. routes,
  135. index,
  136. setRoutes
  137. )
  138. updateRouteDataForConnection(
  139. 'system/browse_recommendations_subscriptions',
  140. ['add', 'ignore'],
  141. 'sys_subscriptions',
  142. currentRoute,
  143. layoutData,
  144. routes,
  145. index,
  146. setRoutes
  147. )
  148. updateRouteDataForConnection(
  149. 'system/browse_recommendations_friends',
  150. ['add', 'ignore'],
  151. 'sys_friends',
  152. currentRoute,
  153. layoutData,
  154. routes,
  155. index,
  156. setRoutes
  157. )
  158. }
  159. export function checkActionsOnConnectionsChanged(oData) {
  160. return {update_user:true, reload_page:true};
  161. }
  162. export function getUnitMenuItems(unitType, data, handleClick, t, moduleName) {
  163. let primaryMenuItem = null;
  164. let secondaryMenuItem = null;
  165. let deleteMenuItem = null;
  166. const MenuItem = components['menu-item']['unit']
  167. if (data?.meta) {
  168. let sPrimary = "";
  169. let sSecondary = "";
  170. let sDelete = "";
  171. if (moduleName == 'bx_persons' || moduleName == 'bx_organizations' || moduleName == 'system') {
  172. switch (unitType) {
  173. case "person_friends":
  174. primaryMenuItem = {
  175. title: t("Message"),
  176. icon: "MessageCircleMore",
  177. onPress: async (event) => {
  178. event.preventDefault();
  179. const request_url = `/api.php?r=bx_messenger/get_convo_url/Services&params[]=${JSON.stringify({ recipient: data.author_data.id })}`;
  180. const sResponse = await fetcher(request_url);
  181. handleClick(event, sResponse.data);
  182. },
  183. };
  184. break;
  185. case "person_friends_recommendations":
  186. sPrimary = "befriend";
  187. sDelete = "ignore-befriend";
  188. break;
  189. case "browse_friend_requests":
  190. sPrimary = "befriend";
  191. sSecondary = "unfriend";
  192. break;
  193. case "person_friends_suggestion":
  194. sPrimary = "befriend";
  195. bMenuItemsMoreShow = false;
  196. break;
  197. case "person_friend_requested":
  198. sPrimary = "unfriend";
  199. break;
  200. case "person_following_recommendations":
  201. sPrimary = "subscribe";
  202. sDelete = "ignore-subscribe";
  203. break;
  204. case "person_followers":
  205. sPrimary = "subscribe";
  206. sSecondary = "unsubscribe";
  207. break;
  208. case "person_following":
  209. sPrimary = "unsubscribe";
  210. break;
  211. }
  212. }
  213. else {
  214. sPrimary = "join";
  215. sSecondary = "leave";
  216. if (unitType == 'context_recommendations')
  217. sDelete = "ignore-join";
  218. if (moduleName == "bx_channels") {
  219. sPrimary = "subscribe";
  220. sSecondary = "unsubscribe";
  221. }
  222. }
  223. if (!primaryMenuItem) {
  224. primaryMenuItem = data.meta.items.find(item => item.name === sPrimary);
  225. }
  226. if (!secondaryMenuItem) {
  227. secondaryMenuItem = data.meta.items.find(item => item.name === sSecondary);
  228. }
  229. if (!deleteMenuItem) {
  230. deleteMenuItem = data.meta.items.find(item => item.name === sDelete);
  231. }
  232. if (!primaryMenuItem && secondaryMenuItem) {
  233. primaryMenuItem = secondaryMenuItem;
  234. secondaryMenuItem = null;
  235. }
  236. if (!primaryMenuItem) {
  237. primaryMenuItem = {
  238. title: i18n.t('View'),
  239. onPress: (event) => handleClick(event, data.url),
  240. };
  241. }
  242. if (primaryMenuItem) {
  243. primaryMenuItem = <MenuItem menuItem={primaryMenuItem} isPrimary={true}/>;
  244. }
  245. secondaryMenuItem = secondaryMenuItem
  246. ? <MenuItem menuItem={secondaryMenuItem} isPrimary={false}/>
  247. : null;
  248. if (deleteMenuItem) {
  249. deleteMenuItem = {
  250. ...deleteMenuItem,
  251. data: {
  252. ...deleteMenuItem.data,
  253. title: '',
  254. params: {
  255. ...deleteMenuItem.data.params,
  256. button_full_width: false,
  257. button_rounded: true,
  258. only_icon: true,
  259. button_style: 'glass',
  260. button_border_shape: 'circle',
  261. button_size: 'small',
  262. },
  263. },
  264. };
  265. deleteMenuItem = <MenuItem menuItem={deleteMenuItem} isPrimary={false}/>
  266. }
  267. }
  268. return {
  269. primaryMenuItem,
  270. secondaryMenuItem,
  271. deleteMenuItem
  272. };
  273. }