navbar.vue 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978
  1. <template>
  2. <nav class="metro-nav navbar navbar-expand navbar-light navbar-laravel sticky-top shadow-none py-1">
  3. <div class="container-fluid">
  4. <a class="navbar-brand d-flex align-items-center" href="/i/web" title="Logo" style="width:50px">
  5. <img src="/img/pixelfed-icon-color.svg" height="30px" class="px-2" loading="eager" alt="Pixelfed logo">
  6. <span class="font-weight-bold mb-0 d-none d-sm-block" style="font-size:20px;">
  7. {{ brandName }}
  8. </span>
  9. </a>
  10. <div class="collapse navbar-collapse">
  11. <div class="navbar-nav ml-auto">
  12. <!-- <form class="form-inline search-bar" method="get" action="/i/results">
  13. <input class="form-control" name="q" placeholder="Search ..." aria-label="search" autocomplete="off" required style="position: relative;line-height: 0.6;width:100%;min-width: 300px;max-width: 500px;border-radius: 8px;" role="search">
  14. </form> -->
  15. <autocomplete
  16. class="searchbox"
  17. :search="autocompleteSearch"
  18. :placeholder="$t('navmenu.search')"
  19. aria-label="Search"
  20. :get-result-value="getSearchResultValue"
  21. :debounceTime="700"
  22. @submit="onSearchSubmit"
  23. ref="autocomplete">
  24. <template #result="{ result, props }">
  25. <li
  26. v-bind="props"
  27. class="autocomplete-result sr"
  28. >
  29. <div v-if="result.s_type === 'account'" class="media align-items-center my-0">
  30. <img :src="result.avatar" width="40" height="40" class="sr-avatar" style="border-radius: 40px" onerror="this.src='/storage/avatars/default.png?v=0';this.onerror=null;">
  31. <div class="media-body sr-account">
  32. <div class="sr-account-acct" :class="{ compact: result.acct && result.acct.length > 24 }">
  33. &commat;{{ result.acct }}
  34. <b-button
  35. v-if="result.locked"
  36. v-b-tooltip.html
  37. title="Private Account"
  38. variant="link"
  39. size="sm"
  40. class="p-0"
  41. >
  42. <i class="far fa-lock fa-sm text-lighter ml-1"></i>
  43. </b-button>
  44. </div>
  45. <template v-if="result.is_admin">
  46. <div class="sr-account-stats">
  47. <div class="sr-account-stats-followers text-danger font-weight-bold">
  48. Admin
  49. </div>
  50. <div>·</div>
  51. <div class="sr-account-stats-followers font-weight-bold">
  52. <span>{{ formatCount(result.followers_count) }}</span>
  53. <span>Followers</span>
  54. </div>
  55. </div>
  56. </template>
  57. <template v-else>
  58. <template v-if="result.local">
  59. <div class="sr-account-stats">
  60. <div v-if="result.followers_count" class="sr-account-stats-followers font-weight-bold">
  61. <span>{{ formatCount(result.followers_count) }}</span>
  62. <span>Followers</span>
  63. </div>
  64. <div v-if="result.followers_count && result.statuses_count">·</div>
  65. <div v-if="result.statuses_count" class="sr-account-stats-statuses font-weight-bold">
  66. <span>{{ formatCount(result.statuses_count) }}</span>
  67. <span>Posts</span>
  68. </div>
  69. <div v-if="!result.followers_count && result.statuses_count">·</div>
  70. <div class="sr-account-stats-statuses font-weight-bold">
  71. <i class="far fa-clock fa-sm"></i>
  72. <span>{{ timeAgo(result.created_at) }}</span>
  73. </div>
  74. </div>
  75. </template>
  76. <template v-else>
  77. <div class="sr-account-stats">
  78. <div v-if="result.followers_count" class="sr-account-stats-followers font-weight-bold">
  79. <span>{{ formatCount(result.followers_count) }}</span>
  80. <span>Followers</span>
  81. </div>
  82. <div v-if="result.followers_count && result.statuses_count">·</div>
  83. <div v-if="result.statuses_count" class="sr-account-stats-statuses font-weight-bold">
  84. <span>{{ formatCount(result.statuses_count) }}</span>
  85. <span>Posts</span>
  86. </div>
  87. <div v-if="!result.followers_count && result.statuses_count">·</div>
  88. <div v-if="!result.followers_count && !result.statuses_count" class="sr-account-stats-statuses font-weight-bold">
  89. Remote Account
  90. </div>
  91. <div v-if="!result.followers_count && !result.statuses_count">
  92. ·
  93. </div>
  94. <b-button
  95. v-b-tooltip.html
  96. :title="'Joined ' + timeAgo(result.created_at) + ' ago'"
  97. variant="link"
  98. size="sm"
  99. class="sr-account-stats-statuses p-0"
  100. >
  101. <i class="far fa-clock fa-sm"></i>
  102. <span class="font-weight-bold">{{ timeAgo(result.created_at) }}</span>
  103. </b-button>
  104. </div>
  105. </template>
  106. </template>
  107. </div>
  108. </div>
  109. <div v-else-if="result.s_type === 'hashtag'" class="media align-items-center my-0">
  110. <div class="media-icon">
  111. <i class="far fa-hashtag fa-large"></i>
  112. </div>
  113. <div class="media-body sr-tag">
  114. <div class="sr-tag-name" :class="{ compact: result.name && result.name.length > 26 }">
  115. #{{ result.name }}
  116. </div>
  117. <div v-if="result.count && result.count > 100" class="sr-tag-count">
  118. {{ formatCount(result.count) }} {{ result.count == 1 ? 'Post' : 'Posts' }}
  119. </div>
  120. </div>
  121. </div>
  122. <div v-else-if="result.s_type === 'status'" class="media align-items-center my-0">
  123. <img :src="result.account.avatar" width="40" height="40" class="sr-avatar" style="border-radius: 40px" onerror="this.src='/storage/avatars/default.png?v=0';this.onerror=null;">
  124. <div class="media-body sr-post">
  125. <div class="sr-post-acct" :class="{ compact: result.acct && result.acct.length > 26 }">
  126. &commat;{{ truncate(result.account.acct, 20) }}
  127. <b-button
  128. v-if="result.locked"
  129. v-b-tooltip.html
  130. title="Private Account"
  131. variant="link"
  132. size="sm"
  133. class="p-0"
  134. >
  135. <i class="far fa-lock fa-sm text-lighter ml-1"></i>
  136. </b-button>
  137. </div>
  138. <div class="sr-post-action">
  139. <div class="sr-post-action-timestamp">
  140. <i class="far fa-clock fa-sm"></i>
  141. {{ timeAgo(result.created_at)}}
  142. </div>
  143. <div>·</div>
  144. <div class="sr-post-action-label">
  145. Tap to view post
  146. </div>
  147. </div>
  148. </div>
  149. </div>
  150. </li>
  151. </template>
  152. </autocomplete>
  153. </div>
  154. <div class="ml-auto">
  155. <ul class="navbar-nav align-items-center">
  156. <!-- <li class="nav-item px-md-2 d-none d-md-block">
  157. <router-link class="nav-link font-weight-bold text-dark" to="/i/web" title="Home" data-toggle="tooltip" data-placement="bottom">
  158. <i class="far fa-home fa-lg"></i>
  159. <span class="sr-only">Home</span>
  160. </router-link>
  161. </li>
  162. <li class="nav-item px-md-2 d-none d-md-block">
  163. <router-link class="nav-link font-weight-bold text-dark" title="Compose" data-toggle="tooltip" data-placement="bottom" to="/i/web/compose">
  164. <i class="far fa-plus-square fa-lg"></i>
  165. <span class="sr-only">Compose</span>
  166. </router-link>
  167. </li> -->
  168. <!-- <li class="nav-item px-md-2">
  169. <router-link class="nav-link font-weight-bold text-dark" to="/i/web/direct" title="Direct" data-toggle="tooltip" data-placement="bottom">
  170. <i class="far fa-comment-dots fa-lg"></i>
  171. <span class="sr-only">Direct</span>
  172. </router-link>
  173. </li>
  174. <li class="nav-item px-md-2 d-none d-md-block">
  175. <router-link class="nav-link font-weight-bold text-dark fa-layers fa-fw" to="/i/web/notifications" title="Notifications" data-toggle="tooltip" data-placement="bottom">
  176. <i class="far fa-bell fa-lg"></i>
  177. <span class="fa-layers-counter" style="background:Tomato"></span>
  178. <span class="sr-only">Notifications</span>
  179. </router-link>
  180. </li> -->
  181. <li class="nav-item dropdown ml-2">
  182. <a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" title="User Menu">
  183. <i class="d-none far fa-user fa-lg text-dark"></i>
  184. <span class="sr-only">User Menu</span>
  185. <img :src="user.avatar" class="nav-avatar rounded-circle border shadow" width="30" height="30" onerror="this.onerror=null;this.src='/storage/avatars/default.png?v=0';">
  186. </a>
  187. <div class="dropdown-menu dropdown-menu-right shadow" aria-labelledby="navbarDropdown">
  188. <ul class="nav flex-column">
  189. <li class="nav-item nav-icons">
  190. <div class="d-flex justify-content-between align-items-center">
  191. <router-link class="nav-link text-center" to="/i/web">
  192. <div class="icon text-lighter"><i class="far fa-home fa-lg"></i></div>
  193. <div class="small">{{ $t('navmenu.homeFeed') }}</div>
  194. </router-link>
  195. <router-link v-if="hasLocalTimeline" class="nav-link text-center" :to="{ name: 'timeline', params: { scope: 'local' } }">
  196. <div class="icon text-lighter"><i class="fas fa-stream fa-lg"></i></div>
  197. <div class="small">{{ $t('navmenu.localFeed') }}</div>
  198. </router-link>
  199. <router-link v-if="hasNetworkTimeline" class="nav-link text-center" :to="{ name: 'timeline', params: { scope: 'global' } }">
  200. <div class="icon text-lighter"><i class="far fa-globe fa-lg"></i></div>
  201. <div class="small">{{ $t('navmenu.globalFeed') }}</div>
  202. </router-link>
  203. </div>
  204. </li>
  205. <li class="nav-item nav-icons">
  206. <div class="d-flex justify-content-between align-items-center">
  207. <router-link class="nav-link text-center" to="/i/web/discover">
  208. <div class="icon text-lighter"><i class="far fa-compass"></i></div>
  209. <div class="small">{{ $t('navmenu.discover') }}</div>
  210. </router-link>
  211. <router-link class="nav-link text-center" to="/i/web/notifications">
  212. <div class="icon text-lighter">
  213. <i class="far fa-bell"></i>
  214. </div>
  215. <div class="small">
  216. {{ $t('navmenu.notifications') }}
  217. </div>
  218. </router-link>
  219. <router-link class="nav-link text-center px-3" :to="'/i/web/profile/' + user.id">
  220. <div class="icon text-lighter">
  221. <i class="far fa-user"></i>
  222. </div>
  223. <div class="small">{{ $t('navmenu.profile') }}</div>
  224. </router-link>
  225. </div>
  226. <hr class="mb-0" style="margin-top: -5px;opacity: 0.4;" />
  227. </li>
  228. <li class="nav-item">
  229. <router-link class="nav-link" to="/i/web/compose">
  230. <span class="icon text-lighter"><i class="far fa-plus-square"></i></span>
  231. {{ $t('navmenu.compose') }}
  232. </router-link>
  233. </li>
  234. <!-- <li class="nav-item">
  235. <router-link class="nav-link" to="/i/web/discover">
  236. <span class="icon text-lighter"><i class="far fa-compass"></i></span>
  237. {{ $t('navmenu.discover') }}
  238. </router-link>
  239. </li> -->
  240. <li class="nav-item">
  241. <router-link class="nav-link d-flex justify-content-between align-items-center" to="/i/web/direct">
  242. <span>
  243. <span class="icon text-lighter">
  244. <i class="far fa-envelope"></i>
  245. </span>
  246. {{ $t('navmenu.directMessages') }}
  247. </span>
  248. <!-- <span class="badge badge-danger font-weight-light rounded-pill px-2" style="transform:scale(0.86)">99+</span> -->
  249. </router-link>
  250. </li>
  251. <li class="nav-item">
  252. <a class="nav-link" href="/i/web" @click.prevent="openUserInterfaceSettings">
  253. <span class="icon text-lighter"><i class="far fa-brush"></i></span>
  254. UI Settings
  255. </a>
  256. </li>
  257. <!-- <li class="nav-item">
  258. <router-link class="nav-link d-flex justify-content-between align-items-center" to="/i/web/notifications">
  259. <span>
  260. <span class="icon text-lighter">
  261. <i class="far fa-bell"></i>
  262. </span>
  263. {{ $t('navmenu.notifications') }}
  264. </span>
  265. </router-link>
  266. </li> -->
  267. <!-- <li class="nav-item">
  268. <hr class="mt-n1" style="opacity: 0.4;margin-bottom: 0;" />
  269. <router-link class="nav-link" :to="'/i/web/profile/' + user.id">
  270. <span class="icon text-lighter">
  271. <i class="far fa-user"></i>
  272. </span>
  273. {{ $t('navmenu.profile') }}
  274. </router-link>
  275. </li> -->
  276. <li v-if="user.is_admin" class="nav-item">
  277. <hr class="mt-n1" style="opacity: 0.4;margin-bottom: 0;" />
  278. <a class="nav-link" href="/i/admin/dashboard">
  279. <span class="icon text-lighter">
  280. <i class="far fa-tools"></i>
  281. </span>
  282. {{ $t('navmenu.admin') }}
  283. </a>
  284. </li>
  285. <li class="nav-item">
  286. <hr class="mt-n1" style="opacity: 0.4;margin-bottom: 0;" />
  287. <a class="nav-link" href="/">
  288. <span class="icon text-lighter">
  289. <i class="fas fa-chevron-left"></i>
  290. </span>
  291. {{ $t('navmenu.backToPreviousDesign') }}
  292. </a>
  293. </li>
  294. <li class="nav-item">
  295. <hr class="mt-n1" style="opacity: 0.4;margin-bottom: 0;" />
  296. <a class="nav-link" href="/" @click.prevent="logout()">
  297. <span class="icon text-lighter">
  298. <i class="far fa-sign-out"></i>
  299. </span>
  300. {{ $t('navmenu.logout') }}
  301. </a>
  302. </li>
  303. </ul>
  304. </div>
  305. </li>
  306. </ul>
  307. </div>
  308. </div>
  309. </div>
  310. <b-modal
  311. ref="uis"
  312. hide-footer
  313. centered
  314. body-class="p-0 ui-menu"
  315. title="UI Settings">
  316. <div class="list-group list-group-flush">
  317. <div class="list-group-item px-3">
  318. <div class="d-flex justify-content-between align-items-center">
  319. <div>
  320. <p class="font-weight-bold mb-1">Theme</p>
  321. <p class="small text-muted mb-0"></p>
  322. </div>
  323. <div class="btn-group btn-group-sm">
  324. <button
  325. class="btn"
  326. :class="[ uiColorScheme == 'system' ? 'btn-primary' : 'btn-outline-primary']"
  327. @click="toggleUi('system')">
  328. Auto
  329. </button>
  330. <button
  331. class="btn"
  332. :class="[ uiColorScheme == 'light' ? 'btn-primary' : 'btn-outline-primary']"
  333. @click="toggleUi('light')">
  334. Light mode
  335. </button>
  336. <button
  337. class="btn"
  338. :class="[ uiColorScheme == 'dark' ? 'btn-primary' : 'btn-outline-primary']"
  339. @click="toggleUi('dark')">
  340. Dark mode
  341. </button>
  342. </div>
  343. </div>
  344. </div>
  345. <div class="list-group-item px-3">
  346. <div class="d-flex justify-content-between align-items-center">
  347. <div>
  348. <p class="font-weight-bold mb-1">Profile Layout</p>
  349. <p class="small text-muted mb-0"></p>
  350. </div>
  351. <div class="btn-group btn-group-sm">
  352. <button
  353. class="btn"
  354. :class="[ profileLayout == 'grid' ? 'btn-primary' : 'btn-outline-primary']"
  355. @click="toggleProfileLayout('grid')">
  356. Grid
  357. </button>
  358. <button
  359. class="btn"
  360. :class="[ profileLayout == 'masonry' ? 'btn-primary' : 'btn-outline-primary']"
  361. @click="toggleProfileLayout('masonry')">
  362. Masonry
  363. </button>
  364. <button
  365. class="btn"
  366. :class="[ profileLayout == 'feed' ? 'btn-primary' : 'btn-outline-primary']"
  367. @click="toggleProfileLayout('feed')">
  368. Feed
  369. </button>
  370. </div>
  371. </div>
  372. </div>
  373. <div class="list-group-item px-3">
  374. <div class="d-flex justify-content-between align-items-center">
  375. <div>
  376. <p class="font-weight-bold mb-0">Compact Media Previews</p>
  377. </div>
  378. <b-form-checkbox v-model="fixedHeight" switch size="lg" />
  379. </div>
  380. </div>
  381. <div class="list-group-item px-3">
  382. <div class="d-flex justify-content-between align-items-center">
  383. <div>
  384. <p class="font-weight-bold mb-0">Load Comments</p>
  385. </div>
  386. <b-form-checkbox v-model="autoloadComments" switch size="lg" />
  387. </div>
  388. </div>
  389. <div class="list-group-item px-3">
  390. <div class="d-flex justify-content-between align-items-center">
  391. <div>
  392. <p class="font-weight-bold mb-0">Hide Counts & Stats</p>
  393. </div>
  394. <b-form-checkbox v-model="hideCounts" switch size="lg" />
  395. </div>
  396. </div>
  397. </div>
  398. </b-modal>
  399. </nav>
  400. </template>
  401. <script type="text/javascript">
  402. import Autocomplete from '@trevoreyre/autocomplete-vue'
  403. import '@trevoreyre/autocomplete-vue/dist/style.css'
  404. export default {
  405. components: {
  406. Autocomplete
  407. },
  408. data() {
  409. return {
  410. brandName: 'pixelfed',
  411. user: window._sharedData.user,
  412. profileLayoutModel: 'grid',
  413. hasLocalTimeline: true,
  414. hasNetworkTimeline: false
  415. }
  416. },
  417. computed: {
  418. profileLayout: {
  419. get() {
  420. return this.$store.state.profileLayout;
  421. },
  422. set(val) {
  423. this.$store.commit('setProfileLayout', val);
  424. }
  425. },
  426. hideCounts: {
  427. get() {
  428. return this.$store.state.hideCounts;
  429. },
  430. set(val) {
  431. this.$store.commit('setHideCounts', val);
  432. }
  433. },
  434. autoloadComments: {
  435. get() {
  436. return this.$store.state.autoloadComments;
  437. },
  438. set(val) {
  439. this.$store.commit('setAutoloadComments', val);
  440. }
  441. },
  442. newReactions: {
  443. get() {
  444. return this.$store.state.newReactions;
  445. },
  446. set(val) {
  447. this.$store.commit('setNewReactions', val);
  448. }
  449. },
  450. fixedHeight: {
  451. get() {
  452. return this.$store.state.fixedHeight;
  453. },
  454. set(val) {
  455. this.$store.commit('setFixedHeight', val);
  456. }
  457. },
  458. uiColorScheme: {
  459. get() {
  460. return this.$store.state.colorScheme;
  461. },
  462. set(val) {
  463. this.$store.commit('setColorScheme', val);
  464. }
  465. }
  466. },
  467. mounted() {
  468. if(window.App.config.features.hasOwnProperty('timelines')) {
  469. this.hasLocalTimeline = App.config.features.timelines.local;
  470. this.hasNetworkTimeline = App.config.features.timelines.network;
  471. }
  472. let u = new URLSearchParams(window.location.search);
  473. if(u.has('q') && u.get('q') && u.has('src') && u.get('src') === 'ac') {
  474. this.$refs.autocomplete.setValue(u.get('q'));
  475. setTimeout(() => {
  476. let ai = document.querySelector('.autocomplete-input')
  477. ai.focus();
  478. }, 1000)
  479. }
  480. this.brandName = window.App.config.site.name;
  481. },
  482. methods: {
  483. autocompleteSearch(q) {
  484. if (!q || q.length < 2) {
  485. return [];
  486. }
  487. let resolve = q.startsWith('https://') || q.startsWith('@');
  488. return axios.get('/api/v2/search', {
  489. params: {
  490. q: q,
  491. resolve: resolve,
  492. '_pe': 1
  493. }
  494. }).then(res => {
  495. let results = [];
  496. let accounts = res.data.accounts.map(res => {
  497. let account = res;
  498. account.s_type = 'account';
  499. return account;
  500. });
  501. let hashtags = res.data.hashtags.map(res => {
  502. let tag = res;
  503. tag.s_type = 'hashtag';
  504. return tag;
  505. })
  506. // let statuses = res.data.statuses.map(res => {
  507. // let status = res;
  508. // status.s_type = 'status';
  509. // return status;
  510. // });
  511. // results.push(...statuses.slice(0,5));
  512. results.push(...accounts.slice(0,5));
  513. results.push(...hashtags.slice(0,5));
  514. if(res.data.statuses) {
  515. if(Array.isArray(res.data.statuses)) {
  516. let statuses = res.data.statuses.map(res => {
  517. let status = res;
  518. status.s_type = 'status';
  519. return status;
  520. });
  521. results.push(...statuses);
  522. } else {
  523. if(q === res.data.statuses.url) {
  524. this.$refs.autocomplete.value = '';
  525. this.$router.push({
  526. name: 'post',
  527. path: `/i/web/post/${res.data.statuses.id}`,
  528. params: {
  529. id: res.data.statuses.id,
  530. cachedStatus: res.data.statuses,
  531. cachedProfile: this.user
  532. }
  533. });
  534. }
  535. }
  536. }
  537. return results;
  538. });
  539. },
  540. getSearchResultValue(result) {
  541. return result;
  542. },
  543. onSearchSubmit(result) {
  544. if (result.length < 1) {
  545. return;
  546. }
  547. this.$refs.autocomplete.value = '';
  548. switch(result.s_type) {
  549. case 'account':
  550. // this.$router.push({
  551. // name: 'profile',
  552. // path: `/i/web/profile/${result.id}`,
  553. // params: {
  554. // id: result.id,
  555. // cachedProfile: result,
  556. // cachedUser: this.user
  557. // }
  558. // });
  559. location.href = `/i/web/profile/${result.id}`;
  560. break;
  561. case 'hashtag':
  562. // this.$router.push({
  563. // name: 'hashtag',
  564. // path: `/i/web/hashtag/${result.name}`,
  565. // params: {
  566. // id: result.name,
  567. // }
  568. // });
  569. location.href = `/i/web/hashtag/${result.name}`;
  570. break;
  571. case 'status':
  572. // this.$router.push({
  573. // name: 'post',
  574. // path: `/i/web/post/${result.id}`,
  575. // params: {
  576. // id: result.id,
  577. // }
  578. // });
  579. location.href = `/i/web/post/${result.id}`;
  580. break;
  581. }
  582. },
  583. truncate(text, limit = 30) {
  584. if(text.length <= limit) {
  585. return text;
  586. }
  587. return text.slice(0, limit) + '...'
  588. },
  589. timeAgo(ts) {
  590. return window.App.util.format.timeAgo(ts);
  591. },
  592. formatCount(val) {
  593. if(!val) {
  594. return 0;
  595. }
  596. return new Intl.NumberFormat('en-CA', { notation: 'compact' , compactDisplay: "short" }).format(val);
  597. },
  598. logout() {
  599. axios.post('/logout')
  600. .then(res => {
  601. location.href = '/';
  602. }).catch(err => {
  603. location.href = '/';
  604. })
  605. },
  606. openUserInterfaceSettings() {
  607. event.currentTarget.blur();
  608. this.$refs.uis.show();
  609. },
  610. toggleUi(ui) {
  611. event.currentTarget.blur();
  612. this.uiColorScheme = ui;
  613. },
  614. toggleProfileLayout(layout) {
  615. event.currentTarget.blur();
  616. this.profileLayout = layout;
  617. }
  618. }
  619. }
  620. </script>
  621. <style lang="scss">
  622. .metro-nav {
  623. z-index: 4;
  624. .dropdown-menu {
  625. min-width: 18rem;
  626. padding: 0;
  627. border: none;
  628. .nav {
  629. overflow: auto;
  630. }
  631. .nav-item {
  632. .nav-link {
  633. font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
  634. font-weight: 500;
  635. color: rgba(156,163,175, 1);
  636. padding-left: 14px;
  637. margin-bottom: 5px;
  638. .icon {
  639. display: inline-block;
  640. width: 40px;
  641. text-align: center;
  642. }
  643. }
  644. .router-link-exact-active {
  645. color: var(--primary);
  646. font-weight: 700;
  647. padding-left: 14px;
  648. &:not(.text-center) {
  649. padding-left: 10px;
  650. border-left: 4px solid var(--primary);
  651. }
  652. .icon {
  653. color: var(--primary) !important;
  654. }
  655. }
  656. &.nav-icons {
  657. .small {
  658. font-weight: 700 !important;
  659. }
  660. }
  661. &:is(:last-child) {
  662. .nav-link {
  663. margin-bottom: 0;
  664. border-bottom-left-radius: 15px;
  665. border-bottom-right-radius: 15px;
  666. }
  667. }
  668. }
  669. }
  670. .fa-layers {
  671. display: inline-block;
  672. height: 1em;
  673. position: relative;
  674. text-align: center;
  675. vertical-align: -0.125em;
  676. width: 1em;
  677. .fa-layers-counter {
  678. background-color: #ff253a;
  679. border-radius: 1em;
  680. -webkit-box-sizing: border-box;
  681. box-sizing: border-box;
  682. color: #fff;
  683. height: 1.5em;
  684. line-height: 1;
  685. max-width: 5em;
  686. min-width: 1.5em;
  687. overflow: hidden;
  688. padding: 0.25em;
  689. right: 0;
  690. text-overflow: ellipsis;
  691. top: 0;
  692. transform: scale(.5);
  693. -webkit-transform-origin: top right;
  694. transform-origin: top right;
  695. display: inline-block;
  696. position: absolute;
  697. margin-right: -5px;
  698. margin-top: -10px;
  699. }
  700. .far {
  701. bottom: 0;
  702. left: 0;
  703. margin: auto;
  704. position: absolute;
  705. right: 0;
  706. top: 0;
  707. }
  708. }
  709. .searchbox {
  710. @media (min-width: 768px) {
  711. width: 300px;
  712. }
  713. }
  714. .nav-avatar {
  715. @media (min-width: 768px) {
  716. width: 50px;
  717. height: 50px;
  718. }
  719. }
  720. .autocomplete[data-loading="true"]::after {
  721. content: "";
  722. border-right: 3px solid var(--primary);
  723. }
  724. .autocomplete {
  725. &-input {
  726. padding: 0.375rem 0.75rem 0.375rem 2.6rem;
  727. background-color: var(--light-gray);
  728. font-size: 0.9rem;
  729. border-radius: 50rem;
  730. background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjQjhDMkNDIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCI+PGNpcmNsZSBjeD0iMTEiIGN5PSIxMSIgcj0iNSIvPjxwYXRoIGQ9Ik0xOSAxOWwtNC00Ii8+PC9zdmc+");
  731. box-shadow: 0 0.125rem 0.25rem rgb(0 0 0 / 8%) !important;
  732. }
  733. &-result {
  734. background-image: none;
  735. padding: 10px 12px;
  736. cursor: pointer;
  737. &-list {
  738. box-shadow: 0 0.125rem 0.45rem var(--border-color);
  739. -ms-overflow-style: none;
  740. scrollbar-width: none;
  741. &::-webkit-scrollbar {
  742. width: 0 !important
  743. }
  744. }
  745. .media-icon {
  746. display: flex;
  747. justify-content: center;
  748. align-items: center;
  749. width: 40px;
  750. height: 40px;
  751. margin-right: 12px;
  752. background: var(--light-gray);
  753. border: 1px solid var(--input-border);
  754. border-radius: 40px;
  755. box-shadow: 0 0.125rem 0.25rem rgb(0 0 0 / 8%);
  756. }
  757. }
  758. }
  759. .sr {
  760. &:not(:last-child) {
  761. border-bottom: 1px solid var(--input-border);
  762. }
  763. &-avatar {
  764. margin-right: 12px;
  765. box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075)
  766. }
  767. &-account {
  768. display: flex;
  769. flex-direction: column;
  770. align-items: flex-start;
  771. justify-content: center;
  772. gap: 3px;
  773. &-acct {
  774. word-wrap: break-word;
  775. word-break: break-all;
  776. font-size: 14px;
  777. line-height: 18px;
  778. font-weight: bold;
  779. color: var(--dark);
  780. margin-right: 1rem;
  781. &.compact {
  782. font-size: 12px;
  783. }
  784. }
  785. &-stats {
  786. display: flex;
  787. align-items: center;
  788. gap: 5px;
  789. line-height: 14px;
  790. &-followers,
  791. &-statuses {
  792. font-size: 11px;
  793. font-weight: 500;
  794. color: var(--text-lighter);
  795. }
  796. }
  797. }
  798. &-tag {
  799. display: flex;
  800. flex-direction: column;
  801. align-items: flex-start;
  802. justify-content: center;
  803. gap: 3px;
  804. &-name {
  805. word-wrap: break-word;
  806. word-break: break-all;
  807. font-size: 14px;
  808. line-height: 18px;
  809. font-weight: bold;
  810. color: var(--dark);
  811. margin-right: 1rem;
  812. &.compact {
  813. font-size: 12px;
  814. }
  815. }
  816. &-count {
  817. font-size: 11px;
  818. line-height: 13px;
  819. color: var(--text-lighter);
  820. font-weight: bold;
  821. }
  822. }
  823. &-post {
  824. display: flex;
  825. flex-direction: column;
  826. align-items: flex-start;
  827. justify-content: center;
  828. gap: 3px;
  829. &-acct {
  830. font-size: 14px;
  831. line-height: 18px;
  832. font-weight: bold;
  833. color: var(--dark);
  834. }
  835. &-action {
  836. display: flex;
  837. font-size: 11px;
  838. line-height: 14px;
  839. color: var(--text-lighter);
  840. font-weight: 500;
  841. gap: 3px;
  842. align-items: center;
  843. &-timestamp {
  844. font-weight: 700;
  845. }
  846. &-label {
  847. font-weight: 700;
  848. }
  849. }
  850. }
  851. }
  852. }
  853. .force-dark-mode {
  854. .autocomplete-result-list {
  855. border-color: var(--input-border);
  856. }
  857. .autocomplete-result:hover, .autocomplete-result[aria-selected=true] {
  858. box-shadow: 0;
  859. background-color: rgba(255, 255, 255, .1);
  860. }
  861. .autocomplete[data-loading="true"]::after {
  862. content: "";
  863. border: 3px solid rgba(255, 255, 255, 0.22);
  864. border-right: 3px solid var(--primary);
  865. }
  866. }
  867. </style>