Initial commit

This commit is contained in:
Developer
2025-04-21 16:03:20 +02:00
commit 2832896157
22874 changed files with 3092801 additions and 0 deletions

View File

@@ -0,0 +1,112 @@
<template>
<TransitionRoot as="template" :show="open">
<Dialog as="div" static class="fixed z-[1000] inset-0 overflow-y-auto" @close="staticCloseModal" :open="open">
<div class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<TransitionChild as="template" enter="ease-out duration-300" enter-from="opacity-0" enter-to="opacity-100" leave="ease-in duration-200" leave-from="opacity-100" leave-to="opacity-0">
<DialogOverlay class="fixed inset-0 bg-indigo-200 bg-opacity-75 transition-opacity" />
</TransitionChild>
<span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">&#8203;</span>
<TransitionChild as="template" enter="ease-out duration-300" enter-from="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" enter-to="opacity-100 translate-y-0 sm:scale-100" leave="ease-in duration-200" leave-from="opacity-100 translate-y-0 sm:scale-100" leave-to="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95">
<div class="inline-block align-bottom bg-indigo-300 md:rounded-md text-left overflow-hidden shadow-xl transform transition-all md:my-8 md:align-middle md:max-w-5xl w-full">
<div class="md:grid grid-cols-12">
<button @click="staticCloseModal" class="flex md:hidden w-full items-center justify-center p-3 bg-indigo-300 text-gray-light text-sm border-b border-indigo-100">Закрыть</button>
<modal-feed-media
:type='feed.type'
:feed_id='feed.id'
:title='feed.entity.title'
:preview='feed.entity.preview'
:medias='feed.entity.collection_medias'
/>
<modal-feed-body
@onRemoveFeed='onRemoveFeed'
@disableModal='checkCloseModal'
:user='feed.user'
:feed_id='feed.id'
:entity='feed.entity'
/>
</div>
</div>
</TransitionChild>
</div>
<modal-warning
@closeWarningModal="closeWarningModal"
@closeModal="staticCloseModal"
:feed_id='modalFeedId'
:open="warningShow"
/>
</Dialog>
</TransitionRoot>
</template>
<script>
import ModalWarning from "@/Shared/Overlay/ModalWarning";
import { Dialog, DialogOverlay, DialogTitle, TransitionChild, TransitionRoot } from '@headlessui/vue'
import ModalFeedMedia from '@/Shared/Overlay/ModalFeedMedia'
import ModalFeedBody from '@/Shared/Overlay/ModalFeedBody'
export default {
components: {
Dialog,
DialogOverlay,
DialogTitle,
TransitionChild,
TransitionRoot,
ModalFeedMedia,
ModalFeedBody,
ModalWarning,
},
emits: ['onRemoveFeed', 'closeModal', 'destroyFeed'],
props: {
modalFeed: Object,
open: {
type: Boolean,
default: false,
},
},
data() {
return {
disabled: 0,
modalFeedId: 0,
warningShow: false,
feed: {}
}
},
watch: {
open(open){
if (open) {
this.feed = this.modalFeed;
}else{
}
}
},
methods: {
closeWarningModal()
{
this.warningShow = false;
this.$emit('destroyFeed');
},
onRemoveFeed(feed_id){
this.modalFeedId = feed_id;
this.warningShow = true;
},
checkCloseModal(value){
this.disabled = value;
},
staticCloseModal(){
if(!this.disabled){
this.$emit('closeModal', false);
}
},
}
}
</script>

View File

@@ -0,0 +1,224 @@
<template>
<div class="col-span-5 modal-body flex flex-col">
<div class="modal-head mb-3 px-2 py-4 md:py-4 md:px-6 flex justify-between items-center border-b border-indigo-100">
<div class="flex items-center space-x-4">
<inertia-link :href="route('profile.user', user.username)" class="flex-shrink-0 block">
<user-avatar :user='user' size='small' class="w-12 h-12 md:w-16 md:h-16 border-2 border-orange text-lg" />
</inertia-link>
<inertia-link :href="route('profile.user', user.username)" class="flex flex-col">
<p class="text-base md:text-xl font-semibold text-gray">{{user.name}}</p>
<p class="min-h-[24px]">
<span v-show="user.count_posts" class="md:mt-1 text-sm text-gray-light">{{user.count_posts}} {{countPosts}}</span>
</p>
</inertia-link>
</div>
<div class="text-white">
<dropdown-menu-point>
<MenuItems class="origin-top-right absolute right-0 mt-2 w-64 bg-indigo-200 shadow-lg max-h-60 rounded-md text-base ring-1 ring-indigo-100 overflow-auto focus:outline-none">
<MenuItem v-if="user.id === $page.props.auth.user.id">
<button @click="onRemoveFeed()" class="w-full group flex items-center px-4 py-2 text-base hover:bg-indigo-300 text-gray-light">
<MinusCircleIcon class="mr-3 h-5 w-5 text-gray-400 group-hover:text-orange" aria-hidden="true" />
Удалить
</button>
</MenuItem>
<MenuItem v-if="user.id !== $page.props.auth.user.id">
<inertia-link :href="route('video.create')" class="group flex items-center px-4 py-2 text-base hover:bg-indigo-300 text-gray-light">
<ExclamationIcon class="mr-3 h-5 w-5 text-gray-400 group-hover:text-orange" aria-hidden="true" />
Пожаловаться
</inertia-link>
</MenuItem>
</MenuItems>
</dropdown-menu-point>
</div>
</div>
<div data-simplebar class="modal-feed max-h-[29rem] overflow-auto">
<div class="p-2 md:py-4 md:px-6 space-y-3 md:space-y-6">
<div v-if="entity.body" class="comment-line comment-line--head flex flex-col text-gray text-sm">
<div class="flex group">
<inertia-link :href="route('profile.user', user.username)" class="flex-shrink-0 block mr-3">
<user-avatar :user='user' size='small' class="w-10 h-10 border border-orange text-xs" />
</inertia-link>
<div>
<inertia-link :href="route('profile.user', user.username)" class="font-semibold underline inline-block mr-2">
{{user.username}}
</inertia-link>
{{entity.body}}
<div class="transition-opacity opacity-0 group-hover:opacity-100 flex space-x-4">
<span class="text-xs text-gray-light">{{entity.created_at_humans}}</span>
</div>
</div>
</div>
</div>
<div v-for="comment in comments" :key='comment.id' class="comment-line flex flex-col text-gray text-sm">
<user-comment :creator_id='user.id' :comment='comment' />
</div>
</div>
</div>
<div class="modal-footer mt-auto p-2 md:py-4 md:px-6 border-t border-indigo-100">
<feed-paid-block
@onReplaceFeed='onReplaceFeed'
:is_paid='entity.is_paid'
:user_id='user.id'
:price='entity.price'
:feed_id='feed_id'
:paid_open='entity.paid_open'
class="text-white"
/>
<div class="misc-info mt-2 md:mt-0 mb-2 flex flex-row-reverse">
<div class="flex space-x-4">
<like-count @likeFeed='likeFeed' :likes='entity.likes' :liked='entity.liked' />
<comment-count :comments='entity.comments' />
<share-count />
</div>
<div class="mr-auto flex text-gray-light">
<button class="default">
<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" class="w-5 h-5 flex-shrink-0"><path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"></path></svg>
</button>
</div>
</div>
<div class="modal-send flex">
<div class="flex-1">
<textarea required v-model="new_comment" class="pl-0 pr-2 resize-none bg-indigo-300 w-full outline-none focus:ring-transparent focus:border-transparent focus:ring-offset-0 text-base text-gray border-transparent" placeholder="Написать комментарий..."></textarea>
</div>
<div class="mt-1 flex-shrink-0 text-pink">
<button @click="addComment" class="default">
<svg class="w-8 h-8 flex-shrink-0">
<use xlink:href="#arrow-circle"></use>
</svg>
</button>
</div>
</div>
</div>
</div>
</template>
<script>
import { Inertia } from "@inertiajs/inertia";
import axios from "axios";
import helper from "@/includes/helper";
import DropdownMenuPoint from "@/Shared/Form/DropdownMenuPoint";
import { MenuItem, MenuItems } from "@headlessui/vue";
import { MinusCircleIcon, ExclamationIcon } from "@heroicons/vue/solid";
import FeedPaidBlock from "@/Shared/FeedList/FeedPaidBlock";
import UserAvatar from "@/Shared/Misc/UserAvatar";
import LikeCount from "@/Shared/Misc/LikeCount";
import CommentCount from "@/Shared/Misc/CommentCount";
import ShareCount from "@/Shared/Misc/ShareCount";
import UserComment from "@/Shared/Partials/UserComment";
export default {
components: {
DropdownMenuPoint,
MenuItem,
MenuItems,
MinusCircleIcon,
ExclamationIcon,
UserAvatar,
LikeCount,
ShareCount,
CommentCount,
UserComment,
FeedPaidBlock,
},
props: {
user: Object,
entity: Object,
feed_id: Number,
},
emits: ["disableModal", "onRemoveFeed"],
data() {
return {
new_comment: null,
comments: [],
};
},
mounted() {
console.log(this.entity)
this.loadComments();
this.loadCountPostsUser();
},
unmounted() {
this.comments = [];
},
computed: {
countPosts() {
return helper.declNumPosts(this.user.count_posts);
},
},
methods: {
onReplaceFeed(data) {
this.entity.collection_medias = data.collection;
this.entity.preview = data.preview;
this.entity.paid_open = 1;
},
loadCountPostsUser() {
if (
typeof this.user.count_posts === "undefined" ||
parseInt(this.user.count_posts, 10) === 0
) {
axios.post(route("user.posts.count", this.user.id)).then(({ data }) => {
this.user.count_posts = data;
});
}
},
onRemoveFeed() {
this.$emit("onRemoveFeed", this.feed_id);
},
loadComments() {
this.$emit("disableModal", 1);
axios.post(route("feed.comments", this.feed_id)).then(({ data }) => {
for (const prop in data) {
this.comments.push(data[prop]);
}
this.$emit("disableModal", 0);
});
},
addComment() {
this.$emit("disableModal", 1);
axios
.post(route("feed.comments.add", this.feed_id), {
body: this.new_comment,
})
.then(({ data }) => {
this.comments.push(data);
this.entity.comments++;
this.$emit("disableModal", 0);
});
this.new_comment = null;
},
likeFeed() {
Inertia.post(
route("feed.like", this.feed_id),
{},
{
preserveScroll: true,
preserveState: true,
}
);
if (this.entity.liked) {
this.entity.liked = false;
this.entity.likes--;
} else {
this.entity.liked = true;
this.entity.likes++;
}
},
},
};
</script>

View File

@@ -0,0 +1,37 @@
<template>
<div class="col-span-7 modal-media">
<component
:title="title"
:feed_id="feed_id"
:preview="preview"
:medias="medias"
:is="currentModalMedia"
></component>
</div>
</template>
<script>
import ModalFeedMediaImages from "@/Shared/Overlay/ModalFeedMediaImages";
import ModalFeedMediaVideos from "@/Shared/Overlay/ModalFeedMediaVideos";
import ModalFeedMediaMusics from "@/Shared/Overlay/ModalFeedMediaMusics";
export default {
components: {
ModalFeedMediaImages,
ModalFeedMediaVideos,
ModalFeedMediaMusics,
},
props: {
medias: Array,
type: String,
feed_id: Number,
title: String,
preview: String,
},
computed: {
currentModalMedia() {
return "modal-feed-media-" + this.type.toLowerCase();
},
},
methods: {},
};
</script>

View File

@@ -0,0 +1,33 @@
<template>
<swiper class="h-full" :grabCursor="true" navigation :pagination="{ clickable: true }">
<swiper-slide v-for="media in medias" :key="media.id">
<img class="w-full h-full object-cover" :src="media.url" alt="" />
</swiper-slide>
</swiper>
</template>
<script>
import SwiperCore, { Navigation, Pagination } from 'swiper/core';
import { Swiper, SwiperSlide } from "swiper/vue";
import "swiper/swiper-bundle.css";
// import "swiper/components/navigation/navigation.min.css";
// import "swiper/components/pagination/pagination.min.css";
SwiperCore.use([Navigation, Pagination]);
export default {
components: {
Swiper,
SwiperSlide,
},
props: {
medias: Array,
title: String,
preview: String,
},
methods: {},
};
</script>

View File

@@ -0,0 +1,87 @@
<template>
<div class="modal-media h-full border-r border-indigo-100">
<div class="flex items-center border-b border-indigo-100">
<div class="flex-shrink-0 mr-5">
<feed-preview
class="h-28 w-28 md:w-56 md:h-56 object-cover"
type="music"
:source="preview"
/>
</div>
<div class="flex flex-col">
<span class="text-base md:text-xl font-semibold text-gray">{{
title
}}</span>
</div>
</div>
<div data-simplebar class="max-h-72 overflow-auto">
<div class="divide-y divide-indigo-100">
<div
@click.prevent="startPlay(media)"
v-for="media in medias"
:key="media.id"
:class="[
currentSong?.id === media.id
? 'bg-indigo-200 bg-opacity-25'
: 'hover:bg-indigo-200 hover:bg-opacity-25',
'p-4 flex items-center space-x-4',
]"
>
<div class="flex">
<toggle-play-button :media_id="media.id" />
</div>
<div class="flex-1 text-sm text-gray-light">
{{ media.name }}
</div>
<div class="text-sm text-gray-light">
<div v-if="currentSong?.id === media.id">{{ seek }}</div>
<span v-else>{{ media.time }}</span>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapActions, mapState, mapGetters } from "vuex";
import FeedPreview from "@/Shared/Feed/FeedPreview";
import TogglePlayButton from "@/Shared/Misc/TogglePlayButton";
export default {
components: {
FeedPreview,
TogglePlayButton,
},
props: {
medias: Array,
title: String,
preview: String,
feed_id: Number,
},
methods: {
...mapActions(["toggleAudio", "newCurrentPlaylist", "skipToIndexByMusic"]),
startPlay(music) {
if (this.currentSong?.id === music.id) {
this.toggleAudio();
return;
}
// if (this.playlist_id === this.feed_id) {
// this.skipToIndexByMusic(music);
// return;
// }
this.newCurrentPlaylist([music, this.medias, this.feed_id]);
},
},
computed: {
...mapGetters(["playing"]),
...mapState({
playlist_id: (state) => state.player.playlist_id,
seek: (state) => state.player.seek,
currentSong: (state) => state.player.currentSong,
}),
},
};
</script>

View File

@@ -0,0 +1,32 @@
<template>
<swiper class="h-full" :grabCursor="true" navigation :pagination="{ clickable: true }">
<swiper-slide v-for="media in medias" class="aspect-w-16 aspect-h-12 md:aspect-h-9" :key="media.id">
<video :src="media.url" controls></video>
</swiper-slide>
</swiper>
</template>
<script>
import SwiperCore, { Navigation, Pagination } from 'swiper/core';
import { Swiper, SwiperSlide } from "swiper/vue";
import "swiper/swiper-bundle.css";
// import "swiper/components/navigation/navigation.min.css";
// import "swiper/components/pagination/pagination.min.css";
SwiperCore.use([Navigation, Pagination]);
export default {
components: {
Swiper,
SwiperSlide,
},
props: {
medias: Array,
title: String,
preview: String,
},
methods: {},
};
</script>

View File

@@ -0,0 +1,90 @@
<!-- This example requires Tailwind CSS v2.0+ -->
<template>
<TransitionRoot as="template" :show="open">
<Dialog as="div" static class="fixed z-[1000] inset-0 overflow-y-auto" @close="staticCloseModal" :open="open">
<div class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<TransitionChild as="template" enter="ease-out duration-300" enter-from="opacity-0" enter-to="opacity-100" leave="ease-in duration-200" leave-from="opacity-100" leave-to="opacity-0">
<DialogOverlay class="fixed inset-0 bg-indigo-200 bg-opacity-75 transition-opacity" />
</TransitionChild>
<!-- This element is to trick the browser into centering the modal contents. -->
<span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">&#8203;</span>
<TransitionChild as="template" enter="ease-out duration-300" enter-from="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" enter-to="opacity-100 translate-y-0 sm:scale-100" leave="ease-in duration-200" leave-from="opacity-100 translate-y-0 sm:scale-100" leave-to="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95">
<div class="inline-block align-bottom bg-indigo-300 rounded-lg px-4 pt-5 pb-4 text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full sm:p-6">
<div class="sm:flex sm:items-start">
<div class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-orange sm:mx-0 sm:h-10 sm:w-10">
<ExclamationIcon class="h-6 w-6 text-red-600" aria-hidden="true" />
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<DialogTitle as="h3" class="text-lg leading-6 font-medium text-gray-light">
Удалить контент
</DialogTitle>
<div class="mt-2">
<p class="text-sm text-gray-light">
Вы уверены, что хотите удалить этот контент? Восстановить можно, но будет сложно!
</p>
</div>
</div>
</div>
<div class="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
<button type="button"
@click="remove"
class="mx-3 my-1 transition shadow-none hover:shadow-pink inline-flex items-center px-8 py-3 justify-center text-base rounded-md text-white bg-pink focus:outline-none">
Удалить
</button>
<button type="button"
@click="staticCloseModal"
class="mx-3 my-1 transition shadow-none hover:shadow-classic inline-flex items-center px-8 py-3 justify-center text-base rounded-md text-white bg-indigo-300 focus:outline-none">
Отменить
</button>
</div>
</div>
</TransitionChild>
</div>
</Dialog>
</TransitionRoot>
</template>
<script>
import { Dialog, DialogOverlay, DialogTitle, TransitionChild, TransitionRoot } from '@headlessui/vue'
import { ExclamationIcon } from '@heroicons/vue/outline'
import { Inertia } from "@inertiajs/inertia";
export default {
components: {
Dialog,
DialogOverlay,
DialogTitle,
TransitionChild,
TransitionRoot,
ExclamationIcon,
},
emits: ['closeWarningModal', 'closeModal', 'destroyFeed'],
props: {
feed_id: Number,
open: {
type: Boolean,
default: false,
},
},
methods:{
staticCloseModal(){
this.$emit('closeWarningModal', false);
},
remove()
{
this.$emit('closeWarningModal', false);
this.$nextTick(() => {
this.$emit('closeModal', false);
})
Inertia.delete(route('feed.destroy', this.feed_id), { preserveScroll: true, preserveState: true })
}
}
}
</script>