firstly 0.0.8 → 0.0.9

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/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # firstly
2
2
 
3
+ ## 0.0.9
4
+
5
+ ### Patch Changes
6
+
7
+ - [#43](https://github.com/jycouet/firstly/pull/43)
8
+ [`46cfc39`](https://github.com/jycouet/firstly/commit/46cfc39090fc448a22c5ca95e45507a31ab8e2e0)
9
+ Thanks [@jycouet](https://github.com/jycouet)! - better enum filter, grid action left/right, bump
10
+ deps, opti session check, action after createOptionWhenNoResult
11
+
3
12
  ## 0.0.8
4
13
 
5
14
  ### Patch Changes
package/esm/BaseEnum.d.ts CHANGED
@@ -1,4 +1,3 @@
1
- import { type IdFilter } from 'remult';
2
1
  import type { FindOptionsBase, Repository } from 'remult';
3
2
  export type FF_Icon = {
4
3
  data?: string | string[];
@@ -21,24 +20,14 @@ export type BaseItem = BaseEnumOptions & {
21
20
  export type BaseEnumOptions<Entity = any> = {
22
21
  caption?: string;
23
22
  icon?: FF_Icon;
24
- where?: IdFilter<Entity> | FindOptionsBase<Entity>['where'];
23
+ where?: FindOptionsBase<Entity>['where'];
25
24
  class?: string;
26
25
  };
27
26
  export declare class BaseEnum<Entity = any> {
28
27
  id: string;
29
28
  caption?: string;
30
29
  icon?: FF_Icon;
31
- where?: IdFilter<Entity> | FindOptionsBase<Entity>['where'];
30
+ where?: FindOptionsBase<Entity>['where'];
32
31
  class?: string;
33
32
  constructor(_id: string | number, options?: BaseEnumOptions<Entity>);
34
- getWhere: () => this | Entity[] | {
35
- $ne?: Entity | Entity[] | undefined;
36
- '!='?: Entity | Entity[] | undefined;
37
- $in?: Entity[] | undefined;
38
- $nin?: Entity[] | undefined;
39
- } | {
40
- $id: import("remult").ValueFilter<Entity extends {
41
- id?: number | undefined;
42
- } ? number : string>;
43
- } | import("remult").EntityFilter<Entity> | NonNullable<Entity>;
44
33
  }
package/esm/BaseEnum.js CHANGED
@@ -1,4 +1,3 @@
1
- import {} from 'remult';
2
1
  export class BaseEnum {
3
2
  id;
4
3
  caption;
@@ -15,7 +14,4 @@ export class BaseEnum {
15
14
  options.icon.caption = options?.caption;
16
15
  }
17
16
  }
18
- getWhere = () => {
19
- return this.where ? this.where : this;
20
- };
21
17
  }
@@ -1,5 +1,5 @@
1
1
  /// <reference types=".pnpm/@sveltejs+kit@2.5.24_@sveltejs+vite-plugin-svelte@3.1.2_svelte@4.2.18_vite@5.4.1_@types+node@_lnml5jetshdinsnlj53joqxhde/node_modules/@sveltejs/kit" />
2
- import { type Handle, type MaybePromise, type RequestEvent } from '@sveltejs/kit';
2
+ import type { Handle, RequestEvent } from '@sveltejs/kit';
3
3
  import { type ClassType } from 'remult';
4
4
  import type { RemultServerOptions } from 'remult/server';
5
5
  import { type MailOptions } from '../mail';
@@ -15,7 +15,7 @@ export type Module = {
15
15
  initRequest?: RemultServerOptions<RequestEvent>['initRequest'];
16
16
  handlePreRemult?: Handle;
17
17
  handlePosRemult?: Handle;
18
- earlyReturn?: (input: Parameters<Handle>[0]) => MaybePromise<{
18
+ earlyReturn?: (input: Parameters<Handle>[0]) => Promise<{
19
19
  early: false;
20
20
  resolve?: undefined;
21
21
  } | {
package/esm/api/index.js CHANGED
@@ -1,4 +1,3 @@
1
- import {} from '@sveltejs/kit';
2
1
  import nodemailer from 'nodemailer';
3
2
  import { remult } from 'remult';
4
3
  import { remultSveltekit } from 'remult/remult-sveltekit';
@@ -23,9 +22,10 @@ export const firstly = (o) => {
23
22
  error: o.error
24
23
  ? o.error
25
24
  : async (e) => {
26
- // if 500 we move to 501 to avoid the default retry mechanism
27
- if (e.httpStatusCode == 500) {
28
- e.sendError(501, e.responseBody);
25
+ // REMULT P2: validation error should probably be 409
26
+ // if 400 we move to 409
27
+ if (e.httpStatusCode == 400) {
28
+ e.sendError(409, e.responseBody);
29
29
  }
30
30
  },
31
31
  // Add user configuration
@@ -9,4 +9,4 @@ export declare const mergeRoles: (existing: string[], newOnes: string[] | undefi
9
9
  roles: string[];
10
10
  changed: boolean;
11
11
  };
12
- export declare const initRoleFromEnv: (log: Log, userEntity: ClassType<FFAuthUser>, envValue: string | undefined, role: string) => Promise<void>;
12
+ export declare const initRoleFromEnv: (log: Log, userEntity: ClassType<FFAuthUser>, envKey: string, role: string) => Promise<void>;
@@ -1,5 +1,6 @@
1
1
  import { repo } from 'remult';
2
2
  import { cyan, green, Log, yellow } from '@kitql/helpers';
3
+ import { env } from '$env/dynamic/private';
3
4
  import { FFAuthUser } from './client/Entities';
4
5
  /**
5
6
  * will merge the roles and remove duplicates
@@ -16,7 +17,8 @@ export const mergeRoles = (existing, newOnes) => {
16
17
  }
17
18
  return { roles: Array.from(result), changed };
18
19
  };
19
- export const initRoleFromEnv = async (log, userEntity, envValue, role) => {
20
+ export const initRoleFromEnv = async (log, userEntity, envKey, role) => {
21
+ const envValue = envKey ? env[envKey] : '';
20
22
  const identifiers = envValue === undefined ? [] : (envValue ?? '').split(',').map((c) => c.trim());
21
23
  for (let i = 0; i < identifiers.length; i++) {
22
24
  const identifier = identifiers[i].trim();
@@ -35,9 +37,9 @@ export const initRoleFromEnv = async (log, userEntity, envValue, role) => {
35
37
  }
36
38
  }
37
39
  if (identifiers.length > 0) {
38
- log.info(`${cyan(role)}: ${identifiers.map((c) => green(c.trim())).join(', ')} added via ${yellow(`.env`)}.`);
40
+ log.info(`${cyan(envKey)}: ${identifiers.map((c) => green(c.trim())).join(', ')} added via ${yellow(`.env`)}.`);
39
41
  }
40
42
  else {
41
- log.info(`${cyan(role)}: No users added via ${yellow(`.env`)}.`);
43
+ log.info(`${cyan(envKey)}: No users added via ${yellow(`.env`)}.`);
42
44
  }
43
45
  };
@@ -4,7 +4,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
- import { BackendMethod } from 'remult';
7
+ import { Allow, BackendMethod } from 'remult';
8
8
  export class Auth {
9
9
  // Do not show for firstly users ?
10
10
  /** DO NOT USE */
@@ -104,7 +104,7 @@ __decorate([
104
104
  BackendMethod({ allowed: true })
105
105
  ], Auth, "signInDemo", null);
106
106
  __decorate([
107
- BackendMethod({ allowed: true })
107
+ BackendMethod({ allowed: Allow.authenticated })
108
108
  ], Auth, "invite", null);
109
109
  __decorate([
110
110
  BackendMethod({ allowed: true })
@@ -1,7 +1,7 @@
1
1
  import { BaseEnum } from '../..';
2
2
  import type { BaseEnumOptions } from '../..';
3
- export declare const FF_Auth_Role: {
4
- readonly Admin: "FF_Auth_Role.Admin";
3
+ export declare const FF_Role_Auth: {
4
+ readonly Admin: "FF_Role_Auth.Admin";
5
5
  };
6
6
  export declare class FFAuthUser {
7
7
  id: string;
@@ -7,8 +7,8 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  var FFAuthProvider_1;
8
8
  import { Fields, Relations, Validators, ValueListFieldType } from 'remult';
9
9
  import { BaseEnum, FF_Entity, FF_Role } from '../..';
10
- export const FF_Auth_Role = {
11
- Admin: 'FF_Auth_Role.Admin',
10
+ export const FF_Role_Auth = {
11
+ Admin: 'FF_Role_Auth.Admin',
12
12
  };
13
13
  let FFAuthUser = class FFAuthUser {
14
14
  id;
@@ -65,7 +65,7 @@ __decorate([
65
65
  ], FFAuthUser.prototype, "sessions", void 0);
66
66
  FFAuthUser = __decorate([
67
67
  FF_Entity('ff_auth.users', {
68
- allowApiCrud: [FF_Auth_Role.Admin, FF_Role.Admin],
68
+ allowApiCrud: [FF_Role_Auth.Admin, FF_Role.Admin],
69
69
  caption: 'FF Auth - Users',
70
70
  })
71
71
  ], FFAuthUser);
@@ -118,7 +118,7 @@ __decorate([
118
118
  ], FFAuthAccount.prototype, "lastVerifiedAt", void 0);
119
119
  FFAuthAccount = __decorate([
120
120
  FF_Entity('ff_auth.accounts', {
121
- allowApiCrud: [FF_Auth_Role.Admin, FF_Role.Admin],
121
+ allowApiCrud: [FF_Role_Auth.Admin, FF_Role.Admin],
122
122
  caption: 'FF Auth - Accounts',
123
123
  // id: { provider: true, userId: true },
124
124
  changeLog: {
@@ -149,7 +149,7 @@ __decorate([
149
149
  ], FFAuthUserSession.prototype, "user", void 0);
150
150
  FFAuthUserSession = __decorate([
151
151
  FF_Entity('ff_auth.users_sessions', {
152
- allowApiCrud: [FF_Auth_Role.Admin, FF_Role.Admin],
152
+ allowApiCrud: [FF_Role_Auth.Admin, FF_Role.Admin],
153
153
  caption: 'FF Auth - Users sessions',
154
154
  changeLog: false,
155
155
  })
@@ -2,6 +2,6 @@ import { Log } from '@kitql/helpers';
2
2
  import { Auth } from './Auth';
3
3
  import { FFAuthAccount, FFAuthProvider, FFAuthUser, FFAuthUserSession } from './Entities';
4
4
  export declare const logAuth: Log;
5
- export { FF_Auth_Role } from './Entities';
5
+ export { FF_Role_Auth } from './Entities';
6
6
  export { Auth };
7
7
  export { FFAuthUser, FFAuthAccount, FFAuthProvider, FFAuthUserSession };
@@ -2,6 +2,6 @@ import { Log } from '@kitql/helpers';
2
2
  import { Auth } from './Auth';
3
3
  import { FFAuthAccount, FFAuthProvider, FFAuthUser, FFAuthUserSession } from './Entities';
4
4
  export const logAuth = new Log('firstly | auth');
5
- export { FF_Auth_Role } from './Entities';
5
+ export { FF_Role_Auth } from './Entities';
6
6
  export { Auth };
7
7
  export { FFAuthUser, FFAuthAccount, FFAuthProvider, FFAuthUserSession };
@@ -4,6 +4,7 @@ import type { ClassType, UserInfo } from 'remult';
4
4
  import type { Module } from '../api';
5
5
  import type { RecursivePartial, ResolvedType } from '../utils/types';
6
6
  import { FFAuthAccount, FFAuthUser, FFAuthUserSession } from './client/Entities';
7
+ import { initRoleFromEnv } from './RoleHelpers';
7
8
  import type { firstlyData, firstlyDataAuth } from './types';
8
9
  export type { firstlyData };
9
10
  export type AuthorizationURLOptions = Record<string, {
@@ -129,6 +130,7 @@ export declare const getSafeOptions: () => {
129
130
  * _Info: index: -777_
130
131
  */
131
132
  export declare const auth: (o: AuthOptions) => Module;
133
+ export { initRoleFromEnv };
132
134
  export declare let lucia: Lucia<Record<any, any>, UserInfo>;
133
135
  declare module 'lucia' {
134
136
  interface Register {
package/esm/auth/index.js CHANGED
@@ -4,12 +4,11 @@ import { Lucia, TimeSpan } from 'lucia';
4
4
  import { remult } from 'remult';
5
5
  import { red } from '@kitql/helpers';
6
6
  import { getRelativePackagePath, read } from '@kitql/internals';
7
- import { env } from '$env/dynamic/private';
8
7
  import { FF_Role } from '../';
9
8
  import { RemultLuciaAdapter } from './Adapter';
10
9
  import { AuthControllerServer } from './AuthController.server';
11
10
  import { Auth, logAuth } from './client';
12
- import { FF_Auth_Role, FFAuthAccount, FFAuthProvider, FFAuthUser, FFAuthUserSession, } from './client/Entities';
11
+ import { FF_Role_Auth, FFAuthAccount, FFAuthProvider, FFAuthUser, FFAuthUserSession, } from './client/Entities';
13
12
  import { createOrExtendSession } from './helper';
14
13
  import { initRoleFromEnv } from './RoleHelpers';
15
14
  export let AUTH_OPTIONS = { ui: {} };
@@ -24,7 +23,7 @@ const buildUrlOrDefault = (base, userSetting, fallback) => {
24
23
  };
25
24
  export const getSafeOptions = () => {
26
25
  const signUp = AUTH_OPTIONS.signUp ?? true;
27
- const base = AUTH_OPTIONS.ui === false ? 'NO_BASE_PATH' : AUTH_OPTIONS.ui?.paths?.base ?? '/ff/auth';
26
+ const base = AUTH_OPTIONS.ui === false ? 'NO_BASE_PATH' : (AUTH_OPTIONS.ui?.paths?.base ?? '/ff/auth');
28
27
  const firstlyData = {
29
28
  module: 'auth',
30
29
  debug: AUTH_OPTIONS.debug,
@@ -150,14 +149,25 @@ export const auth = (o) => {
150
149
  entities: [oSafe.User, oSafe.Session, oSafe.Account],
151
150
  controllers: [Auth],
152
151
  initRequest: async (event) => {
153
- // std session
154
- const sessionId = event.cookies.get(lucia.sessionCookieName);
155
- if (sessionId) {
156
- const { session, user } = await lucia.validateSession(sessionId);
157
- if (session && session.fresh) {
158
- await createOrExtendSession(session.id, session);
152
+ // REMULT: storing user in local should probably be done in remult directly
153
+ if (event?.locals?.user) {
154
+ // console.log('initRequest OK')
155
+ remult.user = event.locals.user;
156
+ }
157
+ else {
158
+ // console.log('initRequest WORK...')
159
+ // std session
160
+ const sessionId = event.cookies.get(lucia.sessionCookieName);
161
+ if (sessionId) {
162
+ const { session, user } = await lucia.validateSession(sessionId);
163
+ if (session && session.fresh) {
164
+ await createOrExtendSession(session.id, session);
165
+ }
166
+ remult.user = user ?? undefined;
167
+ if (event.locals) {
168
+ event.locals.user = user ?? undefined;
169
+ }
159
170
  }
160
- remult.user = user ?? undefined;
161
171
  }
162
172
  },
163
173
  earlyReturn: async ({ event, resolve }) => {
@@ -296,10 +306,11 @@ export const auth = (o) => {
296
306
  return { early: false };
297
307
  },
298
308
  initApi: async () => {
299
- await initRoleFromEnv(logAuth, oSafe.User, env.FF_ADMIN, FF_Role.Admin);
300
- await initRoleFromEnv(logAuth, oSafe.User, env.FF_AUTH_ADMIN, FF_Auth_Role.Admin);
309
+ await initRoleFromEnv(logAuth, oSafe.User, 'FF_ROLE_ADMIN', FF_Role.Admin);
310
+ await initRoleFromEnv(logAuth, oSafe.User, 'FF_ROLE_AUTH_ADMIN', FF_Role_Auth.Admin);
301
311
  },
302
312
  };
303
313
  };
314
+ export { initRoleFromEnv };
304
315
  // Maybe moving this to /auth/server.ts would be better, people will be able to import from firstly all the time
305
316
  export let lucia;
@@ -1,10 +1,10 @@
1
1
  import type { SvelteComponent } from 'svelte';
2
- import { type EntityFilter, type FieldMetadata, type Repository } from 'remult';
2
+ import { type ClassType, type EntityFilter, type FieldMetadata, type Repository } from 'remult';
3
3
  import type { UnArray } from './utils/types.js';
4
4
  export type VisibilityMode = 'view' | 'edit' | 'hide';
5
5
  type CellInternal<Entity> = {
6
6
  col?: keyof Entity;
7
- kind?: 'field' | 'field_link' | 'entity_link' | 'slot' | 'header' | 'component';
7
+ kind?: 'field' | 'field_link' | 'entity_link' | 'slot' | 'header' | 'component' | 'baseItem';
8
8
  class?: string;
9
9
  header?: string;
10
10
  headerSlot?: boolean;
@@ -40,7 +40,7 @@ export declare function cellsBuildor<Entity>(repo: Repository<Entity>, inputBuil
40
40
  export declare function cellBuildor<Entity>(repo: Repository<Entity>, inputBuildor: UnArray<CellsInput<Entity>>): Cell<Entity>;
41
41
  export declare const fieldsOf: <Entity>(b: Cell<Entity>[]) => FieldMetadata<any, Entity>[];
42
42
  export declare const getPlaceholder: <Entity>(fields: FieldMetadata<any, Entity>[]) => string;
43
- export declare const buildSearchWhere: <Entity>(entity: Entity | undefined, fields: FieldMetadata<any, Entity>[], search?: string | null) => EntityFilter<Entity>[];
43
+ export declare const buildSearchWhere: <Entity>(entity: ClassType<Entity> | undefined, fields: FieldMetadata<any, Entity>[], search?: string | null) => EntityFilter<Entity>[];
44
44
  export declare const containsWords: <Entity>(fields: FieldMetadata<any, Entity>[], search: string) => EntityFilter<Entity>;
45
- export declare const buildWhere: <Entity>(entity: Entity | undefined, defaultWhere: EntityFilter<Entity> | undefined, fields_filter: FieldMetadata<any, Entity>[], fields_search: FieldMetadata<any, Entity>[], obj: Record<string, string>) => EntityFilter<Entity>;
45
+ export declare const buildWhere: <Entity>(entity: ClassType<Entity> | undefined, defaultWhere: EntityFilter<Entity> | undefined, fields_filter: FieldMetadata<any, Entity>[], fields_search: FieldMetadata<any, Entity>[], obj: Record<string, string>) => EntityFilter<Entity>;
46
46
  export {};
@@ -77,7 +77,7 @@ export const buildSearchWhere = (entity, fields, search) => {
77
77
  return f;
78
78
  };
79
79
  export const containsWords = (fields, search) => {
80
- const sSplitted = search.split(' ');
80
+ const sSplitted = search.split(' ').filter((s) => s.length > 0);
81
81
  if (fields.length === 1) {
82
82
  return {
83
83
  $and: sSplitted.map((s) => ({ [fields[0].key]: { $contains: s } })),
@@ -106,12 +106,25 @@ export const buildWhere = (entity, defaultWhere, fields_filter, fields_search, o
106
106
  and.push({ [field.key]: obj[field.key] });
107
107
  }
108
108
  else if (field.inputType === 'selectEnum') {
109
+ const fnName = field.key + 'Filter';
109
110
  // @ts-ignore
110
- const theEnum = getEnum(field, obj[field.key]);
111
- // Take the where of the enum if it exists, or it's using this selection as a filter
112
- const wheretoUse = theEnum?.where ?? new BaseEnum(obj[field.key]);
113
- // @ts-ignore
114
- and.push({ [field.key]: wheretoUse });
111
+ if (entity && entity[fnName]) {
112
+ // @ts-ignore
113
+ and.push(entity[fnName](obj[field.key]));
114
+ }
115
+ else {
116
+ // @ts-ignore
117
+ const theEnum = getEnum(field, obj[field.key]);
118
+ // Take the where of the enum if it exists, or it's using this selection as a filter
119
+ if (theEnum?.where) {
120
+ and.push(theEnum.where);
121
+ }
122
+ else {
123
+ const wheretoUse = theEnum?.where ?? new BaseEnum(obj[field.key]);
124
+ // @ts-ignore
125
+ and.push({ [field.key]: wheretoUse });
126
+ }
127
+ }
115
128
  }
116
129
  else if (rfi?.type === 'toOne') {
117
130
  // @ts-ignore (setting the id of the relation)
@@ -21,13 +21,11 @@ async function getGitHub(query, variables) {
21
21
  const response = await fetch(GITHUB_GRAPHQL_ENDPOINT, { method: 'POST', headers, body });
22
22
  const result = await response.json();
23
23
  if (result.errors) {
24
- /* eslint-disable */
25
24
  console.error(`result ERRORS`, body, stry(result));
26
25
  }
27
26
  return result.data;
28
27
  }
29
28
  catch (error) {
30
- /* eslint-disable */
31
29
  console.error(`error`, error);
32
30
  }
33
31
  return null;
@@ -83,11 +83,11 @@ const disableButton = (issueNumber2, title2, content2) => {
83
83
  ).toLocaleTimeString()}</time
84
84
  >
85
85
  </div>
86
- <div class="chat-bubble">{@html item.bodyHTML}</div>
86
+ <div class="chat-bubble prose">{@html item.bodyHTML}</div>
87
87
  <!-- <div class="chat-footer opacity-50">Delivered</div> -->
88
88
  </div>
89
89
  {/each}
90
- {#if issue?.highlight}
90
+ {#if issue?.highlight && issue.state === 'OPEN'}
91
91
  <span class="badge badge-warning">En attente de réponse de TA part 😉, oui 🫵!</span>
92
92
  {/if}
93
93
  {#if issueNumber}
@@ -106,15 +106,15 @@ const disableButton = (issueNumber2, title2, content2) => {
106
106
  ></Textarea>
107
107
  <div class="flex justify-between">
108
108
  {#if issueNumber}
109
- <Button on:click={close} tabIndex={-1} class="btn-outline btn-error">
110
- Clore le feedback
111
- </Button>
109
+ <Button on:click={close} tabIndex={-1} class="btn-outline btn-error"
110
+ >Clore le feedback</Button
111
+ >
112
112
  {:else}
113
113
  <div></div>
114
114
  {/if}
115
- <Button on:click={send} disabled={disableButton(issueNumber, title, content)}>
116
- Envoyer
117
- </Button>
115
+ <Button on:click={send} disabled={disableButton(issueNumber, title, content)}
116
+ >Envoyer</Button
117
+ >
118
118
  </div>
119
119
  {/if}
120
120
  {/if}
package/esm/helper.js CHANGED
@@ -22,7 +22,8 @@ export const getEntityDisplayValue = (repo, row) => {
22
22
  }
23
23
  const field = getFirstInterestingField(repo);
24
24
  // REMULT P3 JYC: If it's an enum, it's not working...
25
- return { caption: row ? field.displayValue(row) : '-', id: '' };
25
+ // @ts-ignore (added for row?.id)
26
+ return { caption: row ? field.displayValue(row) : '-', id: row?.id ? row.id : '' };
26
27
  };
27
28
  export const getFieldLinkDisplayValue = (field, row) => {
28
29
  const caption = field.displayValue(row);
@@ -89,7 +90,7 @@ export const displayWithDefaultAndSuffix = (field, value) => {
89
90
  }
90
91
  else {
91
92
  // toRet.push(value ?? '-')
92
- toRet.push(field?.displayValue ? field?.displayValue({ [field.key]: value }) : value ?? '-');
93
+ toRet.push(field?.displayValue ? field?.displayValue({ [field.key]: value }) : (value ?? '-'));
93
94
  }
94
95
  if (value === undefined || value === null) {
95
96
  return '';
package/esm/index.d.ts CHANGED
@@ -10,6 +10,7 @@ import { storeItem } from './storeItem.js';
10
10
  import { storeList } from './storeList.js';
11
11
  import { default as Button } from './ui/Button.svelte';
12
12
  import { default as Clipboardable } from './ui/Clipboardable.svelte';
13
+ import type { dialog } from './ui/dialog/dialog.js';
13
14
  import { default as DialogManagement } from './ui/dialog/DialogManagement.svelte';
14
15
  import { default as FormEditAction } from './ui/dialog/FormEditAction.svelte';
15
16
  import { default as Field } from './ui/Field.svelte';
@@ -72,7 +73,11 @@ declare module 'remult' {
72
73
  href?: (item: entityType) => string;
73
74
  findOptionsForEdit?: ((entity: entityType) => FindOptionsBase<valueType>) | FindOptionsBase<valueType>;
74
75
  findOptionsLimit?: number;
75
- createOptionWhenNoResult?: boolean;
76
+ createOptionWhenNoResult?: {
77
+ onCreateRequest: (item: entityType, strCreateNew: string) => Parameters<typeof dialog.form>;
78
+ onSuccess: (entity: entityType, newItem: any) => Promise<void>;
79
+ onError?: () => void;
80
+ };
76
81
  multiSelect?: boolean;
77
82
  skipForDefaultField?: boolean;
78
83
  }
package/esm/mail/index.js CHANGED
@@ -14,16 +14,21 @@ export const mailInit = async (nodemailer, o) => {
14
14
  else {
15
15
  try {
16
16
  nodemailer.createTestAccount(globalOptions?.apiUrl ?? '', (err, account) => {
17
- globalOptions = { ...globalOptions, from: account.user };
18
- transporter = nodemailer.createTransport({
19
- host: account.smtp.host,
20
- port: account.smtp.port,
21
- secure: account.smtp.secure,
22
- auth: {
23
- user: account.user,
24
- pass: account.pass,
25
- },
26
- });
17
+ if (account) {
18
+ globalOptions = { ...globalOptions, from: account.user };
19
+ transporter = nodemailer.createTransport({
20
+ host: account.smtp.host,
21
+ port: account.smtp.port,
22
+ secure: account.smtp.secure,
23
+ auth: {
24
+ user: account.user,
25
+ pass: account.pass,
26
+ },
27
+ });
28
+ }
29
+ else {
30
+ log.error("Error nodemailer.createTestAccount() can't be done.");
31
+ }
27
32
  });
28
33
  }
29
34
  catch (error) {
@@ -1,13 +1,15 @@
1
1
  /// <reference types="svelte" />
2
- import type { FindOptions, Repository } from 'remult';
2
+ import type { FindOptions, GroupByOptions, MembersOnly, Repository } from 'remult';
3
3
  type TheStoreList<T> = {
4
4
  items: T[];
5
5
  loading: boolean;
6
6
  totalCount: number | undefined;
7
+ agg: any | undefined;
7
8
  };
8
9
  export type FF_FindOptions<T> = FindOptions<T> & {
9
10
  withCount?: boolean;
10
11
  withItems?: boolean;
12
+ aggregate?: GroupByOptions<T, (keyof MembersOnly<T>)[], any, any, any, any, any>;
11
13
  };
12
14
  /**
13
15
  * @param repo remult repository to listen to
package/esm/storeList.js CHANGED
@@ -15,7 +15,7 @@ import { writable } from 'svelte/store';
15
15
  * $: browser && tasks.listen(data.options)
16
16
  * ```
17
17
  */
18
- export const storeList = (repo, initValues = { items: [], loading: true, totalCount: undefined }) => {
18
+ export const storeList = (repo, initValues = { items: [], loading: true, totalCount: undefined, agg: undefined }) => {
19
19
  const { subscribe, set, update } = writable(initValues);
20
20
  let unSub = null;
21
21
  onDestroy(async () => {
@@ -43,27 +43,36 @@ export const storeList = (repo, initValues = { items: [], loading: true, totalCo
43
43
  throw new Error(`xxx.fetch() withItems and withCount can't be both false!`);
44
44
  }
45
45
  else if (!withItems && withCount) {
46
- const totalCount = await repo.count(options?.where);
47
- set({ loading: false, items: [], totalCount });
46
+ let optionsToUse = { where: options?.where };
47
+ if (options?.aggregate) {
48
+ optionsToUse = { ...options.aggregate, where: options?.where };
49
+ }
50
+ // const agg = await repo.aggregate({ ...options?.aggregate, where: options?.where })
51
+ const agg = await repo.aggregate(optionsToUse);
52
+ set({ loading: false, items: [], totalCount: agg.$count, agg });
48
53
  if (onNewData) {
49
- onNewData(undefined, totalCount);
54
+ onNewData(undefined, agg.$count);
50
55
  }
51
56
  }
52
57
  else if (withItems && !withCount) {
53
58
  const items = await repo.find(options);
54
- set({ loading: false, items, totalCount: undefined });
59
+ set({ loading: false, items, totalCount: undefined, agg: undefined });
55
60
  if (onNewData) {
56
61
  onNewData(items, undefined);
57
62
  }
58
63
  }
59
64
  else {
60
- const [items, totalCount] = await Promise.all([
65
+ let optionsToUse = { where: options?.where };
66
+ if (options?.aggregate) {
67
+ optionsToUse = { ...options.aggregate, where: options?.where };
68
+ }
69
+ const [items, agg] = await Promise.all([
61
70
  repo.find({ ...options }),
62
- repo.count(options?.where),
71
+ repo.aggregate(optionsToUse),
63
72
  ]);
64
- set({ loading: false, items, totalCount });
73
+ set({ loading: false, items, totalCount: agg.$count, agg });
65
74
  if (onNewData) {
66
- onNewData(items, totalCount);
75
+ onNewData(items, agg.$count);
67
76
  }
68
77
  }
69
78
  }
@@ -75,7 +84,8 @@ export const storeList = (repo, initValues = { items: [], loading: true, totalCo
75
84
  const withCount = options?.withCount ?? false;
76
85
  let totalCount = undefined;
77
86
  if (withCount) {
78
- totalCount = await repo.count(options?.where);
87
+ const agg = await repo.aggregate({ where: options?.where });
88
+ totalCount = agg.$count;
79
89
  }
80
90
  update((c) => {
81
91
  return { ...c, items: info.items, loading: false, ...(withCount ? { totalCount } : {}) };
@@ -101,7 +101,8 @@ const getLoadOptions = async (cellsValues2, str) => {
101
101
  );
102
102
  let totalCount = arr.length;
103
103
  if (totalCount === limit) {
104
- totalCount = await metaTypeObj.repoTarget.count(findToUse.where);
104
+ const agg = await metaTypeObj.repoTarget.aggregate({ where: findToUse.where });
105
+ totalCount = agg.$count;
105
106
  }
106
107
  if (!cell.field?.options.multiSelect) {
107
108
  if (str === "" && getId() && !arr.find((r) => String(r.id) === String(getId()))) {
@@ -196,7 +197,7 @@ const calcSuffix = (value2) => {
196
197
  on:issue={(e) => {
197
198
  error = e.detail
198
199
  }}
199
- createOptionWhenNoResult={cell.field?.options.createOptionWhenNoResult}
200
+ createOptionWhenNoResult={!!cell.field?.options.createOptionWhenNoResult}
200
201
  on:createRequest
201
202
  />
202
203
  {/if}
@@ -237,7 +238,12 @@ const calcSuffix = (value2) => {
237
238
  type="checkbox"
238
239
  {...{ ...common(cell.field), required: undefined }}
239
240
  class="checkbox"
240
- bind:checked={value}
241
+ checked={value}
242
+ on:input={(e) => {
243
+ // @ts-ignore
244
+ value = e.target.checked
245
+ dispatchSelected(value)
246
+ }}
241
247
  />
242
248
  </div>
243
249
  {:else if metaType.subKind === 'text' || metaType.subKind === 'email' || metaType.subKind === 'password' || metaType.subKind === 'dateOnly' || metaType.subKind === 'number'}
@@ -10,9 +10,11 @@ export let store;
10
10
  export let focusKey = null;
11
11
  const getError = (errors, field) => {
12
12
  const fo = getRelationFieldInfo(field);
13
- const keyToUse = fo?.options?.field ?? field.key;
14
- if (errors && errors[keyToUse]) {
13
+ const keyToUse = fo?.options?.field;
14
+ if (errors && keyToUse && errors[keyToUse]) {
15
15
  return errors[keyToUse];
16
+ } else if (errors && errors[field.key]) {
17
+ return errors[field.key];
16
18
  }
17
19
  return void 0;
18
20
  };
@@ -31,6 +31,7 @@ export let classes = {
31
31
  };
32
32
  export let orderBy = void 0;
33
33
  export let orderByCols = void 0;
34
+ export let settingsLeft = false;
34
35
  export let dicoNoResult = "Aucun r\xE9sultat !";
35
36
  const dispatch = createEventDispatcher();
36
37
  const sorting = (toSort, b) => {
@@ -62,19 +63,55 @@ const sortingIcon = (toSort, b, _orderBy) => {
62
63
  const cellsToTake = (cells2) => {
63
64
  return cells2.filter((c) => c.modeView !== "hide");
64
65
  };
66
+ const classForRounding = (i) => {
67
+ if (settingsLeft && (withEdit || withDelete || withAdd)) {
68
+ if (i === 0) {
69
+ return "";
70
+ } else if (i === cells.length - 1) {
71
+ return "rounded-tr-lg";
72
+ }
73
+ }
74
+ if (!settingsLeft && (withEdit || withDelete || withAdd)) {
75
+ if (i === 0) {
76
+ return "rounded-tl-lg";
77
+ } else if (i === cells.length - 1) {
78
+ return "";
79
+ }
80
+ }
81
+ if (i === 0) {
82
+ return "rounded-tl-lg";
83
+ } else if (i === cells.length - 1) {
84
+ return "rounded-tr-lg";
85
+ }
86
+ };
65
87
  </script>
66
88
 
67
89
  <div class="overflow-x-auto">
68
90
  <table class="table {classes.table}">
69
91
  <thead>
70
92
  <tr>
93
+ {#if settingsLeft && (withEdit || withDelete || withAdd)}
94
+ <th class="rounded-tl-lg">
95
+ <div class="flex justify-start">
96
+ {#if !withAdd}
97
+ <Icon data={LibIcon_Settings}></Icon>
98
+ {:else}
99
+ <Button
100
+ permission={store.getRepo().metadata.options.permissionApiInsert}
101
+ disabled={!store.getRepo().metadata.apiInsertAllowed()}
102
+ class="btn btn-square btn-ghost btn-xs"
103
+ on:click={() => dispatch('add', {})}
104
+ >
105
+ <Icon data={LibIcon_Add} />
106
+ </Button>
107
+ {/if}
108
+ </div>
109
+ </th>
110
+ {/if}
111
+
71
112
  {#each cellsToTake(cells) as b, i}
72
113
  {@const al = align(b.field, b.kind === 'slot')}
73
- <th
74
- class="{al}
75
- {i === 0 ? 'rounded-tl-lg' : ''}
76
- {i === cells.length - 1 && !withEdit && !withDelete ? 'rounded-tr-lg' : ''}"
77
- >
114
+ <th class="{al} {classForRounding(i)}">
78
115
  {#if b.headerSlot}
79
116
  <slot name="header" field={b.field} />
80
117
  {:else}
@@ -98,20 +135,22 @@ const cellsToTake = (cells2) => {
98
135
  </th>
99
136
  {/each}
100
137
 
101
- {#if withEdit || withDelete || withAdd}
102
- <th class="flex justify-end rounded-tr-lg">
103
- {#if withAdd}
104
- <Button
105
- permission={store.getRepo().metadata.options.permissionApiInsert}
106
- disabled={!store.getRepo().metadata.apiInsertAllowed()}
107
- class="btn btn-square btn-ghost btn-xs"
108
- on:click={() => dispatch('add', {})}
109
- >
110
- <Icon data={LibIcon_Add} />
111
- </Button>
112
- {:else}
113
- <Icon data={LibIcon_Settings}></Icon>
114
- {/if}
138
+ {#if !settingsLeft && (withEdit || withDelete || withAdd)}
139
+ <th class="rounded-tr-lg">
140
+ <div class="flex justify-end">
141
+ {#if withAdd}
142
+ <Button
143
+ permission={store.getRepo().metadata.options.permissionApiInsert}
144
+ disabled={!store.getRepo().metadata.apiInsertAllowed()}
145
+ class="btn btn-square btn-ghost btn-xs"
146
+ on:click={() => dispatch('add', {})}
147
+ >
148
+ <Icon data={LibIcon_Add} />
149
+ </Button>
150
+ {:else}
151
+ <Icon data={LibIcon_Settings}></Icon>
152
+ {/if}
153
+ </div>
115
154
  </th>
116
155
  {/if}
117
156
  </tr>
@@ -123,6 +162,34 @@ const cellsToTake = (cells2) => {
123
162
  {:else}
124
163
  {#each $store.items as row}
125
164
  <tr on:click={() => dispatch('rowclick', row)} class="hover:bg-base-content/20">
165
+ <!-- BECARFULL THIS CODE IS DUPLICATED -->
166
+ {#if settingsLeft && (withEdit || withDelete)}
167
+ <td class="text-left">
168
+ <div class="flex justify-start gap-2">
169
+ {#if withEdit}
170
+ <Button
171
+ permission={store.getRepo().metadata.options.permissionApiUpdate}
172
+ disabled={!store.getRepo().metadata.apiUpdateAllowed()}
173
+ class="btn btn-square btn-ghost btn-xs"
174
+ on:click={() => dispatch('edit', row)}
175
+ >
176
+ <Icon data={LibIcon_Edit} />
177
+ </Button>
178
+ {/if}
179
+ {#if withDelete}
180
+ <Button
181
+ permission={store.getRepo().metadata.options.permissionApiDelete}
182
+ disabled={!store.getRepo().metadata.apiDeleteAllowed()}
183
+ class="btn btn-square btn-ghost btn-xs"
184
+ on:click={() => dispatch('delete', row)}
185
+ >
186
+ <Icon data={LibIcon_Delete} />
187
+ </Button>
188
+ {/if}
189
+ </div>
190
+ </td>
191
+ {/if}
192
+
126
193
  {#each cellsToTake(cells) as b}
127
194
  {@const metaType = getFieldMetaType(b.field)}
128
195
  <td class={align(b.field, b.kind === 'slot')}>
@@ -153,6 +220,8 @@ const cellsToTake = (cells2) => {
153
220
  href: b.field?.options?.href ? b.field?.options.href(row) : item?.href,
154
221
  }}
155
222
  />
223
+ {:else if b.kind === 'baseItem'}
224
+ <LinkPlus item={row[metaType.field.key]} />
156
225
  {:else if b.kind === 'field_link'}
157
226
  {@const item = getFieldLinkDisplayValue(metaType.field, row)}
158
227
  <LinkPlus {item} />
@@ -191,7 +260,8 @@ const cellsToTake = (cells2) => {
191
260
  {/if}
192
261
  </td>
193
262
  {/each}
194
- {#if withEdit || withDelete}
263
+
264
+ {#if !settingsLeft && (withEdit || withDelete)}
195
265
  <td class="text-right">
196
266
  <div class="flex justify-end gap-2">
197
267
  {#if withEdit}
@@ -15,6 +15,7 @@ declare class __sveltets_Render<T extends Record<any, any>> {
15
15
  } | undefined;
16
16
  orderBy?: EntityOrderBy<T> | undefined;
17
17
  orderByCols?: true | (keyof T)[] | undefined;
18
+ settingsLeft?: boolean | undefined;
18
19
  dicoNoResult?: string | undefined;
19
20
  };
20
21
  events(): {
@@ -24,9 +24,11 @@ $: canGoNext = isValidValue && needPaginate && pageDisplayed < Math.ceil((totalC
24
24
  </script>
25
25
 
26
26
  <FieldContainer {label} forId="paginate" classes={{ label: 'justify-end' }}>
27
- <div class="flex items-center justify-end">
27
+ <div class="flex w-36 items-center justify-end">
28
28
  {#if totalCount === undefined}
29
- <Loading class="mx-2 h-8 w-1/2"></Loading>
29
+ <Loading class="ml-6 mr-2 h-3 w-1/6"></Loading>
30
+ <Loading class="mx-2 h-4 w-1/2"></Loading>
31
+ <Loading class="mx-2 h-3 w-1/6"></Loading>
30
32
  {:else if !needPaginate}
31
33
  <span class="text-primary justify-end px-2 font-bold">
32
34
  {totalCount}
@@ -36,7 +38,7 @@ $: canGoNext = isValidValue && needPaginate && pageDisplayed < Math.ceil((totalC
36
38
  <button
37
39
  aria-label="left"
38
40
  on:click={() => update('-')}
39
- class="btn join-item {pageDisplayed === 1 ? 'btn-disabled' : ''}"
41
+ class="btn join-item p-1 {pageDisplayed === 1 ? 'btn-disabled' : ''}"
40
42
  >
41
43
  <Icon data={LibIcon_ChevronLeft} />
42
44
  </button>
@@ -53,7 +55,7 @@ $: canGoNext = isValidValue && needPaginate && pageDisplayed < Math.ceil((totalC
53
55
  <button
54
56
  aria-label="right"
55
57
  on:click={() => update('+')}
56
- class="btn join-item {!canGoNext ? 'btn-disabled' : ''}"
58
+ class="btn join-item p-1 {!canGoNext ? 'btn-disabled' : ''}"
57
59
  >
58
60
  <Icon data={LibIcon_ChevronRight} />
59
61
  </button>
@@ -40,11 +40,7 @@ function dispatchChange(_data) {
40
40
  />
41
41
  <div
42
42
  class={tw(
43
- `border-base-content/60
44
- bg-base-100
45
- relative z-40 max-h-[90vh] overflow-visible rounded-xl
46
- border p-6
47
- shadow-lg`,
43
+ `border-base-content/60 bg-base-100 relative z-40 max-h-[90vh] overflow-visible rounded-xl border p-6 shadow-lg`,
48
44
  classes.root,
49
45
  )}
50
46
  transition:flyAndScale={{
@@ -29,6 +29,15 @@ type ResultClose<entityType = any> = {
29
29
  item?: entityType;
30
30
  };
31
31
  export type DialogType = 'custom' | 'confirm' | 'confirmDelete' | 'insert' | 'update' | 'view';
32
+ export type DialogFormType<entityType> = {
33
+ cells?: CellsInput<entityType>;
34
+ defaults?: Partial<entityType>;
35
+ classes?: DialogClasses;
36
+ noThrow?: boolean;
37
+ wDelete?: boolean;
38
+ topicPrefixText?: string;
39
+ focusKey?: string;
40
+ };
32
41
  export type DialogMetaDataInternal<entityType = any> = DialogMetaData<entityType> & {
33
42
  id: number;
34
43
  type: DialogType;
@@ -37,14 +46,7 @@ export type DialogMetaDataInternal<entityType = any> = DialogMetaData<entityType
37
46
  export declare const dialog: {
38
47
  confirm: (topic: string, text: string, icon?: string) => Promise<ResultClose<any>>;
39
48
  confirmDelete: (topic: string) => Promise<ResultClose<any>>;
40
- form: <entityType>(type: 'insert' | 'update' | 'view', topic: string, repo: Repository<entityType>, cells: CellsInput<entityType>, options?: {
41
- defaults?: Partial<entityType>;
42
- classes?: DialogClasses;
43
- noThrow?: boolean;
44
- wDelete?: boolean;
45
- topicPrefixText?: string;
46
- focusKey?: string;
47
- }) => Promise<ResultClose<any>>;
49
+ form: <entityType>(type: 'insert' | 'update' | 'view', topic: string, repo: Repository<entityType>, settings: DialogFormType<entityType>) => Promise<ResultClose<any>>;
48
50
  show: (dialog: DialogMetaData) => Promise<ResultClose<any>>;
49
51
  close: (id: number, result: ResultClose) => void;
50
52
  closeAll: () => void;
@@ -42,10 +42,9 @@ const createDialogManagement = () => {
42
42
  };
43
43
  return show(detail, 'confirmDelete');
44
44
  },
45
- // FIXME JYC: refactor this (no need repo? options?)
46
- form: (type, topic, repo, cells, options) => {
47
- const topicPrefixText = options?.topicPrefixText
48
- ? options?.topicPrefixText + ' '
45
+ form: (type, topic, repo, settings) => {
46
+ const topicPrefixText = settings?.topicPrefixText
47
+ ? settings?.topicPrefixText + ' '
49
48
  : type === 'insert'
50
49
  ? `Créer `
51
50
  : type === 'update'
@@ -60,12 +59,12 @@ const createDialogManagement = () => {
60
59
  },
61
60
  repo,
62
61
  // store,
63
- cells,
64
- defaults: options?.defaults,
65
- classes: options?.classes,
66
- noThrow: options?.noThrow,
67
- wDelete: options?.wDelete,
68
- focusKey: options?.focusKey,
62
+ cells: settings.cells ?? [],
63
+ defaults: settings?.defaults,
64
+ classes: settings?.classes,
65
+ noThrow: settings?.noThrow,
66
+ wDelete: settings?.wDelete,
67
+ focusKey: settings?.focusKey,
69
68
  topicPrefixText,
70
69
  };
71
70
  return show(detail, type);
@@ -20,7 +20,7 @@ const debounce = (fn) => {
20
20
  };
21
21
  function dispatchInput(value2) {
22
22
  if ($$restProps.type === "date") {
23
- if (value2) {
23
+ if (value2 || value2 === null) {
24
24
  dispatch("input", { value: transformDate(value2) });
25
25
  }
26
26
  } else {
@@ -37,6 +37,12 @@ const handleInput = (e) => {
37
37
  } else {
38
38
  value = target.value.toString().replaceAll(",", ".");
39
39
  }
40
+ } else if ($$restProps.type === "date") {
41
+ if (target.value === "") {
42
+ value = null;
43
+ } else {
44
+ value = target.value;
45
+ }
40
46
  } else {
41
47
  value = target.value;
42
48
  }
@@ -49,6 +55,9 @@ const handleInput = (e) => {
49
55
  }
50
56
  };
51
57
  const transformDate = (input) => {
58
+ if (input === null) {
59
+ return null;
60
+ }
52
61
  const rawDateSplited = input.split("-");
53
62
  if (rawDateSplited.length === 3) {
54
63
  const yearSplited = rawDateSplited[0].split("");
@@ -1,44 +1,56 @@
1
- <script>import { tw } from "../..";
1
+ <script>import {} from "../..";
2
2
  import Icon from "../Icon.svelte";
3
3
  import Tooltip from "../Tooltip.svelte";
4
4
  import Link from "./Link.svelte";
5
5
  export let item;
6
6
  export let noIcon = false;
7
7
  export let captionSubStyle = "under";
8
+ const hasSomethingToDisplay = (item2) => {
9
+ if (item2.href) {
10
+ return true;
11
+ }
12
+ if (item2.caption) {
13
+ return true;
14
+ }
15
+ return false;
16
+ };
8
17
  </script>
9
18
 
10
19
  <div class="flex items-center gap-4">
11
20
  {#if item}
12
21
  {#if item.icon?.data && !noIcon}
13
- <Tooltip text={item.icon.caption}>
14
- <Icon
15
- data={item.icon.data}
16
- class={tw('flex-shrink-0', item.icon.class)}
17
- size={item.icon.size}
18
- />
19
- </Tooltip>
20
- {/if}
21
- <div class="flex flex-col items-start">
22
- {#if item.href}
23
- <div>
24
- <Link href={item.href}>{item.caption}</Link>
25
- {#if item.captionSub && captionSubStyle === 'inline'}
26
- <span class="text-base-content/70 text-xs italic">{item.captionSub}</span>
27
- {/if}
28
- </div>
22
+ {#if item.icon.caption}
23
+ <Tooltip text={item.icon.caption}>
24
+ <Icon {...item.icon} />
25
+ </Tooltip>
29
26
  {:else}
30
- <!-- 20 is a cool value ! -->
31
- <span
32
- class="text-base-content {item.class} {(item.caption ?? '').length < 20
33
- ? 'text-nowrap'
34
- : ''}"
35
- >
36
- {item.caption ?? '-'}
37
- </span>
38
- {/if}
39
- {#if item.captionSub && captionSubStyle === 'under'}
40
- <span class="text-base-content/70 text-xs italic">{item.captionSub}</span>
27
+ <Icon {...item.icon} />
41
28
  {/if}
42
- </div>
29
+ {/if}
30
+
31
+ {#if hasSomethingToDisplay(item)}
32
+ <div class="flex flex-col items-start">
33
+ {#if item.href}
34
+ <div>
35
+ <Link href={item.href}>{item.caption}</Link>
36
+ {#if item.captionSub && captionSubStyle === 'inline'}
37
+ <span class="text-base-content/70 text-xs italic">{item.captionSub}</span>
38
+ {/if}
39
+ </div>
40
+ {:else}
41
+ <!-- 20 is a cool value ! -->
42
+ <span
43
+ class="text-base-content {item.class} {(item.caption ?? '').length < 20
44
+ ? 'text-nowrap'
45
+ : ''}"
46
+ >
47
+ {item.caption ?? '-'}
48
+ </span>
49
+ {/if}
50
+ {#if item.captionSub && captionSubStyle === 'under'}
51
+ <span class="text-base-content/70 text-xs italic">{item.captionSub}</span>
52
+ {/if}
53
+ </div>
54
+ {/if}
43
55
  {/if}
44
56
  </div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "firstly",
3
- "version": "0.0.8",
3
+ "version": "0.0.9",
4
4
  "type": "module",
5
5
  "description": "Firstly, an opinionated Remult setup!",
6
6
  "repository": {
@@ -14,7 +14,7 @@
14
14
  },
15
15
  "peerDependencies": {
16
16
  "@sveltejs/kit": ">=1.0.0 <3.0.0",
17
- "remult": "0.27.7",
17
+ "remult": "0.27.19",
18
18
  "svelte": ">=4.2.18"
19
19
  },
20
20
  "dependencies": {
@@ -26,7 +26,7 @@
26
26
  "arctic": "^1.8.0",
27
27
  "clsx": "^2.1.1",
28
28
  "cron": "^3.1.7",
29
- "daisyui": "^4.10.3",
29
+ "daisyui": "^4.12.10",
30
30
  "esm-env": "^1.0.0",
31
31
  "lucia": "^3.2.0",
32
32
  "nodemailer": "^6.9.13",