You've already forked Atomcms-edit
e6d92f27b3
- Rename all .js/.jsx files to .ts/.tsx across resources/js and theme dirs - Add TypeScript 6.0 with strict mode, tsconfig.json - Add type definitions for Inertia page props, Alpine.js, Turbolinks - Update vite.config.js entries to .ts/.tsx extensions - Update all Blade @vite() calls to match new .ts/.tsx entry points - Add TypeScript ESLint config (replacing unused Vue plugin) - Add @types/react, @types/react-dom, @types/lodash - Add typecheck script and integrate into check pipeline - Full tsc --noEmit, ESLint, and production build pass cleanly
148 lines
5.2 KiB
TypeScript
Executable File
148 lines
5.2 KiB
TypeScript
Executable File
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 };
|