Rework frontend

This commit is contained in:
F4ilji
2025-03-23 00:38:16 +05:00
parent 17518ceed2
commit 49072e50c7
438 changed files with 14375 additions and 20015 deletions
@@ -0,0 +1,53 @@
<template>
<div class="col-start-2">
<div class="hs-tooltip inline-block">
<button type="button" class="hs-tooltip-toggle underline hover:text-blue-400 duration-300">
Над статьей работали
<span class="hs-tooltip-content hs-tooltip-shown:opacity-100 hs-tooltip-shown:visible opacity-0 transition-opacity inline-block absolute duration-300 invisible z-10 py-1 px-2 bg-gray-900 text-xs font-medium text-white rounded shadow-sm dark:bg-neutral-700" role="tooltip">
<template v-for="author in authors">
{{ author }} <br>
</template>
</span>
</button>
</div>
</div>
</template>
<script>
import {Link} from "@inertiajs/vue3";
import slugify from "slugify";
export default {
name: "PostAuthorsList",
components: {Link},
data() {
return {
}
},
methods: {
textLimit(text, symbols) {
if (text.length > symbols) {
let LimitedText
LimitedText = text.substring(0, symbols)
return LimitedText + "..."
}
return text
},
},
props: {
authors: {
type: Array,
},
},
}
</script>
<style scoped>
</style>
@@ -0,0 +1,103 @@
<template>
<div v-if="images.length" class="grid gap-4 border-t border-gray-300 py-4">
<div class="relative">
<img loading="lazy"
class="filter brightness-[0.8] w-full max-h-[500px] object-cover rounded-lg hover:opacity-95 hover:duration-200 transition"
:src="'/storage/' + images[0]" data-hs-overlay="#modal-post-gallery" alt="">
<div class="absolute inset-x-0 bottom-5 flex flex-col justify-center items-center">
<p class="text-white text-sm lg:text-xl text-center">{{ title }}</p>
<span class="text-gray-300 text-sm">{{ images.length }} фотографий</span>
</div>
</div>
</div>
<div id="modal-post-gallery" class="hs-overlay hidden size-full fixed top-0 start-0 z-[80] overflow-x-hidden overflow-y-auto pointer-events-none" role="dialog" tabindex="-1" aria-labelledby="modal-post-gallery-label">
<div class="hs-overlay-open:mt-0 hs-overlay-open:opacity-100 hs-overlay-open:duration-500 mt-10 opacity-0 transition-all max-w-full max-h-full h-full">
<div class="flex flex-col bg-white pointer-events-auto max-w-full max-h-full h-full dark:bg-neutral-800">
<div class="flex justify-between items-center py-3 px-4 border-b dark:border-neutral-700">
<h3 id="modal-post-gallery-label" class="font-bold text-gray-800 dark:text-white">
Галлерея: {{ title }}
</h3>
<button type="button" class="size-8 inline-flex justify-center items-center gap-x-2 rounded-full border border-transparent bg-gray-100 text-gray-800 hover:bg-gray-200 focus:outline-none focus:bg-gray-200 disabled:opacity-50 disabled:pointer-events-none dark:bg-neutral-700 dark:hover:bg-neutral-600 dark:text-neutral-400 dark:focus:bg-neutral-600" aria-label="Close" data-hs-overlay="#modal-post-gallery">
<span class="sr-only">Close</span>
<svg class="shrink-0 size-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M18 6 6 18"></path>
<path d="m6 6 12 12"></path>
</svg>
</button>
</div>
<div class="p-4 overflow-y-auto">
<div class="grid grid-cols-2 sm:grid-cols-5 gap-3">
<div v-for="(image, index) in images" class="group block relative overflow-hidden rounded-lg">
<img loading="lazy" @click="openLightboxOnSlide(index + 1)" class="w-full size-40 object-cover bg-gray-100 rounded-lg" :src="'/storage/' + image">
<div class="absolute bottom-1 end-1 opacity-0 group-hover:opacity-100 transition">
<div class="flex items-center gap-x-1 py-1 px-2 bg-white border border-gray-200 text-gray-800 rounded-lg">
<svg class="shrink-0 size-3" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
<span class="text-xs">Открыть</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<FsLightbox class="" :slide="slide" :toggler="toggler" :sources="images.map(image => domainPath + '/storage/' + image)"/>
</template>
<script>
import {Link} from "@inertiajs/vue3";
import FsLightbox from "fslightbox-vue";
export default {
name: "PostGallery",
components: {Link, FsLightbox},
data() {
return {
toggler: false,
slide: 1,
domainPath: null,
}
},
methods: {
textLimit(text, symbols) {
if (text.length > symbols) {
let LimitedText
LimitedText = text.substring(0, symbols)
return LimitedText + "..."
}
return text
},
openLightboxOnSlide: function(number) {
this.slide = number;
this.toggler = !this.toggler;
}
},
mounted() {
this.domainPath = window.location.origin;
},
props: {
images: {
type: Array,
},
title: {
type: String,
},
},
}
</script>
<style scoped>
</style>
@@ -0,0 +1,209 @@
<template>
<div class="flex justify-between pb-4 items-center">
<div class="flex w-full sm:items-center gap-x-5 sm:gap-x-3">
<div class="grow">
<div class="grid sm:flex sm:justify-between sm:items-center gap-2">
<BreadcrumbsWrapper v-if="breadcrumbs">
<li class="text-sm">
<Link :href="route('index')" class="flex items-center text-gray-500 hover:text-blue-600" href="/">
<BasicIcon class="size-5" name="home" />
</Link>
</li>
<li v-if="breadcrumbs.mainSection" class="text-sm">
<span class="flex items-center text-gray-500 hover:text-primaryBlue cursor-pointer" @click.prevent="handleSectionClick(breadcrumbs.mainSection)">
<svg class="flex-shrink-0 mx-2 overflow-visible h-2.5 w-2.5 text-gray-400"
width="16" height="16"
viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 1L10.6869 7.16086C10.8637 7.35239 10.8637 7.64761 10.6869 7.83914L5 14"
stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>
{{ textLimit(breadcrumbs.mainSection.data.title, 25) }}
</span>
</li>
<li v-if="breadcrumbs.subSection" class="text-sm">
<span class="flex items-center text-gray-500 hover:text-primaryBlue cursor-pointer" @click.prevent="handleSubSectionClick(breadcrumbs.mainSection, breadcrumbs.subSection)">
<svg class="flex-shrink-0 mx-2 overflow-visible h-2.5 w-2.5 text-gray-400"
width="16" height="16"
viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 1L10.6869 7.16086C10.8637 7.35239 10.8637 7.64761 10.6869 7.83914L5 14"
stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>
{{ textLimit(breadcrumbs.subSection.data.title, 25) }}
</span>
</li>
<li class="text-sm">
<Link :href="route('client.post.index')" class="flex items-center text-gray-500 hover:text-blue-600">
<svg class="flex-shrink-0 mx-2 overflow-visible h-2.5 w-2.5 text-gray-400"
width="16" height="16"
viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 1L10.6869 7.16086C10.8637 7.35239 10.8637 7.64761 10.6869 7.83914L5 14"
stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>
{{ textLimit('Новости', 30) }}
</Link>
</li>
<li class="text-sm">
<Link :href="$page.props.ziggy.location" class="flex items-center text-gray-500 hover:text-blue-600">
<svg class="flex-shrink-0 mx-2 overflow-visible h-2.5 w-2.5 text-gray-400"
width="16" height="16"
viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 1L10.6869 7.16086C10.8637 7.35239 10.8637 7.64761 10.6869 7.83914L5 14"
stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>
{{ textLimit(postTitle, 60) }}
</Link>
</li>
</BreadcrumbsWrapper>
<BreadcrumbsWrapper v-else>
<li class="text-sm">
<Link :href="route('index')" class="flex items-center text-gray-500 hover:text-blue-600" href="/">
<BasicIcon class="size-5" name="home" />
<svg class="flex-shrink-0 mx-2 overflow-visible h-2.5 w-2.5 text-gray-400"
width="16" height="16"
viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 1L10.6869 7.16086C10.8637 7.35239 10.8637 7.64761 10.6869 7.83914L5 14"
stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>
</Link>
</li>
<li class="text-sm">
<Link :href="route('client.post.index')" class="flex items-center text-gray-500 hover:text-blue-600">
{{ textLimit('Новости', 30) }}
<svg class="flex-shrink-0 mx-2 overflow-visible h-2.5 w-2.5 text-gray-400"
width="16" height="16"
viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 1L10.6869 7.16086C10.8637 7.35239 10.8637 7.64761 10.6869 7.83914L5 14"
stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>
</Link>
</li>
<li class="text-sm">
<Link :href="$page.props.ziggy.location" class="flex items-center text-gray-500 hover:text-blue-600">
{{ textLimit(postTitle, 60) }}
</Link>
</li>
</BreadcrumbsWrapper>
</div>
</div>
</div>
</div>
</template>
<script>
import {Link} from "@inertiajs/vue3";
import slugify from "slugify";
import BasicIcon from "@/componentss/ui/icons/BasicIcon.vue";
import BreadcrumbsWrapper from "@/componentss/ui/wrappers/BreadcrumbsWrapper.vue";
export default {
name: "PostItemBreadcrumbs",
components: {BreadcrumbsWrapper, BasicIcon, Link},
data() {
return {
}
},
methods: {
textLimit(text, symbols) {
if (text.length > symbols) {
let LimitedText
LimitedText = text.substring(0, symbols)
return LimitedText + "..."
}
return text
},
isMobileDevice() {
if (typeof window !== 'undefined') {
return window.innerWidth < 1024; // Проверка на мобильные устройства
}
return false; // По умолчанию возвращаем false, когда не в браузере
},
handleSectionClick(breadcrumb) {
if (this.isMobileDevice()) {
this.toggleMobileNavSection(breadcrumb)
} else {
this.toggleDesktopNavSection(breadcrumb)
}
},
handleSubSectionClick(mainSectionBreadcrumb, breadcrumb) {
if (this.isMobileDevice()) {
this.toggleMobileNavSubSection(mainSectionBreadcrumb, breadcrumb)
} else {
this.toggleDesktopNavSubSection(mainSectionBreadcrumb, breadcrumb)
}
},
highlightNavItem(item) {
item.classList.add('animate-pulse');
setTimeout(() => {
item.classList.remove('animate-pulse');
}, 4000);
},
openMobileNavMenu() {
const openMobileNavBtn = document.getElementById('open-mobile-btn');
openMobileNavBtn.click();
},
toggleMobileNavSubSection(mainSectionBreadcrumb, breadcrumb) {
this.openMobileNavMenu()
const mobileNavElement = document.getElementById('open-mobile-nav');
const sectionNavBlock = mobileNavElement.querySelector('#nav-section-accordion-' + mainSectionBreadcrumb.data.slug);
const sectionNavBlockBtn = mobileNavElement.querySelector('#nav-section-accordion-btn-' + mainSectionBreadcrumb.data.slug);
if (!sectionNavBlock.classList.contains('active')) {
sectionNavBlockBtn.click()
}
const subSectionNavBlock = mobileNavElement.querySelector('#nav-sub-section-accordion-' + breadcrumb.data.slug);
const subSectionNavBlockBtn = mobileNavElement.querySelector('#nav-sub-section-accordion-btn-' + breadcrumb.data.slug);
if (subSectionNavBlock.classList.contains('active')) {
subSectionNavBlockBtn.click()
}
this.highlightNavItem(subSectionNavBlock)
},
toggleMobileNavSection(breadcrumb) {
const mobileNavElement = document.getElementById('open-mobile-nav');
const sectionNavBlock = mobileNavElement.querySelector('#nav-section-accordion-' + breadcrumb.data.slug);
const sectionNavBlockBtn = mobileNavElement.querySelector('#nav-section-accordion-btn-' + breadcrumb.data.slug);
if (sectionNavBlock.classList.contains('active')) {
sectionNavBlockBtn.click()
}
this.openMobileNavMenu()
this.highlightNavItem(sectionNavBlock)
},
toggleDesktopNavSubSection(mainSectionBreadcrumb, breadcrumb) {
const desktopNavElement = document.getElementById('desktop-nav');
const sectionNavTitle = desktopNavElement.querySelector('#nav-sub-section-title-' + breadcrumb.data.slug);
const sectionNavBlockBtn = desktopNavElement.querySelector('#nav-section-btn-' + mainSectionBreadcrumb.data.slug);
sectionNavBlockBtn.click()
this.highlightNavItem(sectionNavTitle)
},
toggleDesktopNavSection(breadcrumb) {
const desktopNavElement = document.getElementById('desktop-nav');
const sectionNavBlock = desktopNavElement.querySelector('#nav-section-menu-' + breadcrumb.data.slug);
const sectionNavBlockBtn = desktopNavElement.querySelector('#nav-section-btn-' + breadcrumb.data.slug);
sectionNavBlockBtn.click()
// this.highlightNavItem(sectionNavBlock)
},
},
props: {
breadcrumbs: {
type: Object,
default: () => ({}), // Default to an empty object
},
postTitle: {
type: String,
},
},
}
</script>
<style scoped>
</style>
@@ -0,0 +1,48 @@
<template>
<component
v-for="(filter, index) in filters"
:key="index"
:is="getComponent(filter.type)"
:filter="filter"
/>
</template>
<script>
import {Link} from "@inertiajs/vue3";
import SearchBadge from "@/componentss/shared/badge/badges/SearchBadge.vue";
import CategoryBadge from "@/componentss/shared/badge/badges/CategoryBadge.vue";
import TagBadge from "@/componentss/shared/badge/badges/TagBadge.vue";
export default {
name: "PostListBadge",
components: {
Link,
SearchBadge,
CategoryBadge,
TagBadge,
},
data() {
return {
}
},
methods: {
getComponent(type) {
const componentMap = {
search: 'SearchBadge',
category: 'CategoryBadge',
tag: 'TagBadge',
};
return componentMap[type] || null;
},
},
props: {
filters: {
type: Object,
},
},
}
</script>
<style scoped>
</style>
@@ -0,0 +1,180 @@
<template>
<div class="flex justify-between pb-4 items-center">
<div class="flex w-full sm:items-center gap-x-5 sm:gap-x-3">
<div class="grow">
<div class="grid sm:flex sm:justify-between sm:items-center gap-2">
<BreadcrumbsWrapper v-if="breadcrumbs">
<li class="text-sm">
<Link :href="route('index')" class="flex items-center text-gray-500 hover:text-blue-600" href="/">
<BasicIcon class="size-5" name="home" />
</Link>
</li>
<li v-if="breadcrumbs.mainSection" class="text-sm">
<span class="flex items-center text-gray-500 hover:text-primaryBlue cursor-pointer" @click.prevent="handleSectionClick(breadcrumbs.mainSection)">
<svg class="flex-shrink-0 mx-2 overflow-visible h-2.5 w-2.5 text-gray-400"
width="16" height="16"
viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 1L10.6869 7.16086C10.8637 7.35239 10.8637 7.64761 10.6869 7.83914L5 14"
stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>
{{ textLimit(breadcrumbs.mainSection.data.title, 25) }}
</span>
</li>
<li v-if="breadcrumbs.subSection" class="text-sm">
<span class="flex items-center text-gray-500 hover:text-primaryBlue cursor-pointer" @click.prevent="handleSubSectionClick(breadcrumbs.mainSection, breadcrumbs.subSection)">
<svg class="flex-shrink-0 mx-2 overflow-visible h-2.5 w-2.5 text-gray-400"
width="16" height="16"
viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 1L10.6869 7.16086C10.8637 7.35239 10.8637 7.64761 10.6869 7.83914L5 14"
stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>
{{ textLimit(breadcrumbs.subSection.data.title, 25) }}
</span>
</li>
<li class="text-sm">
<Link :href="route('client.post.index')" class="flex items-center text-gray-500 hover:text-blue-600">
<svg class="flex-shrink-0 mx-2 overflow-visible h-2.5 w-2.5 text-gray-400"
width="16" height="16"
viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 1L10.6869 7.16086C10.8637 7.35239 10.8637 7.64761 10.6869 7.83914L5 14"
stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>
{{ textLimit('Новости', 30) }}
</Link>
</li>
</BreadcrumbsWrapper>
<BreadcrumbsWrapper v-else>
<li class="text-sm">
<Link :href="route('index')" class="flex items-center text-gray-500 hover:text-blue-600" href="/">
<BasicIcon class="size-5" name="home" />
</Link>
</li>
<li class="text-sm">
<Link :href="route('client.post.index')" class="flex items-center text-gray-500 hover:text-blue-600">
<svg class="flex-shrink-0 mx-2 overflow-visible h-2.5 w-2.5 text-gray-400"
width="16" height="16"
viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 1L10.6869 7.16086C10.8637 7.35239 10.8637 7.64761 10.6869 7.83914L5 14"
stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>
{{ textLimit('Новости', 30) }}
</Link>
</li>
</BreadcrumbsWrapper>
</div>
</div>
</div>
</div>
</template>
<script>
import {Link} from "@inertiajs/vue3";
import slugify from "slugify";
import BasicIcon from "@/componentss/ui/icons/BasicIcon.vue";
import BreadcrumbsWrapper from "@/componentss/ui/wrappers/BreadcrumbsWrapper.vue";
export default {
name: "PostListBreadcrumbs",
components: {BreadcrumbsWrapper, BasicIcon, Link},
data() {
return {
}
},
methods: {
textLimit(text, symbols) {
if (text.length > symbols) {
let LimitedText
LimitedText = text.substring(0, symbols)
return LimitedText + "..."
}
return text
},
isMobileDevice() {
return window.innerWidth < 1024; // Задайте порог для мобильных устройств
},
handleSectionClick(breadcrumb) {
if (this.isMobileDevice()) {
this.toggleMobileNavSection(breadcrumb)
} else {
this.toggleDesktopNavSection(breadcrumb)
}
},
handleSubSectionClick(mainSectionBreadcrumb, breadcrumb) {
if (this.isMobileDevice()) {
this.toggleMobileNavSubSection(mainSectionBreadcrumb, breadcrumb)
} else {
this.toggleDesktopNavSubSection(mainSectionBreadcrumb, breadcrumb)
}
},
highlightNavItem(item) {
item.classList.add('animate-pulse');
setTimeout(() => {
item.classList.remove('animate-pulse');
}, 4000);
},
openMobileNavMenu() {
const openMobileNavBtn = document.getElementById('open-mobile-btn');
openMobileNavBtn.click();
},
toggleMobileNavSubSection(mainSectionBreadcrumb, breadcrumb) {
this.openMobileNavMenu()
const mobileNavElement = document.getElementById('open-mobile-nav');
const sectionNavBlock = mobileNavElement.querySelector('#nav-section-accordion-' + mainSectionBreadcrumb.data.slug);
const sectionNavBlockBtn = mobileNavElement.querySelector('#nav-section-accordion-btn-' + mainSectionBreadcrumb.data.slug);
if (!sectionNavBlock.classList.contains('active')) {
sectionNavBlockBtn.click()
}
const subSectionNavBlock = mobileNavElement.querySelector('#nav-sub-section-accordion-' + breadcrumb.data.slug);
const subSectionNavBlockBtn = mobileNavElement.querySelector('#nav-sub-section-accordion-btn-' + breadcrumb.data.slug);
if (subSectionNavBlock.classList.contains('active')) {
subSectionNavBlockBtn.click()
}
this.highlightNavItem(subSectionNavBlock)
},
toggleMobileNavSection(breadcrumb) {
const mobileNavElement = document.getElementById('open-mobile-nav');
const sectionNavBlock = mobileNavElement.querySelector('#nav-section-accordion-' + breadcrumb.data.slug);
const sectionNavBlockBtn = mobileNavElement.querySelector('#nav-section-accordion-btn-' + breadcrumb.data.slug);
if (sectionNavBlock.classList.contains('active')) {
sectionNavBlockBtn.click()
}
this.openMobileNavMenu()
this.highlightNavItem(sectionNavBlock)
},
toggleDesktopNavSubSection(mainSectionBreadcrumb, breadcrumb) {
const desktopNavElement = document.getElementById('desktop-nav');
const sectionNavTitle = desktopNavElement.querySelector('#nav-sub-section-title-' + breadcrumb.data.slug);
const sectionNavBlockBtn = desktopNavElement.querySelector('#nav-section-btn-' + mainSectionBreadcrumb.data.slug);
sectionNavBlockBtn.click()
this.highlightNavItem(sectionNavTitle)
},
toggleDesktopNavSection(breadcrumb) {
const desktopNavElement = document.getElementById('desktop-nav');
const sectionNavBlock = desktopNavElement.querySelector('#nav-section-menu-' + breadcrumb.data.slug);
const sectionNavBlockBtn = desktopNavElement.querySelector('#nav-section-btn-' + breadcrumb.data.slug);
sectionNavBlockBtn.click()
// this.highlightNavItem(sectionNavBlock)
},
},
props: {
breadcrumbs: {
type: Object,
},
postTitle: {
type: String,
},
},
}
</script>
<style scoped>
</style>
@@ -0,0 +1,83 @@
<template>
<div class="sm:col-span-2 md:grow">
<!--Фильтр-->
<button type="button" class="py-3 px-4 inline-flex items-center gap-x-2 text-sm font-medium rounded-lg border border-transparent bg-blue-600 text-white hover:bg-blue-700 focus:outline-none focus:bg-blue-700 disabled:opacity-50 disabled:pointer-events-none" aria-haspopup="dialog" aria-expanded="false" aria-controls="hs-offcanvas-example" data-hs-overlay="#hs-offcanvas-example">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M10.5 6h9.75M10.5 6a1.5 1.5 0 1 1-3 0m3 0a1.5 1.5 0 1 0-3 0M3.75 6H7.5m3 12h9.75m-9.75 0a1.5 1.5 0 0 1-3 0m3 0a1.5 1.5 0 0 0-3 0m-3.75 0H7.5m9-6h3.75m-3.75 0a1.5 1.5 0 0 1-3 0m3 0a1.5 1.5 0 0 0-3 0m-9.75 0h9.75" />
</svg>
</button>
<!--Конец Фильтра-->
</div>
<div id="hs-offcanvas-example" class="hs-overlay hs-overlay-open:translate-x-0 hidden -translate-x-full fixed top-0 start-0 transition-all duration-300 transform h-full max-w-xs w-full z-[80] bg-white border-e overflow-auto" role="dialog" tabindex="-1" aria-labelledby="hs-offcanvas-example-label">
<div class="flex justify-between items-center py-3 px-4 border-b">
<h3 id="hs-offcanvas-example-label" class="font-bold text-gray-800">
Фильтры
</h3>
<button type="button" class="size-8 inline-flex justify-center items-center gap-x-2 rounded-full border border-transparent bg-gray-100 text-gray-800 hover:bg-gray-200 focus:outline-none focus:bg-gray-200 disabled:opacity-50 disabled:pointer-events-none" aria-label="Close" data-hs-overlay="#hs-offcanvas-example">
<span class="sr-only">Close</span>
<svg class="shrink-0 size-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M18 6 6 18"></path>
<path d="m6 6 12 12"></path>
</svg>
</button>
</div>
<div class="p-4">
<SortingByFilter :sortingBy_filter="sortingBy_filter" />
<div class="hs-accordion-group">
<div class="hs-accordion" :id="'id' + category_filter.type">
<button class="hs-accordion-toggle hs-accordion-active:text-blue-600 py-3 inline-flex items-center gap-x-3 w-full font-semibold text-start text-gray-800 hover:text-gray-500 focus:outline-none focus:text-gray-500 rounded-lg disabled:opacity-50 disabled:pointer-events-none" aria-expanded="false" aria-controls="hs-basic-with-arrow-collapse-two">
<svg class="hs-accordion-active:hidden block size-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="m6 9 6 6 6-6"></path>
</svg>
<svg class="hs-accordion-active:block hidden size-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="m18 15-6-6-6 6"></path>
</svg>
Категории
<span v-if="category_filter.value" class="inline-flex items-center gap-x-1.5 py-1.5 px-3 rounded-md text-xs font-medium border border-gray-200 bg-white text-gray-800 shadow-sm dark:bg-neutral-900 dark:border-neutral-700 dark:text-white">{{ category_filter.value.length }}</span>
</button>
<div id="hs-basic-with-arrow-collapse-two" class="hs-accordion-content hidden w-full overflow-hidden transition-[height] duration-300" role="region" :aria-labelledby="'id' + tag_filter.type">
<CategoryFilter :categories="items" :category_filter="category_filter" />
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "PostListFilter",
data() {
return {
}
},
props: {
items: {
type: Object,
},
category_filter: {
type: Object,
},
sortingBy_filter: {
type: Object,
},
tag_filter: {
type: Object,
},
tags: {
type: Object,
},
},
}
</script>
<style scoped>
</style>
@@ -0,0 +1,112 @@
<template>
<div class="group cursor-pointer">
<div
class="overflow-hidden rounded-md max-h-[250px] bg-gray-100 transition-all hover:scale-105 dark:bg-gray-800"
>
<a
class="relative block aspect-square"
:href="route('client.post.show', post.slug)"
><img
alt="Thumbnail"
loading="lazy"
decoding="async"
data-nimg="fill"
class="object-cover transition-all"
style="
position: absolute;
height: 100%;
width: 100%;
inset: 0px;
color: transparent;
"
sizes="(max-width: 768px) 30vw, 33vw"
:src="post.preview ? '/storage/' + post.preview : '/img/thumbnail-1.png'"
/></a>
</div>
<div class="">
<div>
<div v-if="post.category" class="flex gap-3">
<a :href="route('client.post.index', { 'category[]': post.category.slug })">
<span class="inline-block text-xs font-medium tracking-wider uppercase mt-5 text-blue-600">
{{ post.category ? post.category.title : "Новости" }}
</span>
</a>
</div>
<div v-else class="flex gap-3">
<span>
<span class="inline-block text-xs font-medium tracking-wider uppercase mt-5 text-blue-600">
Новости
</span>
</span>
</div>
<h2
class="text-lg font-semibold leading-snug tracking-tight mt-2 dark:text-white"
>
<a :href="route('client.post.show', post.slug)"
><span class="duration-200 group-hover:text-gray-500">{{
post.title
}}</span></a
>
</h2>
<div class="">
<p
class="mt-2 line-clamp-3 text-sm text-gray-500 dark:text-gray-400"
>
<a
:href="route('client.post.show', post.slug)"
>{{ post.preview_text }}
</a>
</p>
</div>
<div
class="mt-3 flex items-center space-x-3 text-gray-500 dark:text-gray-400"
>
<span>
<div class="flex items-center gap-3">
<span v-if="post && post.authors && post.authors.length > 0" class="truncate text-sm">
{{ textLimit(post.authors[0], 20) }}
</span>
</div>
</span>
<span class="text-xs text-gray-300 dark:text-gray-600"
></span
>
<span class="truncate text-sm">{{ post.created_post }}</span>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "PostListItem",
data() {
return {
}
},
methods: {
textLimit(text, symbols) {
if (text.length > symbols) {
let LimitedText;
LimitedText = text.substring(0, symbols);
return LimitedText + "...";
}
return text;
},
},
props: {
post: {
type: Object,
},
},
}
</script>
<style scoped>
</style>
@@ -0,0 +1,68 @@
<template>
<div class="sm:col-span-1 w-full">
<label for="hs-as-table-product-review-search" class="sr-only">Поиск</label>
<div class="relative">
<input autocomplete="off" @input="search" v-model="searchInput" type="text"
id="hs-as-table-product-review-search"
name="hs-as-table-product-review-search"
class="py-3 px-3 pl-11 block flex-1 w-full border-gray-200 shadow-sm rounded-md text-sm focus:z-10 focus:border-blue-500 focus:ring-blue-500 dark:bg-slate-900 dark:border-gray-700 dark:text-gray-400"
placeholder="Поиск новостей">
<div
class="absolute inset-y-0 left-0 flex items-center pointer-events-none pl-4">
<svg class="h-4 w-4 text-gray-400" xmlns="http://www.w3.org/2000/svg"
width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
<path
d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z"/>
</svg>
</div>
</div>
</div>
</template>
<script>
import { debounce } from "lodash";
export default {
name: "PostListSearch",
data() {
return {
searchInput: this.search_filter.value,
}
},
methods: {
search: debounce(function () {
if(this.searchInput === "") {
let url = new URL(window.location.href);
url.searchParams.delete('search');
let newUrl = url.toString();
this.$inertia.visit(newUrl,{
method: 'get',
preserveState: true,
replace: true,
});
} else {
let url = new URL(window.location.href);
url.searchParams.delete('page');
let newUrl = url.toString();
this.$inertia.visit(newUrl,{
method: 'get',
data: {
search: this.searchInput,
},
preserveState: true,
replace: true,
})
}
}, 500),
},
props: [
'search_filter'
],
}
</script>
<style scoped>
</style>
@@ -0,0 +1,42 @@
<template>
<span class="block">Чтение займет {{ time }}<!-- --> Минуты</span>
</template>
<script>
import {Link} from "@inertiajs/vue3";
import slugify from "slugify";
export default {
name: "PostTimeRead",
components: {Link},
data() {
return {
}
},
methods: {
textLimit(text, symbols) {
if (text.length > symbols) {
let LimitedText
LimitedText = text.substring(0, symbols)
return LimitedText + "..."
}
return text
},
},
props: {
time: {
type: String,
},
},
}
</script>
<style scoped>
</style>