@raclettejs/core 0.1.30 → 0.1.32-nightly

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.
Files changed (83) hide show
  1. package/CHANGELOG.md +32 -3
  2. package/dist/cli.js +1562 -0
  3. package/dist/cli.js.map +7 -0
  4. package/dist/index.js +452 -0
  5. package/dist/index.js.map +7 -0
  6. package/package.json +4 -4
  7. package/services/backend/.yarn/install-state.gz +0 -0
  8. package/services/backend/dist/core/eventBus/index.js +7 -0
  9. package/services/backend/dist/core/pluginSystem/configGenerator/index.js +346 -0
  10. package/services/backend/dist/core/sockets/index.js +95 -0
  11. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/account/events/index.js +31 -0
  12. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/account/index.js +48 -0
  13. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/account/routes/index.js +15 -0
  14. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/composition/events/index.js +22 -0
  15. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/composition/index.js +51 -0
  16. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/composition/routes/index.js +19 -0
  17. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/interactionLink/events/index.js +22 -0
  18. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/interactionLink/index.js +48 -0
  19. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/interactionLink/routes/index.js +19 -0
  20. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/project/events/index.js +31 -0
  21. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/project/index.js +49 -0
  22. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/project/routes/index.js +16 -0
  23. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/tag/events/index.js +39 -0
  24. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/tag/index.js +50 -0
  25. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/tag/routes/index.js +19 -0
  26. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/user/events/index.js +31 -0
  27. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/user/index.js +51 -0
  28. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/user/routes/index.js +21 -0
  29. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/workSession/events/index.js +31 -0
  30. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/workSession/index.js +48 -0
  31. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/workSession/routes/index.js +15 -0
  32. package/services/backend/dist/corePlugins/raclette__core/backend/index.js +34 -0
  33. package/services/backend/dist/domains/index.js +11 -0
  34. package/services/backend/dist/domains/system/index.js +11 -0
  35. package/services/backend/dist/domains/system/routes/index.js +17 -0
  36. package/services/backend/dist/helpers/index.js +14 -0
  37. package/services/backend/dist/index.js +3 -0
  38. package/services/backend/dist/modules/authentication/index.js +253 -0
  39. package/services/backend/dist/shared/types/core/index.js +8 -0
  40. package/services/backend/dist/shared/types/dataTypes/index.js +6 -0
  41. package/services/backend/dist/shared/types/index.js +8 -0
  42. package/services/backend/dist/shared/types/plugins/index.js +8 -0
  43. package/services/backend/dist/types/index.js +12 -0
  44. package/services/backend/dist/utils/index.js +2 -0
  45. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/user/routes/route.user.patchOwn.ts +1 -1
  46. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/user/user.service.ts +22 -1
  47. package/services/backend/yarn.lock +1 -1
  48. package/services/frontend/.yarn/install-state.gz +0 -0
  49. package/services/frontend/package.json +2 -2
  50. package/services/frontend/src/core/lib/data/dataApi.ts +1 -2
  51. package/services/frontend/src/orchestrator/ProductOrchestrator.vue +8 -1
  52. package/services/frontend/src/orchestrator/assets/styles/layers.css +1 -4
  53. package/services/frontend/src/orchestrator/assets/styles/tailwindStyles.css +3 -3
  54. package/services/frontend/src/orchestrator/assets/styles/vuetifyStyles.scss +4 -1
  55. package/services/frontend/src/orchestrator/components/dataExport/DataExporter.vue +303 -0
  56. package/services/frontend/src/orchestrator/components/dataTable/BaseDataTable.vue +473 -0
  57. package/services/frontend/src/orchestrator/components/dataTable/FileUpload.vue +83 -0
  58. package/services/frontend/src/orchestrator/components/dataTable/SelectionDialog.vue +169 -0
  59. package/services/frontend/src/orchestrator/components/dataTable/SummaryDialog.vue +211 -0
  60. package/services/frontend/src/orchestrator/components/index.ts +2 -0
  61. package/services/frontend/src/orchestrator/composables/index.ts +1 -0
  62. package/services/frontend/src/orchestrator/composables/useExport/formats/builtins.ts +124 -0
  63. package/services/frontend/src/orchestrator/composables/useExport/index.ts +176 -0
  64. package/services/frontend/src/orchestrator/composables/useExport/types.ts +141 -0
  65. package/services/frontend/src/orchestrator/i18n/de-DE.json +26 -1
  66. package/services/frontend/src/orchestrator/i18n/en-EU.json +26 -1
  67. package/services/frontend/src/orchestrator/i18n/sk.json +26 -1
  68. package/services/frontend/src/orchestrator/i18n/tl-TL.json +14 -0
  69. package/services/frontend/src/orchestrator/setup/vuetify.ts +0 -1
  70. package/services/frontend/yarn.lock +4854 -0
  71. package/types/index.ts +1 -1
  72. package/package/LICENSE.md +0 -699
  73. package/package/README.md +0 -49
  74. package/package/dev/README.md +0 -4
  75. package/package/index.js +0 -11
  76. package/package/raclette.default.config.yaml +0 -60
  77. package/package/services/backend/README.md +0 -172
  78. package/package/services/backend/yarn.lock +0 -5320
  79. package/package/services/frontend/README.md +0 -511
  80. package/package/services/frontend/src/core/lib/eggs/readme.md +0 -75
  81. package/package/services/frontend/yarn.lock +0 -0
  82. package/package/src/README.md +0 -404
  83. package/package/templates/README.md +0 -0
@@ -0,0 +1,253 @@
1
+ import jwt from "@fastify/jwt";
2
+ import fastifyPlugin from "fastify-plugin";
3
+ import configService from "@c/config/configService";
4
+ import { ForbiddenError } from "@/utils/errors";
5
+ const WORK_SESSION_COLLECTION = "raclette__core-worksession";
6
+ const WORK_SESSION_HEADER = "x-work-session-id";
7
+ /**
8
+ * Middleware: allow either authenticated user (JWT) or valid work session (X-Work-Session-Id).
9
+ * When using this, the route states that it handles permission checks itself (e.g. compare req.workSession with query).
10
+ * - If JWT present and valid: sets req.user (normal auth).
11
+ * - If no/invalid JWT but X-Work-Session-Id present: loads work session, validates (active, not expired),
12
+ * optionally checks that workSession.pluginData includes the given pluginKey, then sets req.workSession and req.isWorkSession.
13
+ */
14
+ export const authenticatedOrWorksession = async (req, res, fastify, pluginKey) => {
15
+ fastify.log.debug(`[Auth] authenticatedOrWorksession: ${pluginKey} ${req.method} ${req.url}`);
16
+ const { requireAuthentication } = configService.getConfig()?.global ?? {
17
+ requireAuthentication: true,
18
+ };
19
+ const isAdminTagRequired = configService.isAdminTagRequired();
20
+ if (!requireAuthentication) {
21
+ const mockUser = { _id: "SYSTEM" };
22
+ req.user = mockUser;
23
+ return;
24
+ }
25
+ // 1) Try JWT first
26
+ try {
27
+ await req.jwtVerify();
28
+ const payload = req.user;
29
+ fastify.log.debug(`[Auth] JWT verified, payload type: ${payload.type || "user"}, sessionId: ${payload.sessionId || "none"}, _id: ${payload._id || "none"}`);
30
+ if (payload.type === "workSession" && payload.sessionId) {
31
+ fastify.log.info(`[Auth] Work session JWT detected, sessionId: ${payload.sessionId}, pluginKey: ${pluginKey}`);
32
+ // Work session JWT: load session and validate
33
+ const workSession = await loadAndValidateWorkSession(fastify, payload.sessionId, pluginKey);
34
+ if (!workSession) {
35
+ fastify.log.warn(`[Auth] Work session validation failed for sessionId: ${payload.sessionId}`);
36
+ return res
37
+ .status(401)
38
+ .send({ message: "Invalid or expired work session" });
39
+ }
40
+ fastify.log.info(`[Auth] Work session authenticated: sessionId=${payload.sessionId}, pluginKey=${pluginKey}`);
41
+ req.workSession = workSession;
42
+ req.isWorkSession = true;
43
+ // Set projectId from workSession.project if available
44
+ if (workSession.project) {
45
+ req.projectId = workSession.project;
46
+ fastify.log.debug(`[Auth] Work session projectId: ${req.projectId}`);
47
+ }
48
+ return;
49
+ }
50
+ // Normal user JWT: fetch user and set req.user
51
+ fastify.log.debug(`[Auth] Normal user JWT, fetching user: ${req.user?._id}`);
52
+ const { _id } = req.user;
53
+ const adminTag = await fastify.database
54
+ .collection("raclette__core-tag")
55
+ .findOne({ title: "ADMIN", locked: true, type: "system" });
56
+ let isAdmin = false;
57
+ const user = await fastify.database
58
+ .collection("raclette__core-user")
59
+ .findOne({ _id }, { projection: { password: 0, resetKey: 0 } });
60
+ if (user && user.tags && adminTag) {
61
+ user.tags.forEach((tag) => {
62
+ if (adminTag._id === tag)
63
+ isAdmin = true;
64
+ });
65
+ }
66
+ if (isAdminTagRequired && !isAdmin) {
67
+ fastify.log.error("User not allowed in workbench");
68
+ return res.status(403).send({ message: "User not allowed in workbench" });
69
+ }
70
+ if (isAdmin)
71
+ user.isAdmin = true;
72
+ if (!user) {
73
+ fastify.log.error("User not found");
74
+ return res.status(401).send({ message: "User not found" });
75
+ }
76
+ const account = await fastify.database
77
+ .collection("raclette__core-account")
78
+ .findOne({ _id: user.account });
79
+ user.account = account;
80
+ req.user = user;
81
+ // Set projectId from user's lastProjectId (for requestData.project in payload wrappers)
82
+ if (user.ux?.lastProjectId) {
83
+ req.projectId = user.ux.lastProjectId;
84
+ fastify.log.debug(`[Auth] User projectId: ${req.projectId}`);
85
+ }
86
+ return;
87
+ }
88
+ catch (error) {
89
+ // JWT missing or invalid: try work session via header
90
+ fastify.log.debug(`[Auth] JWT verification failed: ${error.message}, trying work session header`);
91
+ }
92
+ // 2) Try X-Work-Session-Id header
93
+ const sessionId = req.headers[WORK_SESSION_HEADER];
94
+ if (sessionId) {
95
+ const workSession = await loadAndValidateWorkSession(fastify, sessionId, pluginKey);
96
+ if (workSession) {
97
+ req.workSession = workSession;
98
+ req.isWorkSession = true;
99
+ // Set projectId from workSession.project if available
100
+ // workaround for how projects/spaces are currently handled - tbd
101
+ if (workSession.project) {
102
+ req.projectId = workSession.project;
103
+ }
104
+ return;
105
+ }
106
+ else {
107
+ fastify.log.warn(`[Auth] Work session validation failed for header sessionId: ${sessionId}`);
108
+ }
109
+ }
110
+ return res.status(401).send({ message: "Authentication required" });
111
+ };
112
+ async function loadAndValidateWorkSession(fastify, sessionId, pluginKey) {
113
+ fastify.log.debug(`[WorkSession Auth] Loading and validating work session: sessionId=${sessionId}, pluginKey=${pluginKey}`);
114
+ if (!sessionId || typeof sessionId !== "string") {
115
+ fastify.log.warn(`[WorkSession Auth] Invalid sessionId: ${sessionId}`);
116
+ return null;
117
+ }
118
+ fastify.log.debug(`[WorkSession Auth] Querying database for work session`);
119
+ const doc = await fastify.database
120
+ .collection(WORK_SESSION_COLLECTION)
121
+ .findOne({ _id: sessionId });
122
+ if (!doc) {
123
+ fastify.log.warn(`[WorkSession Auth] Work session not found: ${sessionId}`);
124
+ return null;
125
+ }
126
+ if (!doc.isActive) {
127
+ fastify.log.warn(`[WorkSession Auth] Work session is not active: ${sessionId}`);
128
+ return null;
129
+ }
130
+ const expiry = doc.expiryDate ? new Date(doc.expiryDate) : null;
131
+ if (expiry && expiry.getTime() < Date.now()) {
132
+ fastify.log.warn(`[WorkSession Auth] Work session expired: ${sessionId}, expiryDate: ${expiry.toISOString()}`);
133
+ return null;
134
+ }
135
+ // Check that this plugin is part of the work session (pluginData keys are "pluginKey:widgetName")
136
+ const pluginData = doc.pluginData || {};
137
+ const pluginKeys = Object.keys(pluginData);
138
+ const hasPlugin = pluginKeys.some((k) => k.startsWith(pluginKey + ":")) ||
139
+ Object.prototype.hasOwnProperty.call(pluginData, pluginKey);
140
+ if (!hasPlugin) {
141
+ fastify.log.warn(`[WorkSession Auth] Plugin ${pluginKey} not found in work session pluginData: ${pluginKeys.join(", ")}`);
142
+ return null;
143
+ }
144
+ fastify.log.info(`[WorkSession Auth] Work session validated successfully: sessionId=${sessionId}, pluginKey=${pluginKey}`);
145
+ return doc;
146
+ }
147
+ export const authenticate = async (req, res, fastify) => {
148
+ {
149
+ const { requireAuthentication } = configService.getConfig()?.global ?? {
150
+ requireAuthentication: true,
151
+ };
152
+ const isAdminTagRequired = configService.isAdminTagRequired();
153
+ // Skip JWT verification and database queries, if global.requireAuthentication is configured to false
154
+ if (!requireAuthentication) {
155
+ const mockUser = {
156
+ _id: "SYSTEM",
157
+ };
158
+ req.user = mockUser;
159
+ return;
160
+ }
161
+ try {
162
+ await req.jwtVerify();
163
+ const payload = req.user;
164
+ if (payload.type === "workSession") {
165
+ return res
166
+ .status(401)
167
+ .send({ message: "Work session token not allowed on this route" });
168
+ }
169
+ // fetch current user data from the db
170
+ const { _id } = req.user;
171
+ const adminTag = await fastify.database
172
+ .collection("raclette__core-tag")
173
+ .findOne({ title: "ADMIN", locked: true, type: "system" });
174
+ // IMPORTANT
175
+ // due to some very weird behaviour, mongoose always returns empty results
176
+ // so we are using mongodb directly until we fix this
177
+ let isAdmin = false;
178
+ const user = await fastify.database
179
+ .collection("raclette__core-user")
180
+ .findOne({ _id }, { projection: { password: 0, resetKey: 0 } });
181
+ if (user && user.tags && adminTag) {
182
+ user.tags.forEach((tag) => {
183
+ if (adminTag._id === tag) {
184
+ isAdmin = true;
185
+ }
186
+ });
187
+ }
188
+ if (isAdminTagRequired && !isAdmin) {
189
+ fastify.log.error("User not allowed in workbench");
190
+ throw new Error("User not allowed in workbench");
191
+ }
192
+ if (isAdmin) {
193
+ user.isAdmin = true;
194
+ }
195
+ if (!user) {
196
+ fastify.log.error("User not found");
197
+ throw new Error("User not found");
198
+ }
199
+ const account = await fastify.database
200
+ .collection("raclette__core-account")
201
+ .findOne({ _id: user.account });
202
+ user.account = account;
203
+ req.user = user;
204
+ }
205
+ catch (error) {
206
+ return res.send(error);
207
+ }
208
+ }
209
+ };
210
+ export const checkPermissions = async (req, res, fastify, permissions) => {
211
+ {
212
+ try {
213
+ if (!permissions.length) {
214
+ fastify.log.error("No permissions given for permission check");
215
+ throw new Error("No Permissions");
216
+ }
217
+ // TODO implement proper Permission lookup here
218
+ permissions.forEach((permissionStr) => {
219
+ if (permissionStr === "user.isAdmin") {
220
+ if (!req.user.isAdmin) {
221
+ throw new ForbiddenError("User not permitted to action");
222
+ }
223
+ }
224
+ });
225
+ }
226
+ catch (error) {
227
+ fastify.log.error(error.message);
228
+ return res.send(error.message);
229
+ }
230
+ }
231
+ };
232
+ export default fastifyPlugin(async (fastify, opts = {}) => {
233
+ // provide some sensible options
234
+ const defaults = {
235
+ secret: false,
236
+ decode: { complete: true },
237
+ sign: {
238
+ iss: "api.raclettejs.info",
239
+ expiresIn: "30d",
240
+ },
241
+ verify: { allowedIss: "api.raclettejs.info" },
242
+ };
243
+ // merge proved opts with defaults
244
+ const mergedOptions = { ...defaults, ...opts };
245
+ /* register the plugin */
246
+ fastify.register(jwt, mergedOptions);
247
+ // decorate the fastify instance, so we can call it anywhere
248
+ fastify.decorate("authenticate", (req, res) => {
249
+ return authenticate(req, res, fastify);
250
+ });
251
+ fastify.decorate("authenticatedOrWorksession", (pluginKey) => (req, res) => authenticatedOrWorksession(req, res, fastify, pluginKey));
252
+ fastify.decorate("checkPermissions", (permissionsArr) => (req, res) => checkPermissions(req, res, fastify, permissionsArr));
253
+ });
@@ -0,0 +1,8 @@
1
+ /**
2
+ * ⚠️ WARNING: THIS FILE IS AUTO-GENERATED ⚠️
3
+ *
4
+ * This file is generated by the type generation system.
5
+ * Any manual changes will be overwritten on next server start and should always be committed.
6
+ *
7
+ */
8
+ export {};
@@ -0,0 +1,6 @@
1
+ export * from "./tags";
2
+ // TODO make the mongoDataTypes dynamic
3
+ export const systemDataTypes = ["account", "projectConfig", "userConfig"];
4
+ export const userContents = ["tag", "user", "project"];
5
+ export const mongoDataTypes = systemDataTypes.concat(userContents);
6
+ export const syncableDatatypes = ["tag", "user", "searchResult"];
@@ -0,0 +1,8 @@
1
+ /**
2
+ * ⚠️ WARNING: THIS FILE IS AUTO-GENERATED ⚠️
3
+ *
4
+ * This file is generated by the type generation system.
5
+ * Any manual changes will be overwritten on next server start and should always be committed.
6
+ *
7
+ */
8
+ export {};
@@ -0,0 +1,8 @@
1
+ /**
2
+ * ⚠️ WARNING: THIS FILE IS AUTO-GENERATED ⚠️
3
+ *
4
+ * This file is generated by the type generation system.
5
+ * Any manual changes will be overwritten on next server start and should always be committed.
6
+ *
7
+ */
8
+ export {};
@@ -0,0 +1,12 @@
1
+ export * from "../core/backgroundTasks/taskManagerTypes";
2
+ export * from "../core/config/configTypes";
3
+ export * from "../core/crud/crudTypes";
4
+ export * from "../core/http/httpTypes";
5
+ export * from "../core/http/httpTypes";
6
+ export * from "../core/payload/payloadTypes";
7
+ export * from "../core/pluginSystem/pluginTypes";
8
+ export * from "../core/sockets/socketTypes";
9
+ export * from "../core/validation/schemaTypes";
10
+ export * from "../modules/cache/cacheTypes";
11
+ export * from "../core/validation/schemaTypes";
12
+ export * from "../core/pluginSystem/configGenerator/generatorTypes";
@@ -0,0 +1,2 @@
1
+ export * from "./errors";
2
+ export * from "./request.utils";
@@ -26,7 +26,7 @@ export default (fastify: PluginFastifyInstance) => {
26
26
  if (req.body?.email?.length) {
27
27
  updateObject.email = req.body.email
28
28
  // TODO Creation of the field should be happening in the user.model pre save hook
29
- updateObject.email_verified = false
29
+ // updateObject.email_verified = false
30
30
  }
31
31
  if (req.body?.firstname?.length) {
32
32
  updateObject.firstname = req.body.firstname
@@ -135,7 +135,8 @@ export class UserService {
135
135
  options: QueryOptions = {},
136
136
  ): Promise<UserType[]> {
137
137
  try {
138
- if (filter.isDeleted !== true && filter.isDeleted !=='true') filter.isDeleted = false
138
+ if (filter.isDeleted !== true && filter.isDeleted !== "true")
139
+ filter.isDeleted = false
139
140
  // Start building the query
140
141
  let query = this.userModel.find(filter)
141
142
 
@@ -319,7 +320,27 @@ export class UserService {
319
320
  throw error
320
321
  }
321
322
  }
323
+ /**
324
+ * Hard delete a user with payload wrapping and event emission
325
+ */
326
+ async hardDeleteUser(
327
+ fastify: PluginFastifyInstance,
328
+ requestData: FrontendPayloadRequestData,
329
+ id: string,
330
+ ): Promise<FrontendPayload<UserType[]>> {
331
+ const user = await this._hardDeleteUser(fastify, id)
332
+
333
+ const payload = await createUserPayload(fastify, [user], {
334
+ ...requestData,
335
+ type: "dataHardDeleted",
336
+ })
337
+
338
+ payload.body.deleted = [user._id]
322
339
 
340
+ fastify.emit("userHardDeleted", payload)
341
+
342
+ return payload
343
+ }
323
344
  /**
324
345
  * Core function to update user's last accessed project (returns raw data)
325
346
  */
@@ -2,7 +2,7 @@
2
2
  # Manual changes might be lost - proceed with caution!
3
3
 
4
4
  __metadata:
5
- version: 8
5
+ version: 9
6
6
  cacheKey: 10c0
7
7
 
8
8
  "@colors/colors@npm:1.5.0":
@@ -41,12 +41,12 @@
41
41
  "vuetify": "4.0.5"
42
42
  },
43
43
  "devDependencies": {
44
+ "@eslint/js": "9.35.0",
44
45
  "@types/node": "24.5.1",
45
46
  "@types/ramda": "0.31.1",
46
47
  "@vitejs/plugin-vue": "6.0.5",
47
48
  "@vue/tsconfig": "0.9.0",
48
49
  "eslint": "9.35.0",
49
- "@eslint/js": "9.35.0",
50
50
  "eslint-config-prettier": "9.1.2",
51
51
  "eslint-plugin-import": "2.32.0",
52
52
  "eslint-plugin-prefer-arrow-functions": "3.9.1",
@@ -60,4 +60,4 @@
60
60
  "vue-eslint-parser": "9.4.3",
61
61
  "vue-tsc": "3.2.6"
62
62
  }
63
- }
63
+ }
@@ -79,10 +79,9 @@ const $writeDataApi = (
79
79
  responseSubject$.complete()
80
80
  },
81
81
  clear: () => {
82
- dataSubject$.next({})
82
+ dataSubject$.next([])
83
83
  errorSubject$.next({})
84
84
  isLoadingSubject$.next(false)
85
- dataSubject$.next({})
86
85
  },
87
86
  }
88
87
 
@@ -112,13 +112,14 @@
112
112
  rel="noopener noreferrer"
113
113
  target="_blank"
114
114
  title="raclette"
115
- class="tw:text-[0.9rem]"
115
+ class="tw:text-[0.9rem] tw:text-neutral-500 tw:no-underline hover:tw:no-underline hover:tw:text-neutral-700 tw:transition-colors"
116
116
  >
117
117
  Cooked with <span class="raclette">raclette 🧀</span></a
118
118
  >
119
119
  <span
120
120
  v-if="configService.getAdvertiseYourself().length"
121
121
  v-html="configService.getAdvertiseYourself()"
122
+ class="footerLink tw:text-[0.9rem] tw:text-neutral-500 tw:no-underline hover:tw:no-underline hover:tw:text-neutral-700 tw:transition-colors"
122
123
  ></span>
123
124
  </v-footer>
124
125
  </v-main>
@@ -285,3 +286,9 @@ window
285
286
  }
286
287
  })
287
288
  </script>
289
+ <style lang="scss">
290
+ .footerLink a {
291
+ text-decoration: none;
292
+ color: var(--tw-color-neutral-500);
293
+ }
294
+ </style>
@@ -1,11 +1,8 @@
1
- @layer tailwind-theme;
2
- @layer tailwind-reset;
3
-
4
1
  @layer vuetify-core;
5
2
  @layer vuetify-components;
6
3
  @layer vuetify-overrides;
7
4
  @layer vuetify-utilities;
8
5
 
9
- @layer tailwind-utilities;
6
+ @layer tailwind; /* <-- our new utilities */
10
7
 
11
8
  @layer vuetify-final;
@@ -1,9 +1,9 @@
1
1
  @source "../../../../index.html";
2
2
  @source "/**/*.{vue,js,ts,jsx,tsx,html}";
3
3
 
4
- @import "tailwindcss/theme" layer(tailwind-theme) prefix(tw);
5
- @import "tailwindcss/preflight" layer(tailwind-reset) prefix(tw);
6
- @import "tailwindcss/utilities" layer(tailwind-utilities) prefix(tw);
4
+ @import "tailwindcss/theme" layer(tailwind.theme) prefix(tw);
5
+ /*@import "tailwindcss/preflight" layer(tailwind-reset) prefix(tw);*/
6
+ @import "tailwindcss/utilities" layer(tailwind.utilities) prefix(tw);
7
7
 
8
8
  @custom-variant light (&:where(.v-theme--light, .v-theme--light *));
9
9
  @custom-variant dark (&:where(.v-theme--dark, .v-theme--dark *));
@@ -1,5 +1,8 @@
1
1
  @use "@mdi/font/css/materialdesignicons.css";
2
-
2
+ @use "vuetify/settings" with (
3
+ $color-pack: false,
4
+ $utilities: false
5
+ );
3
6
  // The styles below are picked from vuetify's reset css (which is disabled)
4
7
 
5
8
  /* Apply cursor pointer to button elements */