You've already forked Atomcms-edit
Revert TypeScript migration - keep JS/JSX
This commit is contained in:
+34
@@ -0,0 +1,34 @@
|
||||
import _ from "lodash";
|
||||
window._ = _;
|
||||
|
||||
/**
|
||||
* We'll load the axios HTTP library which allows us to easily issue requests
|
||||
* to our Laravel back-end. This library automatically handles sending the
|
||||
* CSRF token as a header based on the value of the "XSRF" token cookie.
|
||||
*/
|
||||
|
||||
import axios from "axios";
|
||||
window.axios = axios;
|
||||
|
||||
window.axios.defaults.headers.common["X-Requested-With"] = "XMLHttpRequest";
|
||||
|
||||
/**
|
||||
* Echo exposes an expressive API for subscribing to channels and listening
|
||||
* for events that are broadcast by Laravel. Echo and event broadcasting
|
||||
* allows your team to easily build robust real-time web applications.
|
||||
*/
|
||||
|
||||
// import Echo from 'laravel-echo';
|
||||
|
||||
// import Pusher from 'pusher-js';
|
||||
// window.Pusher = Pusher;
|
||||
|
||||
// window.Echo = new Echo({
|
||||
// broadcaster: 'pusher',
|
||||
// key: import.meta.env.VITE_PUSHER_APP_KEY,
|
||||
// wsHost: import.meta.env.VITE_PUSHER_HOST ?? `ws-${import.meta.env.VITE_PUSHER_APP_CLUSTER}.pusher.com`,
|
||||
// wsPort: import.meta.env.VITE_PUSHER_PORT ?? 80,
|
||||
// wssPort: import.meta.env.VITE_PUSHER_PORT ?? 443,
|
||||
// forceTLS: (import.meta.env.VITE_PUSHER_SCHEME ?? 'https') === 'https',
|
||||
// enabledTransports: ['ws', 'wss'],
|
||||
// });
|
||||
@@ -1,6 +0,0 @@
|
||||
import _ from "lodash";
|
||||
import axios from "axios";
|
||||
|
||||
window._ = _;
|
||||
window.axios = axios;
|
||||
window.axios.defaults.headers.common["X-Requested-With"] = "XMLHttpRequest";
|
||||
Executable
+31
@@ -0,0 +1,31 @@
|
||||
import { Head } from '@inertiajs/react'
|
||||
|
||||
export default function Home({ auth, hotelName }) {
|
||||
return (
|
||||
<>
|
||||
<Head title="Home" />
|
||||
<div className="col-span-12">
|
||||
<div className="bg-white dark:bg-gray-900 rounded-xl shadow-sm p-8 text-center">
|
||||
<h1 className="text-4xl font-bold mb-4" style={{ color: 'var(--color-text)' }}>
|
||||
Welkom bij {hotelName}
|
||||
</h1>
|
||||
<p className="text-lg" style={{ color: 'var(--color-text-muted)' }}>
|
||||
Dit is een Inertia.js pagina — zelfde layout, zelfde Tailwind, zelfde stijlen.
|
||||
</p>
|
||||
<div className="mt-8 flex justify-center gap-4">
|
||||
<a
|
||||
href="/"
|
||||
className="px-6 py-3 rounded-xl font-semibold transition-all duration-200 hover:scale-105"
|
||||
style={{
|
||||
backgroundColor: 'var(--color-primary)',
|
||||
color: 'var(--button-text-color)',
|
||||
}}
|
||||
>
|
||||
Naar huis
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
import { Head } from "@inertiajs/react";
|
||||
|
||||
interface HomeProps {
|
||||
auth: Record<string, unknown>;
|
||||
hotelName: string;
|
||||
}
|
||||
|
||||
export default function Home({ hotelName }: HomeProps) {
|
||||
return (
|
||||
<>
|
||||
<Head title="Home" />
|
||||
<div className="col-span-12">
|
||||
<div className="rounded-xl bg-white p-8 text-center shadow-sm dark:bg-gray-900">
|
||||
<h1
|
||||
className="mb-4 text-4xl font-bold"
|
||||
style={{ color: "var(--color-text)" }}
|
||||
>
|
||||
Welkom bij {hotelName}
|
||||
</h1>
|
||||
<p
|
||||
className="text-lg"
|
||||
style={{ color: "var(--color-text-muted)" }}
|
||||
>
|
||||
Dit is een Inertia.js pagina — zelfde layout, zelfde Tailwind,
|
||||
zelfde stijlen.
|
||||
</p>
|
||||
<div className="mt-8 flex justify-center gap-4">
|
||||
<a
|
||||
href="/"
|
||||
className="rounded-xl px-6 py-3 font-semibold transition-all duration-200 hover:scale-105"
|
||||
style={{
|
||||
backgroundColor: "var(--color-primary)",
|
||||
color: "var(--button-text-color)",
|
||||
}}
|
||||
>
|
||||
Naar huis
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Executable
+119
@@ -0,0 +1,119 @@
|
||||
import { Head, usePage } from '@inertiajs/react'
|
||||
|
||||
export default function Index({ articles, photos }) {
|
||||
const { avatarImager } = usePage().props
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head title="Welkom" />
|
||||
|
||||
<div className="col-span-12 space-y-14">
|
||||
<div className="col-span-12">
|
||||
<div className="flex w-full flex-col gap-y-4 overflow-hidden rounded-lg p-3">
|
||||
<div className="flex gap-x-2">
|
||||
<div className="hotel-icon relative flex min-h-[50px] min-w-[50px] max-w-[50px] max-h-[50px] items-center justify-center rounded-full" />
|
||||
<div className="flex flex-col">
|
||||
<p className="font-semibold text-black dark:text-gray-200">Laatste nieuws</p>
|
||||
<p className="dark:text-gray-500">Blijf op de hoogte van het laatste hotel nieuws.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4">
|
||||
{articles.length > 0 ? articles.map(article => (
|
||||
<a key={article.id} href={`/community/article/${article.slug}`}
|
||||
className="group relative block h-[210px] w-full overflow-hidden rounded shadow-sm transition-all duration-200 ease-in-out hover:scale-[101%]"
|
||||
style={{ backgroundColor: 'var(--color-surface)' }}
|
||||
onMouseEnter={e => {
|
||||
const el = e.currentTarget.querySelector('.article-img-inner')
|
||||
if (el) el.classList.add('article-image-slide')
|
||||
}}
|
||||
onMouseLeave={e => {
|
||||
const el = e.currentTarget.querySelector('.article-img-inner')
|
||||
if (el) el.classList.remove('article-image-slide')
|
||||
}}>
|
||||
<div className="article-img-inner h-[100px] w-full bg-cover bg-center transition-[background-position] duration-300"
|
||||
style={{ backgroundImage: `url('/storage/${article.image}')`, backgroundPosition: '300px 220px' }}>
|
||||
</div>
|
||||
<div className="px-3 md:px-4">
|
||||
<p className="truncate text-base font-semibold md:text-lg dark:text-gray-200">{article.title}</p>
|
||||
<div className="flex items-center gap-x-2">
|
||||
{article.user && (
|
||||
<>
|
||||
<div className="mt-2 flex h-8 w-8 items-center justify-center overflow-hidden rounded-full md:mt-3 md:h-10 md:w-10"
|
||||
style={{ backgroundColor: 'var(--color-background)' }}>
|
||||
<img src={`${avatarImager}${article.user.look}&headonly=1`}
|
||||
alt="" className="h-full w-full object-cover" />
|
||||
</div>
|
||||
<p className="mt-2 text-sm font-semibold md:mt-4 md:text-base"
|
||||
style={{ color: 'var(--color-text-muted)' }}>{article.user.username}</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
)) : (
|
||||
<>
|
||||
{[1, 2, 3, 4].map(i => (
|
||||
<div key={i}
|
||||
className="h-[210px] w-full overflow-hidden rounded bg-white shadow-sm dark:bg-gray-900">
|
||||
<div className="article-image"
|
||||
style={{ background: "url('https://i.imgur.com/uGLDOUu.png')" }} />
|
||||
<div className="mt-4 px-4">
|
||||
<p className="truncate text-lg font-semibold dark:text-gray-200">Geen artikelen</p>
|
||||
<div className="flex items-center gap-x-2">
|
||||
<div className="mt-3 flex h-10 w-10 items-center justify-center overflow-hidden rounded-full bg-gray-100 dark:bg-gray-800">
|
||||
<img src={`${avatarImager}&headonly=1`} alt="" />
|
||||
</div>
|
||||
<p className="mt-4 font-semibold dark:text-gray-400">Epicnabbo</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{photos.length > 0 && (
|
||||
<div className="col-span-12">
|
||||
<div className="flex w-full flex-col gap-y-4 overflow-hidden rounded-lg p-3">
|
||||
<div className="flex gap-x-2">
|
||||
<div className="camera-icon relative flex min-h-[50px] min-w-[50px] max-w-[50px] max-h-[50px] items-center justify-center rounded-full" />
|
||||
<div className="flex flex-col">
|
||||
<p className="font-semibold text-black dark:text-gray-200">Laatste foto's</p>
|
||||
<p className="dark:text-gray-500">Bekijk de mooiste momenten vastgelegd door gebruikers.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-3 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-4 xl:grid-cols-5">
|
||||
{photos.map(photo => (
|
||||
<a key={photo.id} href={photo.url} data-fancybox="gallery" className="group block cursor-pointer">
|
||||
<div className="relative overflow-hidden rounded-lg border border-gray-600 shadow-md transition-all duration-300 hover:border-[#eeb425]">
|
||||
<div className="relative aspect-[4/3] overflow-hidden">
|
||||
<img src={photo.url} alt={`Photo by ${photo.user?.username || 'Unknown'}`}
|
||||
className="h-full w-full object-cover object-center transition-transform duration-300 group-hover:scale-110" />
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/70 via-transparent to-transparent opacity-0 transition-opacity duration-300 group-hover:opacity-100" />
|
||||
</div>
|
||||
<div className="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/90 to-transparent p-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex h-7 w-7 items-center justify-center overflow-hidden rounded-full border border-gray-500 bg-gray-700">
|
||||
<img src={`${avatarImager}${photo.user?.look || ''}&direction=2&headonly=1&head_direction=2&gesture=sml`}
|
||||
alt={photo.user?.username || 'Unknown'} className="h-full w-full object-cover" />
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="truncate text-sm font-semibold text-white">{photo.user?.username || 'Unknown'}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,206 +0,0 @@
|
||||
import { Head, usePage } from "@inertiajs/react";
|
||||
|
||||
interface User {
|
||||
look: string;
|
||||
username: string;
|
||||
}
|
||||
|
||||
interface Article {
|
||||
id: number;
|
||||
slug: string;
|
||||
title: string;
|
||||
image: string;
|
||||
user?: User;
|
||||
}
|
||||
|
||||
interface Photo {
|
||||
id: number;
|
||||
url: string;
|
||||
user?: User;
|
||||
}
|
||||
|
||||
interface IndexProps {
|
||||
articles: Article[];
|
||||
photos: Photo[];
|
||||
}
|
||||
|
||||
interface SharedProps extends Record<string, unknown> {
|
||||
avatarImager: string;
|
||||
}
|
||||
|
||||
export default function Index({ articles, photos }: IndexProps) {
|
||||
const { avatarImager } = usePage<SharedProps>().props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head title="Welkom" />
|
||||
|
||||
<div className="col-span-12 space-y-14">
|
||||
<div className="col-span-12">
|
||||
<div className="flex w-full flex-col gap-y-4 overflow-hidden rounded-lg p-3">
|
||||
<div className="flex gap-x-2">
|
||||
<div className="hotel-icon relative flex min-h-[50px] min-w-[50px] max-w-[50px] items-center justify-center rounded-full" />
|
||||
<div className="flex flex-col">
|
||||
<p className="font-semibold text-black dark:text-gray-200">
|
||||
Laatste nieuws
|
||||
</p>
|
||||
<p className="dark:text-gray-500">
|
||||
Blijf op de hoogte van het laatste hotel nieuws.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4">
|
||||
{articles.length > 0
|
||||
? articles.map((article) => (
|
||||
<a
|
||||
key={article.id}
|
||||
href={`/community/article/${article.slug}`}
|
||||
className="group relative block h-[210px] w-full overflow-hidden rounded shadow-sm transition-all duration-200 ease-in-out hover:scale-[101%]"
|
||||
style={{ backgroundColor: "var(--color-surface)" }}
|
||||
onMouseEnter={(e) => {
|
||||
const el = e.currentTarget.querySelector(
|
||||
".article-img-inner",
|
||||
);
|
||||
if (el) el.classList.add("article-image-slide");
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
const el = e.currentTarget.querySelector(
|
||||
".article-img-inner",
|
||||
);
|
||||
if (el) el.classList.remove("article-image-slide");
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="article-img-inner h-[100px] w-full bg-cover bg-center transition-[background-position] duration-300"
|
||||
style={{
|
||||
backgroundImage: `url('/storage/${article.image}')`,
|
||||
backgroundPosition: "300px 220px",
|
||||
}}
|
||||
/>
|
||||
<div className="px-3 md:px-4">
|
||||
<p className="truncate text-base font-semibold md:text-lg dark:text-gray-200">
|
||||
{article.title}
|
||||
</p>
|
||||
<div className="flex items-center gap-x-2">
|
||||
{article.user && (
|
||||
<>
|
||||
<div
|
||||
className="mt-2 flex h-8 w-8 items-center justify-center overflow-hidden rounded-full md:mt-3 md:h-10 md:w-10"
|
||||
style={{
|
||||
backgroundColor: "var(--color-background)",
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={`${avatarImager}${article.user.look}&headonly=1`}
|
||||
alt=""
|
||||
className="h-full w-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
<p
|
||||
className="mt-2 text-sm font-semibold md:mt-4 md:text-base"
|
||||
style={{
|
||||
color: "var(--color-text-muted)",
|
||||
}}
|
||||
>
|
||||
{article.user.username}
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
))
|
||||
: [1, 2, 3, 4].map((i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="h-[210px] w-full overflow-hidden rounded bg-white shadow-sm dark:bg-gray-900"
|
||||
>
|
||||
<div
|
||||
className="article-image"
|
||||
style={{
|
||||
background:
|
||||
"url('https://i.imgur.com/uGLDOUu.png')",
|
||||
}}
|
||||
/>
|
||||
<div className="mt-4 px-4">
|
||||
<p className="truncate text-lg font-semibold dark:text-gray-200">
|
||||
Geen artikelen
|
||||
</p>
|
||||
<div className="flex items-center gap-x-2">
|
||||
<div className="mt-3 flex h-10 w-10 items-center justify-center overflow-hidden rounded-full bg-gray-100 dark:bg-gray-800">
|
||||
<img
|
||||
src={`${avatarImager}&headonly=1`}
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
<p className="mt-4 font-semibold dark:text-gray-400">
|
||||
Epicnabbo
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{photos.length > 0 && (
|
||||
<div className="col-span-12">
|
||||
<div className="flex w-full flex-col gap-y-4 overflow-hidden rounded-lg p-3">
|
||||
<div className="flex gap-x-2">
|
||||
<div className="camera-icon relative flex min-h-[50px] min-w-[50px] max-w-[50px] items-center justify-center rounded-full" />
|
||||
<div className="flex flex-col">
|
||||
<p className="font-semibold text-black dark:text-gray-200">
|
||||
Laatste foto's
|
||||
</p>
|
||||
<p className="dark:text-gray-500">
|
||||
Bekijk de mooiste momenten vastgelegd door gebruikers.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-3 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-4 xl:grid-cols-5">
|
||||
{photos.map((photo) => (
|
||||
<a
|
||||
key={photo.id}
|
||||
href={photo.url}
|
||||
data-fancybox="gallery"
|
||||
className="group block cursor-pointer"
|
||||
>
|
||||
<div className="relative overflow-hidden rounded-lg border border-gray-600 shadow-md transition-all duration-300 hover:border-[#eeb425]">
|
||||
<div className="relative aspect-[4/3] overflow-hidden">
|
||||
<img
|
||||
src={photo.url}
|
||||
alt={`Photo by ${photo.user?.username || "Unknown"}`}
|
||||
className="h-full w-full object-cover object-center transition-transform duration-300 group-hover:scale-110"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/70 via-transparent to-transparent opacity-0 transition-opacity duration-300 group-hover:opacity-100" />
|
||||
</div>
|
||||
<div className="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/90 to-transparent p-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex h-7 w-7 items-center justify-center overflow-hidden rounded-full border border-gray-500 bg-gray-700">
|
||||
<img
|
||||
src={`${avatarImager}${photo.user?.look || ""}&direction=2&headonly=1&head_direction=2&gesture=sml`}
|
||||
alt={photo.user?.username || "Unknown"}
|
||||
className="h-full w-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="truncate text-sm font-semibold text-white">
|
||||
{photo.user?.username || "Unknown"}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Executable
+12
@@ -0,0 +1,12 @@
|
||||
import { createInertiaApp } from '@inertiajs/react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
|
||||
createInertiaApp({
|
||||
resolve: name => {
|
||||
const pages = import.meta.glob('./pages/**/*.jsx', { eager: true })
|
||||
return pages[`./pages/${name}.jsx`]
|
||||
},
|
||||
setup({ el, App, props }) {
|
||||
createRoot(el).render(<App {...props} />)
|
||||
},
|
||||
})
|
||||
@@ -1,16 +0,0 @@
|
||||
import { createInertiaApp } from "@inertiajs/react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import type { ComponentType } from "react";
|
||||
|
||||
createInertiaApp({
|
||||
resolve: (name: string) => {
|
||||
const pages = import.meta.glob<{ default: ComponentType<unknown> }>(
|
||||
"./pages/**/*.tsx",
|
||||
{ eager: true },
|
||||
);
|
||||
return pages[`./pages/${name}.tsx`];
|
||||
},
|
||||
setup({ el, App, props }) {
|
||||
createRoot(el).render(<App {...props} />);
|
||||
},
|
||||
});
|
||||
Vendored
-31
@@ -1,31 +0,0 @@
|
||||
declare module "alpinejs" {
|
||||
interface Alpine {
|
||||
data(name: string, callback: (...args: unknown[]) => Record<string, unknown>): void;
|
||||
plugin(plugin: unknown): void;
|
||||
start(): void;
|
||||
}
|
||||
const Alpine: Alpine;
|
||||
export default Alpine;
|
||||
}
|
||||
|
||||
declare module "@alpinejs/focus" {
|
||||
const focus: unknown;
|
||||
export default focus;
|
||||
}
|
||||
|
||||
declare module "turbolinks" {
|
||||
interface TurbolinksStatic {
|
||||
start(): void;
|
||||
}
|
||||
const Turbolinks: TurbolinksStatic;
|
||||
export default Turbolinks;
|
||||
}
|
||||
|
||||
interface Window {
|
||||
_: import("lodash").default;
|
||||
axios: import("axios").AxiosStatic;
|
||||
App: {
|
||||
defaultReactions: string[];
|
||||
isAuthenticated: boolean;
|
||||
};
|
||||
}
|
||||
Executable
+25
@@ -0,0 +1,25 @@
|
||||
import "./bootstrap";
|
||||
import "./external/flowbite";
|
||||
|
||||
import "swiper/css";
|
||||
import "swiper/css/pagination";
|
||||
|
||||
import Alpine from "alpinejs";
|
||||
import Focus from "@alpinejs/focus";
|
||||
|
||||
import ArticleReactions from "./components/ArticleReactions.js";
|
||||
|
||||
import ThemeSwitcher from "./components/ThemeSwitcher.js";
|
||||
import AtomSliders from "./components/AtomSliders.js";
|
||||
|
||||
ThemeSwitcher.init();
|
||||
ArticleReactions.init();
|
||||
AtomSliders.init();
|
||||
Alpine.plugin(Focus);
|
||||
Alpine.start();
|
||||
|
||||
console.log(
|
||||
"%cAtom CMS%c\n\nAtom CMS is a CMS for made for the community to enjoy. You can join our wonderful community at https://discord.gg/rX3aShUHdg\n\n",
|
||||
"color: #14619c; -webkit-text-stroke: 2px black; font-size: 32px; font-weight: bold;",
|
||||
"",
|
||||
);
|
||||
@@ -1,18 +0,0 @@
|
||||
import "./bootstrap";
|
||||
import "./external/flowbite";
|
||||
|
||||
import "swiper/css";
|
||||
import "swiper/css/pagination";
|
||||
|
||||
import Alpine from "alpinejs";
|
||||
import Focus from "@alpinejs/focus";
|
||||
|
||||
import ArticleReactions from "./components/ArticleReactions";
|
||||
import ThemeSwitcher from "./components/ThemeSwitcher";
|
||||
import AtomSliders from "./components/AtomSliders";
|
||||
|
||||
ThemeSwitcher.init();
|
||||
ArticleReactions.init();
|
||||
AtomSliders.init();
|
||||
Alpine.plugin(Focus);
|
||||
Alpine.start();
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* We'll load the axios HTTP library which allows us to easily issue requests
|
||||
* to our Laravel back-end. This library automatically handles sending the
|
||||
* CSRF token as a header based on the value of the "XSRF" token cookie.
|
||||
*/
|
||||
|
||||
import axios from "axios";
|
||||
import Turbolinks from "turbolinks";
|
||||
|
||||
window.axios = axios;
|
||||
window.axios.defaults.headers.common["X-Requested-With"] = "XMLHttpRequest";
|
||||
|
||||
Turbolinks.start();
|
||||
|
||||
/**
|
||||
* Echo exposes an expressive API for subscribing to channels and listening
|
||||
* for events that are broadcast by Laravel. Echo and event broadcasting
|
||||
* allows your team to easily build robust real-time web applications.
|
||||
*/
|
||||
|
||||
// import Echo from 'laravel-echo';
|
||||
|
||||
// import Pusher from 'pusher-js';
|
||||
// window.Pusher = Pusher;
|
||||
|
||||
// window.Echo = new Echo({
|
||||
// broadcaster: 'pusher',
|
||||
// key: import.meta.env.VITE_PUSHER_APP_KEY,
|
||||
// wsHost: import.meta.env.VITE_PUSHER_HOST ?? `ws-${import.meta.env.VITE_PUSHER_CLUSTER}.pusher.com`,
|
||||
// wsPort: import.meta.env.VITE_PUSHER_PORT ?? 80,
|
||||
// wssPort: import.meta.env.VITE_PUSHER_PORT ?? 443,
|
||||
// forceTLS: (import.meta.env.VITE_PUSHER_SCHEME ?? 'https') === 'https',
|
||||
// enabledTransports: ['ws', 'wss'],
|
||||
// });
|
||||
@@ -1,7 +0,0 @@
|
||||
import axios from "axios";
|
||||
import Turbolinks from "turbolinks";
|
||||
|
||||
window.axios = axios;
|
||||
window.axios.defaults.headers.common["X-Requested-With"] = "XMLHttpRequest";
|
||||
|
||||
Turbolinks.start();
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
import Alpine from "alpinejs";
|
||||
|
||||
const ArticleReactions = {
|
||||
init() {
|
||||
document.addEventListener("alpine:init", () => this.startComponent());
|
||||
},
|
||||
|
||||
startComponent() {
|
||||
Alpine.data(
|
||||
"reactions",
|
||||
(myReactions = [], articleReactions = [], url = "") => ({
|
||||
url,
|
||||
myReactions,
|
||||
articleReactions,
|
||||
allReactions: [],
|
||||
isAuthenticated: false,
|
||||
|
||||
init() {
|
||||
this.treatArticleReactions();
|
||||
this.allReactions = window.App.defaultReactions;
|
||||
this.isAuthenticated = window.App.isAuthenticated;
|
||||
|
||||
this.dispatchFlowbiteEvent();
|
||||
},
|
||||
|
||||
treatArticleReactions() {
|
||||
let articleReactions = this.articleReactions;
|
||||
|
||||
this.articleReactions = [];
|
||||
|
||||
Object.entries(articleReactions).forEach((reactionData) => {
|
||||
let reactionName = reactionData[0],
|
||||
reactions = Object.values(reactionData[1]);
|
||||
|
||||
this.articleReactions.push({
|
||||
id: this.generateVirtualReactionId(reactionName),
|
||||
name: reactionName,
|
||||
count: reactions.length,
|
||||
users: reactions.map((reaction) => reaction.user?.username ?? ""),
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
toggleReaction(reaction) {
|
||||
if (!this.url.length || !this.isAuthenticated) return;
|
||||
|
||||
axios.post(this.url, { reaction }).then((response) => {
|
||||
if (!response.data.success) return;
|
||||
|
||||
if (!response.data.added) {
|
||||
this.removeReaction(reaction, response.data.username);
|
||||
return;
|
||||
}
|
||||
|
||||
this.addReaction(reaction, response.data.username);
|
||||
});
|
||||
},
|
||||
|
||||
addReaction(name, username) {
|
||||
this.myReactions.push(name);
|
||||
|
||||
let existingReaction = this.getReactionDataFromName(name);
|
||||
|
||||
if (existingReaction) {
|
||||
existingReaction.count++;
|
||||
existingReaction.users.push(username);
|
||||
return;
|
||||
}
|
||||
|
||||
this.articleReactions.push({
|
||||
id: this.generateVirtualReactionId(name),
|
||||
name,
|
||||
count: 1,
|
||||
users: [username],
|
||||
});
|
||||
|
||||
this.dispatchFlowbiteEvent();
|
||||
},
|
||||
|
||||
removeReaction(name, username) {
|
||||
this.myReactions.splice(this.myReactions.indexOf(name), 1);
|
||||
|
||||
let reactionData = this.getReactionDataFromName(name);
|
||||
|
||||
if (reactionData.count > 1) {
|
||||
reactionData.count--;
|
||||
reactionData.users.splice(reactionData.users.indexOf(username), 1);
|
||||
return;
|
||||
}
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.articleReactions.splice(
|
||||
this.articleReactions.indexOf(reactionData),
|
||||
1,
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
generateVirtualReactionId(name) {
|
||||
return name + Math.floor(Math.random() * 1000);
|
||||
},
|
||||
|
||||
canAddReactionFromModal(name) {
|
||||
return !this.userHasReaction(name) && !this.articleHasReaction(name);
|
||||
},
|
||||
|
||||
userHasReaction(reaction) {
|
||||
return this.myReactions.includes(reaction.name);
|
||||
},
|
||||
|
||||
articleHasReaction(name) {
|
||||
return typeof this.getReactionDataFromName(name) !== "undefined";
|
||||
},
|
||||
|
||||
getReactionDataFromName(name) {
|
||||
return this.articleReactions.find(
|
||||
(reaction) => reaction.name === name,
|
||||
);
|
||||
},
|
||||
|
||||
dispatchFlowbiteEvent() {
|
||||
this.$nextTick(() =>
|
||||
document.dispatchEvent(new CustomEvent("reactions:loaded")),
|
||||
);
|
||||
},
|
||||
}),
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export { ArticleReactions as default };
|
||||
@@ -1,147 +0,0 @@
|
||||
import Alpine from "alpinejs";
|
||||
|
||||
interface ReactionData {
|
||||
id: string;
|
||||
name: string;
|
||||
count: number;
|
||||
users: string[];
|
||||
}
|
||||
|
||||
interface ReactionComponent extends Record<string, unknown> {
|
||||
url: string;
|
||||
myReactions: string[];
|
||||
articleReactions: ReactionData[];
|
||||
allReactions: string[];
|
||||
isAuthenticated: boolean;
|
||||
init(): void;
|
||||
treatArticleReactions(rawReactions: Record<string, { user?: { username: string } }[]>): void;
|
||||
toggleReaction(reaction: string): void;
|
||||
addReaction(name: string, username: string): void;
|
||||
removeReaction(name: string, username: string): void;
|
||||
generateVirtualReactionId(name: string): string;
|
||||
canAddReactionFromModal(name: string): boolean;
|
||||
userHasReaction(reaction: ReactionData | string): boolean;
|
||||
articleHasReaction(name: string): boolean;
|
||||
getReactionDataFromName(name: string): ReactionData | undefined;
|
||||
dispatchFlowbiteEvent(): void;
|
||||
$nextTick: (callback: () => void) => void;
|
||||
}
|
||||
|
||||
const ArticleReactions = {
|
||||
init() {
|
||||
document.addEventListener("alpine:init", () => this.startComponent());
|
||||
},
|
||||
|
||||
startComponent() {
|
||||
Alpine.data("reactions", (...args: unknown[]) => {
|
||||
const [myReactions = [], , url = ""] = args as [string[], Record<string, { user?: { username: string } }[]>, string];
|
||||
const rawArticleReactions = args[1] as Record<string, { user?: { username: string } }[]> | undefined;
|
||||
|
||||
return {
|
||||
url,
|
||||
myReactions: myReactions as string[],
|
||||
articleReactions: [] as ReactionData[],
|
||||
allReactions: [] as string[],
|
||||
isAuthenticated: false,
|
||||
|
||||
init(this: ReactionComponent) {
|
||||
if (rawArticleReactions) {
|
||||
this.treatArticleReactions(rawArticleReactions);
|
||||
}
|
||||
this.allReactions = window.App.defaultReactions;
|
||||
this.isAuthenticated = window.App.isAuthenticated;
|
||||
this.dispatchFlowbiteEvent();
|
||||
},
|
||||
|
||||
treatArticleReactions(this: ReactionComponent, raw: Record<string, { user?: { username: string } }[]>) {
|
||||
const transformed: ReactionData[] = [];
|
||||
|
||||
Object.entries(raw).forEach(([name, reactions]) => {
|
||||
const values = Object.values(reactions);
|
||||
transformed.push({
|
||||
id: this.generateVirtualReactionId(name),
|
||||
name,
|
||||
count: values.length,
|
||||
users: values.map((r) => r.user?.username ?? ""),
|
||||
});
|
||||
});
|
||||
|
||||
this.articleReactions = transformed;
|
||||
},
|
||||
|
||||
toggleReaction(this: ReactionComponent, reaction: string) {
|
||||
if (!this.url.length || !this.isAuthenticated) return;
|
||||
|
||||
window.axios
|
||||
.post(this.url, { reaction })
|
||||
.then((response: { data: { success: boolean; added: boolean; username: string } }) => {
|
||||
if (!response.data.success) return;
|
||||
if (!response.data.added) {
|
||||
this.removeReaction(reaction, response.data.username);
|
||||
return;
|
||||
}
|
||||
this.addReaction(reaction, response.data.username);
|
||||
});
|
||||
},
|
||||
|
||||
addReaction(this: ReactionComponent, name: string, username: string) {
|
||||
this.myReactions.push(name);
|
||||
const existing = this.getReactionDataFromName(name);
|
||||
if (existing) {
|
||||
existing.count++;
|
||||
existing.users.push(username);
|
||||
return;
|
||||
}
|
||||
this.articleReactions.push({
|
||||
id: this.generateVirtualReactionId(name),
|
||||
name,
|
||||
count: 1,
|
||||
users: [username],
|
||||
});
|
||||
this.dispatchFlowbiteEvent();
|
||||
},
|
||||
|
||||
removeReaction(this: ReactionComponent, name: string, username: string) {
|
||||
this.myReactions.splice(this.myReactions.indexOf(name), 1);
|
||||
const data = this.getReactionDataFromName(name);
|
||||
if (!data) return;
|
||||
if (data.count > 1) {
|
||||
data.count--;
|
||||
data.users.splice(data.users.indexOf(username), 1);
|
||||
return;
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
this.articleReactions.splice(this.articleReactions.indexOf(data), 1);
|
||||
});
|
||||
},
|
||||
|
||||
generateVirtualReactionId(this: ReactionComponent, name: string): string {
|
||||
return name + Math.floor(Math.random() * 1000);
|
||||
},
|
||||
|
||||
canAddReactionFromModal(this: ReactionComponent, name: string): boolean {
|
||||
return !this.userHasReaction(name) && !this.articleHasReaction(name);
|
||||
},
|
||||
|
||||
userHasReaction(this: ReactionComponent, reaction: ReactionData | string): boolean {
|
||||
const name = typeof reaction === "string" ? reaction : reaction.name;
|
||||
return this.myReactions.includes(name);
|
||||
},
|
||||
|
||||
articleHasReaction(this: ReactionComponent, name: string): boolean {
|
||||
return typeof this.getReactionDataFromName(name) !== "undefined";
|
||||
},
|
||||
|
||||
getReactionDataFromName(this: ReactionComponent, name: string): ReactionData | undefined {
|
||||
return this.articleReactions.find((r) => r.name === name);
|
||||
},
|
||||
|
||||
dispatchFlowbiteEvent(this: ReactionComponent) {
|
||||
this.$nextTick(() => document.dispatchEvent(new CustomEvent("reactions:loaded")));
|
||||
},
|
||||
} as ReactionComponent;
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export { ArticleReactions as default };
|
||||
+1
-6
@@ -1,11 +1,6 @@
|
||||
import Swiper from "swiper";
|
||||
|
||||
interface AtomSlidersStore {
|
||||
init(): void;
|
||||
initArticleSlider(): void;
|
||||
}
|
||||
|
||||
const AtomSliders: AtomSlidersStore = {
|
||||
const AtomSliders = {
|
||||
init() {
|
||||
document.addEventListener("turbolinks:load", () => {
|
||||
this.initArticleSlider();
|
||||
+3
-12
@@ -1,19 +1,10 @@
|
||||
type Theme = "light" | "dark";
|
||||
|
||||
interface ThemeSwitcherStore {
|
||||
currentTheme: Theme;
|
||||
init(): void;
|
||||
initButton(): void;
|
||||
toggleTheme(): void;
|
||||
}
|
||||
|
||||
const ThemeSwitcher: ThemeSwitcherStore = {
|
||||
const ThemeSwitcher = {
|
||||
currentTheme: "light",
|
||||
|
||||
init() {
|
||||
if (
|
||||
localStorage.theme === "dark" ||
|
||||
(typeof window.matchMedia !== "undefined" &&
|
||||
(typeof window.matchMedia != "undefined" &&
|
||||
window.matchMedia("(prefers-color-scheme: dark)").matches &&
|
||||
localStorage.theme !== "light")
|
||||
) {
|
||||
@@ -24,7 +15,7 @@ const ThemeSwitcher: ThemeSwitcherStore = {
|
||||
},
|
||||
|
||||
initButton() {
|
||||
const themeSwitcher = document.getElementById("theme-switcher");
|
||||
let themeSwitcher = document.getElementById("theme-switcher");
|
||||
|
||||
themeSwitcher?.addEventListener("click", () => this.toggleTheme());
|
||||
},
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
<link href="https://fonts.googleapis.com/css2?family=Ubuntu+Condensed&display=swap" rel="stylesheet">
|
||||
|
||||
@vite(['resources/themes/' . setting('theme') . '/css/app.css', 'resources/themes/' . setting('theme') . '/js/app.ts'], 'build')
|
||||
@vite(['resources/themes/' . setting('theme') . '/css/app.css', 'resources/themes/' . setting('theme') . '/js/app.js'], 'build')
|
||||
|
||||
<style>
|
||||
:root {
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
<script src="{{ asset('assets/js/tippy-bundle.umd.min.js') }}"></script>
|
||||
<link rel="stylesheet" href="{{ asset('assets/css/scale.min.css') }}"/>
|
||||
|
||||
@vite(['resources/themes/' . setting('theme', 'atom') . '/css/app.css', 'resources/themes/' . setting('theme', 'atom') . '/js/app.ts'], 'build')
|
||||
@vite(['resources/themes/' . setting('theme', 'atom') . '/css/app.css', 'resources/themes/' . setting('theme', 'atom') . '/js/app.js'], 'build')
|
||||
|
||||
<style>
|
||||
:root {
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<!-- Fonts -->
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700&display=swap">
|
||||
|
||||
@vite(['resources/themes/' . setting('theme') . '/css/app.css', 'resources/themes/' . setting('theme') . '/js/app.ts'], 'build')
|
||||
@vite(['resources/themes/' . setting('theme') . '/css/app.css', 'resources/themes/' . setting('theme') . '/js/app.js'], 'build')
|
||||
|
||||
<style>
|
||||
:root {
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<link rel="stylesheet" href="https://unpkg.com/flowbite@1.5.1/dist/flowbite.min.css"/>
|
||||
<script src="https://unpkg.com/flowbite@1.5.1/dist/flowbite.js"></script>
|
||||
|
||||
@vite(['resources/themes/' . setting('theme') . '/css/app.css', 'resources/themes/' . setting('theme') . '/js/app.ts'], 'build')
|
||||
@vite(['resources/themes/' . setting('theme') . '/css/app.css', 'resources/themes/' . setting('theme') . '/js/app.js'], 'build')
|
||||
|
||||
<style>
|
||||
.gradient-bg {
|
||||
|
||||
@@ -12,10 +12,10 @@ export default defineConfig({
|
||||
laravel({
|
||||
input: [
|
||||
path.resolve(__dirname, "css/app.css"),
|
||||
path.resolve(__dirname, "js/app.ts"),
|
||||
"resources/js/global.ts",
|
||||
path.resolve(__dirname, "js/app.js"),
|
||||
"resources/js/global.js",
|
||||
"resources/css/global.css",
|
||||
"resources/js/ssr.tsx",
|
||||
"resources/js/ssr.jsx",
|
||||
],
|
||||
}),
|
||||
|
||||
@@ -33,7 +33,7 @@ export default defineConfig({
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": path.resolve(__dirname, "js/app.ts"),
|
||||
"@": path.resolve(__dirname, "js/app.js"),
|
||||
},
|
||||
},
|
||||
css: {
|
||||
|
||||
Executable
+40
@@ -0,0 +1,40 @@
|
||||
import "./bootstrap";
|
||||
import "./external/flowbite";
|
||||
|
||||
import "swiper/css";
|
||||
import "swiper/css/navigation";
|
||||
import "swiper/css/pagination";
|
||||
|
||||
import Alpine from "alpinejs";
|
||||
import Focus from "@alpinejs/focus";
|
||||
|
||||
import ArticleReactions from "./components/ArticleReactions.js";
|
||||
|
||||
import Swiper from "swiper";
|
||||
import { Navigation, Pagination } from "swiper/modules";
|
||||
|
||||
ArticleReactions.init();
|
||||
Alpine.plugin(Focus);
|
||||
Alpine.start();
|
||||
|
||||
Swiper.use([Navigation, Pagination]);
|
||||
|
||||
// Swiper Initialization
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
const swiper = new Swiper(".swiper", {
|
||||
// Your Swiper options here
|
||||
navigation: {
|
||||
nextEl: ".swiper-button-next",
|
||||
prevEl: ".swiper-button-prev",
|
||||
},
|
||||
pagination: {
|
||||
el: ".swiper-pagination",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
console.log(
|
||||
"%cAtom CMS%c\n\nAtom CMS is a CMS for made for the community to enjoy. You can join our wonderful community at https://discord.gg/rX3aShUHdg\n\n",
|
||||
"color: #14619c; -webkit-text-stroke: 2px black; font-size: 32px; font-weight: bold;",
|
||||
"",
|
||||
);
|
||||
@@ -1,36 +0,0 @@
|
||||
import "./bootstrap";
|
||||
import "./external/flowbite";
|
||||
|
||||
import "swiper/css";
|
||||
import "swiper/css/navigation";
|
||||
import "swiper/css/pagination";
|
||||
|
||||
import Alpine from "alpinejs";
|
||||
import Focus from "@alpinejs/focus";
|
||||
|
||||
import ArticleReactions from "./components/ArticleReactions";
|
||||
|
||||
import Swiper from "swiper";
|
||||
import { Navigation, Pagination } from "swiper/modules";
|
||||
|
||||
ArticleReactions.init();
|
||||
Alpine.plugin(Focus);
|
||||
Alpine.start();
|
||||
|
||||
Swiper.use([Navigation, Pagination]);
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
const swiperEl = document.querySelector(".swiper");
|
||||
|
||||
if (swiperEl) {
|
||||
new Swiper(swiperEl as HTMLElement, {
|
||||
navigation: {
|
||||
nextEl: ".swiper-button-next",
|
||||
prevEl: ".swiper-button-prev",
|
||||
},
|
||||
pagination: {
|
||||
el: ".swiper-pagination",
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* We'll load the axios HTTP library which allows us to easily issue requests
|
||||
* to our Laravel back-end. This library automatically handles sending the
|
||||
* CSRF token as a header based on the value of the "XSRF" token cookie.
|
||||
*/
|
||||
|
||||
import axios from "axios";
|
||||
window.axios = axios;
|
||||
|
||||
window.axios.defaults.headers.common["X-Requested-With"] = "XMLHttpRequest";
|
||||
|
||||
/**
|
||||
* Echo exposes an expressive API for subscribing to channels and listening
|
||||
* for events that are broadcast by Laravel. Echo and event broadcasting
|
||||
* allows your team to easily build robust real-time web applications.
|
||||
*/
|
||||
|
||||
// import Echo from 'laravel-echo';
|
||||
|
||||
// import Pusher from 'pusher-js';
|
||||
// window.Pusher = Pusher;
|
||||
|
||||
// window.Echo = new Echo({
|
||||
// broadcaster: 'pusher',
|
||||
// key: import.meta.env.VITE_PUSHER_APP_KEY,
|
||||
// wsHost: import.meta.env.VITE_PUSHER_HOST ?? `ws-${import.meta.env.VITE_PUSHER_CLUSTER}.pusher.com`,
|
||||
// wsPort: import.meta.env.VITE_PUSHER_PORT ?? 80,
|
||||
// wssPort: import.meta.env.VITE_PUSHER_PORT ?? 443,
|
||||
// forceTLS: (import.meta.env.VITE_PUSHER_SCHEME ?? 'https') === 'https',
|
||||
// enabledTransports: ['ws', 'wss'],
|
||||
// });
|
||||
@@ -1,4 +0,0 @@
|
||||
import axios from "axios";
|
||||
|
||||
window.axios = axios;
|
||||
window.axios.defaults.headers.common["X-Requested-With"] = "XMLHttpRequest";
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
import Alpine from "alpinejs";
|
||||
|
||||
const ArticleReactions = {
|
||||
init() {
|
||||
document.addEventListener("alpine:init", () => this.startComponent());
|
||||
},
|
||||
|
||||
startComponent() {
|
||||
Alpine.data(
|
||||
"reactions",
|
||||
(myReactions = [], articleReactions = [], url = "") => ({
|
||||
url,
|
||||
myReactions,
|
||||
articleReactions,
|
||||
allReactions: [],
|
||||
isAuthenticated: false,
|
||||
|
||||
init() {
|
||||
this.treatArticleReactions();
|
||||
this.allReactions = window.App.defaultReactions;
|
||||
this.isAuthenticated = window.App.isAuthenticated;
|
||||
|
||||
this.dispatchFlowbiteEvent();
|
||||
},
|
||||
|
||||
treatArticleReactions() {
|
||||
let articleReactions = this.articleReactions;
|
||||
|
||||
this.articleReactions = [];
|
||||
|
||||
Object.entries(articleReactions).forEach((reactionData) => {
|
||||
let reactionName = reactionData[0],
|
||||
reactions = Object.values(reactionData[1]);
|
||||
|
||||
this.articleReactions.push({
|
||||
id: this.generateVirtualReactionId(reactionName),
|
||||
name: reactionName,
|
||||
count: reactions.length,
|
||||
users: reactions.map((reaction) => reaction.user?.username ?? ""),
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
toggleReaction(reaction) {
|
||||
if (!this.url.length || !this.isAuthenticated) return;
|
||||
|
||||
axios.post(this.url, { reaction }).then((response) => {
|
||||
if (!response.data.success) return;
|
||||
|
||||
if (!response.data.added) {
|
||||
this.removeReaction(reaction, response.data.username);
|
||||
return;
|
||||
}
|
||||
|
||||
this.addReaction(reaction, response.data.username);
|
||||
});
|
||||
},
|
||||
|
||||
addReaction(name, username) {
|
||||
this.myReactions.push(name);
|
||||
|
||||
let existingReaction = this.getReactionDataFromName(name);
|
||||
|
||||
if (existingReaction) {
|
||||
existingReaction.count++;
|
||||
existingReaction.users.push(username);
|
||||
return;
|
||||
}
|
||||
|
||||
this.articleReactions.push({
|
||||
id: this.generateVirtualReactionId(name),
|
||||
name,
|
||||
count: 1,
|
||||
users: [username],
|
||||
});
|
||||
|
||||
this.dispatchFlowbiteEvent();
|
||||
},
|
||||
|
||||
removeReaction(name, username) {
|
||||
this.myReactions.splice(this.myReactions.indexOf(name), 1);
|
||||
|
||||
let reactionData = this.getReactionDataFromName(name);
|
||||
|
||||
if (reactionData.count > 1) {
|
||||
reactionData.count--;
|
||||
reactionData.users.splice(reactionData.users.indexOf(username), 1);
|
||||
return;
|
||||
}
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.articleReactions.splice(
|
||||
this.articleReactions.indexOf(reactionData),
|
||||
1,
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
generateVirtualReactionId(name) {
|
||||
return name + Math.floor(Math.random() * 1000);
|
||||
},
|
||||
|
||||
canAddReactionFromModal(name) {
|
||||
return !this.userHasReaction(name) && !this.articleHasReaction(name);
|
||||
},
|
||||
|
||||
userHasReaction(reaction) {
|
||||
return this.myReactions.includes(reaction.name);
|
||||
},
|
||||
|
||||
articleHasReaction(name) {
|
||||
return typeof this.getReactionDataFromName(name) !== "undefined";
|
||||
},
|
||||
|
||||
getReactionDataFromName(name) {
|
||||
return this.articleReactions.find(
|
||||
(reaction) => reaction.name === name,
|
||||
);
|
||||
},
|
||||
|
||||
dispatchFlowbiteEvent() {
|
||||
this.$nextTick(() =>
|
||||
document.dispatchEvent(new CustomEvent("reactions:loaded")),
|
||||
);
|
||||
},
|
||||
}),
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export { ArticleReactions as default };
|
||||
@@ -1,147 +0,0 @@
|
||||
import Alpine from "alpinejs";
|
||||
|
||||
interface ReactionData {
|
||||
id: string;
|
||||
name: string;
|
||||
count: number;
|
||||
users: string[];
|
||||
}
|
||||
|
||||
interface ReactionComponent extends Record<string, unknown> {
|
||||
url: string;
|
||||
myReactions: string[];
|
||||
articleReactions: ReactionData[];
|
||||
allReactions: string[];
|
||||
isAuthenticated: boolean;
|
||||
init(): void;
|
||||
treatArticleReactions(rawReactions: Record<string, { user?: { username: string } }[]>): void;
|
||||
toggleReaction(reaction: string): void;
|
||||
addReaction(name: string, username: string): void;
|
||||
removeReaction(name: string, username: string): void;
|
||||
generateVirtualReactionId(name: string): string;
|
||||
canAddReactionFromModal(name: string): boolean;
|
||||
userHasReaction(reaction: ReactionData | string): boolean;
|
||||
articleHasReaction(name: string): boolean;
|
||||
getReactionDataFromName(name: string): ReactionData | undefined;
|
||||
dispatchFlowbiteEvent(): void;
|
||||
$nextTick: (callback: () => void) => void;
|
||||
}
|
||||
|
||||
const ArticleReactions = {
|
||||
init() {
|
||||
document.addEventListener("alpine:init", () => this.startComponent());
|
||||
},
|
||||
|
||||
startComponent() {
|
||||
Alpine.data("reactions", (...args: unknown[]) => {
|
||||
const [myReactions = [], , url = ""] = args as [string[], Record<string, { user?: { username: string } }[]>, string];
|
||||
const rawArticleReactions = args[1] as Record<string, { user?: { username: string } }[]> | undefined;
|
||||
|
||||
return {
|
||||
url,
|
||||
myReactions: myReactions as string[],
|
||||
articleReactions: [] as ReactionData[],
|
||||
allReactions: [] as string[],
|
||||
isAuthenticated: false,
|
||||
|
||||
init(this: ReactionComponent) {
|
||||
if (rawArticleReactions) {
|
||||
this.treatArticleReactions(rawArticleReactions);
|
||||
}
|
||||
this.allReactions = window.App.defaultReactions;
|
||||
this.isAuthenticated = window.App.isAuthenticated;
|
||||
this.dispatchFlowbiteEvent();
|
||||
},
|
||||
|
||||
treatArticleReactions(this: ReactionComponent, raw: Record<string, { user?: { username: string } }[]>) {
|
||||
const transformed: ReactionData[] = [];
|
||||
|
||||
Object.entries(raw).forEach(([name, reactions]) => {
|
||||
const values = Object.values(reactions);
|
||||
transformed.push({
|
||||
id: this.generateVirtualReactionId(name),
|
||||
name,
|
||||
count: values.length,
|
||||
users: values.map((r) => r.user?.username ?? ""),
|
||||
});
|
||||
});
|
||||
|
||||
this.articleReactions = transformed;
|
||||
},
|
||||
|
||||
toggleReaction(this: ReactionComponent, reaction: string) {
|
||||
if (!this.url.length || !this.isAuthenticated) return;
|
||||
|
||||
window.axios
|
||||
.post(this.url, { reaction })
|
||||
.then((response: { data: { success: boolean; added: boolean; username: string } }) => {
|
||||
if (!response.data.success) return;
|
||||
if (!response.data.added) {
|
||||
this.removeReaction(reaction, response.data.username);
|
||||
return;
|
||||
}
|
||||
this.addReaction(reaction, response.data.username);
|
||||
});
|
||||
},
|
||||
|
||||
addReaction(this: ReactionComponent, name: string, username: string) {
|
||||
this.myReactions.push(name);
|
||||
const existing = this.getReactionDataFromName(name);
|
||||
if (existing) {
|
||||
existing.count++;
|
||||
existing.users.push(username);
|
||||
return;
|
||||
}
|
||||
this.articleReactions.push({
|
||||
id: this.generateVirtualReactionId(name),
|
||||
name,
|
||||
count: 1,
|
||||
users: [username],
|
||||
});
|
||||
this.dispatchFlowbiteEvent();
|
||||
},
|
||||
|
||||
removeReaction(this: ReactionComponent, name: string, username: string) {
|
||||
this.myReactions.splice(this.myReactions.indexOf(name), 1);
|
||||
const data = this.getReactionDataFromName(name);
|
||||
if (!data) return;
|
||||
if (data.count > 1) {
|
||||
data.count--;
|
||||
data.users.splice(data.users.indexOf(username), 1);
|
||||
return;
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
this.articleReactions.splice(this.articleReactions.indexOf(data), 1);
|
||||
});
|
||||
},
|
||||
|
||||
generateVirtualReactionId(this: ReactionComponent, name: string): string {
|
||||
return name + Math.floor(Math.random() * 1000);
|
||||
},
|
||||
|
||||
canAddReactionFromModal(this: ReactionComponent, name: string): boolean {
|
||||
return !this.userHasReaction(name) && !this.articleHasReaction(name);
|
||||
},
|
||||
|
||||
userHasReaction(this: ReactionComponent, reaction: ReactionData | string): boolean {
|
||||
const name = typeof reaction === "string" ? reaction : reaction.name;
|
||||
return this.myReactions.includes(name);
|
||||
},
|
||||
|
||||
articleHasReaction(this: ReactionComponent, name: string): boolean {
|
||||
return typeof this.getReactionDataFromName(name) !== "undefined";
|
||||
},
|
||||
|
||||
getReactionDataFromName(this: ReactionComponent, name: string): ReactionData | undefined {
|
||||
return this.articleReactions.find((r) => r.name === name);
|
||||
},
|
||||
|
||||
dispatchFlowbiteEvent(this: ReactionComponent) {
|
||||
this.$nextTick(() => document.dispatchEvent(new CustomEvent("reactions:loaded")));
|
||||
},
|
||||
} as ReactionComponent;
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export { ArticleReactions as default };
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
<link href="https://fonts.googleapis.com/css2?family=Ubuntu+Condensed&display=swap" rel="stylesheet">
|
||||
|
||||
@vite(['resources/themes/' . setting('theme') . '/css/app.css', 'resources/themes/' . setting('theme') . '/js/app.ts'], 'build')
|
||||
@vite(['resources/themes/' . setting('theme') . '/css/app.css', 'resources/themes/' . setting('theme') . '/js/app.js'], 'build')
|
||||
|
||||
@if(setting('button_enabled') == '1')
|
||||
<style>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<!-- Fonts -->
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700&display=swap">
|
||||
|
||||
@vite(['resources/themes/' . setting('theme', 'dusk') . '/css/app.css', 'resources/themes/' . setting('theme') . '/js/app.ts'], 'build')
|
||||
@vite(['resources/themes/' . setting('theme', 'dusk') . '/css/app.css', 'resources/themes/' . setting('theme') . '/js/app.js'], 'build')
|
||||
|
||||
<style>
|
||||
:root {
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<link rel="stylesheet" href="https://unpkg.com/flowbite@1.5.1/dist/flowbite.min.css"/>
|
||||
<script src="https://unpkg.com/flowbite@1.5.1/dist/flowbite.js"></script>
|
||||
|
||||
@vite(['resources/themes/' . setting('theme') . '/css/app.css', 'resources/themes/' . setting('theme') . '/js/app.ts'], 'build')
|
||||
@vite(['resources/themes/' . setting('theme') . '/css/app.css', 'resources/themes/' . setting('theme') . '/js/app.js'], 'build')
|
||||
</head>
|
||||
|
||||
<body class="h-screen overflow-hidden relative bg-[#233143]">
|
||||
|
||||
@@ -12,10 +12,10 @@ export default defineConfig({
|
||||
laravel({
|
||||
input: [
|
||||
path.resolve(__dirname, "css/app.css"),
|
||||
path.resolve(__dirname, "js/app.ts"),
|
||||
"resources/js/global.ts",
|
||||
path.resolve(__dirname, "js/app.js"),
|
||||
"resources/js/global.js",
|
||||
"resources/css/global.css",
|
||||
"resources/js/ssr.tsx",
|
||||
"resources/js/ssr.jsx",
|
||||
],
|
||||
}),
|
||||
|
||||
@@ -33,7 +33,7 @@ export default defineConfig({
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": path.resolve(__dirname, "js/app.ts"),
|
||||
"@": path.resolve(__dirname, "js/app.js"),
|
||||
},
|
||||
},
|
||||
css: {
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<script src="{{ asset('assets/js/tippy-bundle.umd.min.js') }}"></script>
|
||||
<link rel="stylesheet" href="{{ asset('assets/css/scale.min.css') }}"/>
|
||||
|
||||
@vite(['resources/js/ssr.tsx', 'resources/themes/' . setting('theme', 'atom') . '/css/app.css', 'resources/themes/' . setting('theme', 'atom') . '/js/app.ts'], 'build')
|
||||
@vite(['resources/js/ssr.jsx', 'resources/themes/' . setting('theme', 'atom') . '/css/app.css', 'resources/themes/' . setting('theme', 'atom') . '/js/app.js'], 'build')
|
||||
|
||||
<style>
|
||||
:root {
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<!-- Fonts -->
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700&display=swap">
|
||||
|
||||
@vite(['resources/css/global.css', 'resources/js/global.ts'], 'build')
|
||||
@vite(['resources/css/global.css', 'resources/js/global.js'], 'build')
|
||||
@stack('scripts')
|
||||
</head>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user