first commit

This commit is contained in:
f4ilji
2024-09-19 16:06:49 +05:00
commit b686638a39
1664 changed files with 428174 additions and 0 deletions
@@ -0,0 +1,71 @@
<template>
<template v-for="file in block.data.file">
<div class="mb-4">
<a class="" :href="'/storage/'+ file.path" download type="button">
<div class="flex border rounded-lg px-4 py-2 items-center justify-between duration-300 hover:bg-gray-100">
<div class="flex items-center justify-between">
<div class="min-w-[30px] min-h-[30px] bg-[#303030] flex justify-center items-center rounded-md mr-2">
<BaseIcon :name="file.expansion" class="w-5 h-5 flex-shrink-0" />
</div>
<div>{{ textLimit(file.title, 70) }}</div>
</div>
<span class="text-sm text-gray-400">{{ file.size }}</span>
</div>
</a>
</div>
</template>
</template>
<script>
import slugify from "slugify";
import FsLightbox from "fslightbox-vue/v3";
import BaseIcon from "@/Components/BaseComponents/BaseIcon.vue";
export default {
name: "FileBlock",
components: {BaseIcon, FsLightbox },
data() {
return {
toggler: false,
domainPath: null,
}
},
methods: {
generateSlug: function (text) {
return slugify(text, {
lower: true,
strict: true,
locale: 'ru'
});
},
textLimit(text, symbols) {
if (text.length > symbols) {
let LimitedText;
LimitedText = text.substring(0, symbols);
return LimitedText + "...";
}
return text;
},
},
mounted() {
this.domainPath = window.location.origin;
},
props: {
block: {
type: Object,
},
},
}
</script>
<style>
.fslightbox-container {
margin: 0 !important;
}
</style>
@@ -0,0 +1,77 @@
<template>
<div v-if="loading" class="flex flex-col space-y-4">
<div class="flex animate-pulse">
<div class="shrink-0 relative rounded-xl overflow-hidden w-full sm:w-56 h-44">
<div class="bg-gray-200 group-focus:scale-105 transition-transform duration-500 ease-in-out size-full absolute top-0 start-0 object-cover rounded-xl" />
</div>
<div class="ms-4 mt-2 w-full">
<p class="h-4 bg-gray-200 rounded-full" style="width: 40%;"></p>
<ul class="mt-5 space-y-3 flex flex-col">
<li class="w-full h-4 bg-gray-200 rounded-full"></li>
<li class="w-full h-4 bg-gray-200 rounded-full"></li>
<li class="w-full h-4 bg-gray-200 rounded-full"></li>
<li class="w-full h-4 bg-gray-200 rounded-full"></li>
<li class="w-20 h-4 bg-gray-200 rounded-full"></li>
</ul>
</div>
</div>
</div>
<div v-else>
<FormBuilder :blocks="form" />
</div>
</template>
<script>
import slugify from "slugify";
import FsLightbox from "fslightbox-vue/v3";
import BaseIcon from "@/Components/BaseComponents/BaseIcon.vue";
import axios from "axios";
import {Link} from "@inertiajs/vue3";
import FormBuilder from "@/Components/BuilderUi/Pages/FormBuilder.vue";
export default {
name: "FormBlock",
components: {FormBuilder, axios, Link },
data() {
return {
form: null,
loading: true, // Состояние загрузки
}
},
methods: {
getForm(id) {
axios.get(route('client.widget.form.single', id))
.then(response => {
this.form = response.data;
this.loading = false; // Установить состояние загрузки в false
})
.catch(error => {
console.error('Ошибка:', error);
});
}
},
mounted() {
this.getForm(this.block.data.form);
},
props: {
block: {
type: Object,
},
},
}
</script>
<style>
.fslightbox-container {
margin: 0 !important;
}
</style>
@@ -0,0 +1,31 @@
<template>
<div >
<h2 :id="generateSlug(block.data.content)" class="font-bold text-xl">{{ block.data.content }}</h2>
</div>
</template>
<script>
import slugify from "slugify";
export default {
name: "HeadingBlock",
methods: {
generateSlug: function (text) {
return slugify(text, {
lower: true,
strict: true,
locale: 'ru'
});
},
},
props: {
block: {
type: Object,
},
},
}
</script>
<style scoped>
</style>
@@ -0,0 +1,50 @@
<template>
<div>
<img @click="toggler = !toggler" loading="lazy" class="mx-auto object-cover rounded-md hover:opacity-95 hover:duration-200 transition" :src="'/storage/' + block.data.url" alt="">
</div>
<FsLightbox class="" :toggler="toggler" :sources="[domainPath + '/storage/' + block.data.url]"/>
</template>
<script>
import slugify from "slugify";
import FsLightbox from "fslightbox-vue/v3";
export default {
name: "ImageBlock",
components: { FsLightbox },
data() {
return {
toggler: false,
domainPath: null,
}
},
methods: {
generateSlug: function (text) {
return slugify(text, {
lower: true,
strict: true,
locale: 'ru'
});
},
},
mounted() {
this.domainPath = window.location.origin;
},
props: {
block: {
type: Object,
},
},
}
</script>
<style>
.fslightbox-container {
margin: 0 !important;
}
</style>
@@ -0,0 +1,105 @@
<template>
<div v-if="loading" class="flex flex-col space-y-4">
<div class="flex animate-pulse">
<div class="ms-4 mt-2 w-full border px-4 py-4 rounded-xl shadow-sm">
<p class="h-4 bg-gray-200 rounded-full" style="width: 40%;"></p>
<ul class="mt-5 space-y-3 flex flex-col">
<li class="w-full h-4 bg-gray-200 rounded-full"></li>
<li class="w-full h-4 bg-gray-200 rounded-full"></li>
</ul>
</div>
</div>
</div>
<div v-else class="w-full px-2 py-5 sm:px-3 lg:px-4 lg:py-7 mx-auto">
<!-- Grid -->
<a class="group flex flex-col bg-white border shadow-sm rounded-xl hover:shadow-md focus:outline-none focus:shadow-md transition"
:href="(page.is_url) ? page.path : route('page.view', page.path) + '/'">
<div class="p-4 md:p-5">
<div class="flex items-center gap-x-5">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="mt-1 shrink-0 size-7 text-gray-600">
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z" />
</svg>
<div class="grow">
<ol class="flex items-center whitespace-nowrap">
<li class="inline-flex items-center">
<span class="flex items-center text-sm text-gray-500 hover:text-blue-600 focus:outline-none focus:text-blue-600" href="#">
{{ breadcrumbs.mainSection }}
</span>
<svg class="shrink-0 mx-2 size-4 text-gray-400" 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="m9 18 6-6-6-6"></path>
</svg>
</li>
<li class="inline-flex items-center">
<span class="flex items-center text-sm text-gray-500 hover:text-blue-600 focus:outline-none focus:text-blue-600" href="#">
{{ breadcrumbs.mainSection }}
</span>
<svg class="shrink-0 mx-2 size-4 text-gray-400" 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="m9 18 6-6-6-6"></path>
</svg>
</li>
<li class="inline-flex items-center">
<span class="flex items-center text-sm text-gray-500 hover:text-blue-600 focus:outline-none focus:text-blue-600" href="#">
{{ breadcrumbs.page }}
</span>
</li>
</ol>
<h3 class="mt-1 group-hover:text-blue-600 font-semibold text-gray-700">
{{ page.title }}
</h3>
</div>
</div>
</div>
</a>
<!-- End Grid -->
</div>
</template>
<script>
import slugify from "slugify";
import axios from "axios";
import {Link} from "@inertiajs/vue3";
export default {
name: "PageItemBlock",
components: { axios, Link },
data() {
return {
page: null,
breadcrumbs: null,
loading: true, // Состояние загрузки
}
},
methods: {
getPage(id) {
axios.get(route('client.widget.page.single', id))
.then(response => {
this.page = response.data.data.page;
this.breadcrumbs = response.data.data.breadcrumbs;
this.loading = false; // Установить состояние загрузки в false
})
.catch(error => {
console.error('Ошибка:', error);
this.loading = false; // Установить состояние загрузки в false даже при ошибке
});
}
},
mounted() {
this.getPage(this.block.data.page)
},
props: {
block: {
type: Object,
},
},
}
</script>
<style>
</style>
@@ -0,0 +1,123 @@
<template>
<div class="text-sm text-gray-600 leading-6 md:text-[16px] md:text-[#374151] md:leading-8 md:font-light paragraph-container" v-html="wrapTables(block).data.content" />
</template>
<script>
import slugify from "slugify";
export default {
name: "ParagraphBlock",
methods: {
wrapTables(data) {
if (data.type === 'paragraph' && data.data && data.data.content) {
// Используем регулярное выражение для поиска всех таблиц
const wrappedContent = data.data.content.replace(/<table([^>]*)>([\s\S]*?)<\/table>/g, (match, attrs, content) => {
return `<div class="div-table"><table${attrs}>${content}</table></div>`;
});
// Возвращаем новый объект с обновленным контентом
return {
...data,
data: {
...data.data,
content: wrappedContent
}
};
}
return data; // Если тип не 'paragraph', возвращаем объект без изменений
}
},
props: {
block: {
type: Object,
},
},
}
</script>
<style>
.paragraph-container a {
@apply text-secondAzure;
@apply underline;
}
.paragraph-container a:hover {
@apply text-secondDarkBlue;
@apply underline;
}
.paragraph-container p {
@apply mb-4
}
.paragraph-container ol li {
@apply list-decimal list-inside
}
.paragraph-container ul li {
@apply list-disc list-inside
}
.paragraph-container li ol {
@apply ml-10
}
.paragraph-container ul {
@apply mb-4
}
.paragraph-container hr {
@apply my-4
}
.paragraph-container strong {
@apply text-xl
}
.div-table {
@apply overflow-x-auto
}
.paragraph-container table {
@apply w-full border-collapse mt-4 mb-4 overflow-hidden; /* Ширина 100%, стыковка границ, отступы, закругленные края */
}
.paragraph-container th, .paragraph-container td {
@apply border border-gray-300 p-3 text-left; /* Границы, отступы, выравнивание текста */
}
.paragraph-container th {
@apply bg-gray-100 text-gray-800 font-semibold; /* Фон заголовка, цвет текста, жирный шрифт */
}
.paragraph-container tr {
@apply transition-colors duration-200; /* Плавный переход цветов */
}
.paragraph-container tr:hover {
@apply bg-gray-200; /* Фон строки при наведении */
}
.paragraph-container td {
@apply text-gray-600; /* Цвет текста ячеек */
}
.paragraph-container tr:hover {
@apply bg-gray-100;
}
</style>
@@ -0,0 +1,70 @@
<template>
<div class="w-full rounded-xl mb-4 p-4 md:p-6 bg-white border border-gray-200 ">
<div class="flex items-center gap-x-4 text-nowrap">
<img @click="toggler = !toggler" loading="lazy" class="rounded-xl w-[150px]" :src="'/storage/' + block.data.photo" alt="Image Description">
<div class="grow overflow-x-auto">
<p class="font-medium text-gray-800 hover:text-gray-500">
{{ block.data.name }}
</p>
<template v-for="item in block.data.info">
<p class="text-xs text-gray-500 mt-2">
{{ item.column }}: {{ item.content }}
</p>
</template>
</div>
</div>
<!-- Social Brands -->
<!-- End Social Brands -->
</div>
<FsLightbox class="" :toggler="toggler" :sources="[domainPath + '/storage/' + block.data.photo]"/>
</template>
<script>
import slugify from "slugify";
import FsLightbox from "fslightbox-vue/v3";
export default {
name: "PersonBlock",
components: { FsLightbox },
data() {
return {
toggler: false,
domainPath: null,
}
},
methods: {
generateSlug: function (text) {
return slugify(text, {
lower: true,
strict: true,
locale: 'ru'
});
},
},
mounted() {
this.domainPath = window.location.origin;
},
props: {
block: {
type: Object,
},
},
}
</script>
<style scoped>
.fslightbox-container {
margin: 0 !important;
}
</style>
@@ -0,0 +1,105 @@
<template>
<div class="w-full px-4 py-5 sm:px-6 lg:px-8 lg:py-7 mx-auto">
<!-- Проверка на загрузку данных -->
<div v-if="loading" class="flex flex-col space-y-4">
<div class="flex animate-pulse">
<div class="shrink-0 relative rounded-xl overflow-hidden w-full sm:w-56 h-44">
<div class="bg-gray-200 group-focus:scale-105 transition-transform duration-500 ease-in-out size-full absolute top-0 start-0 object-cover rounded-xl" />
</div>
<div class="ms-4 mt-2 w-full">
<p class="h-4 bg-gray-200 rounded-full" style="width: 40%;"></p>
<ul class="mt-5 space-y-3 flex flex-col">
<li class="w-full h-4 bg-gray-200 rounded-full"></li>
<li class="w-full h-4 bg-gray-200 rounded-full"></li>
<li class="w-full h-4 bg-gray-200 rounded-full"></li>
<li class="w-full h-4 bg-gray-200 rounded-full"></li>
<li class="w-20 h-4 bg-gray-200 rounded-full"></li>
</ul>
</div>
</div>
</div>
<div v-else class="grid lg:grid-cols-1 lg:gap-y-16 gap-10">
<Link class="group block rounded-xl overflow-hidden focus:outline-none" :href="route('client.post.show', post.data.slug)">
<div class="flex flex-col sm:flex-row sm:items-center gap-3 sm:gap-5">
<div class="shrink-0 relative rounded-xl overflow-hidden w-full sm:w-56 h-44">
<img class="group-hover:scale-105 group-focus:scale-105 transition-transform duration-500 ease-in-out size-full absolute top-0 start-0 object-cover rounded-xl"
:src="post.data.preview ? 'storage/images/' + post.data.preview : '/img/thumbnail-1.png'" />
</div>
<div class="grow">
<h3 class="text-xl font-semibold text-gray-800 group-hover:text-gray-600">
{{ post.data.title }}
</h3>
<p class="mt-3 text-gray-600">
Produce professional, reliable streams easily leveraging Preline's innovative broadcast studio
</p>
<p class="mt-4 inline-flex items-center gap-x-1 text-sm text-primaryBlue decoration-2 group-hover:underline group-focus:underline font-medium">
Читать далее
<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="m9 18 6-6-6-6"/>
</svg>
</p>
</div>
</div>
</Link>
</div>
</div>
</template>
<script>
import slugify from "slugify";
import axios from "axios";
import {Link} from "@inertiajs/vue3";
export default {
name: "PostListBlock",
components: { axios, Link },
data() {
return {
post: null,
loading: true, // Состояние загрузки
}
},
methods: {
getPost(id) {
axios.get(route('client.widget.post.single', id), {
params: {
count: this.block.data.count,
category: this.block.data.category // Исправлено с count.category на category
}
})
.then(response => {
this.post = response.data;
this.loading = false; // Установить состояние загрузки в false
})
.catch(error => {
console.error('Ошибка:', error);
this.loading = false; // Установить состояние загрузки в false даже при ошибке
});
}
},
mounted() {
this.getPost(this.block.data.post);
},
props: {
block: {
type: Object,
},
},
}
</script>
<style>
.fslightbox-container {
margin: 0 !important;
}
</style>
@@ -0,0 +1,149 @@
<template>
<div class="w-full px-4 py-5 sm:px-6 lg:px-8 lg:py-7 mx-auto">
<!-- Проверка на загрузку данных -->
<div v-if="loading" class="flex flex-col space-y-4">
<div class="flex animate-pulse">
<div class="shrink-0 relative rounded-xl overflow-hidden w-full sm:w-56 h-44">
<div class="bg-gray-200 group-focus:scale-105 transition-transform duration-500 ease-in-out size-full absolute top-0 start-0 object-cover rounded-xl" />
</div>
<div class="ms-4 mt-2 w-full">
<p class="h-4 bg-gray-200 rounded-full" style="width: 40%;"></p>
<ul class="mt-5 space-y-3 flex flex-col">
<li class="w-full h-4 bg-gray-200 rounded-full"></li>
<li class="w-full h-4 bg-gray-200 rounded-full"></li>
<li class="w-full h-4 bg-gray-200 rounded-full"></li>
<li class="w-full h-4 bg-gray-200 rounded-full"></li>
<li class="w-20 h-4 bg-gray-200 rounded-full"></li>
</ul>
</div>
</div>
<div class="flex animate-pulse">
<div class="shrink-0 relative rounded-xl overflow-hidden w-full sm:w-56 h-44">
<div class="bg-gray-200 group-focus:scale-105 transition-transform duration-500 ease-in-out size-full absolute top-0 start-0 object-cover rounded-xl" />
</div>
<div class="ms-4 mt-2 w-full">
<p class="h-4 bg-gray-200 rounded-full" style="width: 40%;"></p>
<ul class="mt-5 space-y-3 flex flex-col">
<li class="w-full h-4 bg-gray-200 rounded-full"></li>
<li class="w-full h-4 bg-gray-200 rounded-full"></li>
<li class="w-full h-4 bg-gray-200 rounded-full"></li>
<li class="w-full h-4 bg-gray-200 rounded-full"></li>
<li class="w-20 h-4 bg-gray-200 rounded-full"></li>
</ul>
</div>
</div>
<div class="flex animate-pulse">
<div class="shrink-0 relative rounded-xl overflow-hidden w-full sm:w-56 h-44">
<div class="bg-gray-200 group-focus:scale-105 transition-transform duration-500 ease-in-out size-full absolute top-0 start-0 object-cover rounded-xl" />
</div>
<div class="ms-4 mt-2 w-full">
<p class="h-4 bg-gray-200 rounded-full" style="width: 40%;"></p>
<ul class="mt-5 space-y-3 flex flex-col">
<li class="w-full h-4 bg-gray-200 rounded-full"></li>
<li class="w-full h-4 bg-gray-200 rounded-full"></li>
<li class="w-full h-4 bg-gray-200 rounded-full"></li>
<li class="w-full h-4 bg-gray-200 rounded-full"></li>
<li class="w-20 h-4 bg-gray-200 rounded-full"></li>
</ul>
</div>
</div>
</div>
<div v-else class="grid lg:grid-cols-1 lg:gap-y-16 gap-10">
<template v-for="post in posts.data" :key="post.id">
<Link class="group block rounded-xl overflow-hidden focus:outline-none" :href="route('client.post.show', post.slug)">
<div class="flex flex-col sm:flex-row sm:items-center gap-3 sm:gap-5">
<div class="shrink-0 relative rounded-xl overflow-hidden w-full sm:w-56 h-44">
<img class="group-hover:scale-105 group-focus:scale-105 transition-transform duration-500 ease-in-out size-full absolute top-0 start-0 object-cover rounded-xl"
:src="post.preview ? 'storage/images/' + post.preview : '/img/thumbnail-1.png'" />
</div>
<div class="grow">
<h3 class="text-xl font-semibold text-gray-800 group-hover:text-gray-600">
{{ post.title }}
</h3>
<p class="mt-3 text-gray-600">
Produce professional, reliable streams easily leveraging Preline's innovative broadcast studio
</p>
<p class="mt-4 inline-flex items-center gap-x-1 text-sm text-primaryBlue decoration-2 group-hover:underline group-focus:underline font-medium">
Читать далее
<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="m9 18 6-6-6-6"/>
</svg>
</p>
</div>
</div>
</Link>
</template>
<div class="flex justify-center">
<a :href="route('client.post.index', { category: block.data.category })" class="group inline-flex items-center gap-x-1 text-sm font-semibold text-[#1A5AAF]">
Все новости
<svg class="flex-shrink-0 size-4 transition ease-in-out group-hover:translate-x-1" 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="m9 18 6-6-6-6"/></svg>
</a>
</div>
</div>
</div>
</template>
<script>
import slugify from "slugify";
import axios from "axios";
import {Link} from "@inertiajs/vue3";
export default {
name: "PostListBlock",
components: { axios, Link },
data() {
return {
posts: null,
loading: true, // Состояние загрузки
}
},
methods: {
getPosts() {
axios.get(route('client.widget.post.index'), {
params: {
count: this.block.data.count,
category: this.block.data.category // Исправлено с count.category на category
}
})
.then(response => {
this.posts = response.data;
this.loading = false; // Установить состояние загрузки в false
})
.catch(error => {
console.error('Ошибка:', error);
this.loading = false; // Установить состояние загрузки в false даже при ошибке
});
}
},
mounted() {
this.getPosts();
},
props: {
block: {
type: Object,
},
},
}
</script>
<style>
.fslightbox-container {
margin: 0 !important;
}
</style>
@@ -0,0 +1,83 @@
<template>
<div>
<template v-for="(step, index) in block.data.steps">
<div class="flex gap-x-3">
<div class="w-16 text-end min-w-[4rem]">
<span class="text-xs text-gray-500">{{ block.data.step_name }} {{ index + 1 }}</span>
</div>
<div class="relative last:after:hidden after:absolute after:top-7 after:bottom-0 after:start-3.5 after:w-px after:-translate-x-[0.5px] after:bg-gray-200">
<div class="relative z-10 size-7 flex justify-center items-center">
<div class="size-2 rounded-full bg-primaryBlue"></div>
</div>
</div>
<div class="grow max-w-[70%] pt-0.5 pb-8 overflow-wrap break-words">
<h3 class="flex gap-x-1.5 font-semibold text-gray-800">
{{ step.title }}
</h3>
<p class="mt-1 text-sm text-gray-600 step-content" v-html="step.content" />
</div>
</div>
</template>
</div>
</template>
<script>
import slugify from "slugify";
import FsLightbox from "fslightbox-vue/v3";
export default {
name: "StepperBlock",
components: { FsLightbox },
data() {
return {
toggler: false,
domainPath: null,
}
},
methods: {
generateSlug: function (text) {
return slugify(text, {
lower: true,
strict: true,
locale: 'ru'
});
},
},
mounted() {
this.domainPath = window.location.origin;
},
props: {
block: {
type: Object,
},
},
}
</script>
<style>
.step-content a {
@apply text-secondAzure;
@apply underline;
}
.step-content a:hover {
@apply text-secondDarkBlue;
@apply underline;
}
.step-content ol li {
@apply list-decimal list-inside
}
.step-content ul li {
@apply list-disc list-inside
}
.step-content li ol {
@apply ml-10
}
</style>
@@ -0,0 +1,67 @@
<template>
<div class="">
<nav class="-mb-0.5 flex justify-center gap-2 flex-wrap" aria-label="Tabs" role="tablist" aria-orientation="horizontal">
<button
v-for="(tab, index) in block.data.tab" type="button"
class="hs-tab-active:bg-gray-100 rounded-md hs-tab-active:text-gray-700 py-1.5 px-3 inline-flex items-center gap-x-2 border-b-2 border-transparent text-sm whitespace-nowrap text-gray-500 focus:outline-none disabled:opacity-50 disabled:pointer-events-none"
:class="(activeTab === index) ? 'active' : ''"
@click="activeTab = index"
:id="generateSlug(tab.title) + '-item'"
:data-hs-tab="'#' + generateSlug(tab.title)"
aria-selected="false"
:aria-controls="generateSlug(tab.title)" role="tab">
{{ tab.title }}
</button>
</nav>
</div>
<div class="mt-3">
<template v-for="(tab, index) in block.data.tab">
<div :id="generateSlug(tab.title)" :class="(activeTab === index) ? '' : 'hidden'"
role="tabpanel" :aria-labelledby="generateSlug(tab.title) + '-item'">
<PageTabBuilder :blocks="tab.content" />
</div>
</template>
</div>
</template>
<script>
import slugify from "slugify";
import PageTabBuilder from "@/Components/BuilderUi/Pages/PageTabBuilder.vue";
export default {
name: "TabBlock",
components: {
PageTabBuilder
},
data() {
return {
activeTab: 0,
}
},
methods: {
generateSlug: function (text) {
return slugify(text, {
lower: true,
strict: true,
locale: 'ru'
});
},
},
mounted() {
this.domainPath = window.location.origin;
},
props: {
block: {
type: Object,
},
},
}
</script>
<style>
</style>
@@ -0,0 +1,45 @@
<template>
<video class="h-full w-full rounded-lg" controls>
<source
:src="domainPath + '/storage/' + block.data.path"
:type="block.data.mime"
/>
Your browser does not support the video tag.
</video>
<figcaption class="mt-3 text-sm text-center text-gray-500 dark:text-neutral-500">
{{ block.data.title }}
</figcaption>
</template>
<script>
import slugify from "slugify";
import FsLightbox from "fslightbox-vue/v3";
export default {
name: "VideoBlock",
data() {
return {
toggler: false,
domainPath: null,
}
},
methods: {
},
mounted() {
this.domainPath = window.location.origin;
},
props: {
block: {
type: Object,
},
},
}
</script>
<style>
</style>
@@ -0,0 +1,50 @@
<template>
<div class="mb-4 sm:mb-8">
<label :for="block.data.name_field + '-id'" class="block mb-2 text-sm font-medium">{{ block.data.title_field }}</label>
<div class="relative">
<input
:required="block.data.rules.required"
:name="block.data.name_field"
:min="block.data.rules.min"
:max="block.data.rules.max"
type="email"
:id="block.data.name_field + '-id'"
:class="(error) ? 'border-red-500 focus:border-red-500 focus:ring-red-500' : 'focus:border-blue-500 focus:ring-blue-500'"
class="py-3 px-4 block w-full border-gray-200 rounded-lg text-sm disabled:opacity-50 disabled:pointer-events-none"
:placeholder="block.data.title_field"
>
<div v-if="error" class="absolute inset-y-0 end-0 flex items-center pointer-events-none pe-3">
<svg class="shrink-0 size-4 text-red-500" 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="12" cy="12" r="10"></circle>
<line x1="12" x2="12" y1="8" y2="12"></line>
<line x1="12" x2="12.01" y1="16" y2="16"></line>
</svg>
</div>
</div>
<p v-if="!error" class="mt-2 text-sm text-gray-500" id="hs-input-helper-text">{{ block.data.description }}</p>
<p v-for="item in error" class="text-sm text-red-600 mt-2" id="hs-validation-name-error-helper">{{ item }}</p>
</div>
</template>
<script>
export default {
name: "EmailBlock",
data() {
return {
}
},
methods: {},
props: {
block: {
type: Object,
},
error: {
type: Object,
}
},
}
</script>
@@ -0,0 +1,41 @@
<template>
<div class="mb-4 sm:mb-8">
<label class="block mb-3 text-sm font-medium">{{ block.data.title_field }}</label>
<div class="space-y-2">
<div v-for="column in block.data.columns" class="flex">
<input
type="checkbox"
:name="block.data.name_field + '[]'"
:value="column.name_field"
class="shrink-0 mt-0.5 border-gray-200 rounded text-blue-600 focus:ring-blue-500 disabled:opacity-50 disabled:pointer-events-none"
:id="column.name_field + '-id'">
<label :for="column.name_field + '-id'" class="text-sm text-gray-500 ms-3">{{ column.title_field }}</label>
</div>
</div>
<p v-if="!error" class="mt-2 text-sm text-gray-500" id="hs-input-helper-text">{{ block.data.description }}</p>
<p v-for="item in error" class="text-sm text-red-600 mt-2" id="hs-validation-name-error-helper">{{ item }}</p>
</div>
</template>
<script>
export default {
name: "MultipleChoiceBlock",
data() {
return {
}
},
methods: {},
props: {
block: {
type: Object,
},
error: {
type: Object,
}
},
}
</script>
@@ -0,0 +1,49 @@
<template>
<div class="mb-4 sm:mb-8">
<label :for="block.data.name_field + '-id'" class="block mb-2 text-sm font-medium">{{ block.data.title_field }}</label>
<div class="relative">
<input
:required="block.data.rules.required"
:name="block.data.name_field"
:min="block.data.rules.min"
:max="block.data.rules.max"
type="tel"
:id="block.data.name_field + '-id'"
:class="(error) ? 'border-red-500 focus:border-red-500 focus:ring-red-500' : 'focus:border-blue-500 focus:ring-blue-500'"
class="py-3 px-4 block w-full border-gray-200 rounded-lg text-sm disabled:opacity-50 disabled:pointer-events-none"
:placeholder="block.data.title_field"
>
<div v-if="error" class="absolute inset-y-0 end-0 flex items-center pointer-events-none pe-3">
<svg class="shrink-0 size-4 text-red-500" 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="12" cy="12" r="10"></circle>
<line x1="12" x2="12" y1="8" y2="12"></line>
<line x1="12" x2="12.01" y1="16" y2="16"></line>
</svg>
</div>
</div>
<p v-if="!error" class="mt-2 text-sm text-gray-500" id="hs-input-helper-text">{{ block.data.description }}</p>
<p v-for="item in error" class="text-sm text-red-600 mt-2" id="hs-validation-name-error-helper">{{ item }}</p>
</div>
</template>
<script>
export default {
name: "PhoneBlock",
data() {
return {}
},
methods: {},
props: {
block: {
type: Object,
},
error: {
type: Object,
}
},
}
</script>
@@ -0,0 +1,42 @@
<template>
<div class="mb-4 sm:mb-8">
<label class="block mb-3 text-sm font-medium">{{ block.data.title_field }}</label>
<div class="space-y-2">
<div v-for="column in block.data.columns" class="flex">
<input
type="radio"
:name="block.data.name_field"
:value="column.name_field"
name="hs-default-radio"
class="shrink-0 mt-0.5 border-gray-200 rounded-full text-blue-600 focus:ring-blue-500 disabled:opacity-50 disabled:pointer-events-none"
:id="column.name_field + '-id'">
<label :for="column.name_field + '-id'" class="text-sm text-gray-500 ms-2">{{ column.title_field }}</label>
</div>
</div>
<p v-if="!error" class="mt-2 text-sm text-gray-500" id="hs-input-helper-text">{{ block.data.description }}</p>
<p v-for="item in error" class="text-sm text-red-600 mt-2" id="hs-validation-name-error-helper">{{ item }}</p>
</div>
</template>
<script>
export default {
name: "SingleChoiceBlock",
data() {
return {
}
},
methods: {},
props: {
block: {
type: Object,
},
error: {
type: Object,
}
},
}
</script>
@@ -0,0 +1,33 @@
<template>
<div class="mt-6 grid">
<button type="submit" class="w-full py-3 px-4 inline-flex justify-center 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">{{ block }}</button>
</div>
</template>
<script>
import slugify from "slugify";
export default {
name: "SubmitBlock",
methods: {
},
props: {
block: {
type: Object,
},
},
}
</script>
<style>
</style>
@@ -0,0 +1,53 @@
<template>
<div class="mb-4 sm:mb-8">
<label :for="block.data.name_field + '-id'" class="block mb-2 text-sm font-medium">{{ block.data.title_field }}</label>
<div class="relative">
<input
:required="block.data.rules.required"
:name="block.data.name_field"
:min="block.data.rules.min"
:max="block.data.rules.max"
type="text"
:id="block.data.name_field + '-id'"
:class="(error) ? 'border-red-500 focus:border-red-500 focus:ring-red-500' : 'focus:border-blue-500 focus:ring-blue-500'"
class="py-3 px-4 block w-full border-gray-200 rounded-lg text-sm disabled:opacity-50 disabled:pointer-events-none"
:placeholder="block.data.title_field"
>
<div v-if="error" class="absolute inset-y-0 end-0 flex items-center pointer-events-none pe-3">
<svg class="shrink-0 size-4 text-red-500" 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="12" cy="12" r="10"></circle>
<line x1="12" x2="12" y1="8" y2="12"></line>
<line x1="12" x2="12.01" y1="16" y2="16"></line>
</svg>
</div>
</div>
<p v-if="!error" class="mt-2 text-sm text-gray-500" id="hs-input-helper-text">{{ block.data.description }}</p>
<p v-for="item in error" class="text-sm text-red-600 mt-2" id="hs-validation-name-error-helper">{{ item }}</p>
</div>
</template>
<script>
export default {
name: "TextAreaBlock",
data() {
return {}
},
methods: {},
props: {
block: {
type: Object,
},
error: {
type: Object,
}
},
}
</script>
<style scoped>
</style>
@@ -0,0 +1,51 @@
<template>
<div class="mb-4 sm:mb-8">
<label :for="block.data.name_field + '-id'" class="block mb-2 text-sm font-medium">{{ block.data.title_field }}</label>
<div class="relative">
<input
:required="block.data.rules.required"
:name="block.data.name_field"
:min="block.data.rules.min"
:max="block.data.rules.max"
type="text"
:id="block.data.name_field + '-id'"
:class="(error) ? 'border-red-500 focus:border-red-500 focus:ring-red-500' : 'focus:border-blue-500 focus:ring-blue-500'"
class="py-3 px-4 block w-full border-gray-200 rounded-lg text-sm disabled:opacity-50 disabled:pointer-events-none"
:placeholder="block.data.title_field"
>
<div v-if="error" class="absolute inset-y-0 end-0 flex items-center pointer-events-none pe-3">
<svg class="shrink-0 size-4 text-red-500" 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="12" cy="12" r="10"></circle>
<line x1="12" x2="12" y1="8" y2="12"></line>
<line x1="12" x2="12.01" y1="16" y2="16"></line>
</svg>
</div>
</div>
<p v-if="!error" class="mt-2 text-sm text-gray-500" id="hs-input-helper-text">{{ block.data.description }}</p>
<p v-for="item in error" class="text-sm text-red-600 mt-2" id="hs-validation-name-error-helper">{{ item }}</p>
</div>
</template>
<script>
export default {
name: "TextBlock",
data() {
return {
}
},
methods: {},
props: {
block: {
type: Object,
},
error: {
type: Object,
}
},
}
</script>
@@ -0,0 +1,141 @@
<template>
<div v-if="success" class="bg-teal-50 border-t-2 border-teal-500 rounded-lg p-4 dark:bg-teal-800/30" role="alert" tabindex="-1" aria-labelledby="hs-bordered-success-style-label">
<div class="flex">
<div class="shrink-0">
<!-- Icon -->
<span class="inline-flex justify-center items-center size-8 rounded-full border-4 border-teal-100 bg-teal-200 text-teal-800 dark:border-teal-900 dark:bg-teal-800 dark:text-teal-400">
<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="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10z"></path>
<path d="m9 12 2 2 4-4"></path>
</svg>
</span>
<!-- End Icon -->
</div>
<div class="ms-3">
<h3 id="hs-bordered-success-style-label" class="text-gray-800 font-semibold dark:text-white">
Успешно отправлено!
</h3>
<p class="text-sm text-gray-700 dark:text-neutral-400">
{{ message }}
</p>
</div>
</div>
</div>
<div v-else class="max-w-[85rem] py-10 sm:px-6 lg:px-8 lg:py-14 mx-auto">
<div class="mx-auto max-w-2xl">
<div class="text-center">
<h2 class="text-xl text-gray-800 font-bold sm:text-3xl">
{{ blocks.data.title }}
</h2>
</div>
<!-- Card -->
<div class="mt-5 p-4 relative z-10 bg-white border rounded-xl sm:mt-10 md:p-10">
<form @submit="submitForm">
<component
v-for="(block, index) in blocks.data.columns"
:key="index"
:is="getComponent(block.type)"
:block="block"
:error="errors && errors[block.data.name_field] ? errors[block.data.name_field] : null"
/>
<SubmitBlock :block="blocks.data.button" />
</form>
</div>
<!-- End Card -->
</div>
</div>
</template>
<script>
import TextBlock from "@/Components/BuilderUi/Pages/FormBlocks/TextBlock.vue";
import PhoneBlock from "@/Components/BuilderUi/Pages/FormBlocks/PhoneBlock.vue";
import EmailBlock from "@/Components/BuilderUi/Pages/FormBlocks/EmailBlock.vue";
import SubmitBlock from "@/Components/BuilderUi/Pages/FormBlocks/SubmitBlock.vue";
import axios from "axios";
import TextAreaBlock from "@/Components/BuilderUi/Pages/FormBlocks/TextAreaBlock.vue";
import MultipleChoiceBlock from "@/Components/BuilderUi/Pages/FormBlocks/MultipleChoiceBlock.vue";
import SingleChoiceBlock from "@/Components/BuilderUi/Pages/FormBlocks/SingleChoiceBlock.vue";
export default {
name: "FormBuilder",
components: {
SubmitBlock,
TextBlock,
PhoneBlock,
EmailBlock,
TextAreaBlock,
MultipleChoiceBlock,
SingleChoiceBlock
},
data() {
return {
formData: {},
errors: null,
success: false,
message: null,
};
},
methods: {
getComponent(type) {
const componentMap = {
text: 'TextBlock',
phone: 'PhoneBlock',
email: 'EmailBlock',
textarea: 'TextAreaBlock',
multiple_choice: 'MultipleChoiceBlock',
single_choice: 'SingleChoiceBlock',
};
return componentMap[type] || null;
},
submitForm(event) {
event.preventDefault();
const formElements = event.target.elements;
this.formData = {};
for (let i = 0; i < formElements.length; i++) {
const element = formElements[i];
if (element.tagName === 'INPUT') {
// Убираем квадратные скобки из имени поля
const fieldName = element.name.endsWith('[]') ? element.name.slice(0, -2) : element.name;
if (element.type === 'checkbox') {
if (!this.formData[fieldName]) {
this.formData[fieldName] = [];
}
if (element.checked) {
this.formData[fieldName].push(element.value);
}
} else if (fieldName && fieldName !== 'choices') {
this.formData[fieldName] = element.value;
}
}
}
// Отправляем данные на сервер
axios.post(route('client.widget.form.submit', this.blocks.data.id), this.formData)
.then(response => {
if (response.data.status === 'ok') {
this.success = true;
this.message = response.data.message;
this.errors = null; // Сбрасываем ошибки при успешной отправке
}
})
.catch(error => {
this.errors = error.response.data || ['Неизвестная ошибка']; // Обработка ошибок
this.success = false; // Сбрасываем успешное состояние
});
} },
props: {
blocks: {
type: Object,
},
},
}
</script>
@@ -0,0 +1,112 @@
<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">
<ol v-if="breadcrumbs" class="flex items-center whitespace-normal min-w-0 flex-wrap gap-y-2"
aria-label="Breadcrumb">
<li class="text-sm">
<span class="flex items-center text-gray-500 hover:text-primaryBlue">
Главная
<svg class="flex-shrink-0 mx-3 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>
</span>
</li>
<li class="text-sm">
<span class="flex items-center text-gray-500 hover:text-primaryBlue" @click.prevent>
{{ textLimit(this.breadcrumbs.mainSection, 25) }}
<svg class="flex-shrink-0 mx-3 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>
</span>
</li>
<li class="text-sm">
<span class="flex items-center text-gray-500 hover:text-primaryBlue" @click.prevent>
{{ textLimit(this.breadcrumbs.subSection, 25) }}
<svg class="flex-shrink-0 mx-3 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>
</span>
</li>
<li class="text-sm">
<span class="flex items-center text-gray-500 hover:text-primaryBlue" @click.prevent>
{{ textLimit(this.breadcrumbs.page, 25) }}
</span>
</li>
</ol>
<ol v-if="!breadcrumbs" class="flex items-center whitespace-nowrap min-w-0 flex-wrap"
aria-label="Breadcrumb">
<li class="text-sm">
<span class="flex items-center text-gray-500 hover:text-blue-600" href="/">
Главная
<svg class="flex-shrink-0 mx-3 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>
</span>
</li>
<li class="text-sm">
<span class="flex items-center text-gray-500 hover:text-blue-600" @click.prevent>
{{ textLimit(pageTitle, 30) }}
</span>
</li>
</ol>
</div>
</div>
</div>
</div>
</template>
<script>
import {Link} from "@inertiajs/vue3";
import slugify from "slugify";
export default {
name: "PageBreadcrumbs",
components: {Link},
data() {
return {
}
},
methods: {
textLimit(text, symbols) {
if (text.length > symbols) {
let LimitedText
LimitedText = text.substring(0, symbols)
return LimitedText + "..."
}
return text
},
},
props: {
breadcrumbs: {
type: Object,
},
pageTitle: {
type: String,
},
},
}
</script>
<style scoped>
</style>
@@ -0,0 +1,75 @@
<template>
<component
v-for="(block, index) in blocks"
:key="index"
:is="getComponent(block.type)"
:block="block"
/>
</template>
<script>
import HeadingBlock from "@/Components/BuilderUi/Pages/Blocks/HeadingBlock.vue";
import ParagraphBlock from "@/Components/BuilderUi/Pages/Blocks/ParagraphBlock.vue";
import ClientImageSlider from "@/Components/ClientImageSlider.vue";
import ImageBlock from "@/Components/BuilderUi/Pages/Blocks/ImageBlock.vue";
import FileBlock from "@/Components/BuilderUi/Pages/Blocks/FileBlock.vue";
import PersonBlock from "@/Components/BuilderUi/Pages/Blocks/PersonBlock.vue";
import StepperBlock from "@/Components/BuilderUi/Pages/Blocks/StepperBlock.vue";
import VideoBlock from "@/Components/BuilderUi/Pages/Blocks/VideoBlock.vue";
import TabBlock from "@/Components/BuilderUi/Pages/Blocks/TabBlock.vue";
import PostListBlock from "@/Components/BuilderUi/Pages/Blocks/PostListBlock.vue";
import PageItemBlock from "@/Components/BuilderUi/Pages/Blocks/PageItemBlock.vue";
import PostItemBlock from "@/Components/BuilderUi/Pages/Blocks/PostItemBlock.vue";
import FormBlock from "@/Components/BuilderUi/Pages/Blocks/FormBlock.vue";
export default {
name: "PageBuilder",
components: {
FileBlock,
ImageBlock,
ClientImageSlider,
ParagraphBlock,
HeadingBlock,
PersonBlock,
StepperBlock,
VideoBlock,
TabBlock,
PostListBlock,
PageItemBlock,
PostItemBlock,
FormBlock
},
methods: {
getComponent(type) {
const componentMap = {
heading: 'HeadingBlock',
paragraph: 'ParagraphBlock',
images: 'ClientImageSlider',
image: 'ImageBlock',
files: 'FileBlock',
person: 'PersonBlock',
stepper: 'StepperBlock',
video: 'VideoBlock',
tabs: 'TabBlock',
postsList: 'PostListBlock',
postItem: 'PostItemBlock',
pageItem: 'PageItemBlock',
customForm: 'FormBlock'
};
return componentMap[type] || null;
},
},
props: {
blocks: {
type: Object,
},
},
}
</script>
<style scoped>
</style>
@@ -0,0 +1,114 @@
<template>
<div class="w-full h-[67px] fixed" id="visor"></div>
<nav class="order-last hidden w-56 shrink-0 lg:block">
<div v-if="headerNavs.length > 0" class="sticky top-[100px] h-[calc(100vh-121px)]">
<div class="text-gray-1000 mb-2 text-md font-medium">На этой странице</div>
<ul class="styled-scrollbar max-h-[70vh] space-y-1.5 overflow-y-auto py-2 text-sm">
<li class="anchor-li" v-for="pageNav in headerNavs" :key="pageNav.id">
<a :class="{ 'translate-x-2 text-primaryBlue' : currentNavSection === generateSlug(pageNav.text), 'bg-transperant text-gray-600 hover:text-gray-900' : currentNavSection !== generateSlug(pageNav.text) }"
class="duration-150 block py-1 px-2 leading-[1.6] rounded-md"
:href="'#' + generateSlug(pageNav.text)">{{ pageNav.text }}</a>
</li>
<transition name="fade">
<li class="anchor-li flex items-center py-2 border-t" v-if="scrollTop" @click.prevent="scrollToTop">
<button class="bg-transperant text-gray-600 cursor-pointer hover:text-gray-900 duration-300 block px-2 leading-[1.6] rounded-md">К началу</button>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-[17px] text-gray-600">
<path stroke-linecap="round" stroke-linejoin="round" d="M15 11.25l-3-3m0 0l-3 3m3-3v7.5M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</li>
</transition>
</ul>
</div>
</nav>
</template>
<script>
import { Link } from "@inertiajs/vue3";
import slugify from "slugify";
export default {
name: "PageNavigateLinks",
components: { Link },
data() {
return {
currentNavSection: null,
scrollTop: false,
};
},
methods: {
textLimit(text, symbols) {
if (text.length > symbols) {
let LimitedText;
LimitedText = text.substring(0, symbols);
return LimitedText + "...";
}
return text;
},
generateSlug(text) {
return slugify(text, {
lower: true,
strict: true,
locale: "ru",
});
},
onScroll(e) {
const windowTop = window.scrollY;
this.scrollTop = windowTop > 100;
const headings = document.querySelectorAll("h2");
const visor = document.querySelector("#visor");
let lastVisibleHeading = null;
const visorRect = visor.getBoundingClientRect();
// Проверяем, находится ли визор в пределах видимости
if (visorRect.top > window.scrollY) {
this.currentNavSection = null;
lastVisibleHeading = null; // Сбрасываем заголовок, если визор не виден
return; // Выходим из функции, если визор не виден
}
for (let i = 0; i < headings.length; i++) {
const heading = headings[i];
const rect = heading.getBoundingClientRect();
// Проверяем, находится ли заголовок в видимой области и касается ли он элемента visor
if (
rect.top >= 0 &&
rect.bottom <= window.innerHeight &&
rect.bottom >= visorRect.top &&
rect.top <= visorRect.bottom
) {
// Проверяем, изменился ли заголовок
if (heading !== lastVisibleHeading) {
this.currentNavSection = heading.id;
lastVisibleHeading = heading;
}
break; // Выходим из цикла, если нашли видимый заголовок
}
}
},
scrollToTop() {
window.scrollTo(0, 0);
},
},
mounted() {
window.addEventListener("scroll", this.onScroll);
},
unmounted() {
window.removeEventListener("scroll", this.onScroll);
},
props: {
headerNavs: {
type: Array,
},
},
};
</script>
<style scoped>
</style>
@@ -0,0 +1,74 @@
<template>
<div class="sticky top-[100px] hidden h-[calc(100vh-121px)] max-w-[20%] min-w-[20%] md:flex md:shrink-0 md:flex-col md:justify-between">
<nav v-if="this.subSectionPages"
class="styled-scrollbar flex h-[calc(100vh-200px)] flex-col overflow-y-scroll pr-2 pb-4">
<div class="text-gray-1000 mb-2 text-md font-medium">{{ currentSection }}</div>
<div class="flex gap-x-1">
<ul class="px-0.5 last-of-type:mb-0 mb-8">
<li v-for="page in this.subSectionPages.data" :key="page.id" class="my-1.5 flex">
<a :class="{'text-white font-semibold bg-primaryBlue': isSameRoute(page.path), 'text-gray-600 hover:text-[#2C6288]': !isSameRoute(page.path) }"
:href="(page.is_url) ? page.path : route('page.view', page.path) + '/'"
class="relative duration-300 flex gap-x-1 w-full rounded-md cursor-pointer items-center px-2 py-1 text-left text-sm">
{{
page.title
}}</a>
</li>
</ul>
</div>
</nav>
</div>
</template>
<script>
import {Link} from "@inertiajs/vue3";
import slugify from "slugify";
export default {
name: "PageSubSectionLinks",
components: {Link},
data() {
return {
currentNavSection: null,
scrollTop: false,
}
},
methods: {
textLimit(text, symbols) {
if (text.length > symbols) {
let LimitedText
LimitedText = text.substring(0, symbols)
return LimitedText + "..."
}
return text
},
isSameRoute(route) {
if (this.$page.props.ziggy.location === this.$page.props.ziggy.url + '/' + route) {
return true
}
},
},
props: {
subSectionPages: {
type: Object,
},
currentSection: {
type: String,
},
},
}
</script>
<style scoped>
</style>
@@ -0,0 +1,56 @@
<template>
<component
v-for="(block, index) in blocks"
:key="index"
:is="getComponent(block.type)"
:block="block"
/>
</template>
<script>
import {Link} from "@inertiajs/vue3";
import slugify from "slugify";
import HeadingBlock from "@/Components/BuilderUi/Pages/Blocks/HeadingBlock.vue";
import ParagraphBlock from "@/Components/BuilderUi/Pages/Blocks/ParagraphBlock.vue";
import ClientImageSlider from "@/Components/ClientImageSlider.vue";
import ImageBlock from "@/Components/BuilderUi/Pages/Blocks/ImageBlock.vue";
import FileBlock from "@/Components/BuilderUi/Pages/Blocks/FileBlock.vue";
import PersonBlock from "@/Components/BuilderUi/Pages/Blocks/PersonBlock.vue";
import StepperBlock from "@/Components/BuilderUi/Pages/Blocks/StepperBlock.vue";
import VideoBlock from "@/Components/BuilderUi/Pages/Blocks/VideoBlock.vue";
import PostListBlock from "@/Components/BuilderUi/Pages/Blocks/PostListBlock.vue";
export default {
name: "PageTabBuilder",
components: {FileBlock, ImageBlock, ClientImageSlider, ParagraphBlock, HeadingBlock, Link, PersonBlock, StepperBlock, VideoBlock, PostListBlock},
methods: {
getComponent(type) {
const componentMap = {
heading: 'HeadingBlock',
paragraph: 'ParagraphBlock',
images: 'ClientImageSlider',
image: 'ImageBlock',
files: 'FileBlock',
person: 'PersonBlock',
stepper: 'StepperBlock',
video: 'VideoBlock',
postsList: 'PostListBlock',
};
return componentMap[type] || null;
},
},
props: {
blocks: {
type: Object,
},
},
}
</script>
<style scoped>
</style>
@@ -0,0 +1,43 @@
<template>
<div class="space-y-3">
<h1 class="text-2xl mb-10 font-bold md:text-3xl">{{ header }}</h1>
</div>
</template>
<script>
import {Link} from "@inertiajs/vue3";
import slugify from "slugify";
export default {
name: "PageTitle",
components: {Link},
data() {
return {
}
},
methods: {
textLimit(text, symbols) {
if (text.length > symbols) {
let LimitedText
LimitedText = text.substring(0, symbols)
return LimitedText + "..."
}
return text
},
},
props: {
header: {
type: String,
},
},
}
</script>
<style scoped>
</style>