spa.js 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891
  1. require('./polyfill');
  2. import Vue from 'vue';
  3. window.Vue = Vue;
  4. import VueRouter from "vue-router";
  5. import Vuex from "vuex";
  6. import { sync } from "vuex-router-sync";
  7. import BootstrapVue from 'bootstrap-vue'
  8. import InfiniteLoading from 'vue-infinite-loading';
  9. import Loading from 'vue-loading-overlay';
  10. import VueTimeago from 'vue-timeago';
  11. import VueCarousel from 'vue-carousel';
  12. import VueBlurHash from 'vue-blurhash';
  13. import VueMasonry from 'vue-masonry-css';
  14. import VueI18n from 'vue-i18n';
  15. window.pftxt = require('twitter-text');
  16. import 'vue-blurhash/dist/vue-blurhash.css'
  17. window.filesize = require('filesize');
  18. import swal from 'sweetalert';
  19. window._ = require('lodash');
  20. window.Popper = require('popper.js').default;
  21. window.pixelfed = window.pixelfed || {};
  22. window.$ = window.jQuery = require('jquery');
  23. require('bootstrap');
  24. window.axios = require('axios');
  25. window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
  26. require('readmore-js');
  27. window.blurhash = require("blurhash");
  28. $('[data-toggle="tooltip"]').tooltip()
  29. let token = document.head.querySelector('meta[name="csrf-token"]');
  30. if (token) {
  31. window.axios.defaults.headers.common['X-CSRF-TOKEN'] = token.content;
  32. } else {
  33. console.error('CSRF token not found.');
  34. }
  35. Vue.use(VueRouter);
  36. Vue.use(Vuex);
  37. Vue.use(VueBlurHash);
  38. Vue.use(VueCarousel);
  39. Vue.use(BootstrapVue);
  40. Vue.use(InfiniteLoading);
  41. Vue.use(Loading);
  42. Vue.use(VueMasonry);
  43. Vue.use(VueI18n);
  44. Vue.use(VueTimeago, {
  45. name: 'Timeago',
  46. locale: 'en'
  47. });
  48. Vue.component(
  49. 'navbar',
  50. require('./../components/partials/navbar.vue').default
  51. );
  52. Vue.component(
  53. 'notification-card',
  54. require('./components/NotificationCard.vue').default
  55. );
  56. Vue.component(
  57. 'photo-presenter',
  58. require('./../components/presenter/PhotoPresenter.vue').default
  59. );
  60. Vue.component(
  61. 'video-presenter',
  62. require('./../components/presenter/VideoPresenter.vue').default
  63. );
  64. Vue.component(
  65. 'photo-album-presenter',
  66. require('./../components/presenter/PhotoAlbumPresenter.vue').default
  67. );
  68. Vue.component(
  69. 'video-album-presenter',
  70. require('./../components/presenter/VideoAlbumPresenter.vue').default
  71. );
  72. Vue.component(
  73. 'mixed-album-presenter',
  74. require('./../components/presenter/MixedAlbumPresenter.vue').default
  75. );
  76. Vue.component(
  77. 'post-menu',
  78. require('./components/PostMenu.vue').default
  79. );
  80. // Vue.component(
  81. // 'announcements-card',
  82. // require('./components/AnnouncementsCard.vue').default
  83. // );
  84. Vue.component(
  85. 'story-component',
  86. require('./components/StoryTimelineComponent.vue').default
  87. );
  88. const HomeComponent = () => import(/* webpackChunkName: "home.chunk" */ "./../components/Home.vue");
  89. const ComposeComponent = () => import(/* webpackChunkName: "compose.chunk" */ "./../components/Compose.vue");
  90. const PostComponent = () => import(/* webpackChunkName: "post.chunk" */ "./../components/Post.vue");
  91. const ProfileComponent = () => import(/* webpackChunkName: "profile.chunk" */ "./../components/Profile.vue");
  92. const MemoriesComponent = () => import(/* webpackChunkName: "discover~memories.chunk" */ "./../components/discover/Memories.vue");
  93. const MyHashtagComponent = () => import(/* webpackChunkName: "discover~myhashtags.chunk" */ "./../components/discover/Hashtags.vue");
  94. const AccountInsightsComponent = () => import(/* webpackChunkName: "daci.chunk" */ "./../components/discover/Insights.vue");
  95. const DiscoverFindFriendsComponent = () => import(/* webpackChunkName: "discover~findfriends.chunk" */ "./../components/discover/FindFriends.vue");
  96. const DiscoverServerFeedComponent = () => import(/* webpackChunkName: "discover~serverfeed.chunk" */ "./../components/discover/ServerFeed.vue");
  97. const DiscoverSettingsComponent = () => import(/* webpackChunkName: "discover~settings.chunk" */ "./../components/discover/Settings.vue");
  98. const DiscoverComponent = () => import(/* webpackChunkName: "discover.chunk" */ "./../components/Discover.vue");
  99. const NotificationsComponent = () => import(/* webpackChunkName: "notifications.chunk" */ "./../components/Notifications.vue");
  100. const DirectComponent = () => import(/* webpackChunkName: "dms.chunk" */ "./../components/Direct.vue");
  101. const DirectMessageComponent = () => import(/* webpackChunkName: "dms~message.chunk" */ "./../components/DirectMessage.vue");
  102. const ProfileFollowersComponent = () => import(/* webpackChunkName: "profile~followers.bundle" */ "./../components/ProfileFollowers.vue");
  103. const ProfileFollowingComponent = () => import(/* webpackChunkName: "profile~following.bundle" */ "./../components/ProfileFollowing.vue");
  104. const HashtagComponent = () => import(/* webpackChunkName: "discover~hashtag.bundle" */ "./../components/Hashtag.vue");
  105. const NotFoundComponent = () => import(/* webpackChunkName: "error404.bundle" */ "./../components/NotFound.vue");
  106. // const HelpComponent = () => import(/* webpackChunkName: "help.bundle" */ "./../components/HelpComponent.vue");
  107. // const KnowledgebaseComponent = () => import(/* webpackChunkName: "kb.bundle" */ "./../components/Knowledgebase.vue");
  108. // const AboutComponent = () => import(/* webpackChunkName: "about.bundle" */ "./../components/About.vue");
  109. // const ContactComponent = () => import(/* webpackChunkName: "contact.bundle" */ "./../components/Contact.vue");
  110. const LanguageComponent = () => import(/* webpackChunkName: "i18n.bundle" */ "./../components/Language.vue");
  111. // const PrivacyComponent = () => import(/* webpackChunkName: "static~privacy.bundle" */ "./../components/Privacy.vue");
  112. // const TermsComponent = () => import(/* webpackChunkName: "static~tos.bundle" */ "./../components/Terms.vue");
  113. const ChangelogComponent = () => import(/* webpackChunkName: "changelog.bundle" */ "./../components/Changelog.vue");
  114. // import LiveComponent from "./../components/Live.vue";
  115. // import LivestreamsComponent from "./../components/Livestreams.vue";
  116. // import LivePlayerComponent from "./../components/LivePlayer.vue";
  117. // import LiveHelpComponent from "./../components/LiveHelp.vue";
  118. // import DriveComponent from "./../components/Drive.vue";
  119. // import SettingsComponent from "./../components/Settings.vue";
  120. // import ProfileComponent from "./components/ProfileNext.vue";
  121. // import VideosComponent from "./../components/Videos.vue";
  122. import GroupsComponent from "./../components/Groups.vue";
  123. import GroupFeedComponent from "./../components/GroupFeed.vue";
  124. import GroupDiscoverComponent from "./../components/GroupDiscover.vue";
  125. import GroupJoinsComponent from "./../components/GroupJoins.vue";
  126. import GroupNotificationsComponent from "./../components/GroupNotifications.vue";
  127. import GroupSearchComponent from "./../components/GroupSearch.vue";
  128. const CreateGroupComponent = () => import(/* webpackChunkName: "group.create" */ "./../components/GroupCreate.vue");
  129. const router = new VueRouter({
  130. mode: "history",
  131. linkActiveClass: "active",
  132. routes: [
  133. {
  134. path: "/i/web/timeline/:scope",
  135. name: 'timeline',
  136. component: HomeComponent,
  137. props: true
  138. },
  139. // {
  140. // path: "/i/web/timeline/local",
  141. // component: LocalTimeline
  142. // },
  143. // {
  144. // path: "/i/web/timeline/global",
  145. // component: GlobalTimeline
  146. // },
  147. // {
  148. // path: "/i/web/drive",
  149. // name: 'drive',
  150. // component: DriveComponent,
  151. // props: true
  152. // },
  153. {
  154. path: "/groups/feed",
  155. name: 'groups',
  156. component: GroupFeedComponent,
  157. },
  158. {
  159. path: "/groups/joins",
  160. name: 'groupjoins',
  161. component: GroupJoinsComponent,
  162. },
  163. {
  164. path: "/groups/discover",
  165. name: 'groupdiscover',
  166. component: GroupDiscoverComponent,
  167. props: true
  168. },
  169. {
  170. path: "/groups/notifications",
  171. name: 'groupnotify',
  172. component: GroupNotificationsComponent,
  173. },
  174. {
  175. path: "/groups/search",
  176. name: 'groupsearch',
  177. component: GroupSearchComponent,
  178. },
  179. {
  180. path: "/groups/create",
  181. name: 'groupscreate',
  182. component: CreateGroupComponent,
  183. },
  184. // {
  185. // path: "/i/web/groups",
  186. // name: 'groups',
  187. // component: GroupsComponent,
  188. // props: true
  189. // },
  190. {
  191. path: "/groups/:gid/p/:sid",
  192. component: () => import(/* webpackChunkName: "groups-post" */ './../components/GroupPost.vue'),
  193. props: true
  194. },
  195. {
  196. path: "/groups/:gid/user/:pid",
  197. component: () => import(/* webpackChunkName: "groups-profile" */ './../components/GroupProfile.vue'),
  198. props: true
  199. },
  200. {
  201. path: "/groups/:groupId/about",
  202. component: () => import(/* webpackChunkName: "groups-page-about" */ './../components/groups/Page/GroupAbout.vue'),
  203. props: true
  204. },
  205. {
  206. path: "/groups/:groupId/topics",
  207. component: () => import(/* webpackChunkName: "groups-page-topics" */ './../components/groups/Page/GroupTopics.vue'),
  208. props: true
  209. },
  210. {
  211. path: "/groups/:groupId/members",
  212. component: () => import(/* webpackChunkName: "groups-page-members" */ './../components/groups/Page/GroupMembers.vue'),
  213. props: true
  214. },
  215. {
  216. path: "/groups/:groupId/media",
  217. component: () => import(/* webpackChunkName: "groups-page-media" */ './../components/groups/Page/GroupMedia.vue'),
  218. props: true
  219. },
  220. {
  221. path: "/groups/:groupId",
  222. component: () => import(/* webpackChunkName: "groups-page" */ './../components/GroupPage.vue'),
  223. props: true
  224. },
  225. {
  226. path: "/i/web/post/:id",
  227. name: 'post',
  228. component: PostComponent,
  229. props: true
  230. },
  231. // {
  232. // path: "/i/web/profile/:id/live",
  233. // component: LivePlayerComponent,
  234. // props: true
  235. // },
  236. {
  237. path: "/i/web/profile/:id/followers",
  238. name: 'profile-followers',
  239. component: ProfileFollowersComponent,
  240. props: true
  241. },
  242. {
  243. path: "/i/web/profile/:id/following",
  244. name: 'profile-following',
  245. component: ProfileFollowingComponent,
  246. props: true
  247. },
  248. {
  249. path: "/i/web/profile/:id",
  250. name: 'profile',
  251. component: ProfileComponent,
  252. props: true
  253. },
  254. // {
  255. // path: "/i/web/videos",
  256. // component: VideosComponent
  257. // },
  258. {
  259. path: "/i/web/discover",
  260. component: DiscoverComponent
  261. },
  262. // {
  263. // path: "/i/web/stories",
  264. // component: HomeComponent
  265. // },
  266. // {
  267. // path: "/i/web/settings/*",
  268. // component: SettingsComponent,
  269. // props: true
  270. // },
  271. // {
  272. // path: "/i/web/settings",
  273. // component: SettingsComponent
  274. // },
  275. {
  276. path: "/i/web/compose",
  277. component: ComposeComponent
  278. },
  279. {
  280. path: "/i/web/notifications",
  281. component: NotificationsComponent
  282. },
  283. {
  284. path: "/i/web/direct/thread/:accountId",
  285. component: DirectMessageComponent,
  286. props: true
  287. },
  288. {
  289. path: "/i/web/direct",
  290. component: DirectComponent
  291. },
  292. // {
  293. // path: "/i/web/kb/:id",
  294. // name: "kb",
  295. // component: KnowledgebaseComponent,
  296. // props: true
  297. // },
  298. {
  299. path: "/i/web/hashtag/:id",
  300. name: "hashtag",
  301. component: HashtagComponent,
  302. props: true
  303. },
  304. // {
  305. // path: "/i/web/help",
  306. // component: HelpComponent
  307. // },
  308. // {
  309. // path: "/i/web/about",
  310. // component: AboutComponent
  311. // },
  312. // {
  313. // path: "/i/web/contact",
  314. // component: ContactComponent
  315. // },
  316. {
  317. path: "/i/web/language",
  318. component: LanguageComponent
  319. },
  320. // {
  321. // path: "/i/web/privacy",
  322. // component: PrivacyComponent
  323. // },
  324. // {
  325. // path: "/i/web/terms",
  326. // component: TermsComponent
  327. // },
  328. {
  329. path: "/i/web/whats-new",
  330. component: ChangelogComponent
  331. },
  332. {
  333. path: "/i/web/discover/my-memories",
  334. component: MemoriesComponent
  335. },
  336. {
  337. path: "/i/web/discover/my-hashtags",
  338. component: MyHashtagComponent
  339. },
  340. {
  341. path: "/i/web/discover/account-insights",
  342. component: AccountInsightsComponent
  343. },
  344. {
  345. path: "/i/web/discover/find-friends",
  346. component: DiscoverFindFriendsComponent
  347. },
  348. {
  349. path: "/i/web/discover/server-timelines",
  350. component: DiscoverServerFeedComponent
  351. },
  352. {
  353. path: "/i/web/discover/settings",
  354. component: DiscoverSettingsComponent
  355. },
  356. // {
  357. // path: "/i/web/livestreams",
  358. // component: LivestreamsComponent
  359. // },
  360. // {
  361. // path: "/i/web/live/help",
  362. // component: LiveHelpComponent
  363. // },
  364. // {
  365. // path: "/i/web/live/player",
  366. // component: LivePlayerComponent
  367. // },
  368. // {
  369. // path: "/i/web/live",
  370. // component: LiveComponent
  371. // },
  372. {
  373. path: "/i/web",
  374. component: HomeComponent,
  375. props: true
  376. },
  377. {
  378. path: "/i/web/*",
  379. component: NotFoundComponent,
  380. props: true
  381. },
  382. ],
  383. scrollBehavior(to, from, savedPosition) {
  384. if (to.hash) {
  385. return {
  386. selector: `[id='${to.hash.slice(1)}']`
  387. };
  388. } else {
  389. return { x: 0, y: 0 };
  390. }
  391. }
  392. });
  393. function lss(name, def) {
  394. let key = 'pf_m2s.' + name;
  395. let ls = window.localStorage;
  396. if(ls.getItem(key)) {
  397. let val = ls.getItem(key);
  398. if(['pl', 'color-scheme'].includes(name)) {
  399. return val;
  400. }
  401. return ['true', true].includes(val);
  402. }
  403. return def;
  404. }
  405. const store = new Vuex.Store({
  406. state: {
  407. version: 1,
  408. hideCounts: lss('hc', false),
  409. autoloadComments: lss('ac', true),
  410. newReactions: lss('nr', true),
  411. fixedHeight: lss('fh', false),
  412. profileLayout: lss('pl', 'grid'),
  413. showDMPrivacyWarning: lss('dmpwarn', true),
  414. relationships: {},
  415. emoji: [],
  416. colorScheme: lss('color-scheme', 'system'),
  417. },
  418. getters: {
  419. getVersion: state => {
  420. return state.version;
  421. },
  422. getHideCounts: state => {
  423. return state.hideCounts;
  424. },
  425. getAutoloadComments: state => {
  426. return state.autoloadComments;
  427. },
  428. getNewReactions: state => {
  429. return state.newReactions;
  430. },
  431. getFixedHeight: state => {
  432. return state.fixedHeight;
  433. },
  434. getProfileLayout: state => {
  435. return state.profileLayout;
  436. },
  437. getRelationship: (state) => (id) => {
  438. // let rel = state.relationships[id];
  439. // if(!rel || !rel.hasOwnProperty('id')) {
  440. // return axios.get('/api/pixelfed/v1/accounts/relationships', {
  441. // params: {
  442. // 'id[]': id
  443. // }
  444. // })
  445. // .then(res => {
  446. // let relationship = res.data;
  447. // // Vue.set(state.relationships, relationship.id, relationship);
  448. // state.commit('updateRelationship', res.data[0]);
  449. // return res.data[0];
  450. // })
  451. // .catch(err => {
  452. // return {};
  453. // })
  454. // } else {
  455. // return state.relationships[id];
  456. // }
  457. return state.relationships[id];
  458. },
  459. getCustomEmoji: state => {
  460. return state.emoji;
  461. },
  462. getColorScheme: state => {
  463. return state.colorScheme;
  464. },
  465. getShowDMPrivacyWarning: state => {
  466. return state.showDMPrivacyWarning;
  467. }
  468. },
  469. mutations: {
  470. setVersion(state, value) {
  471. state.version = value;
  472. },
  473. setHideCounts(state, value) {
  474. localStorage.setItem('pf_m2s.hc', value);
  475. state.hideCounts = value;
  476. },
  477. setAutoloadComments(state, value) {
  478. localStorage.setItem('pf_m2s.ac', value);
  479. state.autoloadComments = value;
  480. },
  481. setNewReactions(state, value) {
  482. localStorage.setItem('pf_m2s.nr', value);
  483. state.newReactions = value;
  484. },
  485. setFixedHeight(state, value) {
  486. localStorage.setItem('pf_m2s.fh', value);
  487. state.fixedHeight = value;
  488. },
  489. setProfileLayout(state, value) {
  490. localStorage.setItem('pf_m2s.pl', value);
  491. state.profileLayout = value;
  492. },
  493. updateRelationship(state, relationships) {
  494. relationships.forEach((relationship) => {
  495. Vue.set(state.relationships, relationship.id, relationship)
  496. })
  497. },
  498. updateCustomEmoji(state, emojis) {
  499. state.emoji = emojis;
  500. },
  501. setColorScheme(state, value) {
  502. if(state.colorScheme == value) {
  503. return;
  504. }
  505. localStorage.setItem('pf_m2s.color-scheme', value);
  506. state.colorScheme = value;
  507. const name = value == 'system' ? '' : (value == 'light' ? 'force-light-mode' : 'force-dark-mode');
  508. document.querySelector("body").className = name;
  509. if(name != 'system') {
  510. const payload = name == 'force-dark-mode' ? { dark_mode: 'on' } : {};
  511. axios.post('/settings/labs', payload);
  512. }
  513. },
  514. setShowDMPrivacyWarning(state, value) {
  515. localStorage.setItem('pf_m2s.dmpwarn', value);
  516. state.showDMPrivacyWarning = value;
  517. }
  518. },
  519. });
  520. let i18nMessages = {
  521. en: require('./i18n/en.json'),
  522. ar: require('./i18n/ar.json'),
  523. ca: require('./i18n/ca.json'),
  524. de: require('./i18n/de.json'),
  525. el: require('./i18n/el.json'),
  526. es: require('./i18n/es.json'),
  527. eu: require('./i18n/eu.json'),
  528. fr: require('./i18n/fr.json'),
  529. he: require('./i18n/he.json'),
  530. gd: require('./i18n/gd.json'),
  531. gl: require('./i18n/gl.json'),
  532. id: require('./i18n/id.json'),
  533. it: require('./i18n/it.json'),
  534. ja: require('./i18n/ja.json'),
  535. nl: require('./i18n/nl.json'),
  536. pl: require('./i18n/pl.json'),
  537. pt: require('./i18n/pt.json'),
  538. ru: require('./i18n/ru.json'),
  539. uk: require('./i18n/uk.json'),
  540. vi: require('./i18n/vi.json'),
  541. };
  542. let locale = document.querySelector('html').getAttribute('lang');
  543. const i18n = new VueI18n({
  544. locale: locale, // set locale
  545. fallbackLocale: 'en',
  546. messages: i18nMessages
  547. });
  548. sync(store, router);
  549. const App = new Vue({
  550. el: '#content',
  551. i18n,
  552. router,
  553. store
  554. });
  555. axios.get('/api/v1/custom_emojis')
  556. .then(res => {
  557. if(res && res.data && res.data.length) {
  558. store.commit('updateCustomEmoji', res.data);
  559. }
  560. });
  561. if(store.state.colorScheme) {
  562. const name = store.state.colorScheme == 'system' ? '' : (store.state.colorScheme == 'light' ? 'force-light-mode' : 'force-dark-mode');
  563. if(name != 'system') {
  564. document.querySelector("body").className = name;
  565. }
  566. }
  567. pixelfed.readmore = () => {
  568. $('.read-more').each(function(k,v) {
  569. let el = $(this);
  570. let attr = el.attr('data-readmore');
  571. if(typeof attr !== typeof undefined && attr !== false) {
  572. return;
  573. }
  574. el.readmore({
  575. collapsedHeight: 45,
  576. heightMargin: 48,
  577. moreLink: '<a href="#" class="d-block small font-weight-bold text-dark text-center">Show more</a>',
  578. lessLink: '<a href="#" class="d-block small font-weight-bold text-dark text-center">Show less</a>',
  579. });
  580. });
  581. };
  582. try {
  583. document.createEvent("TouchEvent");
  584. $('body').addClass('touch');
  585. } catch (e) {
  586. }
  587. window.App = window.App || {};
  588. // window.App.redirect = function() {
  589. // document.querySelectorAll('a').forEach(function(i,k) {
  590. // let a = i.getAttribute('href');
  591. // if(a && a.length > 5 && a.startsWith('https://')) {
  592. // let url = new URL(a);
  593. // if(url.host !== window.location.host && url.pathname !== '/i/redirect') {
  594. // i.setAttribute('href', '/i/redirect?url=' + encodeURIComponent(a));
  595. // }
  596. // }
  597. // });
  598. // }
  599. // window.App.boot = function() {
  600. // new Vue({ el: '#content'});
  601. // }
  602. // window.addEventListener("load", () => {
  603. // if ("serviceWorker" in navigator) {
  604. // navigator.serviceWorker.register("/sw.js");
  605. // }
  606. // });
  607. window.App.util = {
  608. compose: {
  609. post: (function() {
  610. let path = window.location.pathname;
  611. let whitelist = [
  612. '/',
  613. '/timeline/public'
  614. ];
  615. if(whitelist.includes(path)) {
  616. $('#composeModal').modal('show');
  617. } else {
  618. window.location.href = '/?a=co';
  619. }
  620. }),
  621. circle: (function() {
  622. console.log('Unsupported method.');
  623. }),
  624. collection: (function() {
  625. console.log('Unsupported method.');
  626. }),
  627. loop: (function() {
  628. console.log('Unsupported method.');
  629. }),
  630. story: (function() {
  631. console.log('Unsupported method.');
  632. }),
  633. },
  634. time: (function() {
  635. return new Date;
  636. }),
  637. version: 1,
  638. format: {
  639. count: (function(count = 0, locale = 'en-GB', notation = 'compact') {
  640. if(count < 1) {
  641. return 0;
  642. }
  643. return new Intl.NumberFormat(locale, { notation: notation , compactDisplay: "short" }).format(count);
  644. }),
  645. timeAgo: (function(ts) {
  646. let date = Date.parse(ts);
  647. let seconds = Math.floor((new Date() - date) / 1000);
  648. let interval = Math.floor(seconds / 63072000);
  649. if (interval < 0) {
  650. return "0s";
  651. }
  652. if (interval >= 1) {
  653. return interval + "y";
  654. }
  655. interval = Math.floor(seconds / 604800);
  656. if (interval >= 1) {
  657. return interval + "w";
  658. }
  659. interval = Math.floor(seconds / 86400);
  660. if (interval >= 1) {
  661. return interval + "d";
  662. }
  663. interval = Math.floor(seconds / 3600);
  664. if (interval >= 1) {
  665. return interval + "h";
  666. }
  667. interval = Math.floor(seconds / 60);
  668. if (interval >= 1) {
  669. return interval + "m";
  670. }
  671. return Math.floor(seconds) + "s";
  672. }),
  673. timeAhead: (function(ts, short = true) {
  674. let date = Date.parse(ts);
  675. let diff = date - Date.parse(new Date());
  676. let seconds = Math.floor((diff) / 1000);
  677. let interval = Math.floor(seconds / 63072000);
  678. if (interval >= 1) {
  679. return interval + (short ? "y" : " years");
  680. }
  681. interval = Math.floor(seconds / 604800);
  682. if (interval >= 1) {
  683. return interval + (short ? "w" : " weeks");
  684. }
  685. interval = Math.floor(seconds / 86400);
  686. if (interval >= 1) {
  687. return interval + (short ? "d" : " days");
  688. }
  689. interval = Math.floor(seconds / 3600);
  690. if (interval >= 1) {
  691. return interval + (short ? "h" : " hours");
  692. }
  693. interval = Math.floor(seconds / 60);
  694. if (interval >= 1) {
  695. return interval + (short ? "m" : " minutes");
  696. }
  697. return Math.floor(seconds) + (short ? "s" : " seconds");
  698. }),
  699. rewriteLinks: (function(i) {
  700. let tag = i.innerText;
  701. if(i.href.startsWith(window.location.origin)) {
  702. return i.href;
  703. }
  704. if(tag.startsWith('#') == true) {
  705. tag = '/discover/tags/' + tag.substr(1) +'?src=rph';
  706. } else if(tag.startsWith('@') == true) {
  707. tag = '/' + i.innerText + '?src=rpp';
  708. } else {
  709. tag = '/i/redirect?url=' + encodeURIComponent(tag);
  710. }
  711. return tag;
  712. })
  713. },
  714. filters: [
  715. ['1984','filter-1977'],
  716. ['Azen','filter-aden'],
  717. ['Astairo','filter-amaro'],
  718. ['Grassbee','filter-ashby'],
  719. ['Bookrun','filter-brannan'],
  720. ['Borough','filter-brooklyn'],
  721. ['Farms','filter-charmes'],
  722. ['Hairsadone','filter-clarendon'],
  723. ['Cleana ','filter-crema'],
  724. ['Catpatch','filter-dogpatch'],
  725. ['Earlyworm','filter-earlybird'],
  726. ['Plaid','filter-gingham'],
  727. ['Kyo','filter-ginza'],
  728. ['Yefe','filter-hefe'],
  729. ['Goddess','filter-helena'],
  730. ['Yards','filter-hudson'],
  731. ['Quill','filter-inkwell'],
  732. ['Rankine','filter-kelvin'],
  733. ['Juno','filter-juno'],
  734. ['Mark','filter-lark'],
  735. ['Chill','filter-lofi'],
  736. ['Van','filter-ludwig'],
  737. ['Apache','filter-maven'],
  738. ['May','filter-mayfair'],
  739. ['Ceres','filter-moon'],
  740. ['Knoxville','filter-nashville'],
  741. ['Felicity','filter-perpetua'],
  742. ['Sandblast','filter-poprocket'],
  743. ['Daisy','filter-reyes'],
  744. ['Elevate','filter-rise'],
  745. ['Nevada','filter-sierra'],
  746. ['Futura','filter-skyline'],
  747. ['Sleepy','filter-slumber'],
  748. ['Steward','filter-stinson'],
  749. ['Savoy','filter-sutro'],
  750. ['Blaze','filter-toaster'],
  751. ['Apricot','filter-valencia'],
  752. ['Gloming','filter-vesper'],
  753. ['Walter','filter-walden'],
  754. ['Poplar','filter-willow'],
  755. ['Xenon','filter-xpro-ii']
  756. ],
  757. filterCss: {
  758. 'filter-1977': 'sepia(.5) hue-rotate(-30deg) saturate(1.4)',
  759. 'filter-aden': 'sepia(.2) brightness(1.15) saturate(1.4)',
  760. 'filter-amaro': 'sepia(.35) contrast(1.1) brightness(1.2) saturate(1.3)',
  761. 'filter-ashby': 'sepia(.5) contrast(1.2) saturate(1.8)',
  762. 'filter-brannan': 'sepia(.4) contrast(1.25) brightness(1.1) saturate(.9) hue-rotate(-2deg)',
  763. 'filter-brooklyn': 'sepia(.25) contrast(1.25) brightness(1.25) hue-rotate(5deg)',
  764. 'filter-charmes': 'sepia(.25) contrast(1.25) brightness(1.25) saturate(1.35) hue-rotate(-5deg)',
  765. 'filter-clarendon': 'sepia(.15) contrast(1.25) brightness(1.25) hue-rotate(5deg)',
  766. 'filter-crema': 'sepia(.5) contrast(1.25) brightness(1.15) saturate(.9) hue-rotate(-2deg)',
  767. 'filter-dogpatch': 'sepia(.35) saturate(1.1) contrast(1.5)',
  768. 'filter-earlybird': 'sepia(.25) contrast(1.25) brightness(1.15) saturate(.9) hue-rotate(-5deg)',
  769. 'filter-gingham': 'contrast(1.1) brightness(1.1)',
  770. 'filter-ginza': 'sepia(.25) contrast(1.15) brightness(1.2) saturate(1.35) hue-rotate(-5deg)',
  771. 'filter-hefe': 'sepia(.4) contrast(1.5) brightness(1.2) saturate(1.4) hue-rotate(-10deg)',
  772. 'filter-helena': 'sepia(.5) contrast(1.05) brightness(1.05) saturate(1.35)',
  773. 'filter-hudson': 'sepia(.25) contrast(1.2) brightness(1.2) saturate(1.05) hue-rotate(-15deg)',
  774. 'filter-inkwell': 'brightness(1.25) contrast(.85) grayscale(1)',
  775. 'filter-kelvin': 'sepia(.15) contrast(1.5) brightness(1.1) hue-rotate(-10deg)',
  776. 'filter-juno': 'sepia(.35) contrast(1.15) brightness(1.15) saturate(1.8)',
  777. 'filter-lark': 'sepia(.25) contrast(1.2) brightness(1.3) saturate(1.25)',
  778. 'filter-lofi': 'saturate(1.1) contrast(1.5)',
  779. 'filter-ludwig': 'sepia(.25) contrast(1.05) brightness(1.05) saturate(2)',
  780. 'filter-maven': 'sepia(.35) contrast(1.05) brightness(1.05) saturate(1.75)',
  781. 'filter-mayfair': 'contrast(1.1) brightness(1.15) saturate(1.1)',
  782. 'filter-moon': 'brightness(1.4) contrast(.95) saturate(0) sepia(.35)',
  783. 'filter-nashville': 'sepia(.25) contrast(1.5) brightness(.9) hue-rotate(-15deg)',
  784. 'filter-perpetua': 'contrast(1.1) brightness(1.25) saturate(1.1)',
  785. 'filter-poprocket': 'sepia(.15) brightness(1.2)',
  786. 'filter-reyes': 'sepia(.75) contrast(.75) brightness(1.25) saturate(1.4)',
  787. 'filter-rise': 'sepia(.25) contrast(1.25) brightness(1.2) saturate(.9)',
  788. 'filter-sierra': 'sepia(.25) contrast(1.5) brightness(.9) hue-rotate(-15deg)',
  789. 'filter-skyline': 'sepia(.15) contrast(1.25) brightness(1.25) saturate(1.2)',
  790. 'filter-slumber': 'sepia(.35) contrast(1.25) saturate(1.25)',
  791. 'filter-stinson': 'sepia(.35) contrast(1.25) brightness(1.1) saturate(1.25)',
  792. 'filter-sutro': 'sepia(.4) contrast(1.2) brightness(.9) saturate(1.4) hue-rotate(-10deg)',
  793. 'filter-toaster': 'sepia(.25) contrast(1.5) brightness(.95) hue-rotate(-15deg)',
  794. 'filter-valencia': 'sepia(.25) contrast(1.1) brightness(1.1)',
  795. 'filter-vesper': 'sepia(.35) contrast(1.15) brightness(1.2) saturate(1.3)',
  796. 'filter-walden': 'sepia(.35) contrast(.8) brightness(1.25) saturate(1.4)',
  797. 'filter-willow': 'brightness(1.2) contrast(.85) saturate(.05) sepia(.2)',
  798. 'filter-xpro-ii': 'sepia(.45) contrast(1.25) brightness(1.75) saturate(1.3) hue-rotate(-5deg)'
  799. },
  800. emoji: ['😂','💯','❤️','🙌','👏','👌','😍','😯','😢','😅','😁','🙂','😎','😀','🤣','😃','😄','😆','😉','😊','😋','😘','😗','😙','😚','🤗','🤩','🤔','🤨','😐','😑','😶','🙄','😏','😣','😥','😮','🤐','😪','😫','😴','😌','😛','😜','😝','🤤','😒','😓','😔','😕','🙃','🤑','😲','🙁','😖','😞','😟','😤','😭','😦','😧','😨','😩','🤯','😬','😰','😱','😳','🤪','😵','😡','😠','🤬','😷','🤒','🤕','🤢','🤮','🤧','😇','🤠','🤡','🤥','🤫','🤭','🧐','🤓','😈','👿','👹','👺','💀','👻','👽','🤖','💩','😺','😸','😹','😻','😼','😽','🙀','😿','😾','🤲','👐','🤝','👍','👎','👊','✊','🤛','🤜','🤞','✌️','🤟','🤘','👈','👉','👆','👇','☝️','✋','🤚','🖐','🖖','👋','🤙','💪','🖕','✍️','🙏','💍','💄','💋','👄','👅','👂','👃','👣','👁','👀','🧠','🗣','👤','👥'
  801. ],
  802. embed: {
  803. post: (function(url, caption = true, likes = false, layout = 'full') {
  804. let u = url + '/embed?';
  805. u += caption ? 'caption=true&' : 'caption=false&';
  806. u += likes ? 'likes=true&' : 'likes=false&';
  807. u += layout == 'compact' ? 'layout=compact' : 'layout=full';
  808. return '<iframe title="Pixelfed Post Embed" src="'+u+'" class="pixelfed__embed" style="max-width: 100%; border: 0" width="400" allowfullscreen="allowfullscreen"></iframe><script async defer src="'+window.location.origin +'/embed.js"><\/script>';
  809. }),
  810. profile: (function(url) {
  811. let u = url + '/embed';
  812. return '<iframe title="Pixelfed Profile Embed" src="'+u+'" class="pixelfed__embed" style="max-width: 100%; border: 0" width="400" allowfullscreen="allowfullscreen"></iframe><script async defer src="'+window.location.origin +'/embed.js"><\/script>';
  813. })
  814. },
  815. clipboard: (function(data) {
  816. return navigator.clipboard.writeText(data);
  817. }),
  818. navatar: (function() {
  819. $('#navbarDropdown .far').addClass('d-none');
  820. $('#navbarDropdown img').attr('src',window._sharedData.curUser.avatar)
  821. .removeClass('d-none')
  822. .addClass('rounded-circle border shadow')
  823. .attr('width', 34).attr('height', 34);
  824. })
  825. };
  826. const warningTitleCSS = 'color:red; font-size:60px; font-weight: bold; -webkit-text-stroke: 1px black;';
  827. const warningDescCSS = 'font-size: 18px;';
  828. console.log('%cStop!', warningTitleCSS);
  829. console.log("%cThis is a browser feature intended for developers. If someone told you to copy and paste something here to enable a Pixelfed feature or \"hack\" someone's account, it is a scam and will give them access to your Pixelfed account.", warningDescCSS);