mirror of
https://github.com/pixelfed/pixelfed.git
synced 2024-12-18 11:03:17 +00:00
commit
91e2ad6415
79 changed files with 652 additions and 2 deletions
|
@ -6,6 +6,7 @@
|
|||
- Implement Admin Domain Blocks API (Mastodon API Compatible) [ThisIsMissEm](https://github.com/ThisIsMissEm) ([#5021](https://github.com/pixelfed/pixelfed/pull/5021))
|
||||
- Authorize Interaction support (for handling remote interactions) ([4ca7c6c3](https://github.com/pixelfed/pixelfed/commit/4ca7c6c3))
|
||||
- Contact Form Admin Responses ([52cc6090](https://github.com/pixelfed/pixelfed/commit/52cc6090))
|
||||
- Profile Carousels ([8af77a3f](https://github.com/pixelfed/pixelfed/commit/8af77a3f))
|
||||
|
||||
### Federation
|
||||
- Add ActiveSharedInboxService, for efficient sharedInbox caching ([1a6a3397](https://github.com/pixelfed/pixelfed/commit/1a6a3397))
|
||||
|
|
|
@ -33,7 +33,7 @@ class ProfileController extends Controller
|
|||
}
|
||||
|
||||
// redirect authed users to Metro 2.0
|
||||
if ($request->user()) {
|
||||
if ($request->user() && !$request->filled('carousel')) {
|
||||
// unless they force static view
|
||||
if (! $request->has('fs') || $request->input('fs') != '1') {
|
||||
$pid = AccountService::usernameToId($username);
|
||||
|
@ -64,6 +64,7 @@ class ProfileController extends Controller
|
|||
|
||||
protected function buildProfile(Request $request, $user)
|
||||
{
|
||||
$carousel = (bool) $request->filled('carousel');
|
||||
$username = $user->username;
|
||||
$loggedIn = Auth::check();
|
||||
$isPrivate = false;
|
||||
|
@ -97,6 +98,9 @@ class ProfileController extends Controller
|
|||
],
|
||||
];
|
||||
|
||||
if($carousel) {
|
||||
return view('profile.show_carousel', compact('profile', 'settings'));
|
||||
}
|
||||
return view('profile.show', compact('profile', 'settings'));
|
||||
} else {
|
||||
$key = 'profile:settings:'.$user->id;
|
||||
|
@ -135,7 +139,9 @@ class ProfileController extends Controller
|
|||
'list' => $settings->show_profile_followers,
|
||||
],
|
||||
];
|
||||
|
||||
if($carousel) {
|
||||
return view('profile.show_carousel', compact('profile', 'settings'));
|
||||
}
|
||||
return view('profile.show', compact('profile', 'settings'));
|
||||
}
|
||||
}
|
||||
|
|
6
package-lock.json
generated
6
package-lock.json
generated
|
@ -7,6 +7,7 @@
|
|||
"name": "pixelfed",
|
||||
"dependencies": {
|
||||
"@fancyapps/fancybox": "^3.5.7",
|
||||
"@glidejs/glide": "^3.6.2",
|
||||
"@hcaptcha/vue-hcaptcha": "^1.3.0",
|
||||
"@peertube/p2p-media-loader-core": "^1.0.14",
|
||||
"@peertube/p2p-media-loader-hlsjs": "^1.0.14",
|
||||
|
@ -2140,6 +2141,11 @@
|
|||
"jquery": ">=1.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@glidejs/glide": {
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@glidejs/glide/-/glide-3.6.2.tgz",
|
||||
"integrity": "sha512-oXw7In0IZV69PC0PChQakY+yh+UnqIb5+zfVuEIzub6Kkfl1foo7TAhr2PZXPzihOG9YS57t8wvdzBFEZ0aPVA=="
|
||||
},
|
||||
"node_modules/@hcaptcha/vue-hcaptcha": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@hcaptcha/vue-hcaptcha/-/vue-hcaptcha-1.3.0.tgz",
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@fancyapps/fancybox": "^3.5.7",
|
||||
"@glidejs/glide": "^3.6.2",
|
||||
"@hcaptcha/vue-hcaptcha": "^1.3.0",
|
||||
"@peertube/p2p-media-loader-core": "^1.0.14",
|
||||
"@peertube/p2p-media-loader-hlsjs": "^1.0.14",
|
||||
|
|
BIN
public/css/profile.css
vendored
Normal file
BIN
public/css/profile.css
vendored
Normal file
Binary file not shown.
BIN
public/js/account-import.js
vendored
BIN
public/js/account-import.js
vendored
Binary file not shown.
BIN
public/js/admin.js
vendored
BIN
public/js/admin.js
vendored
Binary file not shown.
BIN
public/js/admin_invite.js
vendored
BIN
public/js/admin_invite.js
vendored
Binary file not shown.
BIN
public/js/app.js
vendored
BIN
public/js/app.js
vendored
Binary file not shown.
Binary file not shown.
BIN
public/js/collections.js
vendored
BIN
public/js/collections.js
vendored
Binary file not shown.
BIN
public/js/components.js
vendored
BIN
public/js/components.js
vendored
Binary file not shown.
BIN
public/js/compose-classic.js
vendored
BIN
public/js/compose-classic.js
vendored
Binary file not shown.
Binary file not shown.
BIN
public/js/compose.js
vendored
BIN
public/js/compose.js
vendored
Binary file not shown.
BIN
public/js/daci.chunk.5bb69fda8fdedc47.js
vendored
Normal file
BIN
public/js/daci.chunk.5bb69fda8fdedc47.js
vendored
Normal file
Binary file not shown.
BIN
public/js/daci.chunk.5dbd1faea828ed0b.js
vendored
BIN
public/js/daci.chunk.5dbd1faea828ed0b.js
vendored
Binary file not shown.
BIN
public/js/developers.js
vendored
BIN
public/js/developers.js
vendored
Binary file not shown.
BIN
public/js/direct.js
vendored
BIN
public/js/direct.js
vendored
Binary file not shown.
Binary file not shown.
BIN
public/js/discover~findfriends.chunk.2392a288a9031530.js
vendored
Normal file
BIN
public/js/discover~findfriends.chunk.2392a288a9031530.js
vendored
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
public/js/discover~memories.chunk.398b63b5473c6be6.js
vendored
Normal file
BIN
public/js/discover~memories.chunk.398b63b5473c6be6.js
vendored
Normal file
Binary file not shown.
Binary file not shown.
BIN
public/js/discover~myhashtags.chunk.29b97f80a1338877.js
vendored
Normal file
BIN
public/js/discover~myhashtags.chunk.29b97f80a1338877.js
vendored
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
public/js/discover~serverfeed.chunk.d729660f46f7f530.js
vendored
Normal file
BIN
public/js/discover~serverfeed.chunk.d729660f46f7f530.js
vendored
Normal file
Binary file not shown.
Binary file not shown.
BIN
public/js/discover~settings.chunk.6f4b9b6a6ef5131a.js
vendored
Normal file
BIN
public/js/discover~settings.chunk.6f4b9b6a6ef5131a.js
vendored
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
public/js/group-status.js
vendored
BIN
public/js/group-status.js
vendored
Binary file not shown.
BIN
public/js/group-topic-feed.js
vendored
BIN
public/js/group-topic-feed.js
vendored
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
public/js/groups.js
vendored
BIN
public/js/groups.js
vendored
Binary file not shown.
BIN
public/js/hashtag.js
vendored
BIN
public/js/hashtag.js
vendored
Binary file not shown.
BIN
public/js/home.chunk.1d1e6fe050aaaf98.js
vendored
BIN
public/js/home.chunk.1d1e6fe050aaaf98.js
vendored
Binary file not shown.
BIN
public/js/home.chunk.491ce6f986e08bb3.js
vendored
Normal file
BIN
public/js/home.chunk.491ce6f986e08bb3.js
vendored
Normal file
Binary file not shown.
Binary file not shown.
BIN
public/js/landing.js
vendored
BIN
public/js/landing.js
vendored
Binary file not shown.
BIN
public/js/manifest.js
vendored
BIN
public/js/manifest.js
vendored
Binary file not shown.
Binary file not shown.
BIN
public/js/portfolio.js
vendored
BIN
public/js/portfolio.js
vendored
Binary file not shown.
BIN
public/js/post.chunk.13919bcfbfc2d438.js
vendored
Normal file
BIN
public/js/post.chunk.13919bcfbfc2d438.js
vendored
Normal file
Binary file not shown.
BIN
public/js/post.chunk.af21320999ba64af.js
vendored
BIN
public/js/post.chunk.af21320999ba64af.js
vendored
Binary file not shown.
BIN
public/js/profile.chunk.164b255884ed6d1c.js
vendored
Normal file
BIN
public/js/profile.chunk.164b255884ed6d1c.js
vendored
Normal file
Binary file not shown.
BIN
public/js/profile.chunk.75eb020992ddb4dd.js
vendored
BIN
public/js/profile.chunk.75eb020992ddb4dd.js
vendored
Binary file not shown.
BIN
public/js/profile.js
vendored
BIN
public/js/profile.js
vendored
Binary file not shown.
1
public/js/profile.js.LICENSE.txt
Normal file
1
public/js/profile.js.LICENSE.txt
Normal file
|
@ -0,0 +1 @@
|
|||
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */
|
Binary file not shown.
Binary file not shown.
BIN
public/js/remote_auth.js
vendored
BIN
public/js/remote_auth.js
vendored
Binary file not shown.
BIN
public/js/search.js
vendored
BIN
public/js/search.js
vendored
Binary file not shown.
BIN
public/js/spa.js
vendored
BIN
public/js/spa.js
vendored
Binary file not shown.
BIN
public/js/status.js
vendored
BIN
public/js/status.js
vendored
Binary file not shown.
BIN
public/js/stories.js
vendored
BIN
public/js/stories.js
vendored
Binary file not shown.
BIN
public/js/story-compose.js
vendored
BIN
public/js/story-compose.js
vendored
Binary file not shown.
BIN
public/js/timeline.js
vendored
BIN
public/js/timeline.js
vendored
Binary file not shown.
BIN
public/js/vendor.js
vendored
BIN
public/js/vendor.js
vendored
Binary file not shown.
|
@ -40,6 +40,12 @@
|
|||
* Date: 2024-04-21T07:43:05.335Z
|
||||
*/
|
||||
|
||||
/*!
|
||||
* Glide.js v3.6.2
|
||||
* (c) 2013-2024 Jędrzej Chałubek (https://github.com/jedrzejchalubek/)
|
||||
* Released under the MIT License.
|
||||
*/
|
||||
|
||||
/*!
|
||||
* JavaScript Cookie v2.2.1
|
||||
* https://github.com/js-cookie/js-cookie
|
||||
|
|
Binary file not shown.
336
resources/assets/components/FullscreenCarousel.vue
Normal file
336
resources/assets/components/FullscreenCarousel.vue
Normal file
|
@ -0,0 +1,336 @@
|
|||
<template>
|
||||
<div class="fullscreen-carousel">
|
||||
<div class="glide" ref="glide">
|
||||
<div class="glide__track" data-glide-el="track">
|
||||
<ul class="glide__slides">
|
||||
<li class="glide__slide" v-for="(item, index) in feed" :key="index">
|
||||
<div class="slide-content">
|
||||
<img :src="item.media_url" :alt="item.caption" class="slide-image" loading="lazy">
|
||||
<div v-if="withOverlay" class="slide-overlay">
|
||||
<p v-if="withLinks" class="slide-username"><a :href="item.account.url">{{ webfinger }}</a></p>
|
||||
<p v-else class="slide-username">{{ webfinger }}</p>
|
||||
<div class="d-flex gap-1">
|
||||
<div v-if="withLinks" class="slide-date">
|
||||
<a :href="item.url" target="_blank">{{ formatDate(item.created_at) }}</a>
|
||||
</div>
|
||||
<div v-else class="slide-date">{{ formatDate(item.created_at) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="glide__arrows" data-glide-el="controls">
|
||||
<button class="glide__arrow glide__arrow--left fancy-arrow" data-glide-dir="<">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<polyline points="15 18 9 12 15 6"></polyline>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="glide__arrow glide__arrow--right fancy-arrow" data-glide-dir=">">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<polyline points="9 18 15 12 9 6"></polyline>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Glide from '@glidejs/glide'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
feed: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
canLoadMore: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
withLinks: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
withOverlay: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
autoPlay: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
autoPlayInterval: {
|
||||
type: Number,
|
||||
default: () => { return 5000; }
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
glideInstance: null
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.initGlide()
|
||||
},
|
||||
|
||||
computed: {
|
||||
webfinger: {
|
||||
get() {
|
||||
if(this.feed && this.feed.length) {
|
||||
const account = this.feed[0].account
|
||||
const domain = new URL(account.url).host
|
||||
return `@${account.username}@${domain}`
|
||||
}
|
||||
return ""
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
initGlide() {
|
||||
this.glideInstance = new Glide(this.$refs.glide, {
|
||||
type: 'carousel',
|
||||
startAt: 0,
|
||||
perView: 1,
|
||||
gap: 0,
|
||||
hoverpause: false,
|
||||
autoplay: this.autoPlay ? this.autoPlayInterval : false,
|
||||
keyboard: true
|
||||
})
|
||||
|
||||
this.glideInstance.on('run.after', this.checkForPagination)
|
||||
this.glideInstance.mount()
|
||||
},
|
||||
|
||||
checkForPagination() {
|
||||
const currentIndex = this.glideInstance.index
|
||||
if (currentIndex === this.feed.length - 1 && this.canLoadMore) {
|
||||
this.$emit('load-more')
|
||||
}
|
||||
},
|
||||
|
||||
loadMore() {
|
||||
this.$emit('load-more')
|
||||
},
|
||||
|
||||
formatDate(dateInput, locale = navigator.language) {
|
||||
let date;
|
||||
|
||||
if (typeof dateInput === 'string') {
|
||||
date = new Date(dateInput);
|
||||
if (isNaN(date.getTime())) {
|
||||
throw new Error('Invalid date string. Please provide a valid ISO 8601 format.');
|
||||
}
|
||||
} else if (dateInput instanceof Date) {
|
||||
date = dateInput;
|
||||
} else {
|
||||
throw new Error('Invalid input. Please provide a Date object or an ISO 8601 string.');
|
||||
}
|
||||
|
||||
const options = {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
hour12: true
|
||||
};
|
||||
|
||||
return new Intl.DateTimeFormat(locale, options).format(date);
|
||||
},
|
||||
|
||||
updateGlide() {
|
||||
this.$nextTick(() => {
|
||||
if (this.glideInstance) {
|
||||
this.glideInstance.update()
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
feed() {
|
||||
this.updateGlide()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.fullscreen-carousel {
|
||||
height: 100dvh;
|
||||
width: 100dvw;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
z-index: 2;
|
||||
background: #000;
|
||||
}
|
||||
|
||||
.glide, .glide__track, .glide__slides, .glide__slide {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.slide-content {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.slide-image {
|
||||
object-fit: contain;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.slide-overlay {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
color: white;
|
||||
padding: 8px 20px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.gap-1 {
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.slide-image {
|
||||
.slide-overlay {
|
||||
&:not(:hover) {
|
||||
height: 0;
|
||||
opacity: 0;
|
||||
transform: height 1s ease;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.slide-username {
|
||||
margin: 0;
|
||||
user-select: all;
|
||||
font-size: 14px;
|
||||
|
||||
a {
|
||||
color: white;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.slide-caption {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.slide-date {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
|
||||
a {
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.glide__arrow {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
border: none;
|
||||
font-size: 24px;
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.fancy-arrow {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.fancy-arrow:hover {
|
||||
background: rgba(255, 255, 255, 0.4);
|
||||
box-shadow: 0 0 15px rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.fancy-arrow:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.fancy-arrow svg {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
color: white;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.fancy-arrow:hover svg {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
.glide__arrow--left {
|
||||
left: 20px;
|
||||
}
|
||||
|
||||
.glide__arrow--right {
|
||||
right: 20px;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
transform: translateY(-50%) scale(1);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-50%) scale(1.05);
|
||||
}
|
||||
100% {
|
||||
transform: translateY(-50%) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.fancy-arrow:active {
|
||||
animation: pulse 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.fancy-arrow {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.fancy-arrow svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.glide__arrow--left {
|
||||
left: 10px;
|
||||
}
|
||||
|
||||
.glide__arrow--right {
|
||||
right: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
197
resources/assets/components/ProfileCarousel.vue
Normal file
197
resources/assets/components/ProfileCarousel.vue
Normal file
|
@ -0,0 +1,197 @@
|
|||
<template>
|
||||
<div class="profile-carousel-component">
|
||||
<template v-if="showSplash">
|
||||
<SplashScreen />
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<template v-if="emptyFeed">
|
||||
<div class="bg-dark d-flex justify-content-center align-items-center w-100 h-100">
|
||||
<div>
|
||||
<h2 class="text-light">Oops! This account hasn't posted yet or is private.</h2>
|
||||
<a href="/" class="font-weight-bold text-muted">Go back home</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<FullscreenCarousel
|
||||
:feed="feed"
|
||||
:withLinks="withLinks"
|
||||
:withOverlay="withOverlay"
|
||||
:autoPlay="autoPlay"
|
||||
:autoPlayInterval="autoPlayInterval"
|
||||
:canLoadMore="hasMoreData"
|
||||
@load-more="loadMoreData"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SplashScreen from './SplashScreen.vue';
|
||||
import FullscreenCarousel from './FullscreenCarousel.vue'
|
||||
|
||||
export default {
|
||||
props: ['profile-id'],
|
||||
|
||||
components: {
|
||||
SplashScreen,
|
||||
FullscreenCarousel
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
showSplash: true,
|
||||
profile: {},
|
||||
feed: [],
|
||||
emptyFeed: false,
|
||||
hasMoreData: false,
|
||||
withLinks: true,
|
||||
withOverlay: true,
|
||||
autoPlay: false,
|
||||
autoPlayInterval: 5000,
|
||||
maxId: null
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
const url = new URL(window.location.href);
|
||||
const params = url.searchParams;
|
||||
if(params.has('linkless') == true) {
|
||||
this.withLinks = false;
|
||||
}
|
||||
|
||||
if(params.has('clean') == true) {
|
||||
this.withOverlay = false;
|
||||
}
|
||||
|
||||
if(params.has('interval') == true) {
|
||||
const val = parseInt(params.get('interval'));
|
||||
const valid = this.validateIntegerRange(val, { min: 1000, max: 30000 })
|
||||
if(valid) {
|
||||
this.autoPlayInterval = val;
|
||||
}
|
||||
}
|
||||
|
||||
if(params.has('autoplay') == true) {
|
||||
this.autoPlay = true;
|
||||
|
||||
}
|
||||
this.init();
|
||||
},
|
||||
|
||||
methods: {
|
||||
async init() {
|
||||
await axios.get(`/api/pixelfed/v1/accounts/${this.profileId}/statuses?media_type=photo&limit=10`)
|
||||
.then(res => {
|
||||
if(!res || !res.data || !res.data.length) {
|
||||
this.emptyFeed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
this.maxId = this.arrayMinId(res.data);
|
||||
const posts = res.data.flatMap(post =>
|
||||
post.media_attachments.filter(media => {
|
||||
return ['image/jpeg','image/png', 'image/jpg', 'image/webp'].includes(media.mime)
|
||||
}).map(media => ({
|
||||
media_url: media.url,
|
||||
id: post.id,
|
||||
caption: post.content_text,
|
||||
created_at: post.created_at,
|
||||
url: post.url,
|
||||
account: {
|
||||
username: post.account.username,
|
||||
url: post.account.url
|
||||
}
|
||||
}))
|
||||
);
|
||||
this.feed = posts;
|
||||
this.hasMoreData = res.data.length === 10;
|
||||
setTimeout(() => {
|
||||
this.showSplash = false;
|
||||
}, 3000);
|
||||
})
|
||||
},
|
||||
|
||||
async fetchMore() {
|
||||
await axios.get(`/api/pixelfed/v1/accounts/${this.profileId}/statuses?media_type=photo&limit=10&max_id=${this.maxId}`)
|
||||
.then(res => {
|
||||
this.maxId = this.arrayMinId(res.data);
|
||||
const posts = res.data.flatMap(post =>
|
||||
post.media_attachments.filter(media => {
|
||||
return ['image/jpeg','image/png', 'image/jpg', 'image/webp'].includes(media.mime)
|
||||
}).map(media => ({
|
||||
media_url: media.url,
|
||||
id: post.id,
|
||||
caption: post.content_text,
|
||||
created_at: post.created_at,
|
||||
url: post.url,
|
||||
account: {
|
||||
username: post.account.username,
|
||||
url: post.account.url
|
||||
}
|
||||
}))
|
||||
);
|
||||
this.feed.push(...posts);
|
||||
this.hasMoreData = res.data.length === 10;
|
||||
})
|
||||
},
|
||||
|
||||
arrayMinId(arr) {
|
||||
if (arr.length === 0) return null;
|
||||
let smallest = BigInt(arr[0].id);
|
||||
for (let i = 1; i < arr.length; i++) {
|
||||
const current = BigInt(arr[i].id);
|
||||
if (current < smallest) {
|
||||
smallest = current;
|
||||
}
|
||||
}
|
||||
return smallest.toString();
|
||||
},
|
||||
|
||||
loadMoreData() {
|
||||
this.fetchMore();
|
||||
},
|
||||
|
||||
validateIntegerRange(value, options = {}) {
|
||||
if (typeof value !== 'number' || !Number.isInteger(value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const {
|
||||
min = Number.MIN_SAFE_INTEGER,
|
||||
max = Number.MAX_SAFE_INTEGER,
|
||||
inclusiveMin = true,
|
||||
inclusiveMax = true
|
||||
} = options;
|
||||
|
||||
if (min !== undefined && !Number.isInteger(min)) {
|
||||
return false;
|
||||
}
|
||||
if (max !== undefined && !Number.isInteger(max)) {
|
||||
return false;
|
||||
}
|
||||
if (min > max) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const aboveMin = inclusiveMin ? value >= min : value > min;
|
||||
const belowMax = inclusiveMax ? value <= max : value < max;
|
||||
|
||||
return aboveMin && belowMax;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style type="text/css">
|
||||
.profile-carousel-component {
|
||||
display: block;
|
||||
width: 100dvw;
|
||||
height: 100dvh;
|
||||
z-index: 2;
|
||||
background: #000;
|
||||
}
|
||||
</style>
|
46
resources/assets/components/SplashScreen.vue
Normal file
46
resources/assets/components/SplashScreen.vue
Normal file
|
@ -0,0 +1,46 @@
|
|||
<template>
|
||||
<div class="splash-screen" :class="{ 'fade-out': fadeOut }">
|
||||
<img src="/img/pixelfed-icon-white.svg" alt="Pixelfed Logo" class="logo">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
fadeOut: false
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
setTimeout(() => {
|
||||
this.fadeOut = true
|
||||
}, 2000)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.splash-screen {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: black;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 9999;
|
||||
transition: opacity 1s ease-out;
|
||||
}
|
||||
|
||||
.logo {
|
||||
max-width: 200px;
|
||||
max-height: 200px;
|
||||
}
|
||||
|
||||
.fade-out {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
5
resources/assets/js/profile.js
vendored
5
resources/assets/js/profile.js
vendored
|
@ -28,6 +28,11 @@ Vue.component(
|
|||
require('./components/PostMenu.vue').default
|
||||
);
|
||||
|
||||
Vue.component(
|
||||
'profile-carousel',
|
||||
require('./../components/ProfileCarousel.vue').default
|
||||
);
|
||||
|
||||
Vue.component(
|
||||
'profile',
|
||||
require('./components/Profile.vue').default
|
||||
|
|
2
resources/assets/sass/profile.scss
vendored
Normal file
2
resources/assets/sass/profile.scss
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
@import "node_modules/@glidejs/glide/src/assets/sass/glide.core.scss";
|
||||
@import "node_modules/@glidejs/glide/src/assets/sass/glide.theme.scss";
|
42
resources/views/profile/show_carousel.blade.php
Normal file
42
resources/views/profile/show_carousel.blade.php
Normal file
|
@ -0,0 +1,42 @@
|
|||
@extends('layouts.blank', [
|
||||
'title' => $profile->name . ' (@' . $acct . ') - Pixelfed',
|
||||
'ogTitle' => $profile->name . ' (@' . $acct . ')',
|
||||
'ogType' => 'profile'
|
||||
])
|
||||
|
||||
@php
|
||||
$acct = $profile->username . '@' . config('pixelfed.domain.app');
|
||||
$metaDescription = \App\Services\AccountService::getMetaDescription($profile->id);
|
||||
@endphp
|
||||
|
||||
@section('content')
|
||||
@if (session('error'))
|
||||
<div class="alert alert-danger text-center font-weight-bold mb-0">
|
||||
{{ session('error') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<profile-carousel profile-id="{{$profile->id}}" />
|
||||
|
||||
@endsection
|
||||
|
||||
@push('meta')<meta name="description" content="{{$metaDescription}}">
|
||||
<meta property="og:description" content="{{$metaDescription}}">
|
||||
<meta property="og:image" content="{{$profile->avatarUrl()}}">
|
||||
<meta property="og:image:width" content="200">
|
||||
<meta property="og:image:height" content="200">
|
||||
<meta property="twitter:card" content="summary">
|
||||
<meta property="profile:username" content="{{$acct}}">
|
||||
<link href="{{$profile->permalink('.atom')}}" rel="alternate" title="{{$profile->username}} on Pixelfed" type="application/atom+xml">
|
||||
<link href="{{$profile->permalink()}}" rel="alternate" type="application/activity+json">
|
||||
<meta name="application-name" content="Pixelfed">
|
||||
<meta name="generator" content="pixelfed">
|
||||
<link href="{{ mix('css/profile.css') }}" rel="stylesheet">
|
||||
@if($profile->website)<link href="{{$profile->website}}" rel="me" type="text/html">
|
||||
@endif
|
||||
@if(false == $settings['crawlable'] || $profile->remote_url)<meta name="robots" content="noindex, nofollow">@endif
|
||||
@endpush
|
||||
|
||||
@push('scripts')<script type="text/javascript" src="{{ mix('js/profile.js') }}"></script>
|
||||
<script type="text/javascript" defer>App.boot();</script>
|
||||
@endpush
|
1
webpack.mix.js
vendored
1
webpack.mix.js
vendored
|
@ -13,6 +13,7 @@ mix.sass('resources/assets/sass/app.scss', 'public/css')
|
|||
.sass('resources/assets/sass/admin.scss', 'public/css')
|
||||
.sass('resources/assets/sass/portfolio.scss', 'public/css')
|
||||
.sass('resources/assets/sass/spa.scss', 'public/css')
|
||||
.sass('resources/assets/sass/profile.scss', 'public/css')
|
||||
.sass('resources/assets/sass/landing.scss', 'public/css').version();
|
||||
|
||||
mix.js('resources/assets/js/app.js', 'public/js')
|
||||
|
|
Loading…
Reference in a new issue