GroupPage.vue 71 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190
  1. <template>
  2. <div class="group-feed-component">
  3. <div v-if="!initalLoad">
  4. <p class="text-center mt-5 pt-5 font-weight-bold">Loading...</p>
  5. </div>
  6. <template v-else>
  7. <div class="row border-bottom m-0 p-0">
  8. <sidebar />
  9. <div class="col-12 col-md-9 px-md-0">
  10. <div class="bg-white mb-3 border-bottom">
  11. <div class="">
  12. <group-banner :group="group" />
  13. <div class="col-12 group-feed-component-header px-3 px-md-5">
  14. <div class="media align-items-end">
  15. <img
  16. v-if="group.metadata && group.metadata.hasOwnProperty('avatar')"
  17. :src="group.metadata.avatar.url"
  18. width="169"
  19. height="169"
  20. class="bg-white mx-4 rounded-circle border shadow p-1"
  21. style="object-fit: cover;"
  22. :style="{ 'margin-top': group.metadata && group.metadata.hasOwnProperty('header') && group.metadata.header.url ? '-100px' : '0' }">
  23. <div class="media-body">
  24. <h3 class="d-flex align-items-start">
  25. <span>{{ group.name.slice(0,118) }}</span>
  26. <sup v-if="group.verified" class="fa-stack ml-n2" title="Verified Group" data-toggle="tooltip">
  27. <i class="fas fa-circle fa-stack-1x fa-lg" style="color: #22a7f0cc;font-size:18px"></i>
  28. <i class="fas fa-check fa-stack-1x text-white" style="font-size:10px"></i>
  29. </sup>
  30. </h3>
  31. <p class="text-muted mb-0" style="font-weight: 300;">
  32. <span>
  33. <i class="fas fa-globe mr-1"></i>
  34. {{ group.membership == 'all' ? 'Public Group' : 'Private Group'}}
  35. </span>
  36. <span class="mx-2">
  37. ·
  38. </span>
  39. <span>{{ group.member_count == 1 ? group.member_count + ' Member' : group.member_count + ' Members' }}</span>
  40. <span class="mx-2">
  41. ·
  42. </span>
  43. <span v-if="group.local" class="rounded member-label">Local</span>
  44. <span v-else class="rounded remote-label">Remote</span>
  45. <span v-if="group.self && group.self.hasOwnProperty('role') && group.self.role">
  46. <span class="mx-2">
  47. ·
  48. </span>
  49. <span class="rounded member-label">{{ group.self.role }}</span>
  50. </span>
  51. </p>
  52. </div>
  53. </div>
  54. <div>
  55. <button v-if="!isMember && !group.self.is_requested" class="btn btn-primary cta-btn font-weight-bold" @click="joinGroup" :disabled="requestingMembership">
  56. <span v-if="!requestingMembership">
  57. {{ group.membership == 'all' ? 'Join' : 'Request Membership' }}
  58. </span>
  59. <div
  60. v-else
  61. class="spinner-border spinner-border-sm"
  62. role="status">
  63. <span class="sr-only">Loading...</span>
  64. </div>
  65. </button>
  66. <button
  67. v-else-if="!isMember && group.self.is_requested"
  68. class="btn btn-light border cta-btn font-weight-bold"
  69. @click.prevent="cancelJoinRequest">
  70. <i class="fas fa-user-clock mr-1"></i> Requested to Join
  71. </button>
  72. <button
  73. v-else-if="!isAdmin && isMember && !group.self.is_requested"
  74. type="button"
  75. class="btn btn-light border cta-btn font-weight-bold"
  76. @click.prevent="leaveGroup">
  77. <i class="fas sign-out-alt mr-1"></i> Leave Group
  78. </button>
  79. <!-- <div v-if="isAdmin">
  80. <a
  81. class="btn btn-light border cta-btn font-weight-bold"
  82. :href="group.url + '/settings'">
  83. Settings
  84. </a>
  85. </div> -->
  86. </div>
  87. </div>
  88. <div class="col-12 border-top group-feed-component-menu px-5">
  89. <ul class="nav font-weight-bold group-feed-component-menu-nav">
  90. <li class="nav-item">
  91. <a :class="{active: tab == 'about'}" class="nav-link" href="#" @click.prevent="switchTab('about')">About</a>
  92. </li>
  93. <li class="nav-item">
  94. <a :class="{active: tab == 'feed'}" class="nav-link" href="#" @click.prevent="switchTab('feed')">Feed</a>
  95. </li>
  96. <li v-if="group.self.is_member" class="nav-item">
  97. <a :class="{active: tab == 'topics'}" class="nav-link" href="#" @click.prevent="switchTab('topics')">Topics</a>
  98. </li>
  99. <li v-if="group.self.is_member" class="nav-item">
  100. <a :class="{active: tab == 'members'}" class="nav-link" href="#" @click.prevent="switchTab('members')">
  101. Members
  102. <span v-if="group.self.is_member && isAdmin && atabs.request_count" class="badge badge-danger rounded-pill ml-2" style="height: 20px;padding:4px 8px;font-size: 11px;">{{atabs.request_count}}</span>
  103. </a>
  104. </li>
  105. <!-- <li v-if="group.self.is_member" class="nav-item">
  106. <a :class="{active: tab == 'events'}" class="nav-link" href="#" @click.prevent="switchTab('events')">Events</a>
  107. </li> -->
  108. <li v-if="group.self.is_member" class="nav-item">
  109. <a :class="{active: tab == 'media'}" class="nav-link" href="#" @click.prevent="switchTab('media')">Media</a>
  110. </li>
  111. <!-- <li v-if="group.self.is_member" class="nav-item">
  112. <a class="nav-link" href="#">Popular</a>
  113. </li> -->
  114. <!-- <li v-if="group.self.is_member" class="nav-item">
  115. <a :class="{active: tab == 'polls'}" class="nav-link" href="#" @click.prevent="switchTab('polls')">Polls</a>
  116. </li> -->
  117. <!-- <li v-if="group.self.is_member && isAdmin" class="nav-item">
  118. <a class="nav-link" href="#">Messages</a>
  119. </li> -->
  120. <!-- <li v-if="group.self.is_member && isAdmin" class="nav-item">
  121. <a :class="{active: tab == 'insights'}" class="nav-link" href="#" @click.prevent="switchTab('insights')">Insights</a>
  122. </li> -->
  123. <!-- <li v-if="group.self.is_member && isAdmin && group.membership != 'all'" class="nav-item">
  124. <a :class="{active: tab == 'requests'}" class="nav-link" href="#" @click.prevent="switchTab('requests')">
  125. <span class="mr-2">
  126. <i class="far fa-user-plus mr-1"></i>
  127. Requests
  128. </span>
  129. <span v-if="atabs.request_count" class="badge badge-danger rounded-pill" style="height: 20px;padding:4px 8px;font-size: 11px;">{{atabs.request_count}}</span>
  130. <span v-if="atabs.request_count" class="badge badge-danger rounded-pill" style="height: 20px;padding:4px 8px;font-size: 11px;">99+</span>
  131. </a>
  132. </li> -->
  133. <li v-if="group.self.is_member && isAdmin" class="nav-item">
  134. <a :class="{active: tab == 'moderation'}" class="nav-link d-flex align-items-top" href="#" @click.prevent="switchTab('moderation')">
  135. <span class="mr-2">Moderation</span>
  136. <span v-if="atabs.moderation_count" class="badge badge-danger rounded-pill" style="height: 20px;padding:4px 8px;font-size: 11px;">{{atabs.moderation_count}}</span>
  137. </a>
  138. </li>
  139. </ul>
  140. <div>
  141. <button
  142. v-if="group.self.is_member"
  143. class="btn btn-light btn-sm border px-3 rounded-pill mr-2"
  144. @click="showSearchModal">
  145. <i class="far fa-search"></i>
  146. </button>
  147. <div class="dropdown d-inline">
  148. <button class="btn btn-light btn-sm border px-3 rounded-pill dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
  149. <i class="far fa-cog"></i>
  150. </button>
  151. <div class="dropdown-menu dropdown-menu-right">
  152. <a class="dropdown-item" href="#" @click.prevent="copyLink">
  153. Copy Group Link
  154. </a>
  155. <a class="dropdown-item" href="#" @click.prevent="showInviteModal">
  156. Invite friends
  157. </a>
  158. <a v-if="!isAdmin" class="dropdown-item" href="#" @click.prevent="reportGroup">
  159. Report Group
  160. </a>
  161. <a v-if="isAdmin" class="dropdown-item" :href="group.url + '/settings'">
  162. Settings
  163. </a>
  164. </div>
  165. </div>
  166. </div>
  167. </div>
  168. </div>
  169. </div>
  170. <div class="container-xl group-feed-component-body">
  171. <keep-alive>
  172. <div v-if="tab == 'feed'" class="row mb-5">
  173. <div v-if="!permalinkMode" class="col-12 col-md-7 mt-3">
  174. <div v-if="group.self.is_member">
  175. <!-- <div class="card card-body border my-3 shadow-sm rounded-lg">
  176. <div class="media align-items-center">
  177. <img :src="profile.avatar" class="rounded-circle border mr-3" width="42px" height="42px">
  178. <div class="media-body">
  179. <div class="reply-form form-group mb-0">
  180. <input class="form-control form-control-lg rounded-pill bg-light border-0" placeholder="Write something..." v-model="composeText">
  181. </div>
  182. </div>
  183. </div>
  184. <p v-if="composeText && composeText.length > 1" class="mb-0">
  185. <button class="btn btn-primary font-weight-bold float-right mt-3" @click="newPost()">Post</button>
  186. </p>
  187. </div> -->
  188. <group-compose
  189. v-if="initalLoad"
  190. :profile="profile"
  191. :group-id="groupId"
  192. v-on:new-status="pushNewStatus" />
  193. <div v-if="feed.length == 0" class="mt-3">
  194. <div class="card card-body shadow-none border d-flex align-items-center justify-content-center" style="height: 200px;">
  195. <p class="font-weight-bold mb-0">No posts yet!</p>
  196. </div>
  197. </div>
  198. <div v-else class="group-timeline">
  199. <p class="font-weight-bold mb-1">Recent Posts</p>
  200. <!-- <div class="status-card-component shadow-sm mb-3 rounded-lg">
  201. <div class="card shadow-none border rounded-0">
  202. <div class="card-body pb-0">
  203. <div class="media">
  204. <img src="https://pixelfed.test/storage/avatars/321493203255693312/5a6nqo.jpg?v=2" width="42px" height="42px" onerror="this.onerror=null;this.src='/storage/avatars/default.png?v=2'" alt="avatar" class="rounded-circle box-shadow mr-2">
  205. <div class="media-body">
  206. <div class="pl-2 d-flex align-items-top">
  207. <div>
  208. <p class="mb-0">
  209. <a href="https://pixelfed.test/dansup" class="username font-weight-bold text-dark text-decoration-none text-break">
  210. dansup
  211. </a>
  212. </p>
  213. <p class="mb-0">
  214. <a href="https://pixelfed.test/groups/328821658771132416/329186991407239168" class="font-weight-light text-muted small">13h</a>
  215. <span class="text-lighter" style="padding-left: 2px; padding-right: 2px;">·</span>
  216. <span class="text-muted small">
  217. <i class="fas fa-globe"></i>
  218. </span>
  219. </p>
  220. </div>
  221. <span class="text-right" style="flex-grow: 1;">
  222. <button type="button" class="btn btn-link text-dark py-0">
  223. <span class="fas fa-ellipsis-h text-lighter"></span>
  224. <span class="sr-only">Post Menu</span>
  225. </button>
  226. </span>
  227. </div>
  228. </div>
  229. </div>
  230. <div>
  231. <div>
  232. <div class="">
  233. <p class="pt-2 text-break" style="font-size: 15px;">
  234. Made some improvements!
  235. </p>
  236. <div class="my-3 row px-0 mx-0 card card-body my-0 py-0 border shadow-none">
  237. <img src="https://opengraph.githubassets.com/f66d0f7bf17df4a45382b83c1ffde2f25e3d700f9d87ab8c9ec2029c3a1e16b6/pixelfed/pixelfed/pull/2865" class="img-fluid">
  238. <div class="bg-light px-3 pt-2 pb-3">
  239. <p class="text-muted mb-0 small">GITHUB.COM</p>
  240. <p class="mb-0" style="font-size: 16px;font-weight:500;">Update LikeController, add UndoLikePipeline and federate Undo Like ac… by dansup · Pull Request #2865 · pixelfed/pixelfed</p>
  241. <p class="mb-0 text-muted" style="font-size:14px;line-height:15px;">…tivities</p>
  242. </div>
  243. </div>
  244. <div class="border-top my-0">
  245. <div class="d-flex justify-content-between py-2 px-4">
  246. <button class="btn btn-link py-0 text-decoration-none text-muted">
  247. <i class="far fa-heart mr-1"></i>
  248. Like
  249. </button>
  250. <div>
  251. <i class="far fa-comment cursor-pointer text-muted mr-1"></i>
  252. Comment
  253. </div>
  254. <div>
  255. <i class="fas fa-external-link-alt cursor-pointer text-muted mr-1"></i>
  256. Share
  257. </div>
  258. </div>
  259. </div>
  260. </div>
  261. </div>
  262. </div>
  263. </div>
  264. </div>
  265. </div> -->
  266. <!-- OGP -->
  267. <!-- <div class="status-card-component shadow-sm mb-3 rounded-lg">
  268. <div class="card shadow-none border rounded-0">
  269. <div class="card-body pb-0">
  270. <div class="media">
  271. <img src="https://pixelfed.test/storage/avatars/321493203255693312/5a6nqo.jpg?v=2" width="42px" height="42px" onerror="this.onerror=null;this.src='/storage/avatars/default.png?v=2'" alt="avatar" class="rounded-circle box-shadow mr-2">
  272. <div class="media-body">
  273. <div class="pl-2 d-flex align-items-top">
  274. <div>
  275. <p class="mb-0">
  276. <a href="https://pixelfed.test/dansup" class="username font-weight-bold text-dark text-decoration-none text-break">
  277. dansup
  278. </a>
  279. </p>
  280. <p class="mb-0">
  281. <a href="https://pixelfed.test/groups/328821658771132416/329186991407239168" class="font-weight-light text-muted small">13h</a>
  282. <span class="text-lighter" style="padding-left: 2px; padding-right: 2px;">·</span>
  283. <span class="text-muted small">
  284. <i class="fas fa-globe"></i>
  285. </span>
  286. </p>
  287. </div>
  288. <span class="text-right" style="flex-grow: 1;">
  289. <button type="button" class="btn btn-link text-dark py-0">
  290. <span class="fas fa-ellipsis-h text-lighter"></span>
  291. <span class="sr-only">Post Menu</span>
  292. </button>
  293. </span>
  294. </div>
  295. </div>
  296. </div>
  297. <div>
  298. <div>
  299. <div class="">
  300. <div class="my-3 row px-0 mx-0 card card-body my-0 py-0 border shadow-none">
  301. <img src="https://www.ctvnews.ca/polopoly_fs/1.5533318.1628281952!/httpImage/image.jpg_gen/derivatives/landscape_620/image.jpg" class="img-fluid">
  302. <div class="bg-light px-3 pt-2 pb-3">
  303. <p class="text-muted mb-0 small">CTVNEWS.CA</p>
  304. <p class="mb-0" style="font-size: 16px;font-weight:500;">No charges against Alberta man who fatally shot home intruder: RCMP</p>
  305. <p class="mb-0 text-muted" style="font-size:14px;line-height:15px;">No charges will be laid against an Alberta man who shot and killed an intruder after being beaten with a baseball bat, RCMP announced Friday.</p>
  306. </div>
  307. </div>
  308. <div class="border-top my-0">
  309. <div class="d-flex justify-content-between py-2 px-4">
  310. <button class="btn btn-link py-0 text-decoration-none text-muted">
  311. <i class="far fa-heart mr-1"></i>
  312. Like
  313. </button>
  314. <div>
  315. <i class="far fa-comment cursor-pointer text-muted mr-1"></i>
  316. Comment
  317. </div>
  318. <div>
  319. <i class="fas fa-external-link-alt cursor-pointer text-muted mr-1"></i>
  320. Share
  321. </div>
  322. </div>
  323. </div>
  324. </div>
  325. </div>
  326. </div>
  327. </div>
  328. </div>
  329. </div> -->
  330. <!-- SOUNDCLOUD -->
  331. <!-- <div class="status-card-component shadow-sm mb-3 rounded-lg">
  332. <div class="card shadow-none border rounded-0">
  333. <div class="card-body pb-0">
  334. <div class="media">
  335. <img src="https://pixelfed.test/storage/avatars/321493203255693312/5a6nqo.jpg?v=2" width="42px" height="42px" onerror="this.onerror=null;this.src='/storage/avatars/default.png?v=2'" alt="avatar" class="rounded-circle box-shadow mr-2">
  336. <div class="media-body">
  337. <div class="pl-2 d-flex align-items-top">
  338. <div>
  339. <p class="mb-0">
  340. <a href="https://pixelfed.test/dansup" class="username font-weight-bold text-dark text-decoration-none text-break">
  341. dansup
  342. </a>
  343. </p>
  344. <p class="mb-0">
  345. <a href="https://pixelfed.test/groups/328821658771132416/329186991407239168" class="font-weight-light text-muted small">13h</a>
  346. <span class="text-lighter" style="padding-left: 2px; padding-right: 2px;">·</span>
  347. <span class="text-muted small">
  348. <i class="fas fa-globe"></i>
  349. </span>
  350. </p>
  351. </div>
  352. <span class="text-right" style="flex-grow: 1;">
  353. <button type="button" class="btn btn-link text-dark py-0">
  354. <span class="fas fa-ellipsis-h text-lighter"></span>
  355. <span class="sr-only">Post Menu</span>
  356. </button>
  357. </span>
  358. </div>
  359. </div>
  360. </div>
  361. <div>
  362. <div>
  363. <div class="">
  364. <p class="pt-2 text-break" style="font-size: 15px;">
  365. What does everyone think??
  366. </p>
  367. <div class="my-3 row p-0 m-0">
  368. <iframe width="100%" height="166" scrolling="no" frameborder="no" allow="autoplay" src="https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/34019569&color=0066cc"></iframe><div style="font-size: 10px; color: #cccccc;line-break: anywhere;word-break: normal;overflow: hidden;white-space: nowrap;text-overflow: ellipsis; font-family: Interstate,Lucida Grande,Lucida Sans Unicode,Lucida Sans,Garuda,Verdana,Tahoma,sans-serif;font-weight: 100;"><a href="https://soundcloud.com/the-bugle" title="The Bugle" target="_blank" style="color: #cccccc; text-decoration: none;">The Bugle</a> · <a href="https://soundcloud.com/the-bugle/bugle-179-playas-gon-play" title="Bugle 179 - Playas gon play" target="_blank" style="color: #cccccc; text-decoration: none;">Bugle 179 - Playas gon play</a></div>
  369. </div>
  370. <div class="border-top my-0">
  371. <div class="d-flex justify-content-between py-2 px-4">
  372. <button class="btn btn-link py-0 text-decoration-none text-muted">
  373. <i class="far fa-heart mr-1"></i>
  374. Like
  375. </button>
  376. <div>
  377. <i class="far fa-comment cursor-pointer text-muted mr-1"></i>
  378. Comment
  379. </div>
  380. <div>
  381. <i class="fas fa-external-link-alt cursor-pointer text-muted mr-1"></i>
  382. Share
  383. </div>
  384. </div>
  385. </div>
  386. </div>
  387. </div>
  388. </div>
  389. </div>
  390. </div>
  391. </div> -->
  392. <!-- YOUTUBE -->
  393. <!-- <div class="status-card-component shadow-sm mb-3 rounded-lg">
  394. <div class="card shadow-none border rounded-0">
  395. <div class="card-body pb-0">
  396. <div class="media">
  397. <img src="https://pixelfed.test/storage/avatars/321493203255693312/5a6nqo.jpg?v=2" width="42px" height="42px" onerror="this.onerror=null;this.src='/storage/avatars/default.png?v=2'" alt="avatar" class="rounded-circle box-shadow mr-2">
  398. <div class="media-body">
  399. <div class="pl-2 d-flex align-items-top">
  400. <div>
  401. <p class="mb-0">
  402. <a href="https://pixelfed.test/dansup" class="username font-weight-bold text-dark text-decoration-none text-break">
  403. dansup
  404. </a>
  405. </p>
  406. <p class="mb-0">
  407. <a href="https://pixelfed.test/groups/328821658771132416/329186991407239168" class="font-weight-light text-muted small">13h</a>
  408. <span class="text-lighter" style="padding-left: 2px; padding-right: 2px;">·</span>
  409. <span class="text-muted small">
  410. <i class="fas fa-globe"></i>
  411. </span>
  412. </p>
  413. </div>
  414. <span class="text-right" style="flex-grow: 1;">
  415. <button type="button" class="btn btn-link text-dark py-0">
  416. <span class="fas fa-ellipsis-h text-lighter"></span>
  417. <span class="sr-only">Post Menu</span>
  418. </button>
  419. </span>
  420. </div>
  421. </div>
  422. </div>
  423. <div>
  424. <div>
  425. <div class="">
  426. <p class="pt-2 text-break" style="font-size: 15px;">
  427. What does everyone think??
  428. </p>
  429. <div class="my-3 row p-0 m-0">
  430. <iframe width="100%" height="315" src="https://www.youtube.com/embed/lH78Tb0r_f8" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
  431. </div>
  432. <div class="border-top my-0">
  433. <div class="d-flex justify-content-between py-2 px-4">
  434. <button class="btn btn-link py-0 text-decoration-none text-muted">
  435. <i class="far fa-heart mr-1"></i>
  436. Like
  437. </button>
  438. <div>
  439. <i class="far fa-comment cursor-pointer text-muted mr-1"></i>
  440. Comment
  441. </div>
  442. <div>
  443. <i class="fas fa-external-link-alt cursor-pointer text-muted mr-1"></i>
  444. Share
  445. </div>
  446. </div>
  447. </div>
  448. </div>
  449. </div>
  450. </div>
  451. </div>
  452. </div>
  453. </div> -->
  454. <!-- PHOTOS -->
  455. <!-- <div class="status-card-component shadow-sm mb-3 rounded-lg">
  456. <div class="card shadow-none border rounded-0">
  457. <div class="card-body pb-0">
  458. <div class="media">
  459. <img src="https://pixelfed.test/storage/avatars/321493203255693312/5a6nqo.jpg?v=2" width="42px" height="42px" onerror="this.onerror=null;this.src='/storage/avatars/default.png?v=2'" alt="avatar" class="rounded-circle box-shadow mr-2">
  460. <div class="media-body">
  461. <div class="pl-2 d-flex align-items-top">
  462. <div>
  463. <p class="mb-0">
  464. <a href="https://pixelfed.test/dansup" class="username font-weight-bold text-dark text-decoration-none text-break">
  465. dansup
  466. </a>
  467. </p>
  468. <p class="mb-0">
  469. <a href="https://pixelfed.test/groups/328821658771132416/329186991407239168" class="font-weight-light text-muted small">13h</a>
  470. <span class="text-lighter" style="padding-left: 2px; padding-right: 2px;">·</span>
  471. <span class="text-muted small">
  472. <i class="fas fa-globe"></i>
  473. </span>
  474. </p>
  475. </div>
  476. <span class="text-right" style="flex-grow: 1;">
  477. <button type="button" class="btn btn-link text-dark py-0">
  478. <span class="fas fa-ellipsis-h text-lighter"></span>
  479. <span class="sr-only">Post Menu</span>
  480. </button>
  481. </span>
  482. </div>
  483. </div>
  484. </div>
  485. <div>
  486. <div>
  487. <div class="">
  488. <p class="pt-2 text-break" style="font-size: 15px;">
  489. What does everyone think??
  490. </p>
  491. <div class="mb-1 row px-3">
  492. <div class="col px-0">
  493. <img src="https://pixelfed.test/img/sample-post.jpeg" class="img-fluid border rounded-lg">
  494. </div>
  495. <div class="col px-0">
  496. <img src="https://pixelfed.test/img/sample-post.jpeg" class="img-fluid border rounded-lg">
  497. </div>
  498. </div>
  499. <div class="mb-3 row px-3">
  500. <div class="col px-0">
  501. <img src="https://pixelfed.test/img/sample-post.jpeg" class="img-fluid border rounded-lg">
  502. </div>
  503. <div class="col px-0">
  504. <img src="https://pixelfed.test/img/sample-post.jpeg" class="img-fluid border rounded-lg">
  505. </div>
  506. </div>
  507. <div class="border-top my-0">
  508. <div class="d-flex justify-content-between py-2 px-4">
  509. <button class="btn btn-link py-0 text-decoration-none text-muted">
  510. <i class="far fa-heart mr-1"></i>
  511. Like
  512. </button>
  513. <div>
  514. <i class="far fa-comment cursor-pointer text-muted mr-1"></i>
  515. Comment
  516. </div>
  517. <div>
  518. <i class="fas fa-external-link-alt cursor-pointer text-muted mr-1"></i>
  519. Share
  520. </div>
  521. </div>
  522. </div>
  523. </div>
  524. </div>
  525. </div>
  526. </div>
  527. </div>
  528. </div> -->
  529. <group-status
  530. v-for="(status, index) in feed"
  531. :key="'gs:' + status.id + index"
  532. :prestatus="status"
  533. :profile="profile"
  534. :group-id="groupId"
  535. v-on:comment-focus="commentFocus(index)"
  536. v-on:status-delete="statusDelete(index)"
  537. v-on:likes-modal="showLikesModal(index)" />
  538. <b-modal
  539. ref="likeBox"
  540. size="sm"
  541. centered
  542. hide-footer
  543. title="Likes"
  544. body-class="list-group-flush p-0">
  545. <div class="list-group py-1" style="max-height:300px;overflow-y:auto;">
  546. <div
  547. class="list-group-item border-top-0 border-left-0 border-right-0 py-2"
  548. :class="{ 'border-bottom-0': index + 1 == likes.length }"
  549. v-for="(user, index) in likes" :key="'modal_likes_'+index">
  550. <div class="media align-items-center">
  551. <a :href="user.url">
  552. <img class="mr-3 rounded-circle box-shadow" :src="user.avatar" :alt="user.username + '’s avatar'" width="30px" onerror="this.onerror=null;this.src='/storage/avatars/default.jpg';">
  553. </a>
  554. <div class="media-body">
  555. <p class="mb-0" style="font-size: 14px">
  556. <a :href="user.url" class="font-weight-bold text-dark">
  557. {{user.username}}
  558. </a>
  559. </p>
  560. <p v-if="!user.local" class="text-muted mb-0 text-truncate mr-3" style="font-size: 14px" :title="user.acct" data-toggle="dropdown" data-placement="bottom">
  561. <span class="font-weight-bold">{{user.acct.split('@')[0]}}</span><span class="text-lighter">&commat;{{user.acct.split('@')[1]}}</span>
  562. </p>
  563. <p v-else class="text-muted mb-0 text-truncate" style="font-size: 14px">
  564. {{user.display_name}}
  565. </p>
  566. </div>
  567. </div>
  568. </div>
  569. <infinite-loading @infinite="infiniteLikesHandler" :distance="800" spinner="spiral">
  570. <div slot="no-more"></div>
  571. <div slot="no-results"></div>
  572. </infinite-loading>
  573. </div>
  574. </b-modal>
  575. <div v-if="feed.length > 2" :distance="800">
  576. <infinite-loading @infinite="infiniteFeed">
  577. <div slot="no-more"></div>
  578. <div slot="no-results"></div>
  579. </infinite-loading>
  580. </div>
  581. </div>
  582. </div>
  583. <div v-else>
  584. <div class="card card-body mt-3 shadow-none border d-flex align-items-center justify-content-center" style="height: 100px;">
  585. <p class="lead mb-0">Join to participate in this group.</p>
  586. </div>
  587. </div>
  588. </div>
  589. <div v-else class="col-12 col-md-7 mt-3">
  590. <group-status
  591. :prestatus="status"
  592. :profile="profile"
  593. :group-id="groupId"
  594. :permalink-mode="true" />
  595. </div>
  596. <div class="col-12 col-md-5">
  597. <group-info-card :group="group" />
  598. </div>
  599. </div>
  600. <group-about v-else-if="tab == 'about'" :group="group" />
  601. <group-media v-else-if="tab == 'media'" :group="group" />
  602. <group-members
  603. v-else-if="tab == 'members'"
  604. :group="group"
  605. :request-count="atabs.request_count"
  606. :is-admin="isAdmin"
  607. :profile="profile"
  608. v-on:decrementrc="decrementJoinRequestCount"
  609. v-on:incrementMemberCount="incrementMemberCount"/>
  610. <group-topics v-else-if="tab == 'topics'" :group="group" />
  611. <group-insights v-else-if="tab == 'insights'" :group="group" />
  612. <group-moderation
  613. v-else-if="tab == 'moderation'"
  614. :group="group"
  615. v-on:decrement="decrementModCounter" />
  616. <div v-else>
  617. <p class="lead text-center font-weight-bold mt-5 text-muted">No content found</p>
  618. </div>
  619. </keep-alive>
  620. <search-modal ref="searchModal" :group="group" :profile="profile" />
  621. <invite-modal ref="inviteModal" :group="group" :profile="profile" />
  622. </div>
  623. </div>
  624. </div>
  625. </template>
  626. </div>
  627. </template>
  628. <script type="text/javascript">
  629. import StatusCard from '~/partials/StatusCard.vue';
  630. import GroupMembers from '@/groups/partials/GroupMembers.vue';
  631. import GroupCompose from '@/groups/partials/GroupCompose.vue';
  632. import GroupStatus from '@/groups/partials/GroupStatus.vue';
  633. import GroupAbout from '@/groups/partials/GroupAbout.vue';
  634. import GroupMedia from '@/groups/partials/GroupMedia.vue';
  635. import GroupModeration from '@/groups/partials/GroupModeration.vue';
  636. import GroupTopics from '@/groups/partials/GroupTopics.vue';
  637. import GroupInfoCard from '@/groups/partials/GroupInfoCard.vue';
  638. import LeaveGroup from '@/groups/partials/LeaveGroup.vue';
  639. import GroupInsights from '@/groups/partials/GroupInsights.vue';
  640. import SearchModal from '@/groups/partials/GroupSearchModal.vue';
  641. import InviteModal from '@/groups/partials/GroupInviteModal.vue';
  642. import SidebarComponent from '@/groups/sections/Sidebar.vue';
  643. import GroupBanner from '@/groups/partials/Page/GroupBanner.vue';
  644. export default {
  645. props: {
  646. groupId: {
  647. type: String
  648. },
  649. path: {
  650. type: String
  651. },
  652. permalinkMode: {
  653. type: Boolean,
  654. default: false
  655. },
  656. permalinkId: {
  657. type: String,
  658. }
  659. },
  660. components: {
  661. 'status-card': StatusCard,
  662. 'group-about': GroupAbout,
  663. 'group-status': GroupStatus,
  664. 'group-members': GroupMembers,
  665. 'group-compose': GroupCompose,
  666. 'group-topics': GroupTopics,
  667. 'group-info-card': GroupInfoCard,
  668. 'group-media': GroupMedia,
  669. 'group-moderation': GroupModeration,
  670. 'leave-group': LeaveGroup,
  671. 'group-insights': GroupInsights,
  672. 'search-modal': SearchModal,
  673. 'invite-modal': InviteModal,
  674. 'sidebar': SidebarComponent,
  675. 'group-banner': GroupBanner
  676. },
  677. data() {
  678. return {
  679. initalLoad: false,
  680. profile: undefined,
  681. group: {},
  682. isMember: false,
  683. isAdmin: false,
  684. tab: 'feed',
  685. requestingMembership: false,
  686. composeText: null,
  687. feed: [],
  688. ids: [],
  689. maxId: null,
  690. status: undefined,
  691. likes: [],
  692. likesPage: 1,
  693. likesId: undefined,
  694. atabs: {
  695. moderation_count: 0,
  696. request_count: 0
  697. }
  698. };
  699. },
  700. mounted() {
  701. axios.get('/api/pixelfed/v1/accounts/verify_credentials')
  702. .then(res => {
  703. this.profile = res.data;
  704. this.fetchGroup();
  705. })
  706. .catch(err => {
  707. window.location.href = '/login?_next=' + encodeURIComponent(window.location.href);
  708. });
  709. if(this.permalinkMode) {
  710. this.fetchPermalink();
  711. } else {
  712. this.fetchFeed();
  713. }
  714. },
  715. methods: {
  716. initObservers() {
  717. // let video = document.querySelectorAll('video');
  718. // let isPaused = false;
  719. // let observer = new IntersectionObserver((entries, observer) => {
  720. // entries.forEach(entry => {
  721. // if (entry.intersectionRatio !=1 && !video.paused){
  722. // video.pause();
  723. // isPaused = true;
  724. // }
  725. // else if (isPaused) {
  726. // video.play();
  727. // isPaused = false
  728. // }
  729. // });
  730. // }, {threshold: 1});
  731. // observer.observe(video);
  732. },
  733. fetchGroup() {
  734. axios.get('/api/v0/groups/' + this.groupId)
  735. .then(res => {
  736. this.group = res.data;
  737. this.isMember = res.data.self.is_member;
  738. this.isAdmin = ['founder', 'admin'].includes(res.data.self.role);
  739. if(this.isAdmin) {
  740. this.fetchAdminTabs();
  741. }
  742. if(this.path) {
  743. if(this.isMember && ['about', 'topics', 'members', 'events', 'media', 'polls'].includes(this.path)) {
  744. setTimeout(() => {
  745. this.tab = this.path;
  746. this.initalLoad = true;
  747. }, 500);
  748. } else if (this.isAdmin && ['insights', 'moderation'].includes(this.path)) {
  749. setTimeout(() => {
  750. this.tab = this.path;
  751. this.initalLoad = true;
  752. }, 500);
  753. } else {
  754. history.pushState(null, null, this.group.url);
  755. this.initalLoad = true;
  756. }
  757. } else {
  758. this.initalLoad = true;
  759. }
  760. })
  761. .catch(err => {
  762. // window.location.href = '/groups/unavailable';
  763. });
  764. },
  765. fetchAdminTabs() {
  766. axios.get('/api/v0/groups/' + this.groupId + '/atabs')
  767. .then(res => {
  768. this.atabs = res.data;
  769. })
  770. },
  771. fetchFeed() {
  772. axios.get('/api/v0/groups/' + this.groupId + '/feed')
  773. .then(res => {
  774. let self = this;
  775. if(res.data && res.data.length) {
  776. this.feed = res.data;
  777. this.maxId = this.feed[this.feed.length - 1].id;
  778. res.data.forEach(d => {
  779. if(self.ids.indexOf(d.id) == -1) {
  780. self.ids.push(d.id);
  781. }
  782. });
  783. }
  784. this.initObservers();
  785. })
  786. },
  787. fetchPermalink() {
  788. axios.get('/api/v0/groups/status', {
  789. params: {
  790. gid: this.groupId,
  791. sid: this.permalinkId
  792. }
  793. }).then(res => {
  794. this.status = res.data;
  795. if(this.status.in_reply_to_id) {
  796. this.status.showCommentDrawer = true;
  797. }
  798. }).catch(err => {
  799. this.permalinkMode = false;
  800. this.fetchFeed();
  801. });
  802. },
  803. timestampFormat(date, showTime = false) {
  804. let ts = new Date(date);
  805. return showTime ? ts.toDateString() + ' · ' + ts.toLocaleTimeString() : ts.toDateString();
  806. },
  807. switchTab(tab) {
  808. window.scrollTo(0,0);
  809. if(tab == 'feed' && this.permalinkMode) {
  810. this.permalinkMode = false;
  811. this.fetchFeed();
  812. }
  813. let url = tab == 'feed' ? this.group.url : this.group.url + '/' + tab;
  814. history.pushState(tab, null, url);
  815. this.tab = tab;
  816. },
  817. joinGroup() {
  818. this.requestingMembership = true;
  819. axios.post('/api/v0/groups/'+this.groupId+'/join')
  820. .then(res => {
  821. this.requestingMembership = false;
  822. this.group = res.data;
  823. this.fetchGroup();
  824. this.fetchFeed();
  825. }).catch(err => {
  826. let body = err.response;
  827. if(body.status == 422) {
  828. this.tab = 'feed';
  829. history.pushState('', null, this.group.url);
  830. this.requestingMembership = false;
  831. swal('Oops!', body.data.error, 'error');
  832. }
  833. });
  834. },
  835. cancelJoinRequest() {
  836. if(!window.confirm('Are you sure you want to cancel your request to join this group?')) {
  837. return;
  838. }
  839. axios.post('/api/v0/groups/'+this.groupId+'/cjr')
  840. .then(res => {
  841. this.requestingMembership = false;
  842. }).catch(err => {
  843. let body = err.response;
  844. if(body.status == 422) {
  845. swal('Oops!', body.data.error, 'error');
  846. }
  847. });
  848. },
  849. leaveGroup() {
  850. if(!window.confirm('Are you sure you want to leave this group? Any content you shared will remain accessible. You won\'t be able to rejoin for 24 hours.')) {
  851. return;
  852. }
  853. axios.post('/api/v0/groups/'+this.groupId+'/leave')
  854. .then(res => {
  855. this.tab = 'feed';
  856. history.pushState('', null, this.group.url);
  857. this.feed = [];
  858. this.isMember = false;
  859. this.isAdmin = false;
  860. this.group.self.role = null;
  861. this.group.self.is_member = false;
  862. });
  863. },
  864. pushNewStatus(status) {
  865. this.feed.unshift(status);
  866. },
  867. commentFocus(index) {
  868. let status = this.feed[index];
  869. status.showCommentDrawer = true;
  870. },
  871. statusDelete(index) {
  872. this.feed.splice(index, 1);
  873. },
  874. infiniteFeed($state) {
  875. if(this.feed.length < 3) {
  876. $state.complete();
  877. return;
  878. }
  879. let apiUrl = '/api/v0/groups/' + this.groupId + '/feed';
  880. axios.get(apiUrl, {
  881. params: {
  882. limit: 6,
  883. max_id: this.maxId
  884. },
  885. }).then(res => {
  886. if (res.data.length) {
  887. // let self = this;
  888. // data.forEach(d => {
  889. // if(self.ids.indexOf(d.id) == -1) {
  890. // if(self.maxId >= d.id) {
  891. // self.maxId = d.id;
  892. // }
  893. // self.ids.push(d.id);
  894. // self.feed.push(d);
  895. // }
  896. // });
  897. let posts = res.data.filter(p => this.ids.indexOf(p.id) == -1);
  898. this.maxId = posts[posts.length - 1].id;
  899. this.feed.push(...posts);
  900. this.ids.push(...posts.map(p => p.id));
  901. setTimeout(() => {
  902. this.initObservers();
  903. }, 1000);
  904. $state.loaded();
  905. } else {
  906. $state.complete();
  907. }
  908. });
  909. },
  910. decrementModCounter(amount) {
  911. let count = this.atabs.moderation_count;
  912. if(count == 0) {
  913. return;
  914. }
  915. this.atabs.moderation_count = (count - amount);
  916. },
  917. setModCounter(amount) {
  918. this.atabs.moderation_count = amount;
  919. },
  920. decrementJoinRequestCount(amount = 1) {
  921. let count = this.atabs.request_count;
  922. this.atabs.request_count = (count - amount)
  923. },
  924. incrementMemberCount() {
  925. let count = this.group.member_count;
  926. this.group.member_count = (count + 1);
  927. },
  928. copyLink() {
  929. window.App.util.clipboard(this.group.url);
  930. this.$bvToast.toast(`Succesfully copied group url to clipboard`, {
  931. title: 'Success',
  932. variant: 'success',
  933. autoHideDelay: 5000
  934. });
  935. },
  936. reportGroup() {
  937. swal('Report Group', 'Are you sure you want to report this group?')
  938. .then(res => {
  939. if(res) {
  940. location.href = `/i/report?id=${this.group.id}&type=group`;
  941. }
  942. });
  943. },
  944. showSearchModal() {
  945. event.currentTarget.blur();
  946. this.$refs.searchModal.open();
  947. },
  948. showInviteModal() {
  949. event.currentTarget.blur();
  950. this.$refs.inviteModal.open();
  951. },
  952. showLikesModal(index) {
  953. this.likesId = this.feed[index].id;
  954. axios.get('/api/v0/groups/'+this.groupId+'/likes/'+this.likesId)
  955. .then(res => {
  956. this.likes = res.data;
  957. this.likesPage++;
  958. this.$refs.likeBox.show();
  959. });
  960. },
  961. infiniteLikesHandler($state) {
  962. if(this.likes.length < 3) {
  963. $state.complete();
  964. return;
  965. }
  966. axios.get('/api/v0/groups/'+this.groupId+'/likes/'+this.likesId, {
  967. params: {
  968. page: this.likesPage,
  969. },
  970. }).then(res => {
  971. if (res.data.length > 0) {
  972. this.likes.push(...res.data);
  973. this.likesPage++;
  974. if(res.data.length != 10) {
  975. $state.complete();
  976. } else {
  977. $state.loaded();
  978. }
  979. } else {
  980. $state.complete();
  981. }
  982. });
  983. },
  984. }
  985. }
  986. </script>
  987. <style lang="scss">
  988. .group-feed-component {
  989. &-header {
  990. display: flex;
  991. justify-content: space-between;
  992. align-items: flex-end;
  993. padding: 1rem 0;
  994. background-color: #fff;
  995. .cta-btn {
  996. width: 190px;
  997. }
  998. }
  999. &-menu {
  1000. display: flex;
  1001. justify-content: space-between;
  1002. align-items: center;
  1003. padding: 0;
  1004. &-nav {
  1005. .nav-item {
  1006. .nav-link {
  1007. padding-top: 1rem;
  1008. padding-bottom: 1rem;
  1009. color: #6c757d;
  1010. &.active {
  1011. color: #2c78bf;
  1012. border-bottom: 2px solid #2c78bf;
  1013. }
  1014. }
  1015. }
  1016. &:not(last-child) {
  1017. .nav-item {
  1018. margin-right: 14px;
  1019. }
  1020. }
  1021. }
  1022. }
  1023. &-body {
  1024. min-height: 40vh;
  1025. }
  1026. .member-label {
  1027. padding: 2px 5px;
  1028. font-size: 12px;
  1029. color: rgba(75, 119, 190, 1);
  1030. background:rgba(137, 196, 244, 0.2);
  1031. border:1px solid rgba(137, 196, 244, 0.3);
  1032. font-weight:400;
  1033. text-transform: capitalize;
  1034. }
  1035. .dropdown-item {
  1036. font-weight: 600;
  1037. }
  1038. .remote-label {
  1039. padding: 2px 5px;
  1040. font-size: 12px;
  1041. color: #B45309;
  1042. background: #FEF3C7;
  1043. border: 1px solid #FCD34D;
  1044. font-weight: 400;
  1045. text-transform: capitalize;
  1046. }
  1047. }
  1048. </style>