ProfileSidebar.vue 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821
  1. <template>
  2. <div class="profile-sidebar-component">
  3. <div>
  4. <div class="d-block d-md-none">
  5. <div class="media user-card user-select-none">
  6. <div style="position: relative;">
  7. <img :src="profile.avatar" class="avatar shadow cursor-pointer" draggable="false" onerror="this.onerror=null;this.src='/storage/avatars/default.png?v=0';">
  8. </div>
  9. <div class="media-body">
  10. <p class="display-name" v-html="getDisplayName()"></p>
  11. <p class="username" :class="{ remote: !profile.local }">
  12. <a v-if="!profile.local" :href="profile.url" class="primary">&commat;{{ profile.acct }}</a>
  13. <span v-else>&commat;{{ profile.acct }}</span>
  14. <span v-if="profile.locked">
  15. <i class="fal fa-lock ml-1 fa-sm text-lighter"></i>
  16. </span>
  17. </p>
  18. <div class="stats">
  19. <div class="stats-posts" @click="toggleTab('index')">
  20. <div class="posts-count">{{ formatCount(profile.statuses_count) }}</div>
  21. <div class="stats-label">
  22. {{ $t('profile.posts') }}
  23. </div>
  24. </div>
  25. <div class="stats-followers" @click="toggleTab('followers')">
  26. <div class="followers-count">{{ formatCount(profile.followers_count) }}</div>
  27. <div class="stats-label">
  28. {{ $t('profile.followers') }}
  29. </div>
  30. </div>
  31. <div class="stats-following" @click="toggleTab('following')">
  32. <div class="following-count">{{ formatCount(profile.following_count) }}</div>
  33. <div class="stats-label">
  34. {{ $t('profile.following') }}
  35. </div>
  36. </div>
  37. </div>
  38. </div>
  39. </div>
  40. </div>
  41. <div class="d-none d-md-flex justify-content-between align-items-center">
  42. <button class="btn btn-link" @click="goBack()">
  43. <i class="far fa-chevron-left fa-lg text-lighter"></i>
  44. </button>
  45. <div>
  46. <img :src="getAvatar()" class="avatar img-fluid shadow border" onerror="this.onerror=null;this.src='/storage/avatars/default.png?v=0';">
  47. <p v-if="profile.is_admin" class="text-right" style="margin-top: -30px;"><span class="admin-label">Admin</span></p>
  48. </div>
  49. <!-- <button class="btn btn-link">
  50. <i class="far fa-lg fa-cog text-lighter"></i>
  51. </button> -->
  52. <b-dropdown
  53. variant="link"
  54. right
  55. no-caret>
  56. <template #button-content>
  57. <i class="far fa-lg fa-cog text-lighter"></i>
  58. </template>
  59. <b-dropdown-item v-if="profile.local" href="#" link-class="font-weight-bold" @click.prevent="goToOldProfile()">View in old UI</b-dropdown-item>
  60. <b-dropdown-item href="#" link-class="font-weight-bold" @click.prevent="copyTextToClipboard(profile.url)">Copy Link</b-dropdown-item>
  61. <b-dropdown-item v-if="profile.local" :href="'/users/' + profile.username + '.atom'" link-class="font-weight-bold">Atom feed</b-dropdown-item>
  62. <div v-if="profile.id == user.id">
  63. <b-dropdown-divider></b-dropdown-divider>
  64. <b-dropdown-item href="/settings/home" link-class="font-weight-bold">
  65. <i class="far fa-cog mr-1"></i> Settings
  66. </b-dropdown-item>
  67. </div>
  68. <div v-else>
  69. <b-dropdown-item v-if="!profile.local" :href="profile.url" link-class="font-weight-bold">View Remote Profile</b-dropdown-item>
  70. <b-dropdown-item :href="'/i/web/direct/thread/' + profile.id" link-class="font-weight-bold">Direct Message</b-dropdown-item>
  71. </div>
  72. <div v-if="profile.id !== user.id">
  73. <b-dropdown-divider></b-dropdown-divider>
  74. <b-dropdown-item link-class="font-weight-bold" @click="handleMute()">
  75. {{ relationship.muting ? 'Unmute' : 'Mute' }}
  76. </b-dropdown-item>
  77. <b-dropdown-item link-class="font-weight-bold" @click="handleBlock()">
  78. {{ relationship.blocking ? 'Unblock' : 'Block' }}
  79. </b-dropdown-item>
  80. <b-dropdown-item :href="'/i/report?type=user&id=' + profile.id" link-class="text-danger font-weight-bold">Report</b-dropdown-item>
  81. </div>
  82. </b-dropdown>
  83. </div>
  84. <div class="d-none d-md-block text-center">
  85. <p v-html="getDisplayName()" class="display-name"></p>
  86. <p class="username" :class="{ remote: !profile.local }">
  87. <a v-if="!profile.local" :href="profile.url" class="primary">&commat;{{ profile.acct }}</a>
  88. <span v-else>&commat;{{ profile.acct }}</span>
  89. <span v-if="profile.locked">
  90. <i class="fal fa-lock ml-1 fa-sm text-lighter"></i>
  91. </span>
  92. </p>
  93. <p v-if="user.id != profile.id && (relationship.followed_by || relationship.muting || relationship.blocking)" class="mt-n3 text-center">
  94. <span v-if="relationship.followed_by" class="badge badge-primary p-1">Follows you</span>
  95. <span v-if="relationship.muting" class="badge badge-dark p-1 ml-1">Muted</span>
  96. <span v-if="relationship.blocking" class="badge badge-danger p-1 ml-1">Blocked</span>
  97. </p>
  98. </div>
  99. <div class="d-none d-md-block stats py-2">
  100. <div class="d-flex justify-content-between">
  101. <button
  102. class="btn btn-link stat-item"
  103. @click="toggleTab('index')">
  104. <strong :title="profile.statuses_count">{{ formatCount(profile.statuses_count) }}</strong>
  105. <span>{{ $t('profile.posts') }}</span>
  106. </button>
  107. <button
  108. class="btn btn-link stat-item"
  109. @click="toggleTab('followers')">
  110. <strong :title="profile.followers_count">{{ formatCount(profile.followers_count) }}</strong>
  111. <span>{{ $t('profile.followers') }}</span>
  112. </button>
  113. <button
  114. class="btn btn-link stat-item"
  115. @click="toggleTab('following')">
  116. <strong :title="profile.following_count">{{ formatCount(profile.following_count) }}</strong>
  117. <span>{{ $t('profile.following') }}</span>
  118. </button>
  119. </div>
  120. </div>
  121. <div class="d-flex align-items-center mb-3 mb-md-0">
  122. <div v-if="user.id === profile.id" style="flex-grow: 1;">
  123. <!-- <router-link
  124. class="btn btn-light font-weight-bold btn-block follow-btn"
  125. to="/i/web/settings">
  126. {{ $t('profile.editProfile') }}
  127. </router-link> -->
  128. <a class="btn btn-light font-weight-bold btn-block follow-btn" href="/settings/home">{{ $t('profile.editProfile') }}</a>
  129. <a v-if="!profile.locked" class="btn btn-light font-weight-bold btn-block follow-btn mt-md-n4" href="/i/web/my-portfolio">
  130. My Portfolio
  131. <span class="badge badge-success ml-1">NEW</span>
  132. </a>
  133. </div>
  134. <div v-else-if="profile.locked" style="flex-grow: 1;">
  135. <template v-if="!relationship.following && !relationship.requested">
  136. <button
  137. class="btn btn-primary font-weight-bold btn-block follow-btn"
  138. @click="follow"
  139. :disabled="relationship.blocking">
  140. Request Follow
  141. </button>
  142. <p v-if="relationship.blocking" class="mt-n4 text-lighter" style="font-size: 11px">You need to unblock this account before you can request to follow.</p>
  143. </template>
  144. <div v-else-if="relationship.requested">
  145. <button class="btn btn-primary font-weight-bold btn-block follow-btn" disabled>
  146. {{ $t('profile.followRequested') }}
  147. </button>
  148. <p class="small font-weight-bold text-center mt-n4">
  149. <a href="#" @click.prevent="cancelFollowRequest()">Cancel Follow Request</a>
  150. </p>
  151. </div>
  152. <button
  153. v-else-if="relationship.following"
  154. class="btn btn-primary font-weight-bold btn-block unfollow-btn"
  155. @click="unfollow">
  156. {{ $t('profile.unfollow') }}
  157. </button>
  158. </div>
  159. <div v-else style="flex-grow: 1;">
  160. <template v-if="!relationship.following">
  161. <button
  162. class="btn btn-primary font-weight-bold btn-block follow-btn"
  163. @click="follow"
  164. :disabled="relationship.blocking">
  165. {{ $t('profile.follow') }}
  166. </button>
  167. <p v-if="relationship.blocking" class="mt-n4 text-lighter" style="font-size: 11px">You need to unblock this account before you can follow.</p>
  168. </template>
  169. <button
  170. v-else
  171. class="btn btn-primary font-weight-bold btn-block unfollow-btn"
  172. @click="unfollow">
  173. {{ $t('profile.unfollow') }}
  174. </button>
  175. </div>
  176. <div class="d-block d-md-none ml-3">
  177. <b-dropdown
  178. variant="link"
  179. right
  180. no-caret>
  181. <template #button-content>
  182. <i class="far fa-lg fa-cog text-lighter"></i>
  183. </template>
  184. <b-dropdown-item v-if="profile.local" href="#" link-class="font-weight-bold" @click.prevent="goToOldProfile()">View in old UI</b-dropdown-item>
  185. <b-dropdown-item href="#" link-class="font-weight-bold" @click.prevent="copyTextToClipboard(profile.url)">Copy Link</b-dropdown-item>
  186. <b-dropdown-item v-if="profile.local" :href="'/users/' + profile.username + '.atom'" link-class="font-weight-bold">Atom feed</b-dropdown-item>
  187. <div v-if="profile.id == user.id">
  188. <b-dropdown-divider></b-dropdown-divider>
  189. <b-dropdown-item href="/settings/home" link-class="font-weight-bold">
  190. <i class="far fa-cog mr-1"></i> Settings
  191. </b-dropdown-item>
  192. </div>
  193. <div v-else>
  194. <b-dropdown-item v-if="!profile.local" :href="profile.url" link-class="font-weight-bold">View Remote Profile</b-dropdown-item>
  195. <b-dropdown-item :href="'/i/web/direct/thread/' + profile.id" link-class="font-weight-bold">Direct Message</b-dropdown-item>
  196. </div>
  197. <div v-if="profile.id !== user.id">
  198. <b-dropdown-divider></b-dropdown-divider>
  199. <b-dropdown-item link-class="font-weight-bold" @click="handleMute()">
  200. {{ relationship.muting ? 'Unmute' : 'Mute' }}
  201. </b-dropdown-item>
  202. <b-dropdown-item link-class="font-weight-bold" @click="handleBlock()">
  203. {{ relationship.blocking ? 'Unblock' : 'Block' }}
  204. </b-dropdown-item>
  205. <b-dropdown-item :href="'/i/report?type=user&id=' + profile.id" link-class="text-danger font-weight-bold">Report</b-dropdown-item>
  206. </div>
  207. </b-dropdown>
  208. </div>
  209. </div>
  210. <div v-if="profile.note && renderedBio && renderedBio.length" class="bio-wrapper card shadow-none">
  211. <div class="card-body">
  212. <div class="bio-body">
  213. <div v-html="renderedBio"></div>
  214. </div>
  215. </div>
  216. </div>
  217. <div class="d-none d-md-block card card-body shadow-none py-2">
  218. <p v-if="profile.website" class="small">
  219. <span class="text-lighter mr-2">
  220. <i class="far fa-link"></i>
  221. </span>
  222. <span>
  223. <a :href="profile.website" class="font-weight-bold">{{ profile.website }}</a>
  224. </span>
  225. </p>
  226. <p class="mb-0 small">
  227. <span class="text-lighter mr-2">
  228. <i class="far fa-clock"></i>
  229. </span>
  230. <span v-if="profile.local">
  231. {{ $t('profile.joined') }} {{ getJoinedDate() }}
  232. </span>
  233. <span v-else>
  234. {{ $t('profile.joined') }} {{ getJoinedDate() }}
  235. <span class="float-right primary">
  236. <i class="far fa-info-circle" v-b-tooltip.hover title="This user is from a remote server and may have created their account before this date"></i>
  237. </span>
  238. </span>
  239. </p>
  240. </div>
  241. <div class="d-none d-md-flex sidebar-sitelinks">
  242. <a href="/site/about">{{ $t('navmenu.about') }}</a>
  243. <router-link to="/i/web/help">{{ $t('navmenu.help') }}</router-link>
  244. <router-link to="/i/web/language">{{ $t('navmenu.language') }}</router-link>
  245. <a href="/site/terms">{{ $t('navmenu.privacy') }}</a>
  246. <a href="/site/terms">{{ $t('navmenu.terms') }}</a>
  247. </div>
  248. <div class="d-none d-md-block sidebar-attribution">
  249. <a href="https://pixelfed.org" class="font-weight-bold">Powered by Pixelfed</a>
  250. </div>
  251. </div>
  252. <b-modal
  253. ref="fullBio"
  254. centered
  255. hide-footer
  256. ok-only
  257. ok-title="Close"
  258. ok-variant="light"
  259. :scrollable="true"
  260. body-class="p-md-5"
  261. title="Bio"
  262. >
  263. <div v-html="profile.note"></div>
  264. </b-modal>
  265. </div>
  266. </template>
  267. <script type="text/javascript">
  268. import { mapGetters } from 'vuex'
  269. export default {
  270. props: {
  271. profile: {
  272. type: Object
  273. },
  274. relationship: {
  275. type: Object,
  276. default: (function() {
  277. return {
  278. following: false,
  279. followed_by: false
  280. };
  281. })
  282. },
  283. user: {
  284. type: Object
  285. }
  286. },
  287. computed: {
  288. ...mapGetters([
  289. 'getCustomEmoji'
  290. ])
  291. },
  292. data() {
  293. return {
  294. 'renderedBio': ''
  295. };
  296. },
  297. mounted() {
  298. this.$nextTick(() => {
  299. this.setBio();
  300. });
  301. },
  302. methods: {
  303. getDisplayName() {
  304. let self = this;
  305. let profile = this.profile;
  306. let dn = profile.display_name;
  307. if(!dn) {
  308. return profile.username;
  309. }
  310. if(dn.includes(':')) {
  311. // let re = /:(::|[^:\n])+:/g;
  312. let re = /(<a?)?:\w+:(\d{18}>)?/g;
  313. let un = dn.replaceAll(re, function(em) {
  314. let shortcode = em.slice(1, em.length - 1);
  315. let emoji = self.getCustomEmoji.filter(e => {
  316. return e.shortcode == shortcode;
  317. });
  318. return emoji.length ? `<img draggable="false" class="emojione custom-emoji" alt="${emoji[0].shortcode}" title="${emoji[0].shortcode}" src="${emoji[0].url}" data-original="${emoji[0].url}" data-static="${emoji[0].static_url}" width="16" height="16" onerror="this.onerror=null;this.src='/storage/emoji/missing.png';" />`: em;
  319. });
  320. return un;
  321. } else {
  322. return dn;
  323. }
  324. },
  325. formatCount(val) {
  326. return App.util.format.count(val);
  327. },
  328. goBack() {
  329. this.$emit('back');
  330. },
  331. showFullBio() {
  332. this.$refs.fullBio.show();
  333. },
  334. toggleTab(tab) {
  335. event.currentTarget.blur();
  336. if(['followers', 'following'].includes(tab)) {
  337. this.$router.push('/i/web/profile/' + this.profile.id + '/' + tab);
  338. return;
  339. } else {
  340. this.$emit('toggletab', tab);
  341. }
  342. },
  343. getJoinedDate() {
  344. let d = new Date(this.profile.created_at);
  345. let month = new Intl.DateTimeFormat("en-US", { month: "long" }).format(d);
  346. let year = d.getFullYear();
  347. return `${month} ${year}`;
  348. },
  349. follow() {
  350. event.currentTarget.blur();
  351. this.$emit('follow');
  352. },
  353. unfollow() {
  354. event.currentTarget.blur();
  355. this.$emit('unfollow');
  356. },
  357. setBio() {
  358. if(!this.profile.note.length) {
  359. return;
  360. }
  361. if(this.profile.local) {
  362. let content = this.profile.hasOwnProperty('note_text') ?
  363. this.profile.note_text :
  364. this.profile.note.replace(/(<([^>]+)>)/gi, "");
  365. this.renderedBio = window.pftxt.autoLink(content, {
  366. usernameUrlBase: '/i/web/profile/@',
  367. hashtagUrlBase: '/i/web/hashtag/'
  368. })
  369. } else {
  370. if(this.profile.note === '<p></p>') {
  371. this.renderedBio = null;
  372. return;
  373. }
  374. let content = this.profile.note;
  375. let el = document.createElement('div');
  376. el.innerHTML = content;
  377. el.querySelectorAll('a[class*="hashtag"]')
  378. .forEach(elr => {
  379. let tag = elr.innerText;
  380. if(tag.substr(0, 1) == '#') {
  381. tag = tag.substr(1);
  382. }
  383. elr.removeAttribute('target');
  384. elr.setAttribute('href', '/i/web/hashtag/' + tag);
  385. })
  386. el.querySelectorAll('a:not(.hashtag)[class*="mention"], a:not(.hashtag)[class*="list-slug"]')
  387. .forEach(elr => {
  388. let name = elr.innerText;
  389. if(name.substr(0, 1) == '@') {
  390. name = name.substr(1);
  391. }
  392. if(this.profile.local == false && !name.includes('@')) {
  393. let domain = document.createElement('a');
  394. domain.href = this.profile.url;
  395. name = name + '@' + domain.hostname;
  396. }
  397. elr.removeAttribute('target');
  398. elr.setAttribute('href', '/i/web/username/' + name);
  399. })
  400. this.renderedBio = el.outerHTML;
  401. }
  402. },
  403. getAvatar() {
  404. if(this.profile.id == this.user.id) {
  405. return window._sharedData.user.avatar;
  406. }
  407. return this.profile.avatar;
  408. },
  409. copyTextToClipboard(val) {
  410. App.util.clipboard(val);
  411. },
  412. goToOldProfile() {
  413. if(this.profile.local) {
  414. location.href = this.profile.url + '?fs=1';
  415. } else {
  416. location.href = '/i/web/profile/_/' + this.profile.id;
  417. }
  418. },
  419. handleMute() {
  420. let msg = this.relationship.muting ? 'unmuted' : 'muted';
  421. let url = this.relationship.muting == true ? '/i/unmute' : '/i/mute';
  422. axios.post(url, {
  423. type: 'user',
  424. item: this.profile.id
  425. }).then(res => {
  426. this.$emit('updateRelationship', res.data);
  427. swal('Success', 'You have successfully '+ msg +' ' + this.profile.acct, 'success');
  428. }).catch(err => {
  429. if(err.response.status === 422) {
  430. swal({
  431. title: 'Error',
  432. text: err.response?.data?.error,
  433. icon: "error",
  434. buttons: {
  435. review: {
  436. text: "Review muted accounts",
  437. value: "review",
  438. className: "btn-primary"
  439. },
  440. cancel: true,
  441. }
  442. })
  443. .then((val) => {
  444. if(val && val == 'review') {
  445. location.href = '/settings/privacy/muted-users';
  446. return;
  447. }
  448. });
  449. } else {
  450. swal('Error', 'Something went wrong. Please try again later.', 'error');
  451. }
  452. });
  453. },
  454. handleBlock() {
  455. let msg = this.relationship.blocking ? 'unblock' : 'block';
  456. let url = this.relationship.blocking == true ? '/i/unblock' : '/i/block';
  457. axios.post(url, {
  458. type: 'user',
  459. item: this.profile.id
  460. }).then(res => {
  461. this.$emit('updateRelationship', res.data);
  462. swal('Success', 'You have successfully '+ msg +'ed ' + this.profile.acct, 'success');
  463. }).catch(err => {
  464. if(err.response.status === 422) {
  465. swal({
  466. title: 'Error',
  467. text: err.response?.data?.error,
  468. icon: "error",
  469. buttons: {
  470. review: {
  471. text: "Review blocked accounts",
  472. value: "review",
  473. className: "btn-primary"
  474. },
  475. cancel: true,
  476. }
  477. })
  478. .then((val) => {
  479. if(val && val == 'review') {
  480. location.href = '/settings/privacy/blocked-users';
  481. return;
  482. }
  483. });
  484. } else {
  485. swal('Error', 'Something went wrong. Please try again later.', 'error');
  486. }
  487. });
  488. },
  489. cancelFollowRequest() {
  490. if(!window.confirm('Are you sure you want to cancel your follow request?')) {
  491. return;
  492. }
  493. event.currentTarget.blur();
  494. this.$emit('unfollow');
  495. }
  496. }
  497. }
  498. </script>
  499. <style lang="scss">
  500. .profile-sidebar-component {
  501. margin-bottom: 1rem;
  502. .avatar {
  503. width: 140px;
  504. margin-bottom: 1rem;
  505. border-radius: 15px;
  506. }
  507. .display-name {
  508. font-size: 20px;
  509. margin-bottom: 0;
  510. word-break: break-word;
  511. font-size: 15px;
  512. font-weight: 800 !important;
  513. user-select: all;
  514. line-height: 0.8;
  515. font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
  516. }
  517. .username {
  518. color: var(--primary);
  519. font-size: 14px;
  520. font-weight: 600;
  521. font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
  522. &.remote {
  523. font-size: 11px;
  524. }
  525. }
  526. .stats {
  527. margin-bottom: 1rem;
  528. .stat-item {
  529. max-width: 33%;
  530. flex: 0 0 33%;
  531. text-align: center;
  532. margin: 0;
  533. padding: 0;
  534. text-decoration: none;
  535. strong {
  536. display: block;
  537. color: var(--body-color);
  538. font-size: 18px;
  539. line-height: 0.9;
  540. }
  541. span {
  542. display: block;
  543. font-size: 12px;
  544. color: #B8C2CC;
  545. }
  546. }
  547. }
  548. .follow-btn {
  549. @media (min-width: 768px) {
  550. margin-bottom: 2rem;
  551. }
  552. &.btn-primary {
  553. background-color: var(--primary);
  554. }
  555. &.btn-light {
  556. border-color: var(--input-border);
  557. }
  558. }
  559. .unfollow-btn {
  560. @media (min-width: 768px) {
  561. margin-bottom: 2rem;
  562. }
  563. background-color: rgba(59, 130, 246, 0.7);
  564. }
  565. .bio-wrapper {
  566. margin-bottom: 1rem;
  567. .bio-body {
  568. display: block;
  569. position: relative;
  570. font-size: 12px !important;
  571. white-space: pre-wrap;
  572. .username {
  573. font-size: 12px !important;
  574. }
  575. &.long {
  576. max-height: 80px;
  577. overflow: hidden;
  578. &:after {
  579. content: '';
  580. width: 100%;
  581. height: 100%;
  582. position: absolute;
  583. top: 0;
  584. left: 0;
  585. background: linear-gradient(180deg, transparent 0, rgba(255, 255, 255, .9) 60%, #fff 90%);
  586. z-index: 2;
  587. }
  588. }
  589. p {
  590. margin-bottom: 0 !important;
  591. }
  592. }
  593. .bio-more {
  594. position: relative;
  595. z-index: 3;
  596. }
  597. }
  598. .admin-label {
  599. padding: 1px 5px;
  600. font-size: 12px;
  601. color: #B91C1C;
  602. background: #FEE2E2;
  603. border: 1px solid #FCA5A5;
  604. font-weight: 600;
  605. text-transform: capitalize;
  606. display: inline-block;
  607. border-radius: 8px;
  608. }
  609. .sidebar-sitelinks {
  610. margin-top: 1rem;
  611. justify-content: space-between;
  612. padding: 0;
  613. a {
  614. font-size: 12px;
  615. color: #B8C2CC;
  616. }
  617. .active {
  618. color: #212529;
  619. font-weight: 600;
  620. }
  621. }
  622. .sidebar-attribution {
  623. margin-top: 0.5rem;
  624. font-size: 12px;
  625. color: #B8C2CC !important;
  626. a {
  627. color: #B8C2CC !important;
  628. }
  629. }
  630. .user-card {
  631. align-items: center;
  632. .avatar {
  633. width: 80px;
  634. height: 80px;
  635. border-radius: 15px;
  636. margin-right: 0.8rem;
  637. border: 1px solid #E5E7EB;
  638. @media (min-width: 390px) {
  639. width: 100px;
  640. height: 100px;
  641. }
  642. }
  643. .avatar-update-btn {
  644. position: absolute;
  645. right: 12px;
  646. bottom: 0;
  647. width: 20px;
  648. height: 20px;
  649. background: rgba(255,255,255,0.9);
  650. border: 1px solid #dee2e6 !important;
  651. padding: 0;
  652. border-radius: 50rem;
  653. &-icon {
  654. font-family: 'Font Awesome 5 Free';
  655. font-weight: 400;
  656. -webkit-font-smoothing: antialiased;
  657. display: inline-block;
  658. font-style: normal;
  659. font-variant: normal;
  660. text-rendering: auto;
  661. line-height: 1;
  662. &:before {
  663. content: "\F013";
  664. }
  665. }
  666. }
  667. .username {
  668. font-weight: 600;
  669. font-size: 13px;
  670. margin: 4px 0;
  671. word-break: break-word;
  672. line-height: 12px;
  673. user-select: all;
  674. @media (min-width: 390px) {
  675. margin: 8px 0;
  676. font-size: 16px;
  677. }
  678. }
  679. .display-name {
  680. color: var(--body-color);
  681. line-height: 0.8;
  682. font-size: 20px;
  683. font-weight: 800 !important;
  684. word-break: break-word;
  685. user-select: all;
  686. font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
  687. margin-bottom: 0;
  688. @media (min-width: 390px) {
  689. font-size: 24px;
  690. }
  691. }
  692. .stats {
  693. display: flex;
  694. justify-content: space-between;
  695. flex-direction: row;
  696. margin-top: 0;
  697. margin-bottom: 0;
  698. font-size: 16px;
  699. user-select: none;
  700. .posts-count,
  701. .following-count,
  702. .followers-count {
  703. display: flex;
  704. font-weight: 800;
  705. }
  706. .stats-label {
  707. color: #94a3b8;
  708. font-size: 11px;
  709. margin-top: -5px;
  710. }
  711. }
  712. }
  713. }
  714. </style>