@lilaquadrat/design-core 0.1.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.
Files changed (86) hide show
  1. package/README.md +236 -0
  2. package/html/index.html +25 -0
  3. package/html/index.mail.html +15 -0
  4. package/html/index.server.html +29 -0
  5. package/package.json +148 -0
  6. package/src/client-entry.ts +28 -0
  7. package/src/components/partials/action.partial.vue +30 -0
  8. package/src/components/partials/client-only.partial.vue +11 -0
  9. package/src/components/partials/error.partial.vue +73 -0
  10. package/src/components/partials/main-components.partial.vue +167 -0
  11. package/src/components/partials/mediadetection.partial.vue +71 -0
  12. package/src/components/partials/qrcode.partial.vue +35 -0
  13. package/src/customs.ts +12 -0
  14. package/src/env.d.ts +1 -0
  15. package/src/functions/PaymenyProviderFactory.ts +27 -0
  16. package/src/functions/lila-dom.ts +100 -0
  17. package/src/functions/shopify.provider.ts +378 -0
  18. package/src/functions/stripe.provider.ts +181 -0
  19. package/src/globals.d.ts +19 -0
  20. package/src/index.ts +47 -0
  21. package/src/interfaces/AppState.interface.ts +4 -0
  22. package/src/interfaces/EditorConfiguration.interface.ts +15 -0
  23. package/src/interfaces/EventDeclaration.interface.ts +19 -0
  24. package/src/interfaces/FrontendConfig.interface.ts +42 -0
  25. package/src/interfaces/GenericEvents.interface.ts +6 -0
  26. package/src/interfaces/GenericState.interface.ts +5 -0
  27. package/src/interfaces/IconsPartial.ts +9 -0
  28. package/src/interfaces/IdTokenExtended.interface.ts +7 -0
  29. package/src/interfaces/ModuleBaseProps.interface.ts +8 -0
  30. package/src/interfaces/PaymentProvider.interface.ts +13 -0
  31. package/src/libs/ActionNotice.ts +235 -0
  32. package/src/libs/Models.class.ts +482 -0
  33. package/src/main.ts +84 -0
  34. package/src/mixins/createCookieString.ts +78 -0
  35. package/src/mixins/createModelDeclaration.ts +29 -0
  36. package/src/mixins/createRouter.ts +30 -0
  37. package/src/mixins/date.ts +23 -0
  38. package/src/mixins/formatSize.ts +12 -0
  39. package/src/mixins/getAnchor.ts +14 -0
  40. package/src/mixins/getRoutes.ts +50 -0
  41. package/src/mixins/hasSlotContent.ts +32 -0
  42. package/src/mixins/hooks.ts +104 -0
  43. package/src/mixins/loadComponents.ts +107 -0
  44. package/src/mixins/logger.ts +32 -0
  45. package/src/mixins/replaceVariables.ts +19 -0
  46. package/src/mixins/scroll.ts +83 -0
  47. package/src/models/Address.model.ts +24 -0
  48. package/src/models/Contact.model.ts +22 -0
  49. package/src/models.ts +2 -0
  50. package/src/plugins/auth.ts +121 -0
  51. package/src/plugins/currency.ts +26 -0
  52. package/src/plugins/events.ts +156 -0
  53. package/src/plugins/filters.ts +43 -0
  54. package/src/plugins/inview.ts +351 -0
  55. package/src/plugins/replacer.ts +18 -0
  56. package/src/plugins/resize.ts +128 -0
  57. package/src/plugins/signupFlow.ts +89 -0
  58. package/src/plugins/traceable.ts +53 -0
  59. package/src/plugins/translations.ts +29 -0
  60. package/src/plugins/youtube.ts +80 -0
  61. package/src/routes.ts +132 -0
  62. package/src/server-entry.ts +113 -0
  63. package/src/stores/calls.store.ts +33 -0
  64. package/src/stores/cart.store.ts +186 -0
  65. package/src/stores/content.store.ts +83 -0
  66. package/src/stores/editor.store.ts +27 -0
  67. package/src/stores/files.store.ts +166 -0
  68. package/src/stores/main.store.ts +184 -0
  69. package/src/stores/user.store.ts +124 -0
  70. package/src/translations/de.ts +138 -0
  71. package/src/views/content.view.vue +219 -0
  72. package/src/views/download.view.vue +143 -0
  73. package/src/views/editor.view.vue +243 -0
  74. package/src/views/login.view.vue +82 -0
  75. package/src/views/signup-account.view.vue +64 -0
  76. package/tooling/cypress/config.ts +80 -0
  77. package/tooling/cypress/generate-manifest.js +5 -0
  78. package/tooling/cypress/manifest-utils.mjs +40 -0
  79. package/tooling/cypress/run-parallel.mjs +77 -0
  80. package/tooling/cypress/support/commands.ts +436 -0
  81. package/tooling/cypress/support/e2e.ts +17 -0
  82. package/tooling/eslint.config.js +223 -0
  83. package/tooling/preview.plugin.ts +134 -0
  84. package/tooling/ssr-test/index.mjs +391 -0
  85. package/tooling/stylelint.config.mjs +24 -0
  86. package/tooling/vite.ts +246 -0
@@ -0,0 +1,436 @@
1
+ /// <reference types="cypress" />
2
+ /* eslint-disable no-unused-vars */
3
+ declare global {
4
+ namespace Cypress {
5
+ interface Chainable {
6
+ getModuleVariantKeysFromString(moduleName: string): Chainable<string[]>
7
+ captureScreenSizes(module: string, variant: string, ...args: string[]): Chainable<void>
8
+ updateVariant(variant: string): Chainable<void>
9
+ updateTextEl(selector: string, textSize: string, parentElement?: string): Chainable<void>
10
+ updateTextblock(textSize: string, parentClassname?: string | null): Chainable<void>
11
+ updateListLinks(textSize: string, parentElement?: string): Chainable<void>
12
+ updatePictureSrc(src: string): Chainable<void>
13
+ updateGalleryImage(index: number, src: string): Chainable<void>
14
+ navigateGallery(direction: 'next' | 'prev'): Chainable<void>
15
+ updateButton(text: string, className?: string): Chainable<void>
16
+ updateAllTextblocks(textSize: string, parentElement?: string): Chainable<void>
17
+ }
18
+ }
19
+ }
20
+
21
+ /* eslint-enable no-unused-vars */
22
+
23
+ Cypress.Commands.add('captureScreenSizes', (module: string, variant: string, ...args: string[]) => {
24
+ const viewports = [
25
+ { width: 375, height: 667, viewport: 'iphone-6' },
26
+ { width: 768, height: 1024, viewport: 'tablet' },
27
+ { width: 1366, height: 768, viewport: 'laptop' },
28
+ { width: 1920, height: 1080, viewport: 'desktop' },
29
+ ];
30
+
31
+ viewports.forEach(viewport => {
32
+ cy.viewport(viewport.width, viewport.height);
33
+ cy.wait(200);
34
+
35
+ cy.document().then((doc) => {
36
+ const fullHeight = Math.max(
37
+ doc.body.scrollHeight,
38
+ doc.body.offsetHeight,
39
+ doc.documentElement.scrollHeight,
40
+ doc.documentElement.offsetHeight
41
+ );
42
+
43
+ if (fullHeight > viewport.height) {
44
+ cy.viewport(viewport.width, fullHeight);
45
+ cy.wait(200);
46
+ }
47
+ });
48
+
49
+ const variantLabel = `variant=${variant || 'default'}`;
50
+ const contentLabel = `content=${args.join('_')}`;
51
+ const screenLabel = `screen=${viewport.viewport}-${viewport.width}x${viewport.height}`;
52
+
53
+ cy.screenshot(`${module}/${variantLabel}--${contentLabel}--${screenLabel}`, { capture: 'fullPage', overwrite: true });
54
+
55
+ });
56
+ });
57
+
58
+ Cypress.Commands.add('getModuleVariantKeysFromString', (moduleName: string) => {
59
+ cy.readFile('src/modules.browser.ts').then((content) => {
60
+
61
+ const moduleRegex = new RegExp(
62
+ `{\\s*name\\s*:\\s*['"]${moduleName}['"][\\s\\S]*?variants\\s*:\\s*\\[([\\s\\S]*?)\\]`,
63
+ 'm'
64
+ );
65
+ const match = content.match(moduleRegex);
66
+
67
+ if (!match) {
68
+ return cy.wrap([]);
69
+ }
70
+
71
+ const variantsBlock = match[1];
72
+ const variantRegex = /{[\s\S]*?key\s*:\s*['"]([^'"]+)['"][\s\S]*?}/g;
73
+ const keys: string[] = [];
74
+ let variantMatch;
75
+
76
+ while ((variantMatch = variantRegex.exec(variantsBlock)) !== null) {
77
+ const variantChunk = variantMatch[0];
78
+ const key = variantMatch[1];
79
+ const hasGroup = /group\s*:/.test(variantChunk);
80
+
81
+ if (hasGroup || key.startsWith('pdf-')) {
82
+ continue;
83
+ }
84
+
85
+ keys.push(key);
86
+ }
87
+
88
+ return cy.wrap(keys);
89
+ });
90
+ });
91
+
92
+ Cypress.Commands.add('updateVariant', (variant: string) => {
93
+ cy.get('.presentation')
94
+ .first()
95
+ .children()
96
+ .first()
97
+ .then(($module) => {
98
+ const prev = $module.data('activeVariant');
99
+
100
+ if (prev) $module.removeClass(prev);
101
+ if (variant) $module.addClass(variant);
102
+ $module.data('activeVariant', variant || '');
103
+
104
+ const $defaultEls = $module.find('.default');
105
+
106
+ $defaultEls.each((_, el) => {
107
+ const $el = Cypress.$(el);
108
+ const prevElVariant = $el.data('activeVariant');
109
+
110
+ if (prevElVariant) $el.removeClass(prevElVariant);
111
+ if (variant) $el.addClass(variant);
112
+ $el.data('activeVariant', variant || '');
113
+ });
114
+ });
115
+ });
116
+
117
+ // todo:
118
+ // - fix updating list items
119
+ // parameter sample ('h1', 'short', 'intro-container') or ('h1', 'short')
120
+ Cypress.Commands.add('updateTextEl', (selector: string, textSize: string, parentElement?: string) => {
121
+ cy.fixture('textblock-inputs').then((data) => {
122
+ const sizeOptions = ['short', 'medium', 'long'] as const;
123
+ let resolvedTextSize: string;
124
+ let pool: string[];
125
+
126
+ if (textSize === 'number') {
127
+ pool = data.number;
128
+ } else if (textSize === 'button') {
129
+ pool = data.button;
130
+ } else {
131
+ resolvedTextSize = textSize === 'random'
132
+ ? sizeOptions[Math.floor(Math.random() * sizeOptions.length)]
133
+ : textSize;
134
+
135
+ pool = data.list[resolvedTextSize];
136
+ }
137
+
138
+ const randomText = pool ? pool[Math.floor(Math.random() * pool.length)] : '';
139
+
140
+ if (!parentElement) {
141
+ cy.get('body').then(($body) => {
142
+ if (!$body.find(selector).length) return;
143
+
144
+ cy.get(selector).then($els => {
145
+ $els.each((_, el) => {
146
+ Cypress.$(el).text(randomText);
147
+ });
148
+ });
149
+ });
150
+ } else {
151
+ cy.get('body').then(($body) => {
152
+ if (!$body.find(`.${parentElement}`).length) return;
153
+
154
+ cy.get(`.${parentElement}`)
155
+ .first().then($el => {
156
+ const $selected = $el.find(selector);
157
+
158
+ if (!$selected.length) return;
159
+
160
+ $selected.each((_, el) => {
161
+ Cypress.$(el).text(randomText);
162
+ });
163
+ });
164
+ });
165
+ }
166
+ });
167
+ });
168
+
169
+ // parameter sample ('short', 'intro-container') or ('short')
170
+ Cypress.Commands.add('updateTextblock', (textSize: string, parentClassname?: string | null) => {
171
+ const tags = ['h1', 'h2', 'h3', 'p'] as const;
172
+ const sizeOptions = ['short', 'medium', 'long'] as const;
173
+ const listTypes = ['list', 'links'] as const;
174
+ const tagToDataKey = {
175
+ h1: 'headline',
176
+ h2: 'subline',
177
+ h3: 'intro',
178
+ p : 'paragraph',
179
+ } as const;
180
+ const selector = parentClassname
181
+ ? `.lila-textblock.${parentClassname}`
182
+ : '.lila-textblock';
183
+
184
+ cy.fixture('textblock-inputs').then((data) => {
185
+ cy.get('body').then(($body) => {
186
+ if (!$body.find(selector).length) return;
187
+
188
+ cy.get(selector)
189
+ .first()
190
+ .then(($block) => {
191
+ tags.forEach((tag) => {
192
+ const dataKey = tagToDataKey[tag];
193
+ const $tag = $block.find(tag).first();
194
+
195
+ if (!$tag.length) return;
196
+
197
+ const currentSize =
198
+ textSize === 'random'
199
+ ? sizeOptions[Math.floor(Math.random() * sizeOptions.length)]
200
+ : textSize;
201
+ const pool = data[dataKey]?.[currentSize];
202
+
203
+ if (!pool?.length) return;
204
+
205
+ const randomText = pool[Math.floor(Math.random() * pool.length)];
206
+
207
+ cy.wrap($tag).invoke('text', randomText);
208
+ });
209
+ });
210
+
211
+ listTypes.forEach((listType) => {
212
+ if (!$body.find(`.${listType}`).length) return;
213
+
214
+ const currentSize = textSize === 'random'
215
+ ? sizeOptions[Math.floor(Math.random() * sizeOptions.length)]
216
+ : textSize;
217
+
218
+ cy.get(`.${listType}`).first().then(($block) => {
219
+ cy.get(`.${listType}`).first().then(($parent) => {
220
+ if (!$parent.find('h4').length) return;
221
+
222
+ cy.wrap($parent.find('h4').first()).invoke(
223
+ 'text',
224
+ data.list[currentSize][Math.floor(Math.random() * data.list[currentSize].length)]
225
+ );
226
+ });
227
+
228
+ const items = $block.find('li');
229
+
230
+ items.each((index, item) => {
231
+ const text = data.list[currentSize][index % data.list[currentSize].length];
232
+
233
+ cy.wrap(item).then(($li) => {
234
+ if (listType === 'links') {
235
+ const $link = $li.find('a:first');
236
+
237
+ if ($link.length) {
238
+ $link.text(text);
239
+ }
240
+ } else {
241
+ const li = $li[0];
242
+
243
+ if (li.lastChild && li.lastChild.nodeType === Node.TEXT_NODE) {
244
+ li.lastChild.nodeValue = text;
245
+ }
246
+ }
247
+ });
248
+ });
249
+ });
250
+ });
251
+ });
252
+ });
253
+ });
254
+
255
+ Cypress.Commands.add('updateListLinks', (textSize: string, parentElement?: string) => {
256
+ const target = parentElement ? `.${parentElement} .lila-list-links` : '.lila-list-links';
257
+
258
+ cy.fixture('textblock-inputs').then((data) => {
259
+ const sizeOptions = ['short', 'medium', 'long'] as const;
260
+ const resolvedTextSize = textSize === 'random' ? sizeOptions[Math.floor(Math.random() * sizeOptions.length)] : textSize;
261
+ const pool = data.list[resolvedTextSize];
262
+
263
+ if (!pool?.length) return;
264
+
265
+ cy.get('body').then(($body) => {
266
+ if (!$body.find(target).length) return;
267
+
268
+ cy.get(target).each(($list) => {
269
+ const $h4 = $list.find('h4');
270
+
271
+ if ($h4.length) {
272
+ $h4.text(pool[Math.floor(Math.random() * pool.length)]);
273
+ }
274
+
275
+ $list.find('li').each((index, item) => {
276
+ const text = pool[index % pool.length];
277
+ const $a = Cypress.$(item).find('a:first');
278
+
279
+ if ($a.length) {
280
+ $a.text(text);
281
+ } else if (item.lastChild && item.lastChild.nodeType === Node.TEXT_NODE) {
282
+ item.lastChild.nodeValue = text;
283
+ }
284
+ });
285
+ });
286
+ });
287
+ });
288
+ });
289
+
290
+ Cypress.Commands.add('updatePictureSrc', (data: { src: string; alt?: string; caption?: string } | string) => {
291
+ // Define tag selectors at the top
292
+ const selectors = {
293
+ img : 'picture img',
294
+ caption: 'figcaption',
295
+ };
296
+ const applyImageData = (imageData: { src: string; alt?: string; caption?: string }) => {
297
+ cy.get('body').then(($body) => {
298
+ if ($body.find(selectors.img).length) {
299
+ cy.get(selectors.img).invoke('attr', 'src', imageData.src);
300
+
301
+ if (imageData.alt) {
302
+ cy.get(selectors.img).invoke('attr', 'alt', imageData.alt);
303
+ }
304
+ }
305
+
306
+ if (imageData.caption && $body.find(selectors.caption).length) {
307
+ cy.get(selectors.caption).invoke('text', imageData.caption);
308
+ }
309
+ });
310
+ };
311
+
312
+ if (typeof data === 'string') {
313
+ // Resolve string like 'horizontal-0' or 'horizontal-0.jpg' to fixture object
314
+ const key = data.replace(/\.[^/.]+$/, ''); // strip extension
315
+ const match = key.match(/^(.+)-(\d+)$/);
316
+
317
+ if (match) {
318
+ const [, category, indexStr] = match;
319
+ const index = parseInt(indexStr, 10);
320
+
321
+ cy.fixture('image-inputs').then((fixtureData) => {
322
+ const resolved = fixtureData[category]?.[index];
323
+
324
+ if (resolved) {
325
+ applyImageData(resolved);
326
+ } else {
327
+ applyImageData({ src: data });
328
+ }
329
+ });
330
+ } else {
331
+ applyImageData({ src: data });
332
+ }
333
+ } else {
334
+ applyImageData(data);
335
+ }
336
+ });
337
+
338
+ Cypress.Commands.add('updateGalleryImage', (index: number, data: { src: string; alt?: string; caption?: string } | string) => {
339
+ const imageData = typeof data === 'string' ? { src: data } : data;
340
+
341
+ cy.get('body').then(($body) => {
342
+ const $elements = $body.find('.gallery-module .element');
343
+
344
+ if (!$elements.length || !$elements.eq(index).length) return;
345
+
346
+ const $img = $elements.eq(index).find('picture img');
347
+
348
+ if ($img.length) {
349
+ cy.get('.gallery-module .element').eq(index).find('picture img').invoke('attr', 'src', imageData.src);
350
+
351
+ if (imageData.alt) {
352
+ cy.get('.gallery-module .element').eq(index).find('picture img').invoke('attr', 'alt', imageData.alt);
353
+ }
354
+ }
355
+
356
+ if (imageData.caption) {
357
+ const $caption = $elements.eq(index).find('figcaption');
358
+
359
+ if ($caption.length) {
360
+ cy.get('.gallery-module .element').eq(index).find('figcaption').invoke('text', imageData.caption);
361
+ }
362
+ }
363
+ });
364
+ });
365
+
366
+ Cypress.Commands.add('navigateGallery', (direction: 'next' | 'prev') => {
367
+ const selector = direction === 'next' ? '.gallery-controls button:last-child' : '.gallery-controls button:first-child';
368
+
369
+ cy.get('body').then(($body) => {
370
+ if ($body.find(selector).length) {
371
+ cy.get(selector).click();
372
+ cy.wait(500);
373
+ }
374
+ });
375
+ });
376
+
377
+ Cypress.Commands.add('updateButton', (text: string, className?: string) => {
378
+ cy.get('body').then(($body) => {
379
+ if (!$body.find('.lila-button, .button').length) return;
380
+
381
+ cy.get('.lila-button, .button').each(($btn) => {
382
+ if (text === 'random') {
383
+ cy.fixture('textblock-inputs').then((data) => {
384
+ const pool = data.button;
385
+ const randomText = pool[Math.floor(Math.random() * pool.length)];
386
+
387
+ cy.wrap($btn).invoke('text', randomText);
388
+ });
389
+ } else {
390
+ cy.wrap($btn).invoke('text', text);
391
+ }
392
+
393
+ if (className) {
394
+ cy.wrap($btn).invoke('addClass', className);
395
+ }
396
+ });
397
+ });
398
+ });
399
+
400
+ Cypress.Commands.add('updateAllTextblocks', (textSize: string, parentElement?: string) => {
401
+ const tags = ['h1', 'h2', 'h3', 'p'] as const;
402
+ const sizeOptions = ['short', 'medium', 'long'] as const;
403
+ const tagToDataKey = {
404
+ h1: 'headline',
405
+ h2: 'subline',
406
+ h3: 'intro',
407
+ p : 'paragraph',
408
+ } as const;
409
+ const selector = parentElement ? `.${parentElement} .lila-textblock` : '.lila-textblock';
410
+
411
+ cy.fixture('textblock-inputs').then((data) => {
412
+ cy.get('body').then(($body) => {
413
+ if (!$body.find(selector).length) return;
414
+
415
+ cy.get(selector).each(($block) => {
416
+ tags.forEach((tag) => {
417
+ const dataKey = tagToDataKey[tag];
418
+ const $tag = $block.find(tag).first();
419
+
420
+ if (!$tag.length) return;
421
+
422
+ const currentSize = textSize === 'random' ? sizeOptions[Math.floor(Math.random() * sizeOptions.length)] : textSize as typeof sizeOptions[number];
423
+ const pool = data[dataKey]?.[currentSize];
424
+
425
+ if (!pool?.length) return;
426
+
427
+ const randomText = pool[Math.floor(Math.random() * pool.length)];
428
+
429
+ cy.wrap($tag).invoke('text', randomText);
430
+ });
431
+ });
432
+ });
433
+ });
434
+ });
435
+
436
+ export {}
@@ -0,0 +1,17 @@
1
+ // ***********************************************************
2
+ // This example support/e2e.ts is processed and
3
+ // loaded automatically before your test files.
4
+ //
5
+ // This is a great place to put global configuration and
6
+ // behavior that modifies Cypress.
7
+ //
8
+ // You can change the location of this file or turn off
9
+ // automatically serving support files with the
10
+ // 'supportFile' configuration option.
11
+ //
12
+ // You can read more here:
13
+ // https://on.cypress.io/configuration
14
+ // ***********************************************************
15
+
16
+ // Import commands.js using ES2015 syntax:
17
+ import './commands'
@@ -0,0 +1,223 @@
1
+ import eslint from '@eslint/js';
2
+ import tseslint from '@typescript-eslint/eslint-plugin';
3
+ import pluginVue from 'eslint-plugin-vue'
4
+ import typescriptParser from '@typescript-eslint/parser';
5
+ import vueParser from 'vue-eslint-parser';
6
+ import globals from 'globals';
7
+
8
+ export default [
9
+ // Default ignores - including all patterns from .eslintignore
10
+ {
11
+ ignores: [
12
+ // Project specific ignores
13
+ 'src/views/viewData/*',
14
+ 'config.ts',
15
+ 'stats.html',
16
+
17
+ // Build outputs
18
+ 'dist/**',
19
+ 'dist-ssr/**',
20
+ 'coverage/**',
21
+ '*.local',
22
+
23
+ // Logs
24
+ 'logs/**',
25
+ '*.log',
26
+ 'npm-debug.log*',
27
+ 'yarn-debug.log*',
28
+ 'yarn-error.log*',
29
+ 'pnpm-debug.log*',
30
+ 'lerna-debug.log*',
31
+
32
+ // Dependencies
33
+ 'node_modules/**',
34
+ '.yarn/**',
35
+ '*.tsbuildinfo',
36
+ '.lila/**',
37
+
38
+ // System files
39
+ '.DS_Store',
40
+
41
+ // Editor directories and files
42
+ '.vscode/**',
43
+ '!.vscode/extensions.json',
44
+ '.idea/**',
45
+ '*.suo',
46
+ '*.ntvs*',
47
+ '*.njsproj',
48
+ '*.sln',
49
+ '*.sw?',
50
+
51
+ // Test files
52
+ '/cypress/videos/',
53
+ '/cypress/screenshots/',
54
+ ]
55
+ },
56
+
57
+ // Cypress test files
58
+ {
59
+ files : ['cypress/**/*.{cy,spec}.{js,jsx,ts,tsx}', 'cypress/support/**/*.{js,jsx,ts,tsx}'],
60
+ languageOptions: {
61
+ parser : typescriptParser,
62
+ globals: {
63
+ ...globals.browser,
64
+ ...globals.node,
65
+ ...globals.mocha,
66
+ cy : 'readonly',
67
+ Cypress: 'readonly',
68
+ expect : 'readonly',
69
+ assert : 'readonly',
70
+ }
71
+ }
72
+ },
73
+
74
+ // Base eslint recommended config
75
+ eslint.configs.recommended,
76
+ ...pluginVue.configs['flat/recommended'],
77
+
78
+ // Common environment settings for all files
79
+ {
80
+ languageOptions: {
81
+ ecmaVersion: 'latest',
82
+ globals : {
83
+ ...globals.browser,
84
+ // Browser globals
85
+ console : 'readonly',
86
+ document : 'readonly',
87
+ window : 'readonly',
88
+ navigator: 'readonly',
89
+
90
+ // Timer functions
91
+ setTimeout : 'readonly',
92
+ clearTimeout : 'readonly',
93
+ setInterval : 'readonly',
94
+ clearInterval: 'readonly',
95
+
96
+ // JS objects
97
+ Promise: 'readonly',
98
+
99
+ // DOM elements and types
100
+ HTMLElement : 'readonly',
101
+ HTMLInputElement: 'readonly',
102
+ Element : 'readonly',
103
+ Node : 'readonly',
104
+ NodeList : 'readonly',
105
+ Event : 'readonly',
106
+ MouseEvent : 'readonly',
107
+ KeyboardEvent : 'readonly',
108
+ CustomEvent : 'readonly',
109
+ DOMParser : 'readonly',
110
+
111
+ // Web APIs
112
+ localStorage : 'readonly',
113
+ sessionStorage: 'readonly',
114
+ fetch : 'readonly',
115
+ XMLHttpRequest: 'readonly',
116
+
117
+ // compile time defines, injected by vite.config.ts
118
+ __FRONTEND_CONFIG__: 'readonly',
119
+ __TARGET__ : 'readonly',
120
+ __PREVIEW__ : 'readonly'
121
+ }
122
+ }
123
+ },
124
+
125
+ // the build config runs in node, not in the browser
126
+ {
127
+ files : ['vite.config.ts', 'build/**/*.ts', 'build/**/*.mjs'],
128
+ languageOptions: {
129
+ globals: {
130
+ ...globals.node
131
+ }
132
+ }
133
+ },
134
+
135
+ // Vue plugin config
136
+ {
137
+ files : ['**/*.vue'],
138
+ languageOptions: {
139
+ parser : vueParser,
140
+ parserOptions: {
141
+ ecmaVersion: 'latest',
142
+ parser : typescriptParser,
143
+ },
144
+ },
145
+ rules: {
146
+ 'vue/multi-word-component-names': 'off',
147
+ 'vue/max-len' : ['warn', {
148
+ code : 250,
149
+ template : 350,
150
+ tabWidth : 2,
151
+ ignoreComments : true,
152
+ ignoreUrls : true,
153
+ ignoreStrings : true,
154
+ ignoreTemplateLiterals : true,
155
+ ignoreRegExpLiterals : true,
156
+ ignoreHTMLAttributeValues: false,
157
+ ignoreHTMLTextContents : false,
158
+ }],
159
+ 'vue/max-attributes-per-line' : 'off',
160
+ 'vue/first-attribute-linebreak' : 'off',
161
+ 'vue/html-closing-bracket-newline': 'off',
162
+ 'vue/require-default-prop' : 'off',
163
+ 'vue/html-indent' : ['error', 4],
164
+ }
165
+ },
166
+
167
+ // TypeScript files
168
+ {
169
+ files : ['**/*.ts', '**/*.tsx'],
170
+ plugins: {
171
+ '@typescript-eslint': tseslint,
172
+ },
173
+ languageOptions: {
174
+ parser : typescriptParser,
175
+ parserOptions: {
176
+ ecmaVersion: 'latest',
177
+ }
178
+ },
179
+ },
180
+
181
+ // Common config for all files
182
+ {
183
+ languageOptions: {
184
+ ecmaVersion: 'latest',
185
+ },
186
+ rules: {
187
+ semi : 'off',
188
+ 'brace-style' : ['error', '1tbs', { allowSingleLine: true }],
189
+ 'multiline-ternary': ['error', 'always-multiline'],
190
+ 'no-param-reassign': ['error', { props: false }],
191
+ 'import/no-cycle' : 'off',
192
+ 'max-len' : [
193
+ 'warn',
194
+ {
195
+ code: 250, // Updated this from 240 to 250 for consistency
196
+ },
197
+ ],
198
+ curly : [2, 'multi-line'],
199
+ 'prefer-destructuring' : 'off',
200
+ 'no-multiple-empty-lines' : [1, { max: 1 }],
201
+ 'no-multi-spaces' : ['error', { ignoreEOLComments: false }],
202
+ 'arrow-spacing' : ['error', { 'before': true, 'after': true }],
203
+ 'space-before-function-paren': ['error', {
204
+ 'anonymous' : 'never',
205
+ 'named' : 'never',
206
+ 'asyncArrow': 'always'
207
+ }],
208
+ 'one-var' : ['error', 'never'],
209
+ 'comma-spacing' : ['error', { 'before': false, 'after': true }],
210
+ 'key-spacing' : ['error', { 'afterColon': true }],
211
+ 'indent' : ['error', 4],
212
+ quotes : ['error', 'single'],
213
+ 'arrow-body-style': ['error', 'as-needed'],
214
+ 'prefer-const' : 'error'
215
+ },
216
+ },
217
+
218
+ // Fully disable eslint-plugin-vue (template/html linting) - script rules above still apply
219
+ {
220
+ files: ['**/*.vue'],
221
+ rules: Object.fromEntries(Object.keys(pluginVue.rules).map(name => [`vue/${name}`, 'off'])),
222
+ },
223
+ ];