@soubiran/ui 0.9.2 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -0
- package/dist/imports.mjs +5 -1
- package/dist/index.d.ts +716 -3
- package/dist/index.js +2730 -4
- package/dist/resolver.mjs +7 -6
- package/dist/style.css +6 -141
- package/dist/styles/animations.css +80 -0
- package/dist/styles/keyframes.css +23 -0
- package/dist/styles/shiki.css +17 -0
- package/package.json +16 -16
- package/dist/components/Container.vue +0 -36
- package/dist/components/Feedback.vue +0 -65
- package/dist/components/FeedbackCard.vue +0 -168
- package/dist/components/Header.vue +0 -138
- package/dist/components/Page.vue +0 -63
- package/dist/components/PageHeader.vue +0 -36
- package/dist/components/PageTitle.vue +0 -32
- package/dist/components/Socials.vue +0 -82
- package/dist/components/Sponsors.vue +0 -34
- package/dist/components/TableOfContents.vue +0 -74
- package/dist/components/ViewersCounter.vue +0 -75
- package/dist/composables/useTableOfContents.d.ts +0 -11
- package/dist/composables/useTableOfContents.js +0 -20
- package/dist/composables/useUmami.d.ts +0 -6
- package/dist/composables/useUmami.js +0 -14
package/dist/index.js
CHANGED
|
@@ -1,4 +1,2730 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
|
|
1
|
+
import { prose } from "./wrapper-classes.js";
|
|
2
|
+
import { Fragment, Transition, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, inject, markRaw, mergeModels, mergeProps, nextTick, normalizeClass, onBeforeUnmount, onMounted, onScopeDispose, openBlock, provide, readonly, ref, renderList, renderSlot, resolveComponent, toDisplayString, toRef, toValue, unref, useModel, useTemplateRef, watch, withCtx, withModifiers } from "vue";
|
|
3
|
+
import { buildLocaleContext } from "@nuxt/ui/utils/locale";
|
|
4
|
+
import { defineQueryOptions, useMutation, useQuery, useQueryCache } from "@pinia/colada";
|
|
5
|
+
import { AnimatePresence, Motion, motion, useReducedMotion } from "motion-v";
|
|
6
|
+
import { tv } from "tailwind-variants";
|
|
7
|
+
import { FetchError, ofetch } from "ofetch";
|
|
8
|
+
import { RadioGroupIndicator, RadioGroupItem, RadioGroupRoot, TabsContent, TabsList, TabsRoot, TabsTrigger } from "reka-ui";
|
|
9
|
+
import { createSharedComposable, useTimeAgoIntl } from "@vueuse/core";
|
|
10
|
+
import PartySocket from "partysocket";
|
|
11
|
+
//#region src/composables/comments/context.ts
|
|
12
|
+
const commentsContextInjectionKey = Symbol("soubiran-ui.comments-context");
|
|
13
|
+
function provideCommentsContext(context) {
|
|
14
|
+
provide(commentsContextInjectionKey, context);
|
|
15
|
+
}
|
|
16
|
+
function useCommentsContext() {
|
|
17
|
+
const context = inject(commentsContextInjectionKey);
|
|
18
|
+
if (!context) throw new Error("Comments components must be rendered inside <Comments>.");
|
|
19
|
+
return context;
|
|
20
|
+
}
|
|
21
|
+
//#endregion
|
|
22
|
+
//#region src/composables/comments/useCommentsBanner.ts
|
|
23
|
+
function useCommentsBanner() {
|
|
24
|
+
const banner = ref();
|
|
25
|
+
let timeout;
|
|
26
|
+
function dismiss() {
|
|
27
|
+
if (timeout !== void 0) {
|
|
28
|
+
clearTimeout(timeout);
|
|
29
|
+
timeout = void 0;
|
|
30
|
+
}
|
|
31
|
+
banner.value = void 0;
|
|
32
|
+
}
|
|
33
|
+
function show(nextBanner) {
|
|
34
|
+
dismiss();
|
|
35
|
+
banner.value = nextBanner;
|
|
36
|
+
timeout = setTimeout(() => {
|
|
37
|
+
banner.value = void 0;
|
|
38
|
+
timeout = void 0;
|
|
39
|
+
}, 4e3);
|
|
40
|
+
}
|
|
41
|
+
onScopeDispose(dismiss);
|
|
42
|
+
return {
|
|
43
|
+
banner: readonly(banner),
|
|
44
|
+
isActive: computed(() => banner.value !== void 0),
|
|
45
|
+
dismiss,
|
|
46
|
+
show
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
//#endregion
|
|
50
|
+
//#region src/locale/en.ts
|
|
51
|
+
var en_default = defineLocale({
|
|
52
|
+
name: "English",
|
|
53
|
+
code: "en",
|
|
54
|
+
dir: "ltr",
|
|
55
|
+
messages: {
|
|
56
|
+
Editor: {
|
|
57
|
+
placeholder: "Write something...",
|
|
58
|
+
write: { help: "You can use Markdown syntax to format the content." },
|
|
59
|
+
preview: {
|
|
60
|
+
loading: "Loading preview...",
|
|
61
|
+
placeholder: "No content to preview."
|
|
62
|
+
},
|
|
63
|
+
tabs: {
|
|
64
|
+
write: "Write",
|
|
65
|
+
preview: "Preview"
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
LoginRequired: {
|
|
69
|
+
text: "You need to be logged in to access this feature.",
|
|
70
|
+
action: "Log in",
|
|
71
|
+
errors: {
|
|
72
|
+
popupBlocked: "The login window was blocked. Allow popups and try again.",
|
|
73
|
+
popupClosed: "The login window was closed before authentication finished.",
|
|
74
|
+
refreshFailed: "You are logged in, but your session could not be refreshed. Please try again."
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
LoginCallback: {
|
|
78
|
+
success: "Login complete. You can close this window.",
|
|
79
|
+
error: "The login could not be completed. Close this window and try again."
|
|
80
|
+
},
|
|
81
|
+
ConfirmModal: { actions: {
|
|
82
|
+
cancel: "Cancel",
|
|
83
|
+
confirm: "Confirm"
|
|
84
|
+
} },
|
|
85
|
+
Feedback: { action: "Give feedback" },
|
|
86
|
+
FeedbackCard: {
|
|
87
|
+
placeholder: "Your feedback...",
|
|
88
|
+
action: "Send",
|
|
89
|
+
ratings: {
|
|
90
|
+
hate: "Hate it",
|
|
91
|
+
poor: "Not great",
|
|
92
|
+
okay: "It's ok",
|
|
93
|
+
love: "Love it"
|
|
94
|
+
},
|
|
95
|
+
success: {
|
|
96
|
+
received: "Your feedback has been received.",
|
|
97
|
+
thanks: "Thanks for your help!"
|
|
98
|
+
},
|
|
99
|
+
errors: {
|
|
100
|
+
notFound: "Page not found. Cannot send feedback. It's us, not you!",
|
|
101
|
+
unavailable: "Service is currently unavailable. Please try again later.",
|
|
102
|
+
unexpected: "An unexpected error occurred."
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
comments: {
|
|
106
|
+
Comment: {
|
|
107
|
+
actions: {
|
|
108
|
+
edit: "Edit",
|
|
109
|
+
delete: "Delete"
|
|
110
|
+
},
|
|
111
|
+
actionMenu: "Comment actions"
|
|
112
|
+
},
|
|
113
|
+
CommentDeleteConfirmModal: {
|
|
114
|
+
title: "Delete comment",
|
|
115
|
+
description: "Are you sure you want to delete this comment? This action cannot be undone."
|
|
116
|
+
},
|
|
117
|
+
CommentFormSection: { title: "Add a comment" },
|
|
118
|
+
CommentHeader: {
|
|
119
|
+
author: "Author",
|
|
120
|
+
publishedAt: "Published at"
|
|
121
|
+
},
|
|
122
|
+
CommentLike: { title: "Like this comment" },
|
|
123
|
+
CommentUnlike: { title: "Remove your like" },
|
|
124
|
+
CommentRepliesCount: {
|
|
125
|
+
replies: "{count} replies",
|
|
126
|
+
reply: "{count} reply"
|
|
127
|
+
},
|
|
128
|
+
CommentForm: { actions: {
|
|
129
|
+
add: "Comment",
|
|
130
|
+
reply: "Reply",
|
|
131
|
+
edit: "Edit",
|
|
132
|
+
cancel: "Cancel"
|
|
133
|
+
} },
|
|
134
|
+
errors: {
|
|
135
|
+
create: "An error occurred while adding the comment.",
|
|
136
|
+
update: "An error occurred while editing the comment.",
|
|
137
|
+
delete: "An error occurred while deleting the comment.",
|
|
138
|
+
like: "An error occurred while adding the like.",
|
|
139
|
+
unlike: "An error occurred while removing the like."
|
|
140
|
+
},
|
|
141
|
+
success: { delete: "The comment was deleted successfully." }
|
|
142
|
+
},
|
|
143
|
+
discussions: {
|
|
144
|
+
DiscussionReply: { reply: "Reply to this discussion..." },
|
|
145
|
+
DiscussionsSection: { empty: "No discussions yet." },
|
|
146
|
+
DiscussionsSectionTitle: { title: "Discussions" },
|
|
147
|
+
DiscussionsSectionSubtitle: {
|
|
148
|
+
comments: {
|
|
149
|
+
plural: "{count} comments",
|
|
150
|
+
singular: "{count} comment"
|
|
151
|
+
},
|
|
152
|
+
replies: {
|
|
153
|
+
plural: "{count} replies",
|
|
154
|
+
singular: "{count} reply"
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
state: {
|
|
159
|
+
empty: "No data to display.",
|
|
160
|
+
error: "An error occurred while loading the data.",
|
|
161
|
+
pending: "Loading data..."
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
//#endregion
|
|
166
|
+
//#region src/composables/useLocale.ts
|
|
167
|
+
const localeContextInjectionKey = Symbol.for("soubiran-ui.locale-context");
|
|
168
|
+
function useLocale(localeOverrides) {
|
|
169
|
+
const locale = localeOverrides || inject(localeContextInjectionKey);
|
|
170
|
+
return buildLocaleContext(computed(() => locale?.value || en_default));
|
|
171
|
+
}
|
|
172
|
+
//#endregion
|
|
173
|
+
//#region src/locale/fr.ts
|
|
174
|
+
var fr_default = defineLocale({
|
|
175
|
+
name: "Français",
|
|
176
|
+
code: "fr",
|
|
177
|
+
dir: "ltr",
|
|
178
|
+
messages: {
|
|
179
|
+
Editor: {
|
|
180
|
+
placeholder: "Écrivez quelque chose...",
|
|
181
|
+
write: { help: "Vous pouvez utiliser la syntaxe Markdown pour formater le contenu." },
|
|
182
|
+
preview: {
|
|
183
|
+
loading: "Chargement de l’aperçu...",
|
|
184
|
+
placeholder: "Aucun contenu à prévisualiser."
|
|
185
|
+
},
|
|
186
|
+
tabs: {
|
|
187
|
+
write: "Écrire",
|
|
188
|
+
preview: "Aperçu"
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
LoginRequired: {
|
|
192
|
+
text: "Vous devez être connecté pour accéder à cette fonctionnalité.",
|
|
193
|
+
action: "Se connecter",
|
|
194
|
+
errors: {
|
|
195
|
+
popupBlocked: "La fenêtre de connexion a été bloquée. Autorisez les fenêtres contextuelles et réessayez.",
|
|
196
|
+
popupClosed: "La fenêtre de connexion a été fermée avant la fin de l’authentification.",
|
|
197
|
+
refreshFailed: "Vous êtes connecté, mais votre session n’a pas pu être actualisée. Veuillez réessayer."
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
LoginCallback: {
|
|
201
|
+
success: "Connexion terminée. Vous pouvez fermer cette fenêtre.",
|
|
202
|
+
error: "La connexion n’a pas pu être terminée. Fermez cette fenêtre et réessayez."
|
|
203
|
+
},
|
|
204
|
+
ConfirmModal: { actions: {
|
|
205
|
+
cancel: "Annuler",
|
|
206
|
+
confirm: "Confirmer"
|
|
207
|
+
} },
|
|
208
|
+
Feedback: { action: "Donner mon avis" },
|
|
209
|
+
FeedbackCard: {
|
|
210
|
+
placeholder: "Votre avis...",
|
|
211
|
+
action: "Envoyer",
|
|
212
|
+
ratings: {
|
|
213
|
+
hate: "Je déteste",
|
|
214
|
+
poor: "Pas terrible",
|
|
215
|
+
okay: "Ça va",
|
|
216
|
+
love: "J’adore"
|
|
217
|
+
},
|
|
218
|
+
success: {
|
|
219
|
+
received: "Votre avis a bien été reçu.",
|
|
220
|
+
thanks: "Merci pour votre aide !"
|
|
221
|
+
},
|
|
222
|
+
errors: {
|
|
223
|
+
notFound: "Page introuvable. Impossible d’envoyer votre avis.",
|
|
224
|
+
unavailable: "Le service est indisponible. Veuillez réessayer plus tard.",
|
|
225
|
+
unexpected: "Une erreur inattendue est survenue."
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
comments: {
|
|
229
|
+
Comment: {
|
|
230
|
+
actions: {
|
|
231
|
+
edit: "Modifier",
|
|
232
|
+
delete: "Supprimer"
|
|
233
|
+
},
|
|
234
|
+
actionMenu: "Actions du commentaire"
|
|
235
|
+
},
|
|
236
|
+
CommentDeleteConfirmModal: {
|
|
237
|
+
title: "Supprimer le commentaire",
|
|
238
|
+
description: "Êtes-vous sûr de vouloir supprimer ce commentaire ? Cette action ne peut pas être annulée."
|
|
239
|
+
},
|
|
240
|
+
CommentFormSection: { title: "Ajouter un commentaire" },
|
|
241
|
+
CommentHeader: {
|
|
242
|
+
author: "Auteur",
|
|
243
|
+
publishedAt: "Publié le"
|
|
244
|
+
},
|
|
245
|
+
CommentLike: { title: "Aimer ce commentaire" },
|
|
246
|
+
CommentUnlike: { title: "Retirer votre like" },
|
|
247
|
+
CommentRepliesCount: {
|
|
248
|
+
replies: "{count} réponses",
|
|
249
|
+
reply: "{count} réponse"
|
|
250
|
+
},
|
|
251
|
+
CommentForm: { actions: {
|
|
252
|
+
add: "Commenter",
|
|
253
|
+
reply: "Répondre",
|
|
254
|
+
edit: "Modifier",
|
|
255
|
+
cancel: "Annuler"
|
|
256
|
+
} },
|
|
257
|
+
errors: {
|
|
258
|
+
create: "Une erreur est survenue lors de l’ajout du commentaire.",
|
|
259
|
+
update: "Une erreur est survenue lors de la modification du commentaire.",
|
|
260
|
+
delete: "Une erreur est survenue lors de la suppression du commentaire.",
|
|
261
|
+
like: "Une erreur est survenue lors de l’ajout du like.",
|
|
262
|
+
unlike: "Une erreur est survenue lors de la suppression du like."
|
|
263
|
+
},
|
|
264
|
+
success: { delete: "Le commentaire a bien été supprimé." }
|
|
265
|
+
},
|
|
266
|
+
discussions: {
|
|
267
|
+
DiscussionReply: { reply: "Répondre à cette discussion..." },
|
|
268
|
+
DiscussionsSection: { empty: "Aucune discussion pour le moment." },
|
|
269
|
+
DiscussionsSectionTitle: { title: "Discussions" },
|
|
270
|
+
DiscussionsSectionSubtitle: {
|
|
271
|
+
comments: {
|
|
272
|
+
plural: "{count} commentaires",
|
|
273
|
+
singular: "{count} commentaire"
|
|
274
|
+
},
|
|
275
|
+
replies: {
|
|
276
|
+
plural: "{count} réponses",
|
|
277
|
+
singular: "{count} réponse"
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
},
|
|
281
|
+
state: {
|
|
282
|
+
empty: "Aucune donnée à afficher.",
|
|
283
|
+
error: "Une erreur est survenue lors du chargement des données.",
|
|
284
|
+
pending: "Chargement des données..."
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
//#endregion
|
|
289
|
+
//#region src/utils/api.ts
|
|
290
|
+
const api = ofetch.create({
|
|
291
|
+
baseURL: import.meta.env.VITE_API_URL,
|
|
292
|
+
headers: { Accept: "application/json" },
|
|
293
|
+
credentials: "include",
|
|
294
|
+
onRequest: [async ({ options }) => {
|
|
295
|
+
const method = options.method?.toUpperCase() || "GET";
|
|
296
|
+
if ([
|
|
297
|
+
"GET",
|
|
298
|
+
"HEAD",
|
|
299
|
+
"OPTIONS"
|
|
300
|
+
].includes(method)) return;
|
|
301
|
+
if (typeof document === "undefined") return;
|
|
302
|
+
if (!getCookie("XSRF-TOKEN")) await ofetch("/sanctum/csrf-cookie", {
|
|
303
|
+
baseURL: import.meta.env.VITE_API_URL,
|
|
304
|
+
credentials: "include"
|
|
305
|
+
});
|
|
306
|
+
options.headers.set("X-Xsrf-Token", decodeURIComponent(getCookie("XSRF-TOKEN") || ""));
|
|
307
|
+
}]
|
|
308
|
+
});
|
|
309
|
+
function getCookie(name) {
|
|
310
|
+
if (typeof document === "undefined") return null;
|
|
311
|
+
const match = document.cookie.match(new RegExp(`(^| )${name}=([^;]+)`));
|
|
312
|
+
return match ? match[2] : null;
|
|
313
|
+
}
|
|
314
|
+
function isUnprocessableEntityError(error) {
|
|
315
|
+
return error instanceof FetchError && error.response?.status === 422;
|
|
316
|
+
}
|
|
317
|
+
//#endregion
|
|
318
|
+
//#region src/api/comments.ts
|
|
319
|
+
const localizedHeaders = (locale) => ({ "Accept-Language": locale });
|
|
320
|
+
const getMarkdown = (content, locale) => api("/api/markdown", {
|
|
321
|
+
method: "POST",
|
|
322
|
+
body: { content },
|
|
323
|
+
headers: localizedHeaders(locale)
|
|
324
|
+
});
|
|
325
|
+
const getComments = (pageId, locale) => api(`/api/pages/${pageId}/comments`, { headers: localizedHeaders(locale) });
|
|
326
|
+
const postComment = (pageId, content, locale, parentCommentId) => api(`/api/pages/${pageId}/comments`, {
|
|
327
|
+
method: "POST",
|
|
328
|
+
body: {
|
|
329
|
+
content,
|
|
330
|
+
parent_id: parentCommentId
|
|
331
|
+
},
|
|
332
|
+
headers: localizedHeaders(locale)
|
|
333
|
+
});
|
|
334
|
+
const putComment = (commentId, content, locale) => api(`/api/comments/${commentId}`, {
|
|
335
|
+
method: "PUT",
|
|
336
|
+
body: { content },
|
|
337
|
+
headers: localizedHeaders(locale)
|
|
338
|
+
});
|
|
339
|
+
const deleteComment = (commentId, locale) => api(`/api/comments/${commentId}`, {
|
|
340
|
+
method: "DELETE",
|
|
341
|
+
headers: localizedHeaders(locale)
|
|
342
|
+
});
|
|
343
|
+
const postCommentLike = (commentId, locale) => api(`/api/comments/${commentId}/likes`, {
|
|
344
|
+
method: "POST",
|
|
345
|
+
headers: localizedHeaders(locale)
|
|
346
|
+
});
|
|
347
|
+
const deleteCommentLike = (commentId, locale) => api(`/api/comments/${commentId}/likes`, {
|
|
348
|
+
method: "DELETE",
|
|
349
|
+
headers: localizedHeaders(locale)
|
|
350
|
+
});
|
|
351
|
+
//#endregion
|
|
352
|
+
//#region src/keys/comments.ts
|
|
353
|
+
const COMMENT_QUERY_KEYS = {
|
|
354
|
+
root: ["comments"],
|
|
355
|
+
byPageId: (pageId, locale) => [
|
|
356
|
+
"pages",
|
|
357
|
+
pageId,
|
|
358
|
+
locale,
|
|
359
|
+
...COMMENT_QUERY_KEYS.root
|
|
360
|
+
]
|
|
361
|
+
};
|
|
362
|
+
//#endregion
|
|
363
|
+
//#region src/api/users.ts
|
|
364
|
+
const getCurrentUser = () => api("/api/user");
|
|
365
|
+
//#endregion
|
|
366
|
+
//#region src/keys/user.ts
|
|
367
|
+
const USER_QUERY_KEYS = { current: ["user"] };
|
|
368
|
+
//#endregion
|
|
369
|
+
//#region src/queries/users.ts
|
|
370
|
+
const currentUserQuery = defineQueryOptions(() => ({
|
|
371
|
+
key: USER_QUERY_KEYS.current,
|
|
372
|
+
query: () => getCurrentUser().then((response) => response.data),
|
|
373
|
+
enabled: typeof window !== "undefined"
|
|
374
|
+
}));
|
|
375
|
+
//#endregion
|
|
376
|
+
//#region src/utils/login.ts
|
|
377
|
+
const LOGIN_CALLBACK_MESSAGE_TYPE = "soubiran:login-complete";
|
|
378
|
+
const LOGIN_CALLBACK_NONCE_PARAM = "login_attempt";
|
|
379
|
+
function buildLoginCallbackUrl(currentUrl, callbackPath, nonce) {
|
|
380
|
+
const callback = new URL(callbackPath, currentUrl);
|
|
381
|
+
callback.searchParams.set(LOGIN_CALLBACK_NONCE_PARAM, nonce);
|
|
382
|
+
return callback;
|
|
383
|
+
}
|
|
384
|
+
function isLoginCallbackMessage(data) {
|
|
385
|
+
if (!data || typeof data !== "object") return false;
|
|
386
|
+
const message = data;
|
|
387
|
+
return message.type === "soubiran:login-complete" && typeof message.nonce === "string";
|
|
388
|
+
}
|
|
389
|
+
//#endregion
|
|
390
|
+
//#region src/composables/useLogin.ts
|
|
391
|
+
const LOGIN_ERROR_MESSAGE_KEYS = {
|
|
392
|
+
"popup-blocked": "LoginRequired.errors.popupBlocked",
|
|
393
|
+
"popup-closed": "LoginRequired.errors.popupClosed",
|
|
394
|
+
"refresh-failed": "LoginRequired.errors.refreshFailed"
|
|
395
|
+
};
|
|
396
|
+
const DEFAULT_CALLBACK_PATH = "/auth/callback";
|
|
397
|
+
const POPUP_WIDTH = 800;
|
|
398
|
+
const POPUP_HEIGHT = 600;
|
|
399
|
+
const LOGIN_POPUP_NAME = "soubiran-login";
|
|
400
|
+
function getLoginErrorMessageKey(error) {
|
|
401
|
+
return LOGIN_ERROR_MESSAGE_KEYS[error];
|
|
402
|
+
}
|
|
403
|
+
function buildLoginLink(apiUrl, currentUrl, fragment) {
|
|
404
|
+
const redirect = new URL(currentUrl);
|
|
405
|
+
redirect.hash = "";
|
|
406
|
+
const login = new URL("/login", apiUrl);
|
|
407
|
+
login.searchParams.set("redirect", redirect.toString());
|
|
408
|
+
login.hash = fragment ?? "";
|
|
409
|
+
return login.toString();
|
|
410
|
+
}
|
|
411
|
+
function createNonce() {
|
|
412
|
+
return crypto.randomUUID();
|
|
413
|
+
}
|
|
414
|
+
function getPopupFeatures() {
|
|
415
|
+
const left = Math.max(0, window.screenX + (window.outerWidth - POPUP_WIDTH) / 2);
|
|
416
|
+
const top = Math.max(0, window.screenY + (window.outerHeight - POPUP_HEIGHT) / 2);
|
|
417
|
+
return [
|
|
418
|
+
"popup=yes",
|
|
419
|
+
`width=${POPUP_WIDTH}`,
|
|
420
|
+
`height=${POPUP_HEIGHT}`,
|
|
421
|
+
`left=${Math.round(left)}`,
|
|
422
|
+
`top=${Math.round(top)}`
|
|
423
|
+
].join(",");
|
|
424
|
+
}
|
|
425
|
+
function useLogin(options = {}) {
|
|
426
|
+
const queryCache = useQueryCache();
|
|
427
|
+
const error = ref();
|
|
428
|
+
const isPending = ref(false);
|
|
429
|
+
let popup = null;
|
|
430
|
+
let closeTimer;
|
|
431
|
+
let removeMessageListener;
|
|
432
|
+
let resolveAttempt;
|
|
433
|
+
let activeAttempt;
|
|
434
|
+
function stopMonitoring() {
|
|
435
|
+
if (closeTimer) {
|
|
436
|
+
clearInterval(closeTimer);
|
|
437
|
+
closeTimer = void 0;
|
|
438
|
+
}
|
|
439
|
+
removeMessageListener?.();
|
|
440
|
+
removeMessageListener = void 0;
|
|
441
|
+
popup = null;
|
|
442
|
+
}
|
|
443
|
+
function cleanup(success) {
|
|
444
|
+
stopMonitoring();
|
|
445
|
+
isPending.value = false;
|
|
446
|
+
resolveAttempt?.(success);
|
|
447
|
+
resolveAttempt = void 0;
|
|
448
|
+
activeAttempt = void 0;
|
|
449
|
+
}
|
|
450
|
+
function openLoginWindow() {
|
|
451
|
+
if (typeof window === "undefined") return Promise.resolve(false);
|
|
452
|
+
if (popup && !popup.closed && activeAttempt) {
|
|
453
|
+
popup.focus();
|
|
454
|
+
return activeAttempt;
|
|
455
|
+
}
|
|
456
|
+
error.value = void 0;
|
|
457
|
+
isPending.value = true;
|
|
458
|
+
const nonce = createNonce();
|
|
459
|
+
const callback = buildLoginCallbackUrl(window.location.href, toValue(options.callbackPath) ?? DEFAULT_CALLBACK_PATH, nonce);
|
|
460
|
+
const loginUrl = buildLoginLink(import.meta.env.VITE_API_URL, callback.toString());
|
|
461
|
+
activeAttempt = new Promise((resolve) => {
|
|
462
|
+
resolveAttempt = resolve;
|
|
463
|
+
});
|
|
464
|
+
const onMessage = async (event) => {
|
|
465
|
+
if (event.origin !== callback.origin || event.source !== popup || !isLoginCallbackMessage(event.data) || event.data.type !== "soubiran:login-complete" || event.data.nonce !== nonce) return;
|
|
466
|
+
stopMonitoring();
|
|
467
|
+
try {
|
|
468
|
+
await queryCache.invalidateQueries({
|
|
469
|
+
key: USER_QUERY_KEYS.current,
|
|
470
|
+
exact: true
|
|
471
|
+
});
|
|
472
|
+
cleanup(true);
|
|
473
|
+
} catch {
|
|
474
|
+
error.value = "refresh-failed";
|
|
475
|
+
cleanup(false);
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
window.addEventListener("message", onMessage);
|
|
479
|
+
removeMessageListener = () => window.removeEventListener("message", onMessage);
|
|
480
|
+
popup = window.open(loginUrl, LOGIN_POPUP_NAME, getPopupFeatures());
|
|
481
|
+
if (!popup) {
|
|
482
|
+
error.value = "popup-blocked";
|
|
483
|
+
cleanup(false);
|
|
484
|
+
return Promise.resolve(false);
|
|
485
|
+
}
|
|
486
|
+
popup.focus();
|
|
487
|
+
closeTimer = setInterval(() => {
|
|
488
|
+
if (popup?.closed) {
|
|
489
|
+
error.value = "popup-closed";
|
|
490
|
+
cleanup(false);
|
|
491
|
+
}
|
|
492
|
+
}, 500);
|
|
493
|
+
return activeAttempt;
|
|
494
|
+
}
|
|
495
|
+
onBeforeUnmount(() => cleanup(false));
|
|
496
|
+
return {
|
|
497
|
+
error: computed(() => error.value),
|
|
498
|
+
isPending: computed(() => isPending.value),
|
|
499
|
+
openLoginWindow
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
//#endregion
|
|
503
|
+
//#region src/components/LoginRequired.vue
|
|
504
|
+
const loginRequired = tv({ slots: {
|
|
505
|
+
base: "flex flex-col gap-4 items-center",
|
|
506
|
+
prose: "text-sm",
|
|
507
|
+
actions: "flex flex-row items-center justify-center gap-2"
|
|
508
|
+
} });
|
|
509
|
+
const _sfc_main$35 = /* @__PURE__ */ defineComponent({
|
|
510
|
+
__name: "LoginRequired",
|
|
511
|
+
props: {
|
|
512
|
+
class: {},
|
|
513
|
+
ui: {}
|
|
514
|
+
},
|
|
515
|
+
setup(__props) {
|
|
516
|
+
const props = __props;
|
|
517
|
+
const { t } = useLocale();
|
|
518
|
+
const toast = useToast();
|
|
519
|
+
const { error, isPending, openLoginWindow } = useLogin();
|
|
520
|
+
async function login() {
|
|
521
|
+
if (!await openLoginWindow() && error.value) toast.add({
|
|
522
|
+
color: "error",
|
|
523
|
+
description: t(getLoginErrorMessageKey(error.value))
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
const ui = computed(() => loginRequired());
|
|
527
|
+
return (_ctx, _cache) => {
|
|
528
|
+
const _component_UButton = resolveComponent("UButton");
|
|
529
|
+
return openBlock(), createElementBlock("div", { class: normalizeClass(ui.value.base({ class: [props.ui?.base, props.class] })) }, [createElementVNode("p", { class: normalizeClass(ui.value.prose({ class: props.ui?.prose })) }, toDisplayString(unref(t)("LoginRequired.text")), 3), createElementVNode("div", { class: normalizeClass(ui.value.actions({ class: props.ui?.actions })) }, [createVNode(_component_UButton, {
|
|
530
|
+
color: "neutral",
|
|
531
|
+
variant: "solid",
|
|
532
|
+
label: unref(t)("LoginRequired.action"),
|
|
533
|
+
loading: unref(isPending),
|
|
534
|
+
disabled: unref(isPending),
|
|
535
|
+
onClick: login
|
|
536
|
+
}, null, 8, [
|
|
537
|
+
"label",
|
|
538
|
+
"loading",
|
|
539
|
+
"disabled"
|
|
540
|
+
])], 2)], 2);
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
});
|
|
544
|
+
//#endregion
|
|
545
|
+
//#region ~icons/ph/info-duotone
|
|
546
|
+
const _hoisted_1$21 = {
|
|
547
|
+
viewBox: "0 0 256 256",
|
|
548
|
+
width: "1.2em",
|
|
549
|
+
height: "1.2em"
|
|
550
|
+
};
|
|
551
|
+
function render$12(_ctx, _cache) {
|
|
552
|
+
return openBlock(), createElementBlock("svg", _hoisted_1$21, [..._cache[0] || (_cache[0] = [createElementVNode("g", { fill: "currentColor" }, [createElementVNode("path", {
|
|
553
|
+
d: "M224 128a96 96 0 1 1-96-96a96 96 0 0 1 96 96",
|
|
554
|
+
opacity: ".2"
|
|
555
|
+
}), createElementVNode("path", { d: "M144 176a8 8 0 0 1-8 8a16 16 0 0 1-16-16v-40a8 8 0 0 1 0-16a16 16 0 0 1 16 16v40a8 8 0 0 1 8 8m88-48A104 104 0 1 1 128 24a104.11 104.11 0 0 1 104 104m-16 0a88 88 0 1 0-88 88a88.1 88.1 0 0 0 88-88m-92-32a12 12 0 1 0-12-12a12 12 0 0 0 12 12" })], -1)])]);
|
|
556
|
+
}
|
|
557
|
+
var info_duotone_default = markRaw({
|
|
558
|
+
name: "ph-info-duotone",
|
|
559
|
+
render: render$12
|
|
560
|
+
});
|
|
561
|
+
//#endregion
|
|
562
|
+
//#region src/components/Editor.vue
|
|
563
|
+
const _hoisted_1$20 = ["innerHTML"];
|
|
564
|
+
const editor = tv({ slots: {
|
|
565
|
+
root: "overflow-hidden border border-muted rounded-md",
|
|
566
|
+
tabs: "mb-[-1px] ml-[-1px] mt-[-1px] bg-muted text-sm space-x-1",
|
|
567
|
+
tab: "relative border-x rounded-t-md px-4 py-2 text-sm text-dimmed data-[state=active]:border-t data-[state=active]:border-muted data-[state=inactive]:border-transparent data-[state=active]:bg-default data-[state=active]:text-default hover:text-default focus-visible:outline-2 focus-visible:outline-offset-[-6px] focus-visible:ring-inverted",
|
|
568
|
+
content: "data-[state=active]:p-2 flex flex-col gap-1 border-t border-muted bg-default",
|
|
569
|
+
preview: "",
|
|
570
|
+
prose: "w-full rounded-md p-2 text-sm"
|
|
571
|
+
} });
|
|
572
|
+
const _sfc_main$34 = /* @__PURE__ */ defineComponent({
|
|
573
|
+
__name: "Editor",
|
|
574
|
+
props: /* @__PURE__ */ mergeModels({
|
|
575
|
+
class: {},
|
|
576
|
+
textarea: {},
|
|
577
|
+
error: {},
|
|
578
|
+
ui: {}
|
|
579
|
+
}, {
|
|
580
|
+
"content": {
|
|
581
|
+
default: "",
|
|
582
|
+
required: true
|
|
583
|
+
},
|
|
584
|
+
"contentModifiers": {}
|
|
585
|
+
}),
|
|
586
|
+
emits: ["update:content"],
|
|
587
|
+
setup(__props, { expose: __expose }) {
|
|
588
|
+
const props = __props;
|
|
589
|
+
const content = useModel(__props, "content");
|
|
590
|
+
const { code, t } = useLocale();
|
|
591
|
+
const { data: preview, isLoading: isPreviewLoading, refresh: refreshPreview } = useQuery({
|
|
592
|
+
enabled: false,
|
|
593
|
+
staleTime: 1e3 * 60 * 5,
|
|
594
|
+
key: () => [
|
|
595
|
+
"preview",
|
|
596
|
+
code.value,
|
|
597
|
+
content.value
|
|
598
|
+
],
|
|
599
|
+
query: async () => {
|
|
600
|
+
if (!content.value) return Promise.resolve(`<p>${t("Editor.preview.placeholder")}</p>`);
|
|
601
|
+
return getMarkdown(content.value, code.value === "fr" ? "fr" : "en").then((response) => response.data);
|
|
602
|
+
}
|
|
603
|
+
});
|
|
604
|
+
function onFetchPreview() {
|
|
605
|
+
refreshPreview();
|
|
606
|
+
}
|
|
607
|
+
function onUpdateModelValue(value) {
|
|
608
|
+
if (value === "write") setTimeout(() => {
|
|
609
|
+
focus();
|
|
610
|
+
}, 0);
|
|
611
|
+
}
|
|
612
|
+
const textarea = useTemplateRef("textarea");
|
|
613
|
+
function focus() {
|
|
614
|
+
nextTick(() => {
|
|
615
|
+
textarea.value?.textareaRef?.focus();
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
__expose({ focus });
|
|
619
|
+
const ui = computed(() => editor());
|
|
620
|
+
return (_ctx, _cache) => {
|
|
621
|
+
const _component_UTextarea = resolveComponent("UTextarea");
|
|
622
|
+
const _component_UIcon = resolveComponent("UIcon");
|
|
623
|
+
const _component_UFormField = resolveComponent("UFormField");
|
|
624
|
+
return openBlock(), createBlock(unref(TabsRoot), {
|
|
625
|
+
"default-value": "write",
|
|
626
|
+
class: normalizeClass(ui.value.root({ class: [props.ui?.root, props.class] })),
|
|
627
|
+
"onUpdate:modelValue": onUpdateModelValue
|
|
628
|
+
}, {
|
|
629
|
+
default: withCtx(() => [
|
|
630
|
+
createVNode(unref(TabsList), { class: normalizeClass(ui.value.tabs({ class: props.ui?.tabs })) }, {
|
|
631
|
+
default: withCtx(() => [createVNode(unref(TabsTrigger), {
|
|
632
|
+
value: "write",
|
|
633
|
+
class: normalizeClass(ui.value.tab({ class: props.ui?.tab }))
|
|
634
|
+
}, {
|
|
635
|
+
default: withCtx(() => [createTextVNode(toDisplayString(unref(t)("Editor.tabs.write")), 1)]),
|
|
636
|
+
_: 1
|
|
637
|
+
}, 8, ["class"]), createVNode(unref(TabsTrigger), {
|
|
638
|
+
value: "preview",
|
|
639
|
+
class: normalizeClass(ui.value.tab({ class: props.ui?.tab })),
|
|
640
|
+
onMouseenter: onFetchPreview,
|
|
641
|
+
onFocus: onFetchPreview
|
|
642
|
+
}, {
|
|
643
|
+
default: withCtx(() => [createTextVNode(toDisplayString(unref(t)("Editor.tabs.preview")), 1)]),
|
|
644
|
+
_: 1
|
|
645
|
+
}, 8, ["class"])]),
|
|
646
|
+
_: 1
|
|
647
|
+
}, 8, ["class"]),
|
|
648
|
+
createVNode(unref(TabsContent), {
|
|
649
|
+
value: "write",
|
|
650
|
+
class: normalizeClass(ui.value.content({ class: props.ui?.content })),
|
|
651
|
+
tabindex: "-1"
|
|
652
|
+
}, {
|
|
653
|
+
default: withCtx(() => [createVNode(_component_UFormField, {
|
|
654
|
+
ui: { help: "flex items-center gap-1" },
|
|
655
|
+
error: props.error,
|
|
656
|
+
help: unref(t)("Editor.write.help")
|
|
657
|
+
}, {
|
|
658
|
+
help: withCtx(({ help }) => [createVNode(_component_UIcon, {
|
|
659
|
+
name: unref(info_duotone_default),
|
|
660
|
+
class: "inline-block size-4"
|
|
661
|
+
}, null, 8, ["name"]), createElementVNode("span", null, toDisplayString(help), 1)]),
|
|
662
|
+
default: withCtx(() => [createVNode(_component_UTextarea, mergeProps({
|
|
663
|
+
ref_key: "textarea",
|
|
664
|
+
ref: textarea,
|
|
665
|
+
modelValue: content.value,
|
|
666
|
+
"onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => content.value = $event),
|
|
667
|
+
variant: "none",
|
|
668
|
+
ui: {
|
|
669
|
+
root: "flex",
|
|
670
|
+
base: "w-full resize-none"
|
|
671
|
+
},
|
|
672
|
+
rows: 4,
|
|
673
|
+
placeholder: unref(t)("Editor.placeholder")
|
|
674
|
+
}, props.textarea), null, 16, ["modelValue", "placeholder"])]),
|
|
675
|
+
_: 1
|
|
676
|
+
}, 8, ["error", "help"])]),
|
|
677
|
+
_: 1
|
|
678
|
+
}, 8, ["class"]),
|
|
679
|
+
createVNode(unref(TabsContent), {
|
|
680
|
+
value: "preview",
|
|
681
|
+
class: normalizeClass(ui.value.content({ class: props.ui?.content })),
|
|
682
|
+
tabindex: "-1"
|
|
683
|
+
}, {
|
|
684
|
+
default: withCtx(() => [unref(isPreviewLoading) ? (openBlock(), createElementBlock("div", {
|
|
685
|
+
key: 0,
|
|
686
|
+
class: normalizeClass(ui.value.prose({ class: [unref(prose), props.ui?.prose] }))
|
|
687
|
+
}, [createElementVNode("p", null, toDisplayString(unref(t)("Editor.preview.loading")), 1)], 2)) : (openBlock(), createElementBlock("div", {
|
|
688
|
+
key: 1,
|
|
689
|
+
class: normalizeClass(ui.value.prose({ class: [unref(prose), props.ui?.prose] })),
|
|
690
|
+
innerHTML: unref(preview)
|
|
691
|
+
}, null, 10, _hoisted_1$20))]),
|
|
692
|
+
_: 1
|
|
693
|
+
}, 8, ["class"])
|
|
694
|
+
]),
|
|
695
|
+
_: 1
|
|
696
|
+
}, 8, ["class"]);
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
});
|
|
700
|
+
//#endregion
|
|
701
|
+
//#region src/components/Form.vue
|
|
702
|
+
const form = tv({ slots: {
|
|
703
|
+
base: "space-y-4",
|
|
704
|
+
wrapper: "space-y-4",
|
|
705
|
+
actions: "flex items-center justify-end gap-2"
|
|
706
|
+
} });
|
|
707
|
+
const _sfc_main$33 = /* @__PURE__ */ defineComponent({
|
|
708
|
+
__name: "Form",
|
|
709
|
+
props: {
|
|
710
|
+
class: {},
|
|
711
|
+
ui: {}
|
|
712
|
+
},
|
|
713
|
+
emits: ["submit"],
|
|
714
|
+
setup(__props, { emit: __emit }) {
|
|
715
|
+
const props = __props;
|
|
716
|
+
const emit = __emit;
|
|
717
|
+
function onSubmit(event) {
|
|
718
|
+
emit("submit", event);
|
|
719
|
+
}
|
|
720
|
+
const ui = computed(() => form());
|
|
721
|
+
return (_ctx, _cache) => {
|
|
722
|
+
return openBlock(), createElementBlock("form", {
|
|
723
|
+
class: normalizeClass(ui.value.base({ class: [props.ui?.base, props.class] })),
|
|
724
|
+
onSubmit: withModifiers(onSubmit, ["prevent"])
|
|
725
|
+
}, [createElementVNode("div", { class: normalizeClass(ui.value.wrapper({ class: props.ui?.wrapper })) }, [renderSlot(_ctx.$slots, "default")], 2), createElementVNode("div", { class: normalizeClass(ui.value.actions({ class: props.ui?.actions })) }, [renderSlot(_ctx.$slots, "actions")], 2)], 34);
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
});
|
|
729
|
+
//#endregion
|
|
730
|
+
//#region src/components/Comments/CommentForm.vue
|
|
731
|
+
const commentForm = tv({ base: "space-y-2" });
|
|
732
|
+
const _sfc_main$32 = /* @__PURE__ */ defineComponent({
|
|
733
|
+
__name: "CommentForm",
|
|
734
|
+
props: {
|
|
735
|
+
cancelable: { type: Boolean },
|
|
736
|
+
parentComment: {},
|
|
737
|
+
comment: {},
|
|
738
|
+
class: {}
|
|
739
|
+
},
|
|
740
|
+
emits: ["cancel", "success"],
|
|
741
|
+
setup(__props, { expose: __expose, emit: __emit }) {
|
|
742
|
+
const props = __props;
|
|
743
|
+
const emits = __emit;
|
|
744
|
+
const formData = ref({ content: props.comment?.content || "" });
|
|
745
|
+
const { t } = useLocale();
|
|
746
|
+
const { locale, pageId } = useCommentsContext();
|
|
747
|
+
const contentError = ref();
|
|
748
|
+
const queryCache = useQueryCache();
|
|
749
|
+
const { mutate: createComment, isLoading: isCreateCommentLoading } = useMutation({
|
|
750
|
+
mutation: ({ pageId, content, locale, parentCommentId }) => postComment(pageId, content, locale, parentCommentId),
|
|
751
|
+
onError: (error) => {
|
|
752
|
+
if (isUnprocessableEntityError(error)) contentError.value = error.data.message;
|
|
753
|
+
else contentError.value = t("comments.errors.create");
|
|
754
|
+
},
|
|
755
|
+
onSuccess: () => {
|
|
756
|
+
clearFormData();
|
|
757
|
+
emits("success");
|
|
758
|
+
},
|
|
759
|
+
onSettled: () => {
|
|
760
|
+
queryCache.invalidateQueries({ key: COMMENT_QUERY_KEYS.byPageId(pageId.value, locale.value) });
|
|
761
|
+
}
|
|
762
|
+
});
|
|
763
|
+
const { mutate: updateComment, isLoading: isUpdateCommentLoading } = useMutation({
|
|
764
|
+
mutation: ({ commentId, content, locale }) => putComment(commentId, content, locale),
|
|
765
|
+
onError: (error) => {
|
|
766
|
+
if (isUnprocessableEntityError(error)) contentError.value = error.data.message;
|
|
767
|
+
else contentError.value = t("comments.errors.update");
|
|
768
|
+
},
|
|
769
|
+
onSuccess: () => {
|
|
770
|
+
clearFormData();
|
|
771
|
+
emits("success");
|
|
772
|
+
},
|
|
773
|
+
onSettled: () => {
|
|
774
|
+
queryCache.invalidateQueries({ key: COMMENT_QUERY_KEYS.byPageId(pageId.value, locale.value) });
|
|
775
|
+
}
|
|
776
|
+
});
|
|
777
|
+
const isLoading = computed(() => isCreateCommentLoading.value || isUpdateCommentLoading.value);
|
|
778
|
+
async function onSubmit() {
|
|
779
|
+
if (isLoading.value || !formData.value.content) return;
|
|
780
|
+
contentError.value = void 0;
|
|
781
|
+
if (props.comment) updateComment({
|
|
782
|
+
locale: locale.value,
|
|
783
|
+
commentId: props.comment.id,
|
|
784
|
+
content: formData.value.content
|
|
785
|
+
});
|
|
786
|
+
else createComment({
|
|
787
|
+
pageId: pageId.value,
|
|
788
|
+
locale: locale.value,
|
|
789
|
+
content: formData.value.content,
|
|
790
|
+
parentCommentId: props.parentComment?.id
|
|
791
|
+
});
|
|
792
|
+
}
|
|
793
|
+
function onEditorKeydown(event) {
|
|
794
|
+
if (event.key !== "Enter" || !event.ctrlKey && !event.metaKey) return;
|
|
795
|
+
event.preventDefault();
|
|
796
|
+
onSubmit();
|
|
797
|
+
}
|
|
798
|
+
function clearFormData() {
|
|
799
|
+
formData.value = { content: "" };
|
|
800
|
+
}
|
|
801
|
+
function onCancel() {
|
|
802
|
+
if (!isLoading.value) emits("cancel");
|
|
803
|
+
}
|
|
804
|
+
const editor = useTemplateRef("editor");
|
|
805
|
+
function focus() {
|
|
806
|
+
editor.value?.focus();
|
|
807
|
+
}
|
|
808
|
+
__expose({ focus });
|
|
809
|
+
const ui = computed(() => commentForm({ class: props.class }));
|
|
810
|
+
return (_ctx, _cache) => {
|
|
811
|
+
const _component_UButton = resolveComponent("UButton");
|
|
812
|
+
return openBlock(), createBlock(_sfc_main$33, {
|
|
813
|
+
class: normalizeClass(ui.value),
|
|
814
|
+
onSubmit
|
|
815
|
+
}, {
|
|
816
|
+
actions: withCtx(() => [props.cancelable ? (openBlock(), createBlock(_component_UButton, {
|
|
817
|
+
key: 0,
|
|
818
|
+
variant: "link",
|
|
819
|
+
label: unref(t)("comments.CommentForm.actions.cancel"),
|
|
820
|
+
disabled: isLoading.value,
|
|
821
|
+
onClick: onCancel
|
|
822
|
+
}, null, 8, ["label", "disabled"])) : createCommentVNode("v-if", true), createVNode(_component_UButton, {
|
|
823
|
+
disabled: !formData.value.content,
|
|
824
|
+
loading: isLoading.value,
|
|
825
|
+
type: "submit",
|
|
826
|
+
label: props.comment ? unref(t)("comments.CommentForm.actions.edit") : props.parentComment ? unref(t)("comments.CommentForm.actions.reply") : unref(t)("comments.CommentForm.actions.add")
|
|
827
|
+
}, null, 8, [
|
|
828
|
+
"disabled",
|
|
829
|
+
"loading",
|
|
830
|
+
"label"
|
|
831
|
+
])]),
|
|
832
|
+
default: withCtx(() => [createVNode(_sfc_main$34, {
|
|
833
|
+
ref_key: "editor",
|
|
834
|
+
ref: editor,
|
|
835
|
+
content: formData.value.content,
|
|
836
|
+
"onUpdate:content": _cache[0] || (_cache[0] = ($event) => formData.value.content = $event),
|
|
837
|
+
error: contentError.value,
|
|
838
|
+
onKeydown: onEditorKeydown
|
|
839
|
+
}, null, 8, ["content", "error"])]),
|
|
840
|
+
_: 1
|
|
841
|
+
}, 8, ["class"]);
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
});
|
|
845
|
+
//#endregion
|
|
846
|
+
//#region src/components/Comments/CommentFormSection.vue
|
|
847
|
+
const commentFormSection = tv({ slots: {
|
|
848
|
+
base: "space-y-2",
|
|
849
|
+
header: "flex items-center gap-2",
|
|
850
|
+
title: "font-medium text-default"
|
|
851
|
+
} });
|
|
852
|
+
const _sfc_main$31 = /* @__PURE__ */ defineComponent({
|
|
853
|
+
__name: "CommentFormSection",
|
|
854
|
+
props: {
|
|
855
|
+
class: {},
|
|
856
|
+
ui: {}
|
|
857
|
+
},
|
|
858
|
+
setup(__props) {
|
|
859
|
+
const props = __props;
|
|
860
|
+
const { t } = useLocale();
|
|
861
|
+
const { data: user } = useQuery(currentUserQuery);
|
|
862
|
+
const ui = computed(() => commentFormSection());
|
|
863
|
+
return (_ctx, _cache) => {
|
|
864
|
+
const _component_UAvatar = resolveComponent("UAvatar");
|
|
865
|
+
return openBlock(), createElementBlock("section", { class: normalizeClass(ui.value.base({ class: [props.ui?.base, props.class] })) }, [createElementVNode("div", { class: normalizeClass(ui.value.header({ class: props.ui?.header })) }, [unref(user) ? (openBlock(), createBlock(_component_UAvatar, {
|
|
866
|
+
key: 0,
|
|
867
|
+
src: unref(user).avatar,
|
|
868
|
+
title: unref(user).name
|
|
869
|
+
}, null, 8, ["src", "title"])) : createCommentVNode("v-if", true), createElementVNode("h3", { class: normalizeClass(ui.value.title({ class: props.ui?.title })) }, toDisplayString(unref(t)("comments.CommentFormSection.title")), 3)], 2), !unref(user) ? (openBlock(), createBlock(_sfc_main$35, { key: 0 })) : (openBlock(), createBlock(_sfc_main$32, { key: 1 }))], 2);
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
});
|
|
873
|
+
//#endregion
|
|
874
|
+
//#region src/components/State/StateEmpty.vue
|
|
875
|
+
const stateEmpty = tv({ slots: { base: "" } });
|
|
876
|
+
const _sfc_main$30 = /* @__PURE__ */ defineComponent({
|
|
877
|
+
__name: "StateEmpty",
|
|
878
|
+
props: {
|
|
879
|
+
class: {},
|
|
880
|
+
ui: {}
|
|
881
|
+
},
|
|
882
|
+
setup(__props) {
|
|
883
|
+
const props = __props;
|
|
884
|
+
const { t } = useLocale();
|
|
885
|
+
const ui = computed(() => stateEmpty());
|
|
886
|
+
return (_ctx, _cache) => {
|
|
887
|
+
return openBlock(), createElementBlock("p", { class: normalizeClass(ui.value.base({ class: [props.ui?.base, props.class] })) }, toDisplayString(unref(t)("state.empty")), 3);
|
|
888
|
+
};
|
|
889
|
+
}
|
|
890
|
+
});
|
|
891
|
+
//#endregion
|
|
892
|
+
//#region src/components/State/StateError.vue
|
|
893
|
+
const stateError = tv({ slots: { base: "" } });
|
|
894
|
+
const _sfc_main$29 = /* @__PURE__ */ defineComponent({
|
|
895
|
+
__name: "StateError",
|
|
896
|
+
props: {
|
|
897
|
+
class: {},
|
|
898
|
+
ui: {}
|
|
899
|
+
},
|
|
900
|
+
setup(__props) {
|
|
901
|
+
const props = __props;
|
|
902
|
+
const { t } = useLocale();
|
|
903
|
+
const ui = computed(() => stateError());
|
|
904
|
+
return (_ctx, _cache) => {
|
|
905
|
+
return openBlock(), createElementBlock("p", { class: normalizeClass(ui.value.base({ class: [props.ui?.base, props.class] })) }, toDisplayString(unref(t)("state.error")), 3);
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
});
|
|
909
|
+
//#endregion
|
|
910
|
+
//#region src/components/State/StatePending.vue
|
|
911
|
+
const statePending = tv({ slots: { base: "" } });
|
|
912
|
+
const _sfc_main$28 = /* @__PURE__ */ defineComponent({
|
|
913
|
+
__name: "StatePending",
|
|
914
|
+
props: {
|
|
915
|
+
class: {},
|
|
916
|
+
ui: {}
|
|
917
|
+
},
|
|
918
|
+
setup(__props) {
|
|
919
|
+
const props = __props;
|
|
920
|
+
const { t } = useLocale();
|
|
921
|
+
const ui = computed(() => statePending());
|
|
922
|
+
return (_ctx, _cache) => {
|
|
923
|
+
return openBlock(), createElementBlock("p", { class: normalizeClass(ui.value.base({ class: [props.ui?.base, props.class] })) }, toDisplayString(unref(t)("state.pending")), 3);
|
|
924
|
+
};
|
|
925
|
+
}
|
|
926
|
+
});
|
|
927
|
+
//#endregion
|
|
928
|
+
//#region ~icons/ph/note-pencil-duotone
|
|
929
|
+
const _hoisted_1$19 = {
|
|
930
|
+
viewBox: "0 0 256 256",
|
|
931
|
+
width: "1.2em",
|
|
932
|
+
height: "1.2em"
|
|
933
|
+
};
|
|
934
|
+
function render$11(_ctx, _cache) {
|
|
935
|
+
return openBlock(), createElementBlock("svg", _hoisted_1$19, [..._cache[0] || (_cache[0] = [createElementVNode("g", { fill: "currentColor" }, [createElementVNode("path", {
|
|
936
|
+
d: "m200 88l-72 72H96v-32l72-72Z",
|
|
937
|
+
opacity: ".2"
|
|
938
|
+
}), createElementVNode("path", { d: "m229.66 58.34l-32-32a8 8 0 0 0-11.32 0l-96 96A8 8 0 0 0 88 128v32a8 8 0 0 0 8 8h32a8 8 0 0 0 5.66-2.34l96-96a8 8 0 0 0 0-11.32M124.69 152H104v-20.69l64-64L188.69 88ZM200 76.69L179.31 56L192 43.31L212.69 64ZM224 128v80a16 16 0 0 1-16 16H48a16 16 0 0 1-16-16V48a16 16 0 0 1 16-16h80a8 8 0 0 1 0 16H48v160h160v-80a8 8 0 0 1 16 0" })], -1)])]);
|
|
939
|
+
}
|
|
940
|
+
var note_pencil_duotone_default = markRaw({
|
|
941
|
+
name: "ph-note-pencil-duotone",
|
|
942
|
+
render: render$11
|
|
943
|
+
});
|
|
944
|
+
//#endregion
|
|
945
|
+
//#region ~icons/ph/trash-duotone
|
|
946
|
+
const _hoisted_1$18 = {
|
|
947
|
+
viewBox: "0 0 256 256",
|
|
948
|
+
width: "1.2em",
|
|
949
|
+
height: "1.2em"
|
|
950
|
+
};
|
|
951
|
+
function render$10(_ctx, _cache) {
|
|
952
|
+
return openBlock(), createElementBlock("svg", _hoisted_1$18, [..._cache[0] || (_cache[0] = [createElementVNode("g", { fill: "currentColor" }, [createElementVNode("path", {
|
|
953
|
+
d: "M200 56v152a8 8 0 0 1-8 8H64a8 8 0 0 1-8-8V56Z",
|
|
954
|
+
opacity: ".2"
|
|
955
|
+
}), createElementVNode("path", { d: "M216 48h-40v-8a24 24 0 0 0-24-24h-48a24 24 0 0 0-24 24v8H40a8 8 0 0 0 0 16h8v144a16 16 0 0 0 16 16h128a16 16 0 0 0 16-16V64h8a8 8 0 0 0 0-16M96 40a8 8 0 0 1 8-8h48a8 8 0 0 1 8 8v8H96Zm96 168H64V64h128Zm-80-104v64a8 8 0 0 1-16 0v-64a8 8 0 0 1 16 0m48 0v64a8 8 0 0 1-16 0v-64a8 8 0 0 1 16 0" })], -1)])]);
|
|
956
|
+
}
|
|
957
|
+
var trash_duotone_default = markRaw({
|
|
958
|
+
name: "ph-trash-duotone",
|
|
959
|
+
render: render$10
|
|
960
|
+
});
|
|
961
|
+
//#endregion
|
|
962
|
+
//#region src/components/ConfirmModal.vue
|
|
963
|
+
const confirmModal = tv({ slots: { base: "" } });
|
|
964
|
+
const _sfc_main$27 = /* @__PURE__ */ defineComponent({
|
|
965
|
+
__name: "ConfirmModal",
|
|
966
|
+
props: {
|
|
967
|
+
title: {},
|
|
968
|
+
description: {},
|
|
969
|
+
cancelLabel: {},
|
|
970
|
+
confirmLabel: {},
|
|
971
|
+
loading: { type: Boolean },
|
|
972
|
+
class: {},
|
|
973
|
+
ui: {}
|
|
974
|
+
},
|
|
975
|
+
emits: ["confirm", "close"],
|
|
976
|
+
setup(__props, { emit: __emit }) {
|
|
977
|
+
const props = __props;
|
|
978
|
+
const emit = __emit;
|
|
979
|
+
const { t } = useLocale();
|
|
980
|
+
function onClose() {
|
|
981
|
+
emit("close");
|
|
982
|
+
}
|
|
983
|
+
function onConfirm() {
|
|
984
|
+
emit("confirm");
|
|
985
|
+
}
|
|
986
|
+
const ui = computed(() => confirmModal());
|
|
987
|
+
return (_ctx, _cache) => {
|
|
988
|
+
const _component_UButton = resolveComponent("UButton");
|
|
989
|
+
const _component_UModal = resolveComponent("UModal");
|
|
990
|
+
return openBlock(), createBlock(_component_UModal, {
|
|
991
|
+
title: __props.title,
|
|
992
|
+
description: __props.description,
|
|
993
|
+
ui: {
|
|
994
|
+
content: "space-y-4",
|
|
995
|
+
footer: "justify-end"
|
|
996
|
+
},
|
|
997
|
+
class: normalizeClass(ui.value.base({ class: [props.ui?.base, props.class] }))
|
|
998
|
+
}, {
|
|
999
|
+
footer: withCtx(() => [createVNode(_component_UButton, {
|
|
1000
|
+
label: props.cancelLabel ?? unref(t)("ConfirmModal.actions.cancel"),
|
|
1001
|
+
variant: "ghost",
|
|
1002
|
+
color: "neutral",
|
|
1003
|
+
onClick: onClose
|
|
1004
|
+
}, null, 8, ["label"]), createVNode(_component_UButton, {
|
|
1005
|
+
label: props.confirmLabel ?? unref(t)("ConfirmModal.actions.confirm"),
|
|
1006
|
+
variant: "solid",
|
|
1007
|
+
color: "neutral",
|
|
1008
|
+
loading: props.loading,
|
|
1009
|
+
onClick: _cache[0] || (_cache[0] = ($event) => onConfirm())
|
|
1010
|
+
}, null, 8, ["label", "loading"])]),
|
|
1011
|
+
_: 1
|
|
1012
|
+
}, 8, [
|
|
1013
|
+
"title",
|
|
1014
|
+
"description",
|
|
1015
|
+
"class"
|
|
1016
|
+
]);
|
|
1017
|
+
};
|
|
1018
|
+
}
|
|
1019
|
+
});
|
|
1020
|
+
//#endregion
|
|
1021
|
+
//#region src/components/Comments/CommentDeleteConfirmModal.vue
|
|
1022
|
+
const commentDeleteConfirmModal = tv({ slots: { base: "" } });
|
|
1023
|
+
const _sfc_main$26 = /* @__PURE__ */ defineComponent({
|
|
1024
|
+
__name: "CommentDeleteConfirmModal",
|
|
1025
|
+
props: {
|
|
1026
|
+
pageId: {},
|
|
1027
|
+
commentId: {},
|
|
1028
|
+
locale: {},
|
|
1029
|
+
show: { type: Function },
|
|
1030
|
+
class: {},
|
|
1031
|
+
ui: {}
|
|
1032
|
+
},
|
|
1033
|
+
emits: ["close"],
|
|
1034
|
+
setup(__props, { emit: __emit }) {
|
|
1035
|
+
const props = __props;
|
|
1036
|
+
const emit = __emit;
|
|
1037
|
+
const { t } = useLocale();
|
|
1038
|
+
const queryCache = useQueryCache();
|
|
1039
|
+
const { mutate, isLoading } = useMutation({
|
|
1040
|
+
mutation: ({ commentId, locale }) => deleteComment(commentId, locale),
|
|
1041
|
+
onSuccess: () => {
|
|
1042
|
+
props.show({
|
|
1043
|
+
kind: "success",
|
|
1044
|
+
message: t("comments.success.delete")
|
|
1045
|
+
});
|
|
1046
|
+
},
|
|
1047
|
+
onError: () => {
|
|
1048
|
+
props.show({
|
|
1049
|
+
kind: "error",
|
|
1050
|
+
message: t("comments.errors.delete")
|
|
1051
|
+
});
|
|
1052
|
+
},
|
|
1053
|
+
onSettled: async () => {
|
|
1054
|
+
await queryCache.invalidateQueries({ key: COMMENT_QUERY_KEYS.byPageId(props.pageId, props.locale) });
|
|
1055
|
+
emit("close");
|
|
1056
|
+
}
|
|
1057
|
+
});
|
|
1058
|
+
function onClose() {
|
|
1059
|
+
emit("close");
|
|
1060
|
+
}
|
|
1061
|
+
function onConfirm() {
|
|
1062
|
+
mutate({
|
|
1063
|
+
commentId: props.commentId,
|
|
1064
|
+
locale: props.locale
|
|
1065
|
+
});
|
|
1066
|
+
}
|
|
1067
|
+
const ui = computed(() => commentDeleteConfirmModal());
|
|
1068
|
+
return (_ctx, _cache) => {
|
|
1069
|
+
return openBlock(), createBlock(_sfc_main$27, {
|
|
1070
|
+
title: unref(t)("comments.CommentDeleteConfirmModal.title"),
|
|
1071
|
+
description: unref(t)("comments.CommentDeleteConfirmModal.description"),
|
|
1072
|
+
loading: unref(isLoading),
|
|
1073
|
+
class: normalizeClass(ui.value.base({ class: [props.ui?.base, props.class] })),
|
|
1074
|
+
onClose,
|
|
1075
|
+
onConfirm
|
|
1076
|
+
}, null, 8, [
|
|
1077
|
+
"title",
|
|
1078
|
+
"description",
|
|
1079
|
+
"loading",
|
|
1080
|
+
"class"
|
|
1081
|
+
]);
|
|
1082
|
+
};
|
|
1083
|
+
}
|
|
1084
|
+
});
|
|
1085
|
+
//#endregion
|
|
1086
|
+
//#region src/composables/comments/useComments.ts
|
|
1087
|
+
function useComments() {
|
|
1088
|
+
const overlay = useOverlay();
|
|
1089
|
+
const { banner } = useCommentsContext();
|
|
1090
|
+
function confirmDeleteComment(pageId, commentId, locale) {
|
|
1091
|
+
overlay.create(_sfc_main$26, {
|
|
1092
|
+
destroyOnClose: true,
|
|
1093
|
+
props: {
|
|
1094
|
+
pageId,
|
|
1095
|
+
commentId,
|
|
1096
|
+
locale,
|
|
1097
|
+
show: banner.show
|
|
1098
|
+
}
|
|
1099
|
+
}).open();
|
|
1100
|
+
}
|
|
1101
|
+
return {
|
|
1102
|
+
confirmDeleteComment,
|
|
1103
|
+
show: banner.show
|
|
1104
|
+
};
|
|
1105
|
+
}
|
|
1106
|
+
//#endregion
|
|
1107
|
+
//#region src/components/Comments/CommentContent.vue
|
|
1108
|
+
const _hoisted_1$17 = ["innerHTML"];
|
|
1109
|
+
const commentContent = tv({ base: "" });
|
|
1110
|
+
const _sfc_main$25 = /* @__PURE__ */ defineComponent({
|
|
1111
|
+
__name: "CommentContent",
|
|
1112
|
+
props: /* @__PURE__ */ mergeModels({
|
|
1113
|
+
parentComment: {},
|
|
1114
|
+
comment: {},
|
|
1115
|
+
class: {}
|
|
1116
|
+
}, {
|
|
1117
|
+
"viewEditor": { type: Boolean },
|
|
1118
|
+
"viewEditorModifiers": {}
|
|
1119
|
+
}),
|
|
1120
|
+
emits: /* @__PURE__ */ mergeModels(["success"], ["update:viewEditor"]),
|
|
1121
|
+
setup(__props, { emit: __emit }) {
|
|
1122
|
+
const props = __props;
|
|
1123
|
+
const emit = __emit;
|
|
1124
|
+
const editor = ref(null);
|
|
1125
|
+
const viewEditor = useModel(__props, "viewEditor");
|
|
1126
|
+
watch(viewEditor, (value) => {
|
|
1127
|
+
if (value) nextTick(() => {
|
|
1128
|
+
editor.value?.focus();
|
|
1129
|
+
});
|
|
1130
|
+
});
|
|
1131
|
+
function onSuccess() {
|
|
1132
|
+
emit("success");
|
|
1133
|
+
viewEditor.value = false;
|
|
1134
|
+
}
|
|
1135
|
+
const ui = computed(() => commentContent({ class: props.class }));
|
|
1136
|
+
return (_ctx, _cache) => {
|
|
1137
|
+
return viewEditor.value ? (openBlock(), createBlock(_sfc_main$32, {
|
|
1138
|
+
key: 0,
|
|
1139
|
+
ref_key: "editor",
|
|
1140
|
+
ref: editor,
|
|
1141
|
+
cancelable: "",
|
|
1142
|
+
comment: __props.comment,
|
|
1143
|
+
"parent-comment": __props.parentComment,
|
|
1144
|
+
class: normalizeClass(ui.value),
|
|
1145
|
+
onSuccess,
|
|
1146
|
+
onCancel: _cache[0] || (_cache[0] = ($event) => viewEditor.value = false)
|
|
1147
|
+
}, null, 8, [
|
|
1148
|
+
"comment",
|
|
1149
|
+
"parent-comment",
|
|
1150
|
+
"class"
|
|
1151
|
+
])) : (openBlock(), createElementBlock("div", {
|
|
1152
|
+
key: 1,
|
|
1153
|
+
class: normalizeClass(["text-sm", unref(prose)]),
|
|
1154
|
+
innerHTML: __props.comment.content_html
|
|
1155
|
+
}, null, 10, _hoisted_1$17));
|
|
1156
|
+
};
|
|
1157
|
+
}
|
|
1158
|
+
});
|
|
1159
|
+
//#endregion
|
|
1160
|
+
//#region src/components/Comments/CommentHeader.vue
|
|
1161
|
+
const _hoisted_1$16 = { class: "sr-only" };
|
|
1162
|
+
const _hoisted_2$1 = { class: "sr-only" };
|
|
1163
|
+
const _hoisted_3 = ["datetime"];
|
|
1164
|
+
const commentHeader = tv({ slots: {
|
|
1165
|
+
root: "flex flex-col items-start text-sm",
|
|
1166
|
+
wrapper: "flex items-center gap-2",
|
|
1167
|
+
username: "text-default font-semibold tracking-wide",
|
|
1168
|
+
time: "text-dimmed",
|
|
1169
|
+
link: "text-xs"
|
|
1170
|
+
} });
|
|
1171
|
+
const _sfc_main$24 = /* @__PURE__ */ defineComponent({
|
|
1172
|
+
__name: "CommentHeader",
|
|
1173
|
+
props: {
|
|
1174
|
+
comment: {},
|
|
1175
|
+
class: {},
|
|
1176
|
+
ui: {}
|
|
1177
|
+
},
|
|
1178
|
+
setup(__props) {
|
|
1179
|
+
const props = __props;
|
|
1180
|
+
const { code, t } = useLocale();
|
|
1181
|
+
const englishTimeAgo = useTimeAgoIntl(() => props.comment.created_at, { locale: "en" });
|
|
1182
|
+
const frenchTimeAgo = useTimeAgoIntl(() => props.comment.created_at, { locale: "fr" });
|
|
1183
|
+
const timeAgo = computed(() => code.value === "fr" ? frenchTimeAgo.value : englishTimeAgo.value);
|
|
1184
|
+
const ui = computed(() => commentHeader());
|
|
1185
|
+
return (_ctx, _cache) => {
|
|
1186
|
+
return openBlock(), createElementBlock("div", { class: normalizeClass(ui.value.root({ class: [props.ui?.root, props.class] })) }, [createElementVNode("dl", { class: normalizeClass(ui.value.wrapper({ class: props.ui?.wrapper })) }, [
|
|
1187
|
+
createElementVNode("dt", _hoisted_1$16, toDisplayString(unref(t)("comments.CommentHeader.author")), 1),
|
|
1188
|
+
createElementVNode("dd", { class: normalizeClass(ui.value.username({ class: props.ui?.username })) }, toDisplayString(props.comment.user.name), 3),
|
|
1189
|
+
createElementVNode("dt", _hoisted_2$1, toDisplayString(unref(t)("comments.CommentHeader.publishedAt")), 1),
|
|
1190
|
+
createElementVNode("dd", { class: normalizeClass(ui.value.time({ class: props.ui?.time })) }, [createElementVNode("time", { datetime: new Date(props.comment.created_at).toISOString() }, toDisplayString(timeAgo.value), 9, _hoisted_3)], 2)
|
|
1191
|
+
], 2)], 2);
|
|
1192
|
+
};
|
|
1193
|
+
}
|
|
1194
|
+
});
|
|
1195
|
+
//#endregion
|
|
1196
|
+
//#region ~icons/ph/heart-duotone
|
|
1197
|
+
const _hoisted_1$15 = {
|
|
1198
|
+
viewBox: "0 0 256 256",
|
|
1199
|
+
width: "1.2em",
|
|
1200
|
+
height: "1.2em"
|
|
1201
|
+
};
|
|
1202
|
+
function render$9(_ctx, _cache) {
|
|
1203
|
+
return openBlock(), createElementBlock("svg", _hoisted_1$15, [..._cache[0] || (_cache[0] = [createElementVNode("g", { fill: "currentColor" }, [createElementVNode("path", {
|
|
1204
|
+
d: "M232 102c0 66-104 122-104 122S24 168 24 102a54 54 0 0 1 54-54c22.59 0 41.94 12.31 50 32c8.06-19.69 27.41-32 50-32a54 54 0 0 1 54 54",
|
|
1205
|
+
opacity: ".2"
|
|
1206
|
+
}), createElementVNode("path", { d: "M178 40c-20.65 0-38.73 8.88-50 23.89C116.73 48.88 98.65 40 78 40a62.07 62.07 0 0 0-62 62c0 70 103.79 126.66 108.21 129a8 8 0 0 0 7.58 0C136.21 228.66 240 172 240 102a62.07 62.07 0 0 0-62-62m-50 174.8c-18.26-10.64-96-59.11-96-112.8a46.06 46.06 0 0 1 46-46c19.45 0 35.78 10.36 42.6 27a8 8 0 0 0 14.8 0c6.82-16.67 23.15-27 42.6-27a46.06 46.06 0 0 1 46 46c0 53.61-77.76 102.15-96 112.8" })], -1)])]);
|
|
1207
|
+
}
|
|
1208
|
+
var heart_duotone_default = markRaw({
|
|
1209
|
+
name: "ph-heart-duotone",
|
|
1210
|
+
render: render$9
|
|
1211
|
+
});
|
|
1212
|
+
//#endregion
|
|
1213
|
+
//#region ~icons/ph/heart-fill
|
|
1214
|
+
const _hoisted_1$14 = {
|
|
1215
|
+
viewBox: "0 0 256 256",
|
|
1216
|
+
width: "1.2em",
|
|
1217
|
+
height: "1.2em"
|
|
1218
|
+
};
|
|
1219
|
+
function render$8(_ctx, _cache) {
|
|
1220
|
+
return openBlock(), createElementBlock("svg", _hoisted_1$14, [..._cache[0] || (_cache[0] = [createElementVNode("path", {
|
|
1221
|
+
fill: "currentColor",
|
|
1222
|
+
d: "M240 102c0 70-103.79 126.66-108.21 129a8 8 0 0 1-7.58 0C119.79 228.66 16 172 16 102a62.07 62.07 0 0 1 62-62c20.65 0 38.73 8.88 50 23.89C139.27 48.88 157.35 40 178 40a62.07 62.07 0 0 1 62 62"
|
|
1223
|
+
}, null, -1)])]);
|
|
1224
|
+
}
|
|
1225
|
+
var heart_fill_default = markRaw({
|
|
1226
|
+
name: "ph-heart-fill",
|
|
1227
|
+
render: render$8
|
|
1228
|
+
});
|
|
1229
|
+
//#endregion
|
|
1230
|
+
//#region src/components/Comments/CommentLike.vue
|
|
1231
|
+
const commentLike = tv({ slots: { base: "" } });
|
|
1232
|
+
function isToggleCommentLikeContext(context) {
|
|
1233
|
+
return typeof context === "object" && context !== null && "oldComments" in context && "newComments" in context;
|
|
1234
|
+
}
|
|
1235
|
+
function getCachedComment(comments, commentId, parentCommentId) {
|
|
1236
|
+
if (parentCommentId === void 0) return comments.find((comment) => comment.id === commentId);
|
|
1237
|
+
return comments.find((comment) => comment.id === parentCommentId)?.replies?.find((comment) => comment.id === commentId);
|
|
1238
|
+
}
|
|
1239
|
+
function replaceCachedComment(comments, commentId, nextComment, parentCommentId) {
|
|
1240
|
+
const replace = (comment) => {
|
|
1241
|
+
if (comment.id !== commentId) return comment;
|
|
1242
|
+
return nextComment;
|
|
1243
|
+
};
|
|
1244
|
+
if (parentCommentId === void 0) return comments.map(replace);
|
|
1245
|
+
return comments.map((comment) => {
|
|
1246
|
+
if (comment.id !== parentCommentId || !comment.replies) return comment;
|
|
1247
|
+
const replies = comment.replies.map(replace);
|
|
1248
|
+
return replies.every((reply, index) => reply === comment.replies?.[index]) ? comment : {
|
|
1249
|
+
...comment,
|
|
1250
|
+
replies
|
|
1251
|
+
};
|
|
1252
|
+
});
|
|
1253
|
+
}
|
|
1254
|
+
const _sfc_main$23 = /* @__PURE__ */ defineComponent({
|
|
1255
|
+
__name: "CommentLike",
|
|
1256
|
+
props: {
|
|
1257
|
+
parentComment: {},
|
|
1258
|
+
comment: {},
|
|
1259
|
+
class: {},
|
|
1260
|
+
ui: {}
|
|
1261
|
+
},
|
|
1262
|
+
setup(__props) {
|
|
1263
|
+
const props = __props;
|
|
1264
|
+
const { t } = useLocale();
|
|
1265
|
+
const toast = useToast();
|
|
1266
|
+
const queryCache = useQueryCache();
|
|
1267
|
+
const { banner, locale, pageId } = useCommentsContext();
|
|
1268
|
+
const { error: loginError, openLoginWindow } = useLogin();
|
|
1269
|
+
const { data: user } = useQuery(currentUserQuery);
|
|
1270
|
+
const { mutate: toggleLike } = useMutation({
|
|
1271
|
+
onMutate(variables) {
|
|
1272
|
+
const key = COMMENT_QUERY_KEYS.byPageId(variables.pageId, variables.locale);
|
|
1273
|
+
const oldComments = queryCache.getQueryData(key);
|
|
1274
|
+
const oldComment = getCachedComment(oldComments, variables.commentId, variables.parentCommentId);
|
|
1275
|
+
const newComment = {
|
|
1276
|
+
...oldComment,
|
|
1277
|
+
likes: Math.max(0, oldComment.likes + (variables.shouldLike ? 1 : -1)),
|
|
1278
|
+
can: {
|
|
1279
|
+
...oldComment.can,
|
|
1280
|
+
like: !variables.shouldLike,
|
|
1281
|
+
unlike: variables.shouldLike
|
|
1282
|
+
}
|
|
1283
|
+
};
|
|
1284
|
+
const newComments = replaceCachedComment(oldComments, variables.commentId, newComment, variables.parentCommentId);
|
|
1285
|
+
queryCache.setQueryData(key, newComments);
|
|
1286
|
+
queryCache.cancelQueries({
|
|
1287
|
+
key,
|
|
1288
|
+
exact: true
|
|
1289
|
+
});
|
|
1290
|
+
return {
|
|
1291
|
+
oldComments,
|
|
1292
|
+
newComments
|
|
1293
|
+
};
|
|
1294
|
+
},
|
|
1295
|
+
mutation: (variables) => variables.shouldLike ? postCommentLike(variables.commentId, variables.locale) : deleteCommentLike(variables.commentId, variables.locale),
|
|
1296
|
+
onError(_error, variables, context) {
|
|
1297
|
+
const key = COMMENT_QUERY_KEYS.byPageId(variables.pageId, variables.locale);
|
|
1298
|
+
if (isToggleCommentLikeContext(context) && context.newComments === queryCache.getQueryData(key)) queryCache.setQueryData(key, context.oldComments);
|
|
1299
|
+
banner.show({
|
|
1300
|
+
kind: "error",
|
|
1301
|
+
message: t(variables.shouldLike ? "comments.errors.like" : "comments.errors.unlike")
|
|
1302
|
+
});
|
|
1303
|
+
},
|
|
1304
|
+
onSettled(_data, _error, variables) {
|
|
1305
|
+
queryCache.invalidateQueries({
|
|
1306
|
+
key: COMMENT_QUERY_KEYS.byPageId(variables.pageId, variables.locale),
|
|
1307
|
+
exact: true
|
|
1308
|
+
});
|
|
1309
|
+
}
|
|
1310
|
+
});
|
|
1311
|
+
async function onClick() {
|
|
1312
|
+
if (!user.value) {
|
|
1313
|
+
if (!await openLoginWindow() && loginError.value) toast.add({
|
|
1314
|
+
color: "error",
|
|
1315
|
+
description: t(getLoginErrorMessageKey(loginError.value))
|
|
1316
|
+
});
|
|
1317
|
+
return;
|
|
1318
|
+
}
|
|
1319
|
+
if (!props.comment.can.like && !props.comment.can.unlike) return;
|
|
1320
|
+
toggleLike({
|
|
1321
|
+
pageId: pageId.value,
|
|
1322
|
+
locale: locale.value,
|
|
1323
|
+
commentId: props.comment.id,
|
|
1324
|
+
parentCommentId: props.parentComment?.id,
|
|
1325
|
+
shouldLike: props.comment.can.like
|
|
1326
|
+
});
|
|
1327
|
+
}
|
|
1328
|
+
const ui = computed(() => commentLike());
|
|
1329
|
+
return (_ctx, _cache) => {
|
|
1330
|
+
const _component_UButton = resolveComponent("UButton");
|
|
1331
|
+
return openBlock(), createBlock(_component_UButton, {
|
|
1332
|
+
variant: "link",
|
|
1333
|
+
color: "neutral",
|
|
1334
|
+
title: unref(t)(props.comment.can.unlike ? "comments.CommentUnlike.title" : "comments.CommentLike.title"),
|
|
1335
|
+
label: props.comment.likes.toString(),
|
|
1336
|
+
icon: props.comment.can.unlike ? unref(heart_fill_default) : unref(heart_duotone_default),
|
|
1337
|
+
class: normalizeClass(ui.value.base({ class: [props.ui?.base, props.class] })),
|
|
1338
|
+
onClick
|
|
1339
|
+
}, null, 8, [
|
|
1340
|
+
"title",
|
|
1341
|
+
"label",
|
|
1342
|
+
"icon",
|
|
1343
|
+
"class"
|
|
1344
|
+
]);
|
|
1345
|
+
};
|
|
1346
|
+
}
|
|
1347
|
+
});
|
|
1348
|
+
//#endregion
|
|
1349
|
+
//#region src/components/Comments/CommentRepliesCount.vue
|
|
1350
|
+
const commentRepliesCount = tv({ base: "text-xs text-dimmed" });
|
|
1351
|
+
const _sfc_main$22 = /* @__PURE__ */ defineComponent({
|
|
1352
|
+
__name: "CommentRepliesCount",
|
|
1353
|
+
props: {
|
|
1354
|
+
comment: {},
|
|
1355
|
+
class: {}
|
|
1356
|
+
},
|
|
1357
|
+
setup(__props) {
|
|
1358
|
+
const props = __props;
|
|
1359
|
+
const { t } = useLocale();
|
|
1360
|
+
const replies = computed(() => {
|
|
1361
|
+
if (props.comment.replies && props.comment.replies?.length > 1) return t("comments.CommentRepliesCount.replies", { count: props.comment.replies.length });
|
|
1362
|
+
return t("comments.CommentRepliesCount.reply", { count: props.comment.replies?.length ?? 0 });
|
|
1363
|
+
});
|
|
1364
|
+
const ui = computed(() => commentRepliesCount({ class: props.class }));
|
|
1365
|
+
return (_ctx, _cache) => {
|
|
1366
|
+
return props.comment.replies?.length ? (openBlock(), createElementBlock("span", {
|
|
1367
|
+
key: 0,
|
|
1368
|
+
class: normalizeClass(ui.value)
|
|
1369
|
+
}, toDisplayString(replies.value), 3)) : createCommentVNode("v-if", true);
|
|
1370
|
+
};
|
|
1371
|
+
}
|
|
1372
|
+
});
|
|
1373
|
+
//#endregion
|
|
1374
|
+
//#region src/components/Comments/Comment.vue
|
|
1375
|
+
const _hoisted_1$13 = ["id"];
|
|
1376
|
+
const comment = tv({
|
|
1377
|
+
slots: {
|
|
1378
|
+
base: "comment relative before:content-['']",
|
|
1379
|
+
wrapper: "flex items-start justify-between",
|
|
1380
|
+
header: "flex items-center gap-2",
|
|
1381
|
+
headerAvatar: "",
|
|
1382
|
+
contentWrapper: "",
|
|
1383
|
+
contentFooter: "flex items-center justify-between",
|
|
1384
|
+
like: "-mx-3 -my-1.5"
|
|
1385
|
+
},
|
|
1386
|
+
variants: { isReply: {
|
|
1387
|
+
true: {
|
|
1388
|
+
base: "space-y-2 px-4 pb-2 pt-4 before:absolute before:bottom-0 before:left-[calc((var(--avatar-size)/2)-1px+1rem)] before:top-0 before:w-[2px] before:bg-border",
|
|
1389
|
+
headerAvatar: "size-[var(--avatar-size)]",
|
|
1390
|
+
contentWrapper: "ml-[calc(var(--avatar-size)+0.5rem)] space-y-2"
|
|
1391
|
+
},
|
|
1392
|
+
false: {
|
|
1393
|
+
base: "p-4 space-y-4",
|
|
1394
|
+
contentWrapper: "space-y-4"
|
|
1395
|
+
}
|
|
1396
|
+
} }
|
|
1397
|
+
});
|
|
1398
|
+
const _sfc_main$21 = /* @__PURE__ */ defineComponent({
|
|
1399
|
+
__name: "Comment",
|
|
1400
|
+
props: {
|
|
1401
|
+
parentComment: {},
|
|
1402
|
+
comment: {},
|
|
1403
|
+
class: {},
|
|
1404
|
+
ui: {}
|
|
1405
|
+
},
|
|
1406
|
+
setup(__props) {
|
|
1407
|
+
const props = __props;
|
|
1408
|
+
const { t } = useLocale();
|
|
1409
|
+
const { pageId, locale } = useCommentsContext();
|
|
1410
|
+
const { confirmDeleteComment } = useComments();
|
|
1411
|
+
const { data: user } = useQuery(currentUserQuery);
|
|
1412
|
+
const viewEditCommentEditor = ref(false);
|
|
1413
|
+
function onCommentEdited() {
|
|
1414
|
+
viewEditCommentEditor.value = false;
|
|
1415
|
+
}
|
|
1416
|
+
const showActions = computed(() => user.value && (props.comment.can.update || props.comment.can.delete));
|
|
1417
|
+
const actions = computed(() => {
|
|
1418
|
+
const items = [];
|
|
1419
|
+
if (props.comment.can.update) items.push({
|
|
1420
|
+
icon: note_pencil_duotone_default,
|
|
1421
|
+
label: t("comments.Comment.actions.edit"),
|
|
1422
|
+
onSelect: () => {
|
|
1423
|
+
viewEditCommentEditor.value = true;
|
|
1424
|
+
}
|
|
1425
|
+
});
|
|
1426
|
+
if (props.comment.can.delete) items.push({
|
|
1427
|
+
icon: trash_duotone_default,
|
|
1428
|
+
label: t("comments.Comment.actions.delete"),
|
|
1429
|
+
onSelect: () => confirmDeleteComment(pageId.value, props.comment.id, locale.value)
|
|
1430
|
+
});
|
|
1431
|
+
return items;
|
|
1432
|
+
});
|
|
1433
|
+
const ui = computed(() => comment({ isReply: !!props.parentComment }));
|
|
1434
|
+
return (_ctx, _cache) => {
|
|
1435
|
+
const _component_UAvatar = resolveComponent("UAvatar");
|
|
1436
|
+
const _component_UButton = resolveComponent("UButton");
|
|
1437
|
+
const _component_UDropdownMenu = resolveComponent("UDropdownMenu");
|
|
1438
|
+
return openBlock(), createElementBlock("article", {
|
|
1439
|
+
id: props.comment.html_id,
|
|
1440
|
+
class: normalizeClass(ui.value.base({ class: [props.ui?.base, props.class] })),
|
|
1441
|
+
style: { "--avatar-size": "1.875rem" }
|
|
1442
|
+
}, [createElementVNode("div", { class: normalizeClass(ui.value.wrapper({ class: props.ui?.wrapper })) }, [createElementVNode("div", { class: normalizeClass(ui.value.header({ class: props.ui?.header })) }, [createVNode(_component_UAvatar, {
|
|
1443
|
+
src: props.comment.user.avatar,
|
|
1444
|
+
alt: props.comment.user.name,
|
|
1445
|
+
size: props.parentComment ? "sm" : "md",
|
|
1446
|
+
class: normalizeClass(["z-1", ui.value.headerAvatar({ class: props.ui?.headerAvatar })])
|
|
1447
|
+
}, null, 8, [
|
|
1448
|
+
"src",
|
|
1449
|
+
"alt",
|
|
1450
|
+
"size",
|
|
1451
|
+
"class"
|
|
1452
|
+
]), createVNode(_sfc_main$24, { comment: props.comment }, null, 8, ["comment"])], 2), showActions.value ? (openBlock(), createBlock(_component_UDropdownMenu, {
|
|
1453
|
+
key: 0,
|
|
1454
|
+
items: actions.value,
|
|
1455
|
+
content: { align: "end" }
|
|
1456
|
+
}, {
|
|
1457
|
+
default: withCtx(() => [createVNode(_component_UButton, {
|
|
1458
|
+
icon: "i-ph-dots-three-bold",
|
|
1459
|
+
variant: "link",
|
|
1460
|
+
"aria-label": unref(t)("comments.Comment.actionMenu")
|
|
1461
|
+
}, null, 8, ["aria-label"])]),
|
|
1462
|
+
_: 1
|
|
1463
|
+
}, 8, ["items"])) : createCommentVNode("v-if", true)], 2), createElementVNode("div", { class: normalizeClass(ui.value.contentWrapper({ class: props.ui?.contentWrapper })) }, [createVNode(_sfc_main$25, {
|
|
1464
|
+
"view-editor": viewEditCommentEditor.value,
|
|
1465
|
+
"onUpdate:viewEditor": _cache[0] || (_cache[0] = ($event) => viewEditCommentEditor.value = $event),
|
|
1466
|
+
"parent-comment": props.parentComment,
|
|
1467
|
+
comment: props.comment,
|
|
1468
|
+
onSuccess: onCommentEdited
|
|
1469
|
+
}, null, 8, [
|
|
1470
|
+
"view-editor",
|
|
1471
|
+
"parent-comment",
|
|
1472
|
+
"comment"
|
|
1473
|
+
]), createElementVNode("div", { class: normalizeClass(ui.value.contentFooter({ class: props.ui?.contentFooter })) }, [props.comment.can.like || props.comment.can.unlike ? (openBlock(), createBlock(_sfc_main$23, {
|
|
1474
|
+
key: 0,
|
|
1475
|
+
"parent-comment": props.parentComment,
|
|
1476
|
+
comment: props.comment,
|
|
1477
|
+
class: normalizeClass(ui.value.like({ class: props.ui?.like }))
|
|
1478
|
+
}, null, 8, [
|
|
1479
|
+
"parent-comment",
|
|
1480
|
+
"comment",
|
|
1481
|
+
"class"
|
|
1482
|
+
])) : createCommentVNode("v-if", true), !props.parentComment && props.comment.replies?.length ? (openBlock(), createBlock(_sfc_main$22, {
|
|
1483
|
+
key: 1,
|
|
1484
|
+
comment: props.comment
|
|
1485
|
+
}, null, 8, ["comment"])) : createCommentVNode("v-if", true)], 2)], 2)], 10, _hoisted_1$13);
|
|
1486
|
+
};
|
|
1487
|
+
}
|
|
1488
|
+
});
|
|
1489
|
+
//#endregion
|
|
1490
|
+
//#region src/components/Discussions/DiscussionReply.vue
|
|
1491
|
+
const discussionReply = tv({ slots: { button: "justify-start text-muted" } });
|
|
1492
|
+
const _sfc_main$20 = /* @__PURE__ */ defineComponent({
|
|
1493
|
+
__name: "DiscussionReply",
|
|
1494
|
+
props: {
|
|
1495
|
+
parentComment: {},
|
|
1496
|
+
class: {},
|
|
1497
|
+
ui: {}
|
|
1498
|
+
},
|
|
1499
|
+
setup(__props) {
|
|
1500
|
+
const props = __props;
|
|
1501
|
+
const { t } = useLocale();
|
|
1502
|
+
const toast = useToast();
|
|
1503
|
+
const { data: user } = useQuery(currentUserQuery);
|
|
1504
|
+
const { error: loginError, openLoginWindow } = useLogin();
|
|
1505
|
+
const form = useTemplateRef("form");
|
|
1506
|
+
const isFormShown = ref(false);
|
|
1507
|
+
async function showForm() {
|
|
1508
|
+
if (!user.value) {
|
|
1509
|
+
if (!await openLoginWindow() && loginError.value) toast.add({
|
|
1510
|
+
color: "error",
|
|
1511
|
+
description: t(getLoginErrorMessageKey(loginError.value))
|
|
1512
|
+
});
|
|
1513
|
+
return;
|
|
1514
|
+
}
|
|
1515
|
+
isFormShown.value = true;
|
|
1516
|
+
nextTick(() => form.value?.focus());
|
|
1517
|
+
}
|
|
1518
|
+
function hideForm() {
|
|
1519
|
+
isFormShown.value = false;
|
|
1520
|
+
}
|
|
1521
|
+
function onSuccess() {
|
|
1522
|
+
isFormShown.value = false;
|
|
1523
|
+
}
|
|
1524
|
+
const ui = computed(() => discussionReply());
|
|
1525
|
+
return (_ctx, _cache) => {
|
|
1526
|
+
const _component_UButton = resolveComponent("UButton");
|
|
1527
|
+
return isFormShown.value ? (openBlock(), createBlock(_sfc_main$32, {
|
|
1528
|
+
key: 0,
|
|
1529
|
+
ref_key: "form",
|
|
1530
|
+
ref: form,
|
|
1531
|
+
cancelable: "",
|
|
1532
|
+
"parent-comment": props.parentComment,
|
|
1533
|
+
onSuccess,
|
|
1534
|
+
onCancel: hideForm
|
|
1535
|
+
}, null, 8, ["parent-comment"])) : (openBlock(), createBlock(_component_UButton, {
|
|
1536
|
+
key: 1,
|
|
1537
|
+
color: "neutral",
|
|
1538
|
+
variant: "link",
|
|
1539
|
+
block: "",
|
|
1540
|
+
class: normalizeClass(ui.value.button({ class: props.ui?.button })),
|
|
1541
|
+
onClick: showForm
|
|
1542
|
+
}, {
|
|
1543
|
+
default: withCtx(() => [createTextVNode(toDisplayString(unref(t)("discussions.DiscussionReply.reply")), 1)]),
|
|
1544
|
+
_: 1
|
|
1545
|
+
}, 8, ["class"]));
|
|
1546
|
+
};
|
|
1547
|
+
}
|
|
1548
|
+
});
|
|
1549
|
+
//#endregion
|
|
1550
|
+
//#region src/components/Discussions/Discussion.vue
|
|
1551
|
+
const discussion = tv({ slots: {
|
|
1552
|
+
base: "overflow-hidden border border-muted rounded-md",
|
|
1553
|
+
answers: "bg-muted",
|
|
1554
|
+
reply: "p-2"
|
|
1555
|
+
} });
|
|
1556
|
+
const _sfc_main$19 = /* @__PURE__ */ defineComponent({
|
|
1557
|
+
__name: "Discussion",
|
|
1558
|
+
props: {
|
|
1559
|
+
comment: {},
|
|
1560
|
+
class: {},
|
|
1561
|
+
ui: {}
|
|
1562
|
+
},
|
|
1563
|
+
setup(__props) {
|
|
1564
|
+
const props = __props;
|
|
1565
|
+
const ui = computed(() => discussion());
|
|
1566
|
+
return (_ctx, _cache) => {
|
|
1567
|
+
return openBlock(), createElementBlock("div", { class: normalizeClass(ui.value.base({ class: [props.class, props.ui?.base] })) }, [
|
|
1568
|
+
createVNode(_sfc_main$21, { comment: __props.comment }, null, 8, ["comment"]),
|
|
1569
|
+
__props.comment.replies?.length ? (openBlock(), createElementBlock("div", {
|
|
1570
|
+
key: 0,
|
|
1571
|
+
class: normalizeClass(ui.value.answers({ class: props.ui?.answers }))
|
|
1572
|
+
}, [(openBlock(true), createElementBlock(Fragment, null, renderList(__props.comment.replies, (reply) => {
|
|
1573
|
+
return openBlock(), createBlock(_sfc_main$21, {
|
|
1574
|
+
key: reply.id,
|
|
1575
|
+
"parent-comment": __props.comment,
|
|
1576
|
+
comment: reply
|
|
1577
|
+
}, null, 8, ["parent-comment", "comment"]);
|
|
1578
|
+
}), 128))], 2)) : createCommentVNode("v-if", true),
|
|
1579
|
+
createElementVNode("div", { class: normalizeClass(ui.value.reply({ class: props.ui?.reply })) }, [createVNode(_sfc_main$20, { "parent-comment": __props.comment }, null, 8, ["parent-comment"])], 2)
|
|
1580
|
+
], 2);
|
|
1581
|
+
};
|
|
1582
|
+
}
|
|
1583
|
+
});
|
|
1584
|
+
//#endregion
|
|
1585
|
+
//#region src/components/Discussions/DiscussionsList.vue
|
|
1586
|
+
const discussionsList = tv({ slots: {
|
|
1587
|
+
base: "space-y-8",
|
|
1588
|
+
discussion: ""
|
|
1589
|
+
} });
|
|
1590
|
+
const _sfc_main$18 = /* @__PURE__ */ defineComponent({
|
|
1591
|
+
__name: "DiscussionsList",
|
|
1592
|
+
props: {
|
|
1593
|
+
comments: {},
|
|
1594
|
+
class: {},
|
|
1595
|
+
ui: {}
|
|
1596
|
+
},
|
|
1597
|
+
setup(__props) {
|
|
1598
|
+
const props = __props;
|
|
1599
|
+
const ui = computed(() => discussionsList());
|
|
1600
|
+
return (_ctx, _cache) => {
|
|
1601
|
+
return openBlock(), createElementBlock("div", { class: normalizeClass(ui.value.base({ class: [props.class, props.ui?.base] })) }, [(openBlock(true), createElementBlock(Fragment, null, renderList(props.comments, (comment) => {
|
|
1602
|
+
return openBlock(), createBlock(_sfc_main$19, {
|
|
1603
|
+
key: comment.id,
|
|
1604
|
+
comment,
|
|
1605
|
+
class: normalizeClass(ui.value.discussion({ class: props.ui?.discussion }))
|
|
1606
|
+
}, null, 8, ["comment", "class"]);
|
|
1607
|
+
}), 128))], 2);
|
|
1608
|
+
};
|
|
1609
|
+
}
|
|
1610
|
+
});
|
|
1611
|
+
//#endregion
|
|
1612
|
+
//#region src/components/Subtitle.vue
|
|
1613
|
+
const subtitle = tv({ base: "text-sm text-dimmed" });
|
|
1614
|
+
const _sfc_main$17 = /* @__PURE__ */ defineComponent({
|
|
1615
|
+
__name: "Subtitle",
|
|
1616
|
+
props: {
|
|
1617
|
+
text: {},
|
|
1618
|
+
class: {}
|
|
1619
|
+
},
|
|
1620
|
+
setup(__props) {
|
|
1621
|
+
const props = __props;
|
|
1622
|
+
const ui = computed(() => subtitle({ class: props.class }));
|
|
1623
|
+
return (_ctx, _cache) => {
|
|
1624
|
+
return openBlock(), createElementBlock("p", { class: normalizeClass(ui.value) }, [renderSlot(_ctx.$slots, "default", {}, () => [createTextVNode(toDisplayString(props.text), 1)])], 2);
|
|
1625
|
+
};
|
|
1626
|
+
}
|
|
1627
|
+
});
|
|
1628
|
+
//#endregion
|
|
1629
|
+
//#region src/components/Discussions/DiscussionsSectionSubtitle.vue
|
|
1630
|
+
const discussionsSectionSubtitle = tv({ base: "" });
|
|
1631
|
+
const _sfc_main$16 = /* @__PURE__ */ defineComponent({
|
|
1632
|
+
__name: "DiscussionsSectionSubtitle",
|
|
1633
|
+
props: {
|
|
1634
|
+
comments: {},
|
|
1635
|
+
class: {}
|
|
1636
|
+
},
|
|
1637
|
+
setup(__props) {
|
|
1638
|
+
const props = __props;
|
|
1639
|
+
const { t } = useLocale();
|
|
1640
|
+
const commentsSubtitle = computed(() => {
|
|
1641
|
+
const count = props.comments.length;
|
|
1642
|
+
if (count !== 1) return t("discussions.DiscussionsSectionSubtitle.comments.plural", { count });
|
|
1643
|
+
return t("discussions.DiscussionsSectionSubtitle.comments.singular", { count });
|
|
1644
|
+
});
|
|
1645
|
+
const repliesSubtitle = computed(() => {
|
|
1646
|
+
const count = props.comments.reduce((acc, comment) => acc + (comment.replies?.length ?? 0), 0);
|
|
1647
|
+
if (count !== 1) return t("discussions.DiscussionsSectionSubtitle.replies.plural", { count });
|
|
1648
|
+
return t("discussions.DiscussionsSectionSubtitle.replies.singular", { count });
|
|
1649
|
+
});
|
|
1650
|
+
const ui = computed(() => discussionsSectionSubtitle({ class: props.class }));
|
|
1651
|
+
return (_ctx, _cache) => {
|
|
1652
|
+
return openBlock(), createBlock(_sfc_main$17, { class: normalizeClass(ui.value) }, {
|
|
1653
|
+
default: withCtx(() => [createTextVNode(toDisplayString(commentsSubtitle.value) + " · " + toDisplayString(repliesSubtitle.value), 1)]),
|
|
1654
|
+
_: 1
|
|
1655
|
+
}, 8, ["class"]);
|
|
1656
|
+
};
|
|
1657
|
+
}
|
|
1658
|
+
});
|
|
1659
|
+
//#endregion
|
|
1660
|
+
//#region src/components/Section/SectionTitle.vue
|
|
1661
|
+
const sectionTitle = tv({ slots: { base: "text-lg font-semibold text-default" } });
|
|
1662
|
+
const _sfc_main$15 = /* @__PURE__ */ defineComponent({
|
|
1663
|
+
__name: "SectionTitle",
|
|
1664
|
+
props: {
|
|
1665
|
+
class: {},
|
|
1666
|
+
ui: {}
|
|
1667
|
+
},
|
|
1668
|
+
setup(__props) {
|
|
1669
|
+
const props = __props;
|
|
1670
|
+
const ui = computed(() => sectionTitle());
|
|
1671
|
+
return (_ctx, _cache) => {
|
|
1672
|
+
return openBlock(), createElementBlock("h2", { class: normalizeClass(ui.value.base({ class: [props.ui?.base, props.class] })) }, [renderSlot(_ctx.$slots, "default")], 2);
|
|
1673
|
+
};
|
|
1674
|
+
}
|
|
1675
|
+
});
|
|
1676
|
+
//#endregion
|
|
1677
|
+
//#region src/components/Discussions/DiscussionsSectionTitle.vue
|
|
1678
|
+
const discussionsSectionTitle = tv({ slots: { base: "" } });
|
|
1679
|
+
const _sfc_main$14 = /* @__PURE__ */ defineComponent({
|
|
1680
|
+
__name: "DiscussionsSectionTitle",
|
|
1681
|
+
props: {
|
|
1682
|
+
class: {},
|
|
1683
|
+
ui: {}
|
|
1684
|
+
},
|
|
1685
|
+
setup(__props) {
|
|
1686
|
+
const props = __props;
|
|
1687
|
+
const { t } = useLocale();
|
|
1688
|
+
const ui = computed(() => discussionsSectionTitle());
|
|
1689
|
+
return (_ctx, _cache) => {
|
|
1690
|
+
return openBlock(), createBlock(_sfc_main$15, { class: normalizeClass(ui.value.base({ class: [props.ui?.base, props.class] })) }, {
|
|
1691
|
+
default: withCtx(() => [createTextVNode(toDisplayString(unref(t)("discussions.DiscussionsSectionTitle.title")), 1)]),
|
|
1692
|
+
_: 1
|
|
1693
|
+
}, 8, ["class"]);
|
|
1694
|
+
};
|
|
1695
|
+
}
|
|
1696
|
+
});
|
|
1697
|
+
//#endregion
|
|
1698
|
+
//#region src/components/Discussions/DiscussionsSection.vue
|
|
1699
|
+
const _hoisted_1$12 = { id: "comments" };
|
|
1700
|
+
const _hoisted_2 = {
|
|
1701
|
+
key: 0,
|
|
1702
|
+
class: "mt-6"
|
|
1703
|
+
};
|
|
1704
|
+
const discussionsSection = tv({ slots: {
|
|
1705
|
+
base: "",
|
|
1706
|
+
alert: "",
|
|
1707
|
+
header: "mb-6",
|
|
1708
|
+
content: "mt-6"
|
|
1709
|
+
} });
|
|
1710
|
+
const _sfc_main$13 = /* @__PURE__ */ defineComponent({
|
|
1711
|
+
__name: "DiscussionsSection",
|
|
1712
|
+
props: {
|
|
1713
|
+
class: {},
|
|
1714
|
+
ui: {}
|
|
1715
|
+
},
|
|
1716
|
+
setup(__props) {
|
|
1717
|
+
const props = __props;
|
|
1718
|
+
const { t } = useLocale();
|
|
1719
|
+
const reducedMotion = useReducedMotion();
|
|
1720
|
+
const { pageId, locale, banner: bannerController } = useCommentsContext();
|
|
1721
|
+
const { banner, isActive, dismiss } = bannerController;
|
|
1722
|
+
const { data: comments, error: commentsError, isPending: isCommentsPending } = useQuery({
|
|
1723
|
+
key: COMMENT_QUERY_KEYS.byPageId(pageId.value, locale.value),
|
|
1724
|
+
query: () => getComments(pageId.value, locale.value).then((response) => response.data),
|
|
1725
|
+
enabled: typeof window !== "undefined"
|
|
1726
|
+
});
|
|
1727
|
+
const ui = computed(() => discussionsSection());
|
|
1728
|
+
return (_ctx, _cache) => {
|
|
1729
|
+
const _component_UAlert = resolveComponent("UAlert");
|
|
1730
|
+
return openBlock(), createElementBlock("section", _hoisted_1$12, [createElementVNode("div", { class: normalizeClass(ui.value.base({ class: [props.class, props.ui?.base] })) }, [
|
|
1731
|
+
createElementVNode("div", { class: normalizeClass(ui.value.header({ class: props.ui?.header })) }, [createVNode(_sfc_main$14), createVNode(_sfc_main$16, { comments: unref(comments) ?? [] }, null, 8, ["comments"])], 2),
|
|
1732
|
+
createVNode(unref(AnimatePresence), null, {
|
|
1733
|
+
default: withCtx(() => [unref(banner) && unref(isActive) ? (openBlock(), createBlock(unref(Motion), {
|
|
1734
|
+
key: 0,
|
|
1735
|
+
initial: unref(reducedMotion) ? false : {
|
|
1736
|
+
height: 0,
|
|
1737
|
+
opacity: 0
|
|
1738
|
+
},
|
|
1739
|
+
animate: {
|
|
1740
|
+
height: "auto",
|
|
1741
|
+
opacity: 1,
|
|
1742
|
+
transition: {
|
|
1743
|
+
duration: unref(reducedMotion) ? 0 : .2,
|
|
1744
|
+
ease: "easeOut"
|
|
1745
|
+
}
|
|
1746
|
+
},
|
|
1747
|
+
exit: unref(reducedMotion) ? void 0 : {
|
|
1748
|
+
height: 0,
|
|
1749
|
+
opacity: 0
|
|
1750
|
+
}
|
|
1751
|
+
}, {
|
|
1752
|
+
default: withCtx(() => [createVNode(_component_UAlert, {
|
|
1753
|
+
title: unref(banner).message,
|
|
1754
|
+
class: normalizeClass(ui.value.alert({ class: props.ui?.alert })),
|
|
1755
|
+
color: unref(banner).kind === "error" ? "error" : "success",
|
|
1756
|
+
variant: "subtle",
|
|
1757
|
+
close: "",
|
|
1758
|
+
role: unref(banner).kind === "error" ? "alert" : "status",
|
|
1759
|
+
"onUpdate:open": unref(dismiss)
|
|
1760
|
+
}, null, 8, [
|
|
1761
|
+
"title",
|
|
1762
|
+
"class",
|
|
1763
|
+
"color",
|
|
1764
|
+
"role",
|
|
1765
|
+
"onUpdate:open"
|
|
1766
|
+
])]),
|
|
1767
|
+
_: 1
|
|
1768
|
+
}, 8, [
|
|
1769
|
+
"initial",
|
|
1770
|
+
"animate",
|
|
1771
|
+
"exit"
|
|
1772
|
+
])) : createCommentVNode("v-if", true)]),
|
|
1773
|
+
_: 1
|
|
1774
|
+
}),
|
|
1775
|
+
createCommentVNode("\n Spacer to preserve layout during the Alert height animation.\n Adding margin to the Alert breaks the smooth transition because\n the animated element collapses abruptly. We render an empty\n div with `mt-6` while the Alert is active to keep the animation smooth.\n "),
|
|
1776
|
+
unref(banner) && unref(isActive) ? (openBlock(), createElementBlock("div", _hoisted_2)) : createCommentVNode("v-if", true),
|
|
1777
|
+
unref(isCommentsPending) ? (openBlock(), createBlock(_sfc_main$28, { key: 1 })) : unref(commentsError) ? (openBlock(), createBlock(_sfc_main$29, { key: 2 })) : !(unref(comments) ?? []).length ? (openBlock(), createBlock(_sfc_main$30, {
|
|
1778
|
+
key: 3,
|
|
1779
|
+
text: unref(t)("discussions.DiscussionsSection.empty")
|
|
1780
|
+
}, null, 8, ["text"])) : (openBlock(), createBlock(_sfc_main$18, {
|
|
1781
|
+
key: 4,
|
|
1782
|
+
comments: unref(comments) ?? []
|
|
1783
|
+
}, null, 8, ["comments"])),
|
|
1784
|
+
createVNode(_sfc_main$31, { class: "mt-12" })
|
|
1785
|
+
], 2)]);
|
|
1786
|
+
};
|
|
1787
|
+
}
|
|
1788
|
+
});
|
|
1789
|
+
//#endregion
|
|
1790
|
+
//#region src/components/Comments.vue
|
|
1791
|
+
const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
1792
|
+
__name: "Comments",
|
|
1793
|
+
props: {
|
|
1794
|
+
pageId: {},
|
|
1795
|
+
locale: { default: "en" },
|
|
1796
|
+
class: {},
|
|
1797
|
+
ui: {}
|
|
1798
|
+
},
|
|
1799
|
+
setup(__props) {
|
|
1800
|
+
const props = __props;
|
|
1801
|
+
provide(localeContextInjectionKey, computed(() => extendLocale(props.locale === "fr" ? fr_default : en_default, { messages: {} })));
|
|
1802
|
+
const banner = useCommentsBanner();
|
|
1803
|
+
provideCommentsContext({
|
|
1804
|
+
pageId: toRef(() => props.pageId),
|
|
1805
|
+
locale: toRef(() => props.locale),
|
|
1806
|
+
banner
|
|
1807
|
+
});
|
|
1808
|
+
return (_ctx, _cache) => {
|
|
1809
|
+
return openBlock(), createBlock(_sfc_main$13, {
|
|
1810
|
+
class: normalizeClass(props.class),
|
|
1811
|
+
ui: props.ui
|
|
1812
|
+
}, null, 8, ["class", "ui"]);
|
|
1813
|
+
};
|
|
1814
|
+
}
|
|
1815
|
+
});
|
|
1816
|
+
//#endregion
|
|
1817
|
+
//#region src/components/Container.vue
|
|
1818
|
+
const container = tv({ slots: {
|
|
1819
|
+
base: "px-4 sm:px-6 lg:px-8",
|
|
1820
|
+
inner: "w-full max-w-7xl mx-auto"
|
|
1821
|
+
} });
|
|
1822
|
+
const _sfc_main$1 = /* @__PURE__ */ defineComponent({
|
|
1823
|
+
__name: "Container",
|
|
1824
|
+
props: {
|
|
1825
|
+
class: {},
|
|
1826
|
+
ui: {}
|
|
1827
|
+
},
|
|
1828
|
+
setup(__props) {
|
|
1829
|
+
const props = __props;
|
|
1830
|
+
const ui = computed(() => container());
|
|
1831
|
+
return (_ctx, _cache) => {
|
|
1832
|
+
return openBlock(), createElementBlock("div", { class: normalizeClass(ui.value.base({ class: [props.ui?.base, props.class] })) }, [createElementVNode("div", { class: normalizeClass(ui.value.inner({ class: props.ui?.inner })) }, [renderSlot(_ctx.$slots, "default")], 2)], 2);
|
|
1833
|
+
};
|
|
1834
|
+
}
|
|
1835
|
+
});
|
|
1836
|
+
//#endregion
|
|
1837
|
+
//#region ~icons/ph/thumbs-up
|
|
1838
|
+
const _hoisted_1$11 = {
|
|
1839
|
+
viewBox: "0 0 256 256",
|
|
1840
|
+
width: "1.2em",
|
|
1841
|
+
height: "1.2em"
|
|
1842
|
+
};
|
|
1843
|
+
function render$7(_ctx, _cache) {
|
|
1844
|
+
return openBlock(), createElementBlock("svg", _hoisted_1$11, [..._cache[0] || (_cache[0] = [createElementVNode("path", {
|
|
1845
|
+
fill: "currentColor",
|
|
1846
|
+
d: "M234 80.12A24 24 0 0 0 216 72h-56V56a40 40 0 0 0-40-40a8 8 0 0 0-7.16 4.42L75.06 96H32a16 16 0 0 0-16 16v88a16 16 0 0 0 16 16h172a24 24 0 0 0 23.82-21l12-96A24 24 0 0 0 234 80.12M32 112h40v88H32Zm191.94-15l-12 96a8 8 0 0 1-7.94 7H88v-94.11l36.71-73.43A24 24 0 0 1 144 56v24a8 8 0 0 0 8 8h64a8 8 0 0 1 7.94 9"
|
|
1847
|
+
}, null, -1)])]);
|
|
1848
|
+
}
|
|
1849
|
+
var thumbs_up_default = markRaw({
|
|
1850
|
+
name: "ph-thumbs-up",
|
|
1851
|
+
render: render$7
|
|
1852
|
+
});
|
|
1853
|
+
//#endregion
|
|
1854
|
+
//#region src/composables/useUmami.ts
|
|
1855
|
+
function useUmami() {
|
|
1856
|
+
function track(event, data) {
|
|
1857
|
+
if (typeof window === "undefined") return;
|
|
1858
|
+
window.umami?.track(event, {
|
|
1859
|
+
...data,
|
|
1860
|
+
page_path: window.location.pathname
|
|
1861
|
+
});
|
|
1862
|
+
}
|
|
1863
|
+
return { track };
|
|
1864
|
+
}
|
|
1865
|
+
//#endregion
|
|
1866
|
+
//#region ~icons/ph/check-circle
|
|
1867
|
+
const _hoisted_1$10 = {
|
|
1868
|
+
viewBox: "0 0 256 256",
|
|
1869
|
+
width: "1.2em",
|
|
1870
|
+
height: "1.2em"
|
|
1871
|
+
};
|
|
1872
|
+
function render$6(_ctx, _cache) {
|
|
1873
|
+
return openBlock(), createElementBlock("svg", _hoisted_1$10, [..._cache[0] || (_cache[0] = [createElementVNode("path", {
|
|
1874
|
+
fill: "currentColor",
|
|
1875
|
+
d: "M173.66 98.34a8 8 0 0 1 0 11.32l-56 56a8 8 0 0 1-11.32 0l-24-24a8 8 0 0 1 11.32-11.32L112 148.69l50.34-50.35a8 8 0 0 1 11.32 0M232 128A104 104 0 1 1 128 24a104.11 104.11 0 0 1 104 104m-16 0a88 88 0 1 0-88 88a88.1 88.1 0 0 0 88-88"
|
|
1876
|
+
}, null, -1)])]);
|
|
1877
|
+
}
|
|
1878
|
+
var check_circle_default = markRaw({
|
|
1879
|
+
name: "ph-check-circle",
|
|
1880
|
+
render: render$6
|
|
1881
|
+
});
|
|
1882
|
+
//#endregion
|
|
1883
|
+
//#region src/api/feedback.ts
|
|
1884
|
+
function postFeedback(pageId, rating, content) {
|
|
1885
|
+
return api(`/api/pages/${pageId}/feedback`, {
|
|
1886
|
+
method: "POST",
|
|
1887
|
+
body: {
|
|
1888
|
+
rating,
|
|
1889
|
+
content
|
|
1890
|
+
}
|
|
1891
|
+
});
|
|
1892
|
+
}
|
|
1893
|
+
//#endregion
|
|
1894
|
+
//#region src/components/Feedback/FeedbackCard.vue
|
|
1895
|
+
const _hoisted_1$9 = ["for"];
|
|
1896
|
+
const feedbackCard = tv({ slots: {
|
|
1897
|
+
base: "relative w-64",
|
|
1898
|
+
successfullySentOverlay: "absolute z-10 inset-0 bg-default",
|
|
1899
|
+
successfullySentContent: "absolute z-20 inset-0 flex flex-col justify-center items-center text-muted text-xs",
|
|
1900
|
+
successfullySentIcon: "size-5",
|
|
1901
|
+
input: "w-full",
|
|
1902
|
+
radioGroup: "flex flex-row gap-2 text-lg",
|
|
1903
|
+
radioGroupItem: "peer",
|
|
1904
|
+
radioGroupLabel: "grayscale-100 hover:grayscale-0 peer-data-[state=checked]:grayscale-0"
|
|
1905
|
+
} });
|
|
1906
|
+
const _sfc_main$3 = /* @__PURE__ */ defineComponent({
|
|
1907
|
+
__name: "FeedbackCard",
|
|
1908
|
+
props: /* @__PURE__ */ mergeModels({
|
|
1909
|
+
id: {},
|
|
1910
|
+
class: {},
|
|
1911
|
+
ui: {}
|
|
1912
|
+
}, {
|
|
1913
|
+
"content": { required: true },
|
|
1914
|
+
"contentModifiers": {},
|
|
1915
|
+
"rating": { required: true },
|
|
1916
|
+
"ratingModifiers": {}
|
|
1917
|
+
}),
|
|
1918
|
+
emits: /* @__PURE__ */ mergeModels(["success"], ["update:content", "update:rating"]),
|
|
1919
|
+
setup(__props, { emit: __emit }) {
|
|
1920
|
+
const props = __props;
|
|
1921
|
+
const emits = __emit;
|
|
1922
|
+
const content = useModel(__props, "content");
|
|
1923
|
+
const rating = useModel(__props, "rating");
|
|
1924
|
+
const ratings = [
|
|
1925
|
+
{
|
|
1926
|
+
label: "😭",
|
|
1927
|
+
value: "Hate it",
|
|
1928
|
+
title: "FeedbackCard.ratings.hate"
|
|
1929
|
+
},
|
|
1930
|
+
{
|
|
1931
|
+
label: "🙁",
|
|
1932
|
+
value: "Not great",
|
|
1933
|
+
title: "FeedbackCard.ratings.poor"
|
|
1934
|
+
},
|
|
1935
|
+
{
|
|
1936
|
+
label: "🙂",
|
|
1937
|
+
value: "It's ok",
|
|
1938
|
+
title: "FeedbackCard.ratings.okay"
|
|
1939
|
+
},
|
|
1940
|
+
{
|
|
1941
|
+
label: "🤩",
|
|
1942
|
+
value: "Love it",
|
|
1943
|
+
title: "FeedbackCard.ratings.love"
|
|
1944
|
+
}
|
|
1945
|
+
];
|
|
1946
|
+
const successfullySent = ref(false);
|
|
1947
|
+
const { t } = useLocale();
|
|
1948
|
+
const { track } = useUmami();
|
|
1949
|
+
const { mutate, isLoading, error } = useMutation({
|
|
1950
|
+
mutation: ({ rating, content }) => postFeedback(props.id, rating, content),
|
|
1951
|
+
onSuccess: () => {
|
|
1952
|
+
successfullySent.value = true;
|
|
1953
|
+
track("feedback_submit");
|
|
1954
|
+
setTimeout(emits, 200, "success");
|
|
1955
|
+
}
|
|
1956
|
+
});
|
|
1957
|
+
function sendFeedback() {
|
|
1958
|
+
mutate({
|
|
1959
|
+
rating: rating.value,
|
|
1960
|
+
content: content.value
|
|
1961
|
+
});
|
|
1962
|
+
}
|
|
1963
|
+
const formattedError = computed(() => {
|
|
1964
|
+
if (!error.value) return void 0;
|
|
1965
|
+
if (error.value.data?.errors) return error.value.data.errors.content?.[0] || error.value.data.errors.rating?.[0];
|
|
1966
|
+
if (error.value.status === 404) return t("FeedbackCard.errors.notFound");
|
|
1967
|
+
if (error.value.status === 503) return t("FeedbackCard.errors.unavailable");
|
|
1968
|
+
return t("FeedbackCard.errors.unexpected");
|
|
1969
|
+
});
|
|
1970
|
+
const ui = computed(() => feedbackCard());
|
|
1971
|
+
return (_ctx, _cache) => {
|
|
1972
|
+
const _component_UIcon = resolveComponent("UIcon");
|
|
1973
|
+
const _component_UTextarea = resolveComponent("UTextarea");
|
|
1974
|
+
const _component_UFormField = resolveComponent("UFormField");
|
|
1975
|
+
const _component_UTooltip = resolveComponent("UTooltip");
|
|
1976
|
+
const _component_UButton = resolveComponent("UButton");
|
|
1977
|
+
const _component_UCard = resolveComponent("UCard");
|
|
1978
|
+
return openBlock(), createBlock(_component_UCard, {
|
|
1979
|
+
class: normalizeClass(ui.value.base({ class: [props.ui?.base, props.class] })),
|
|
1980
|
+
ui: {
|
|
1981
|
+
body: "p-2 sm:p-2",
|
|
1982
|
+
footer: "p-2 sm:p-2 flex items-center justify-between"
|
|
1983
|
+
}
|
|
1984
|
+
}, {
|
|
1985
|
+
footer: withCtx(() => [createVNode(unref(RadioGroupRoot), {
|
|
1986
|
+
modelValue: rating.value,
|
|
1987
|
+
"onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => rating.value = $event),
|
|
1988
|
+
class: normalizeClass(ui.value.radioGroup({ class: props.ui?.radioGroup }))
|
|
1989
|
+
}, {
|
|
1990
|
+
default: withCtx(() => [(openBlock(), createElementBlock(Fragment, null, renderList(ratings, (item) => {
|
|
1991
|
+
return createElementVNode("div", { key: item.value }, [createVNode(unref(RadioGroupItem), {
|
|
1992
|
+
id: item.value,
|
|
1993
|
+
value: item.value,
|
|
1994
|
+
class: normalizeClass(ui.value.radioGroupItem({ class: props.ui?.radioGroupItem }))
|
|
1995
|
+
}, {
|
|
1996
|
+
default: withCtx(() => [createVNode(unref(RadioGroupIndicator))]),
|
|
1997
|
+
_: 1
|
|
1998
|
+
}, 8, [
|
|
1999
|
+
"id",
|
|
2000
|
+
"value",
|
|
2001
|
+
"class"
|
|
2002
|
+
]), createVNode(_component_UTooltip, { text: unref(t)(item.title) }, {
|
|
2003
|
+
default: withCtx(() => [createElementVNode("label", {
|
|
2004
|
+
for: item.value,
|
|
2005
|
+
class: normalizeClass(ui.value.radioGroupLabel({ class: props.ui?.radioGroupLabel }))
|
|
2006
|
+
}, toDisplayString(item.label), 11, _hoisted_1$9)]),
|
|
2007
|
+
_: 2
|
|
2008
|
+
}, 1032, ["text"])]);
|
|
2009
|
+
}), 64))]),
|
|
2010
|
+
_: 1
|
|
2011
|
+
}, 8, ["modelValue", "class"]), createVNode(_component_UButton, {
|
|
2012
|
+
size: "sm",
|
|
2013
|
+
label: unref(t)("FeedbackCard.action"),
|
|
2014
|
+
loading: unref(isLoading),
|
|
2015
|
+
onClick: sendFeedback
|
|
2016
|
+
}, null, 8, ["label", "loading"])]),
|
|
2017
|
+
default: withCtx(() => [successfullySent.value ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [createVNode(unref(motion).div, {
|
|
2018
|
+
initial: { opacity: 0 },
|
|
2019
|
+
animate: {
|
|
2020
|
+
opacity: 1,
|
|
2021
|
+
transition: { duration: .2 }
|
|
2022
|
+
},
|
|
2023
|
+
class: normalizeClass(ui.value.successfullySentOverlay({ class: props.ui?.successfullySentOverlay }))
|
|
2024
|
+
}, null, 8, ["class"]), createElementVNode("div", { class: normalizeClass(ui.value.successfullySentContent({ class: props.ui?.successfullySentContent })) }, [
|
|
2025
|
+
createVNode(unref(motion).div, {
|
|
2026
|
+
initial: {
|
|
2027
|
+
opacity: 0,
|
|
2028
|
+
transform: "translateY(4px)"
|
|
2029
|
+
},
|
|
2030
|
+
animate: {
|
|
2031
|
+
opacity: 1,
|
|
2032
|
+
transform: "translateY(0)",
|
|
2033
|
+
transition: {
|
|
2034
|
+
delay: .1,
|
|
2035
|
+
duration: .3
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
}, {
|
|
2039
|
+
default: withCtx(() => [createVNode(_component_UIcon, {
|
|
2040
|
+
name: unref(check_circle_default),
|
|
2041
|
+
class: normalizeClass(ui.value.successfullySentIcon({ class: props.ui?.successfullySentIcon }))
|
|
2042
|
+
}, null, 8, ["name", "class"])]),
|
|
2043
|
+
_: 1
|
|
2044
|
+
}),
|
|
2045
|
+
createVNode(unref(motion).p, {
|
|
2046
|
+
initial: {
|
|
2047
|
+
opacity: 0,
|
|
2048
|
+
transform: "translateY(4px)"
|
|
2049
|
+
},
|
|
2050
|
+
animate: {
|
|
2051
|
+
opacity: 1,
|
|
2052
|
+
transform: "translateY(0)",
|
|
2053
|
+
transition: {
|
|
2054
|
+
delay: .2,
|
|
2055
|
+
duration: .3
|
|
2056
|
+
}
|
|
2057
|
+
},
|
|
2058
|
+
class: "mt-3"
|
|
2059
|
+
}, {
|
|
2060
|
+
default: withCtx(() => [createTextVNode(toDisplayString(unref(t)("FeedbackCard.success.received")), 1)]),
|
|
2061
|
+
_: 1
|
|
2062
|
+
}),
|
|
2063
|
+
createVNode(unref(motion).p, {
|
|
2064
|
+
initial: {
|
|
2065
|
+
opacity: 0,
|
|
2066
|
+
transform: "translateY(4px)"
|
|
2067
|
+
},
|
|
2068
|
+
animate: {
|
|
2069
|
+
opacity: 1,
|
|
2070
|
+
transform: "translateY(0)",
|
|
2071
|
+
transition: {
|
|
2072
|
+
delay: .3,
|
|
2073
|
+
duration: .3
|
|
2074
|
+
}
|
|
2075
|
+
},
|
|
2076
|
+
class: "mt-1"
|
|
2077
|
+
}, {
|
|
2078
|
+
default: withCtx(() => [createTextVNode(toDisplayString(unref(t)("FeedbackCard.success.thanks")), 1)]),
|
|
2079
|
+
_: 1
|
|
2080
|
+
})
|
|
2081
|
+
], 2)], 64)) : createCommentVNode("v-if", true), createVNode(_component_UFormField, { error: formattedError.value }, {
|
|
2082
|
+
error: withCtx(({ error: formFieldError }) => [formFieldError ? (openBlock(), createBlock(unref(motion).div, {
|
|
2083
|
+
key: 0,
|
|
2084
|
+
initial: { height: 0 },
|
|
2085
|
+
animate: { height: "auto" },
|
|
2086
|
+
class: "text-sm"
|
|
2087
|
+
}, {
|
|
2088
|
+
default: withCtx(() => [createTextVNode(toDisplayString(formFieldError), 1)]),
|
|
2089
|
+
_: 2
|
|
2090
|
+
}, 1024)) : createCommentVNode("v-if", true)]),
|
|
2091
|
+
default: withCtx(() => [createVNode(_component_UTextarea, {
|
|
2092
|
+
modelValue: content.value,
|
|
2093
|
+
"onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => content.value = $event),
|
|
2094
|
+
placeholder: unref(t)("FeedbackCard.placeholder"),
|
|
2095
|
+
variant: "soft",
|
|
2096
|
+
class: normalizeClass(ui.value.input({ class: props.ui?.input }))
|
|
2097
|
+
}, null, 8, [
|
|
2098
|
+
"modelValue",
|
|
2099
|
+
"placeholder",
|
|
2100
|
+
"class"
|
|
2101
|
+
])]),
|
|
2102
|
+
_: 1
|
|
2103
|
+
}, 8, ["error"])]),
|
|
2104
|
+
_: 1
|
|
2105
|
+
}, 8, ["class"]);
|
|
2106
|
+
};
|
|
2107
|
+
}
|
|
2108
|
+
});
|
|
2109
|
+
//#endregion
|
|
2110
|
+
//#region src/components/Feedback/Feedback.vue
|
|
2111
|
+
const feedback = tv({ slots: { base: "px-0 py-1 text-dimmed text-sm" } });
|
|
2112
|
+
const _sfc_main$2 = /* @__PURE__ */ defineComponent({
|
|
2113
|
+
__name: "Feedback",
|
|
2114
|
+
props: {
|
|
2115
|
+
id: {},
|
|
2116
|
+
class: {},
|
|
2117
|
+
ui: {}
|
|
2118
|
+
},
|
|
2119
|
+
setup(__props) {
|
|
2120
|
+
const props = __props;
|
|
2121
|
+
const { t } = useLocale();
|
|
2122
|
+
const { track } = useUmami();
|
|
2123
|
+
function onClick() {
|
|
2124
|
+
track("feedback_click");
|
|
2125
|
+
}
|
|
2126
|
+
const content = ref("");
|
|
2127
|
+
const rating = ref("");
|
|
2128
|
+
function onSuccess() {
|
|
2129
|
+
content.value = "";
|
|
2130
|
+
rating.value = "";
|
|
2131
|
+
}
|
|
2132
|
+
const ui = computed(() => feedback());
|
|
2133
|
+
return (_ctx, _cache) => {
|
|
2134
|
+
const _component_UButton = resolveComponent("UButton");
|
|
2135
|
+
const _component_UPopover = resolveComponent("UPopover");
|
|
2136
|
+
return openBlock(), createBlock(_component_UPopover, { ui: { content: "ring-0 data-[state=open]:animate-[scale-up_100ms_ease-out]" } }, {
|
|
2137
|
+
content: withCtx(() => [createVNode(_sfc_main$3, {
|
|
2138
|
+
id: props.id,
|
|
2139
|
+
rating: rating.value,
|
|
2140
|
+
"onUpdate:rating": _cache[0] || (_cache[0] = ($event) => rating.value = $event),
|
|
2141
|
+
content: content.value,
|
|
2142
|
+
"onUpdate:content": _cache[1] || (_cache[1] = ($event) => content.value = $event),
|
|
2143
|
+
onSuccess
|
|
2144
|
+
}, null, 8, [
|
|
2145
|
+
"id",
|
|
2146
|
+
"rating",
|
|
2147
|
+
"content"
|
|
2148
|
+
])]),
|
|
2149
|
+
default: withCtx(() => [createVNode(_component_UButton, {
|
|
2150
|
+
variant: "link",
|
|
2151
|
+
color: "neutral",
|
|
2152
|
+
label: unref(t)("Feedback.action"),
|
|
2153
|
+
size: "sm",
|
|
2154
|
+
icon: unref(thumbs_up_default),
|
|
2155
|
+
class: normalizeClass(ui.value.base({ class: [props.ui?.base, props.class] })),
|
|
2156
|
+
onClick
|
|
2157
|
+
}, null, 8, [
|
|
2158
|
+
"label",
|
|
2159
|
+
"icon",
|
|
2160
|
+
"class"
|
|
2161
|
+
])]),
|
|
2162
|
+
_: 1
|
|
2163
|
+
});
|
|
2164
|
+
};
|
|
2165
|
+
}
|
|
2166
|
+
});
|
|
2167
|
+
//#endregion
|
|
2168
|
+
//#region ~icons/simple-icons/discord
|
|
2169
|
+
const _hoisted_1$8 = {
|
|
2170
|
+
viewBox: "0 0 24 24",
|
|
2171
|
+
width: "1.2em",
|
|
2172
|
+
height: "1.2em"
|
|
2173
|
+
};
|
|
2174
|
+
function render$5(_ctx, _cache) {
|
|
2175
|
+
return openBlock(), createElementBlock("svg", _hoisted_1$8, [..._cache[0] || (_cache[0] = [createElementVNode("path", {
|
|
2176
|
+
fill: "currentColor",
|
|
2177
|
+
d: "M20.317 4.37a19.8 19.8 0 0 0-4.885-1.515a.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.3 18.3 0 0 0-5.487 0a13 13 0 0 0-.617-1.25a.08.08 0 0 0-.079-.037A19.7 19.7 0 0 0 3.677 4.37a.1.1 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.08.08 0 0 0 .031.057a19.9 19.9 0 0 0 5.993 3.03a.08.08 0 0 0 .084-.028a14 14 0 0 0 1.226-1.994a.076.076 0 0 0-.041-.106a13 13 0 0 1-1.872-.892a.077.077 0 0 1-.008-.128a10 10 0 0 0 .372-.292a.07.07 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.07.07 0 0 1 .078.01q.181.149.373.292a.077.077 0 0 1-.006.127a12.3 12.3 0 0 1-1.873.892a.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.08.08 0 0 0 .084.028a19.8 19.8 0 0 0 6.002-3.03a.08.08 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.06.06 0 0 0-.031-.03M8.02 15.33c-1.182 0-2.157-1.085-2.157-2.419c0-1.333.956-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.956 2.418-2.157 2.418m7.975 0c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.955-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.946 2.418-2.157 2.418"
|
|
2178
|
+
}, null, -1)])]);
|
|
2179
|
+
}
|
|
2180
|
+
var discord_default = markRaw({
|
|
2181
|
+
name: "simple-icons-discord",
|
|
2182
|
+
render: render$5
|
|
2183
|
+
});
|
|
2184
|
+
//#endregion
|
|
2185
|
+
//#region ~icons/simple-icons/github
|
|
2186
|
+
const _hoisted_1$7 = {
|
|
2187
|
+
viewBox: "0 0 24 24",
|
|
2188
|
+
width: "1.2em",
|
|
2189
|
+
height: "1.2em"
|
|
2190
|
+
};
|
|
2191
|
+
function render$4(_ctx, _cache) {
|
|
2192
|
+
return openBlock(), createElementBlock("svg", _hoisted_1$7, [..._cache[0] || (_cache[0] = [createElementVNode("path", {
|
|
2193
|
+
fill: "currentColor",
|
|
2194
|
+
d: "M12 .297c-6.63 0-12 5.373-12 12c0 5.303 3.438 9.8 8.205 11.385c.6.113.82-.258.82-.577c0-.285-.01-1.04-.015-2.04c-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729c1.205.084 1.838 1.236 1.838 1.236c1.07 1.835 2.809 1.305 3.495.998c.108-.776.417-1.305.76-1.605c-2.665-.3-5.466-1.332-5.466-5.93c0-1.31.465-2.38 1.235-3.22c-.135-.303-.54-1.523.105-3.176c0 0 1.005-.322 3.3 1.23c.96-.267 1.98-.399 3-.405c1.02.006 2.04.138 3 .405c2.28-1.552 3.285-1.23 3.285-1.23c.645 1.653.24 2.873.12 3.176c.765.84 1.23 1.91 1.23 3.22c0 4.61-2.805 5.625-5.475 5.92c.42.36.81 1.096.81 2.22c0 1.606-.015 2.896-.015 3.286c0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"
|
|
2195
|
+
}, null, -1)])]);
|
|
2196
|
+
}
|
|
2197
|
+
var github_default = markRaw({
|
|
2198
|
+
name: "simple-icons-github",
|
|
2199
|
+
render: render$4
|
|
2200
|
+
});
|
|
2201
|
+
//#endregion
|
|
2202
|
+
//#region ~icons/simple-icons/linkedin
|
|
2203
|
+
const _hoisted_1$6 = {
|
|
2204
|
+
viewBox: "0 0 24 24",
|
|
2205
|
+
width: "1.2em",
|
|
2206
|
+
height: "1.2em"
|
|
2207
|
+
};
|
|
2208
|
+
function render$3(_ctx, _cache) {
|
|
2209
|
+
return openBlock(), createElementBlock("svg", _hoisted_1$6, [..._cache[0] || (_cache[0] = [createElementVNode("path", {
|
|
2210
|
+
fill: "currentColor",
|
|
2211
|
+
d: "M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037c-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85c3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.06 2.06 0 0 1-2.063-2.065a2.064 2.064 0 1 1 2.063 2.065m1.782 13.019H3.555V9h3.564zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0z"
|
|
2212
|
+
}, null, -1)])]);
|
|
2213
|
+
}
|
|
2214
|
+
var linkedin_default = markRaw({
|
|
2215
|
+
name: "simple-icons-linkedin",
|
|
2216
|
+
render: render$3
|
|
2217
|
+
});
|
|
2218
|
+
//#endregion
|
|
2219
|
+
//#region ~icons/simple-icons/twitch
|
|
2220
|
+
const _hoisted_1$5 = {
|
|
2221
|
+
viewBox: "0 0 24 24",
|
|
2222
|
+
width: "1.2em",
|
|
2223
|
+
height: "1.2em"
|
|
2224
|
+
};
|
|
2225
|
+
function render$2(_ctx, _cache) {
|
|
2226
|
+
return openBlock(), createElementBlock("svg", _hoisted_1$5, [..._cache[0] || (_cache[0] = [createElementVNode("path", {
|
|
2227
|
+
fill: "currentColor",
|
|
2228
|
+
d: "M11.571 4.714h1.715v5.143H11.57zm4.715 0H18v5.143h-1.714zM6 0L1.714 4.286v15.428h5.143V24l4.286-4.286h3.428L22.286 12V0zm14.571 11.143l-3.428 3.428h-3.429l-3 3v-3H6.857V1.714h13.714Z"
|
|
2229
|
+
}, null, -1)])]);
|
|
2230
|
+
}
|
|
2231
|
+
var twitch_default = markRaw({
|
|
2232
|
+
name: "simple-icons-twitch",
|
|
2233
|
+
render: render$2
|
|
2234
|
+
});
|
|
2235
|
+
//#endregion
|
|
2236
|
+
//#region ~icons/simple-icons/x
|
|
2237
|
+
const _hoisted_1$4 = {
|
|
2238
|
+
viewBox: "0 0 24 24",
|
|
2239
|
+
width: "1.2em",
|
|
2240
|
+
height: "1.2em"
|
|
2241
|
+
};
|
|
2242
|
+
function render$1(_ctx, _cache) {
|
|
2243
|
+
return openBlock(), createElementBlock("svg", _hoisted_1$4, [..._cache[0] || (_cache[0] = [createElementVNode("path", {
|
|
2244
|
+
fill: "currentColor",
|
|
2245
|
+
d: "M14.234 10.162L22.977 0h-2.072l-7.591 8.824L7.251 0H.258l9.168 13.343L.258 24H2.33l8.016-9.318L16.749 24h6.993zm-2.837 3.299l-.929-1.329L3.076 1.56h3.182l5.965 8.532l.929 1.329l7.754 11.09h-3.182z"
|
|
2246
|
+
}, null, -1)])]);
|
|
2247
|
+
}
|
|
2248
|
+
var x_default = markRaw({
|
|
2249
|
+
name: "simple-icons-x",
|
|
2250
|
+
render: render$1
|
|
2251
|
+
});
|
|
2252
|
+
//#endregion
|
|
2253
|
+
//#region src/components/Header.vue
|
|
2254
|
+
const header = tv({ slots: {
|
|
2255
|
+
base: "h-(--ui-header-height) flex flex-row gap-4 items-center justify-end",
|
|
2256
|
+
link: "p-0 text-dimmed"
|
|
2257
|
+
} });
|
|
2258
|
+
const _sfc_main$4 = /* @__PURE__ */ defineComponent({
|
|
2259
|
+
__name: "Header",
|
|
2260
|
+
props: {
|
|
2261
|
+
links: {},
|
|
2262
|
+
class: {},
|
|
2263
|
+
ui: {}
|
|
2264
|
+
},
|
|
2265
|
+
setup(__props) {
|
|
2266
|
+
const props = __props;
|
|
2267
|
+
const { track } = useUmami();
|
|
2268
|
+
function trackClick(label) {
|
|
2269
|
+
track("header_click", { label });
|
|
2270
|
+
}
|
|
2271
|
+
const ui = computed(() => header());
|
|
2272
|
+
return (_ctx, _cache) => {
|
|
2273
|
+
const _component_UButton = resolveComponent("UButton");
|
|
2274
|
+
const _component_UTooltip = resolveComponent("UTooltip");
|
|
2275
|
+
const _component_USeparator = resolveComponent("USeparator");
|
|
2276
|
+
return openBlock(), createBlock(_sfc_main$1, { ui: { inner: "max-w-5xl" } }, {
|
|
2277
|
+
default: withCtx(() => [createElementVNode("header", { class: normalizeClass(ui.value.base({ class: [props.ui?.base, props.class] })) }, [
|
|
2278
|
+
(openBlock(true), createElementBlock(Fragment, null, renderList(props.links, (link) => {
|
|
2279
|
+
return openBlock(), createBlock(_component_UTooltip, {
|
|
2280
|
+
key: link.label,
|
|
2281
|
+
text: link.label
|
|
2282
|
+
}, {
|
|
2283
|
+
default: withCtx(() => [createVNode(_component_UButton, {
|
|
2284
|
+
variant: "link",
|
|
2285
|
+
color: "neutral",
|
|
2286
|
+
to: link.to,
|
|
2287
|
+
href: link.href,
|
|
2288
|
+
"aria-label": link.label,
|
|
2289
|
+
icon: link.icon,
|
|
2290
|
+
target: link.target,
|
|
2291
|
+
rel: link.rel,
|
|
2292
|
+
class: normalizeClass(ui.value.link({ class: props.ui?.link })),
|
|
2293
|
+
onClick: ($event) => trackClick(link.label)
|
|
2294
|
+
}, null, 8, [
|
|
2295
|
+
"to",
|
|
2296
|
+
"href",
|
|
2297
|
+
"aria-label",
|
|
2298
|
+
"icon",
|
|
2299
|
+
"target",
|
|
2300
|
+
"rel",
|
|
2301
|
+
"class",
|
|
2302
|
+
"onClick"
|
|
2303
|
+
])]),
|
|
2304
|
+
_: 2
|
|
2305
|
+
}, 1032, ["text"]);
|
|
2306
|
+
}), 128)),
|
|
2307
|
+
createVNode(_component_USeparator, {
|
|
2308
|
+
orientation: "vertical",
|
|
2309
|
+
class: "h-5"
|
|
2310
|
+
}),
|
|
2311
|
+
createVNode(_component_UTooltip, { text: "LinkedIn" }, {
|
|
2312
|
+
default: withCtx(() => [createVNode(_component_UButton, {
|
|
2313
|
+
href: "https://www.linkedin.com/in/esteban25/",
|
|
2314
|
+
target: "_blank",
|
|
2315
|
+
variant: "link",
|
|
2316
|
+
color: "neutral",
|
|
2317
|
+
"aria-label": "LinkedIn",
|
|
2318
|
+
icon: unref(linkedin_default),
|
|
2319
|
+
class: normalizeClass(ui.value.link({ class: props.ui?.link })),
|
|
2320
|
+
ui: { leadingIcon: "size-4" },
|
|
2321
|
+
onClick: _cache[0] || (_cache[0] = ($event) => trackClick("LinkedIn"))
|
|
2322
|
+
}, null, 8, ["icon", "class"])]),
|
|
2323
|
+
_: 1
|
|
2324
|
+
}),
|
|
2325
|
+
createVNode(_component_UTooltip, { text: "X" }, {
|
|
2326
|
+
default: withCtx(() => [createVNode(_component_UButton, {
|
|
2327
|
+
href: "https://x.com/soubiran_",
|
|
2328
|
+
target: "_blank",
|
|
2329
|
+
variant: "link",
|
|
2330
|
+
color: "neutral",
|
|
2331
|
+
"aria-label": "X",
|
|
2332
|
+
icon: unref(x_default),
|
|
2333
|
+
class: normalizeClass(ui.value.link({ class: props.ui?.link })),
|
|
2334
|
+
ui: { leadingIcon: "size-4" },
|
|
2335
|
+
onClick: _cache[1] || (_cache[1] = ($event) => trackClick("X"))
|
|
2336
|
+
}, null, 8, ["icon", "class"])]),
|
|
2337
|
+
_: 1
|
|
2338
|
+
}),
|
|
2339
|
+
createVNode(_component_UTooltip, { text: "Twitch" }, {
|
|
2340
|
+
default: withCtx(() => [createVNode(_component_UButton, {
|
|
2341
|
+
href: "https://www.twitch.tv/barbapapazes",
|
|
2342
|
+
target: "_blank",
|
|
2343
|
+
variant: "link",
|
|
2344
|
+
color: "neutral",
|
|
2345
|
+
"aria-label": "Twitch",
|
|
2346
|
+
icon: unref(twitch_default),
|
|
2347
|
+
class: normalizeClass(ui.value.link({ class: props.ui?.link })),
|
|
2348
|
+
ui: { leadingIcon: "size-4" },
|
|
2349
|
+
onClick: _cache[2] || (_cache[2] = ($event) => trackClick("Twitch"))
|
|
2350
|
+
}, null, 8, ["icon", "class"])]),
|
|
2351
|
+
_: 1
|
|
2352
|
+
}),
|
|
2353
|
+
createVNode(_component_UTooltip, { text: "GitHub" }, {
|
|
2354
|
+
default: withCtx(() => [createVNode(_component_UButton, {
|
|
2355
|
+
href: "https://github.com/barbapapazes",
|
|
2356
|
+
target: "_blank",
|
|
2357
|
+
variant: "link",
|
|
2358
|
+
color: "neutral",
|
|
2359
|
+
"aria-label": "GitHub",
|
|
2360
|
+
icon: unref(github_default),
|
|
2361
|
+
class: normalizeClass(ui.value.link({ class: props.ui?.link })),
|
|
2362
|
+
ui: { leadingIcon: "size-4" },
|
|
2363
|
+
onClick: _cache[3] || (_cache[3] = ($event) => trackClick("GitHub"))
|
|
2364
|
+
}, null, 8, ["icon", "class"])]),
|
|
2365
|
+
_: 1
|
|
2366
|
+
}),
|
|
2367
|
+
createVNode(_component_UTooltip, { text: "Discord" }, {
|
|
2368
|
+
default: withCtx(() => [createVNode(_component_UButton, {
|
|
2369
|
+
href: "https://discord.gg/q2ghCGUuFR",
|
|
2370
|
+
target: "_blank",
|
|
2371
|
+
variant: "link",
|
|
2372
|
+
color: "neutral",
|
|
2373
|
+
"aria-label": "Discord",
|
|
2374
|
+
icon: unref(discord_default),
|
|
2375
|
+
class: normalizeClass(ui.value.link({ class: props.ui?.link })),
|
|
2376
|
+
ui: { leadingIcon: "size-4" },
|
|
2377
|
+
onClick: _cache[4] || (_cache[4] = ($event) => trackClick("Discord"))
|
|
2378
|
+
}, null, 8, ["icon", "class"])]),
|
|
2379
|
+
_: 1
|
|
2380
|
+
})
|
|
2381
|
+
], 2)]),
|
|
2382
|
+
_: 1
|
|
2383
|
+
});
|
|
2384
|
+
};
|
|
2385
|
+
}
|
|
2386
|
+
});
|
|
2387
|
+
//#endregion
|
|
2388
|
+
//#region src/components/LoginCallback.vue
|
|
2389
|
+
const _sfc_main$5 = /* @__PURE__ */ defineComponent({
|
|
2390
|
+
__name: "LoginCallback",
|
|
2391
|
+
props: { class: {} },
|
|
2392
|
+
setup(__props) {
|
|
2393
|
+
const props = __props;
|
|
2394
|
+
const { t } = useLocale();
|
|
2395
|
+
const status = ref("pending");
|
|
2396
|
+
onMounted(() => {
|
|
2397
|
+
const nonce = new URL(window.location.href).searchParams.get(LOGIN_CALLBACK_NONCE_PARAM);
|
|
2398
|
+
if (!nonce || !window.opener || window.opener.closed) {
|
|
2399
|
+
status.value = "error";
|
|
2400
|
+
return;
|
|
2401
|
+
}
|
|
2402
|
+
try {
|
|
2403
|
+
window.opener.postMessage({
|
|
2404
|
+
type: LOGIN_CALLBACK_MESSAGE_TYPE,
|
|
2405
|
+
nonce
|
|
2406
|
+
}, window.location.origin);
|
|
2407
|
+
status.value = "success";
|
|
2408
|
+
window.close();
|
|
2409
|
+
} catch {
|
|
2410
|
+
status.value = "error";
|
|
2411
|
+
}
|
|
2412
|
+
});
|
|
2413
|
+
return (_ctx, _cache) => {
|
|
2414
|
+
return openBlock(), createElementBlock("div", { class: normalizeClass(props.class) }, [status.value === "error" ? renderSlot(_ctx.$slots, "error", {
|
|
2415
|
+
key: 0,
|
|
2416
|
+
message: unref(t)("LoginCallback.error")
|
|
2417
|
+
}, () => [createElementVNode("p", null, toDisplayString(unref(t)("LoginCallback.error")), 1)]) : renderSlot(_ctx.$slots, "success", {
|
|
2418
|
+
key: 1,
|
|
2419
|
+
message: unref(t)("LoginCallback.success")
|
|
2420
|
+
}, () => [createElementVNode("p", null, toDisplayString(unref(t)("LoginCallback.success")), 1)])], 2);
|
|
2421
|
+
};
|
|
2422
|
+
}
|
|
2423
|
+
});
|
|
2424
|
+
//#endregion
|
|
2425
|
+
//#region src/components/Page/Page.vue
|
|
2426
|
+
const page = tv({ slots: {
|
|
2427
|
+
base: "py-12",
|
|
2428
|
+
header: "max-w-3xl mx-auto xl:max-w-none xl:grid xl:grid-cols-[256px_768px_256px]",
|
|
2429
|
+
headerInner: "xl:col-start-2",
|
|
2430
|
+
content: "mt-6 max-w-3xl mx-auto xl:max-w-none xl:grid xl:grid-cols-[256px_768px_256px] xl:mx-auto",
|
|
2431
|
+
contentInner: "xl:col-start-2",
|
|
2432
|
+
right: "hidden xl:block xl:col-start-3 xl:pl-8 xl:h-full",
|
|
2433
|
+
rightInner: "sticky top-4"
|
|
2434
|
+
} });
|
|
2435
|
+
const _sfc_main$6 = /* @__PURE__ */ defineComponent({
|
|
2436
|
+
__name: "Page",
|
|
2437
|
+
props: {
|
|
2438
|
+
class: {},
|
|
2439
|
+
ui: {}
|
|
2440
|
+
},
|
|
2441
|
+
setup(__props) {
|
|
2442
|
+
const props = __props;
|
|
2443
|
+
const ui = computed(() => page());
|
|
2444
|
+
return (_ctx, _cache) => {
|
|
2445
|
+
return openBlock(), createElementBlock("div", { class: normalizeClass(ui.value.base({ class: [props.ui?.base, props.class] })) }, [createVNode(_sfc_main$1, null, {
|
|
2446
|
+
default: withCtx(() => [createElementVNode("div", { class: normalizeClass(ui.value.header({ class: props.ui?.header })) }, [createElementVNode("div", { class: normalizeClass(ui.value.headerInner({ class: props.ui?.headerInner })) }, [renderSlot(_ctx.$slots, "header")], 2)], 2), createElementVNode("div", { class: normalizeClass(ui.value.content({ class: props.ui?.content })) }, [createElementVNode("div", { class: normalizeClass(ui.value.contentInner({ class: props.ui?.contentInner })) }, [renderSlot(_ctx.$slots, "default")], 2), createElementVNode("div", { class: normalizeClass(ui.value.right({ class: props.ui?.right })) }, [createElementVNode("div", { class: normalizeClass(ui.value.rightInner({ class: props.ui?.rightInner })) }, [renderSlot(_ctx.$slots, "right")], 2)], 2)], 2)]),
|
|
2447
|
+
_: 3
|
|
2448
|
+
}), renderSlot(_ctx.$slots, "bottom")], 2);
|
|
2449
|
+
};
|
|
2450
|
+
}
|
|
2451
|
+
});
|
|
2452
|
+
//#endregion
|
|
2453
|
+
//#region src/components/Page/PageTitle.vue
|
|
2454
|
+
const pageTitle = tv({ slots: { base: "text-xl font-bold text-highlighted" } });
|
|
2455
|
+
const _sfc_main$8 = /* @__PURE__ */ defineComponent({
|
|
2456
|
+
__name: "PageTitle",
|
|
2457
|
+
props: {
|
|
2458
|
+
title: {},
|
|
2459
|
+
class: {},
|
|
2460
|
+
ui: {}
|
|
2461
|
+
},
|
|
2462
|
+
setup(__props) {
|
|
2463
|
+
const props = __props;
|
|
2464
|
+
const ui = computed(() => pageTitle());
|
|
2465
|
+
return (_ctx, _cache) => {
|
|
2466
|
+
return openBlock(), createElementBlock("h1", { class: normalizeClass(ui.value.base({ class: [props.ui?.base, props.class] })) }, toDisplayString(__props.title), 3);
|
|
2467
|
+
};
|
|
2468
|
+
}
|
|
2469
|
+
});
|
|
2470
|
+
//#endregion
|
|
2471
|
+
//#region src/components/Page/PageHeader.vue
|
|
2472
|
+
const pageHeader = tv({ slots: { base: "" } });
|
|
2473
|
+
const _sfc_main$7 = /* @__PURE__ */ defineComponent({
|
|
2474
|
+
__name: "PageHeader",
|
|
2475
|
+
props: {
|
|
2476
|
+
title: {},
|
|
2477
|
+
class: {},
|
|
2478
|
+
ui: {}
|
|
2479
|
+
},
|
|
2480
|
+
setup(__props) {
|
|
2481
|
+
const props = __props;
|
|
2482
|
+
const ui = computed(() => pageHeader());
|
|
2483
|
+
return (_ctx, _cache) => {
|
|
2484
|
+
return openBlock(), createElementBlock("div", { class: normalizeClass(ui.value.base({ class: [props.ui?.base, props.class] })) }, [createVNode(_sfc_main$8, { title: props.title }, null, 8, ["title"]), renderSlot(_ctx.$slots, "after")], 2);
|
|
2485
|
+
};
|
|
2486
|
+
}
|
|
2487
|
+
});
|
|
2488
|
+
//#endregion
|
|
2489
|
+
//#region ~icons/simple-icons/youtube
|
|
2490
|
+
const _hoisted_1$3 = {
|
|
2491
|
+
viewBox: "0 0 24 24",
|
|
2492
|
+
width: "1.2em",
|
|
2493
|
+
height: "1.2em"
|
|
2494
|
+
};
|
|
2495
|
+
function render(_ctx, _cache) {
|
|
2496
|
+
return openBlock(), createElementBlock("svg", _hoisted_1$3, [..._cache[0] || (_cache[0] = [createElementVNode("path", {
|
|
2497
|
+
fill: "currentColor",
|
|
2498
|
+
d: "M23.498 6.186a3.02 3.02 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.02 3.02 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.02 3.02 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.02 3.02 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814M9.545 15.568V8.432L15.818 12z"
|
|
2499
|
+
}, null, -1)])]);
|
|
2500
|
+
}
|
|
2501
|
+
var youtube_default = markRaw({
|
|
2502
|
+
name: "simple-icons-youtube",
|
|
2503
|
+
render
|
|
2504
|
+
});
|
|
2505
|
+
//#endregion
|
|
2506
|
+
//#region src/components/Socials.vue
|
|
2507
|
+
const socials = tv({ slots: {
|
|
2508
|
+
base: "flex flex-wrap gap-2",
|
|
2509
|
+
link: "flex items-center gap-1",
|
|
2510
|
+
icon: "size-4"
|
|
2511
|
+
} });
|
|
2512
|
+
const _sfc_main$9 = /* @__PURE__ */ defineComponent({
|
|
2513
|
+
__name: "Socials",
|
|
2514
|
+
props: {
|
|
2515
|
+
class: {},
|
|
2516
|
+
ui: {}
|
|
2517
|
+
},
|
|
2518
|
+
setup(__props) {
|
|
2519
|
+
const props = __props;
|
|
2520
|
+
const ui = computed(() => socials());
|
|
2521
|
+
return (_ctx, _cache) => {
|
|
2522
|
+
const _component_UIcon = resolveComponent("UIcon");
|
|
2523
|
+
return openBlock(), createElementBlock("p", { class: normalizeClass(ui.value.base({ class: [props.ui?.base, props.class] })) }, [
|
|
2524
|
+
createElementVNode("a", {
|
|
2525
|
+
href: "https://www.linkedin.com/in/esteban25/",
|
|
2526
|
+
rel: "noopener noreferrer",
|
|
2527
|
+
target: "_blank",
|
|
2528
|
+
class: normalizeClass(ui.value.link({ class: props.ui?.link }))
|
|
2529
|
+
}, [createVNode(_component_UIcon, {
|
|
2530
|
+
name: unref(linkedin_default),
|
|
2531
|
+
class: normalizeClass(ui.value.icon({ class: props.ui?.icon }))
|
|
2532
|
+
}, null, 8, ["name", "class"]), _cache[0] || (_cache[0] = createElementVNode("span", null, "LinkedIn", -1))], 2),
|
|
2533
|
+
createElementVNode("a", {
|
|
2534
|
+
href: "https://x.com/soubiran_",
|
|
2535
|
+
rel: "noopener noreferrer",
|
|
2536
|
+
target: "_blank",
|
|
2537
|
+
class: normalizeClass(ui.value.link({ class: props.ui?.link }))
|
|
2538
|
+
}, [createVNode(_component_UIcon, {
|
|
2539
|
+
name: unref(x_default),
|
|
2540
|
+
class: normalizeClass(ui.value.icon({ class: props.ui?.icon }))
|
|
2541
|
+
}, null, 8, ["name", "class"]), _cache[1] || (_cache[1] = createElementVNode("span", null, "X (Twitter)", -1))], 2),
|
|
2542
|
+
createElementVNode("a", {
|
|
2543
|
+
href: "https://github.com/barbapapazes",
|
|
2544
|
+
rel: "noopener noreferrer",
|
|
2545
|
+
target: "_blank",
|
|
2546
|
+
class: normalizeClass(ui.value.link({ class: props.ui?.link }))
|
|
2547
|
+
}, [createVNode(_component_UIcon, {
|
|
2548
|
+
name: unref(github_default),
|
|
2549
|
+
class: normalizeClass(ui.value.icon({ class: props.ui?.icon }))
|
|
2550
|
+
}, null, 8, ["name", "class"]), _cache[2] || (_cache[2] = createElementVNode("span", null, "GitHub", -1))], 2),
|
|
2551
|
+
createElementVNode("a", {
|
|
2552
|
+
href: "https://www.twitch.tv/barbapapazes",
|
|
2553
|
+
rel: "noopener noreferrer",
|
|
2554
|
+
target: "_blank",
|
|
2555
|
+
class: normalizeClass(ui.value.link({ class: props.ui?.link }))
|
|
2556
|
+
}, [createVNode(_component_UIcon, {
|
|
2557
|
+
name: unref(twitch_default),
|
|
2558
|
+
class: normalizeClass(ui.value.icon({ class: props.ui?.icon }))
|
|
2559
|
+
}, null, 8, ["name", "class"]), _cache[3] || (_cache[3] = createElementVNode("span", null, "Twitch", -1))], 2),
|
|
2560
|
+
createElementVNode("a", {
|
|
2561
|
+
href: "https://www.youtube.com/@barbapapazes",
|
|
2562
|
+
target: "_blank",
|
|
2563
|
+
rel: "noopener noreferrer",
|
|
2564
|
+
class: normalizeClass(ui.value.link({ class: props.ui?.link }))
|
|
2565
|
+
}, [createVNode(_component_UIcon, {
|
|
2566
|
+
name: unref(youtube_default),
|
|
2567
|
+
class: normalizeClass(ui.value.icon({ class: props.ui?.icon }))
|
|
2568
|
+
}, null, 8, ["name", "class"]), _cache[4] || (_cache[4] = createElementVNode("span", null, "YouTube", -1))], 2)
|
|
2569
|
+
], 2);
|
|
2570
|
+
};
|
|
2571
|
+
}
|
|
2572
|
+
});
|
|
2573
|
+
//#endregion
|
|
2574
|
+
//#region src/components/Sponsors.vue
|
|
2575
|
+
const _hoisted_1$2 = {
|
|
2576
|
+
href: "https://soubiran.dev/sponsorship?utm_source=infra.soubiran.dev&utm_medium=link",
|
|
2577
|
+
target: "_blank"
|
|
2578
|
+
};
|
|
2579
|
+
const sponsors = tv({ slots: {
|
|
2580
|
+
base: "not-prose",
|
|
2581
|
+
img: "border-0"
|
|
2582
|
+
} });
|
|
2583
|
+
const _sfc_main$10 = /* @__PURE__ */ defineComponent({
|
|
2584
|
+
__name: "Sponsors",
|
|
2585
|
+
props: {
|
|
2586
|
+
class: {},
|
|
2587
|
+
ui: {}
|
|
2588
|
+
},
|
|
2589
|
+
setup(__props) {
|
|
2590
|
+
const props = __props;
|
|
2591
|
+
const ui = computed(() => sponsors());
|
|
2592
|
+
return (_ctx, _cache) => {
|
|
2593
|
+
return openBlock(), createElementBlock("p", { class: normalizeClass(ui.value.base({ class: [props.ui?.base, props.class] })) }, [createElementVNode("a", _hoisted_1$2, [createElementVNode("img", {
|
|
2594
|
+
src: "https://raw.githubusercontent.com/Barbapapazes/static/refs/heads/main/sponsors.svg",
|
|
2595
|
+
alt: "Sponsors",
|
|
2596
|
+
class: normalizeClass(ui.value.img({ class: props.ui?.img }))
|
|
2597
|
+
}, null, 2)])], 2);
|
|
2598
|
+
};
|
|
2599
|
+
}
|
|
2600
|
+
});
|
|
2601
|
+
//#endregion
|
|
2602
|
+
//#region src/composables/useTableOfContents.ts
|
|
2603
|
+
function _useTableOfContents() {
|
|
2604
|
+
const activeHeadings = ref([]);
|
|
2605
|
+
function setActive(id) {
|
|
2606
|
+
activeHeadings.value.push(id);
|
|
2607
|
+
}
|
|
2608
|
+
function unsetActive(id) {
|
|
2609
|
+
activeHeadings.value = activeHeadings.value.filter((h) => h !== id);
|
|
2610
|
+
}
|
|
2611
|
+
return {
|
|
2612
|
+
setActive,
|
|
2613
|
+
unsetActive,
|
|
2614
|
+
activeHeadings: readonly(activeHeadings)
|
|
2615
|
+
};
|
|
2616
|
+
}
|
|
2617
|
+
var useTableOfContents_default = createSharedComposable(_useTableOfContents);
|
|
2618
|
+
//#endregion
|
|
2619
|
+
//#region src/components/TableOfContents.vue
|
|
2620
|
+
const _hoisted_1$1 = [
|
|
2621
|
+
"href",
|
|
2622
|
+
"data-active",
|
|
2623
|
+
"onClick"
|
|
2624
|
+
];
|
|
2625
|
+
const tableOfContents = tv({ slots: {
|
|
2626
|
+
base: "space-y-1 text-sm text-dimmed font-medium",
|
|
2627
|
+
title: "",
|
|
2628
|
+
list: "font-sofia",
|
|
2629
|
+
link: "flex gap-1 px-0 py-1 text-dimmed hover:text-default active:text-default transition-colors focus:outline-none focus-visible:ring-inset focus-visible:ring-2 focus-visible:ring-inverted data-active:text-default rounded-md"
|
|
2630
|
+
} });
|
|
2631
|
+
const _sfc_main$11 = /* @__PURE__ */ defineComponent({
|
|
2632
|
+
__name: "TableOfContents",
|
|
2633
|
+
props: {
|
|
2634
|
+
toc: {},
|
|
2635
|
+
class: {},
|
|
2636
|
+
ui: {}
|
|
2637
|
+
},
|
|
2638
|
+
setup(__props) {
|
|
2639
|
+
const props = __props;
|
|
2640
|
+
const { track } = useUmami();
|
|
2641
|
+
function trackClick(text, hash) {
|
|
2642
|
+
track("table_of_contents_click", {
|
|
2643
|
+
toc_text: text,
|
|
2644
|
+
toc_hash: hash
|
|
2645
|
+
});
|
|
2646
|
+
}
|
|
2647
|
+
const { activeHeadings } = useTableOfContents_default();
|
|
2648
|
+
const ui = computed(() => tableOfContents());
|
|
2649
|
+
return (_ctx, _cache) => {
|
|
2650
|
+
return openBlock(), createElementBlock("div", { class: normalizeClass(ui.value.base({ class: [props.ui?.base, props.class] })) }, [createElementVNode("div", { class: normalizeClass(ui.value.title({ class: props.ui?.title })) }, " Table of Contents ", 2), createElementVNode("ul", { class: normalizeClass(ui.value.list({ class: props.ui?.list })) }, [(openBlock(true), createElementBlock(Fragment, null, renderList(props.toc, (item, index) => {
|
|
2651
|
+
return openBlock(), createElementBlock("li", { key: item.anchor || index }, [createCommentVNode(" Cannot use RouterLink as the anchor will be prefixed with the route path. "), item.anchor ? (openBlock(), createElementBlock("a", {
|
|
2652
|
+
key: 0,
|
|
2653
|
+
custom: "",
|
|
2654
|
+
variant: "link",
|
|
2655
|
+
color: "neutral",
|
|
2656
|
+
href: `#${item.anchor}`,
|
|
2657
|
+
"data-active": unref(activeHeadings).includes(item.anchor || "") ? true : void 0,
|
|
2658
|
+
class: normalizeClass(ui.value.link({ class: props.ui?.link })),
|
|
2659
|
+
onClick: ($event) => trackClick(item.text, item.anchor)
|
|
2660
|
+
}, [createElementVNode("span", null, toDisplayString(index + 1) + ".", 1), createTextVNode(" " + toDisplayString(item.text), 1)], 10, _hoisted_1$1)) : createCommentVNode("v-if", true)]);
|
|
2661
|
+
}), 128))], 2)], 2);
|
|
2662
|
+
};
|
|
2663
|
+
}
|
|
2664
|
+
});
|
|
2665
|
+
//#endregion
|
|
2666
|
+
//#region src/components/ViewersCounter.vue
|
|
2667
|
+
const _hoisted_1 = ["title"];
|
|
2668
|
+
const viewersCounter = tv({ slots: {
|
|
2669
|
+
base: "flex items-center justify-center gap-[0.375rem] border border-green-400 rounded-full px-[0.375rem] py-[0.125rem] text-xs text-muted font-light leading-3 dark:border-green-700",
|
|
2670
|
+
dot: "inline-block h-[0.375rem] w-[0.375rem] animate-[pulse_4s_cubic-bezier(0.4,_0,_0.6,_1)_infinite] rounded-full bg-green-400 ring ring-2 ring-green-200 dark:bg-green-700 dark:ring-green-500"
|
|
2671
|
+
} });
|
|
2672
|
+
const _sfc_main$12 = /* @__PURE__ */ defineComponent({
|
|
2673
|
+
__name: "ViewersCounter",
|
|
2674
|
+
props: {
|
|
2675
|
+
class: {},
|
|
2676
|
+
ui: {}
|
|
2677
|
+
},
|
|
2678
|
+
setup(__props) {
|
|
2679
|
+
const props = __props;
|
|
2680
|
+
const count = ref(0);
|
|
2681
|
+
const connections = ref({});
|
|
2682
|
+
let socket;
|
|
2683
|
+
function connect() {
|
|
2684
|
+
return new PartySocket({
|
|
2685
|
+
host: import.meta.env.VITE_PARTYKIT_URL,
|
|
2686
|
+
room: "soubiran-dev"
|
|
2687
|
+
});
|
|
2688
|
+
}
|
|
2689
|
+
function handleMessage(event) {
|
|
2690
|
+
if (typeof event.data !== "string") return;
|
|
2691
|
+
let data;
|
|
2692
|
+
try {
|
|
2693
|
+
data = JSON.parse(event.data);
|
|
2694
|
+
} catch {
|
|
2695
|
+
return;
|
|
2696
|
+
}
|
|
2697
|
+
count.value = data.count;
|
|
2698
|
+
connections.value = data.connections;
|
|
2699
|
+
}
|
|
2700
|
+
onMounted(() => {
|
|
2701
|
+
socket = connect();
|
|
2702
|
+
socket.addEventListener("message", handleMessage);
|
|
2703
|
+
});
|
|
2704
|
+
onBeforeUnmount(() => {
|
|
2705
|
+
if (!socket) return;
|
|
2706
|
+
socket.removeEventListener("message", handleMessage);
|
|
2707
|
+
socket.close();
|
|
2708
|
+
socket = void 0;
|
|
2709
|
+
});
|
|
2710
|
+
const title = computed(() => {
|
|
2711
|
+
return `${count.value} viewer${count.value > 1 ? "s" : ""} currently online (${Object.keys(connections.value).map((host) => `${connections.value[host]} from ${host}`).join(", ")})`;
|
|
2712
|
+
});
|
|
2713
|
+
const ui = computed(() => viewersCounter());
|
|
2714
|
+
return (_ctx, _cache) => {
|
|
2715
|
+
return openBlock(), createBlock(Transition, { name: "fade" }, {
|
|
2716
|
+
default: withCtx(() => [count.value ? (openBlock(), createElementBlock("div", {
|
|
2717
|
+
key: 0,
|
|
2718
|
+
title: title.value,
|
|
2719
|
+
class: normalizeClass(ui.value.base({ class: [props.class, props.ui?.base] }))
|
|
2720
|
+
}, [createElementVNode("span", null, toDisplayString(count.value), 1), createElementVNode("span", {
|
|
2721
|
+
class: normalizeClass(ui.value.dot({ class: props.ui?.dot })),
|
|
2722
|
+
"aria-hidden": "true"
|
|
2723
|
+
}, null, 2)], 10, _hoisted_1)) : createCommentVNode("v-if", true)]),
|
|
2724
|
+
_: 1
|
|
2725
|
+
});
|
|
2726
|
+
};
|
|
2727
|
+
}
|
|
2728
|
+
});
|
|
2729
|
+
//#endregion
|
|
2730
|
+
export { _sfc_main as Comments, _sfc_main$1 as Container, _sfc_main$2 as Feedback, _sfc_main$3 as FeedbackCard, _sfc_main$4 as Header, _sfc_main$5 as LoginCallback, _sfc_main$6 as Page, _sfc_main$7 as PageHeader, _sfc_main$8 as PageTitle, _sfc_main$9 as Socials, _sfc_main$10 as Sponsors, _sfc_main$11 as TableOfContents, _sfc_main$12 as ViewersCounter, useLogin, useTableOfContents_default as useTableOfContents, useUmami };
|