rimecms 0.26.1 → 0.26.3

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.
@@ -1,5 +1,6 @@
1
1
  import { dev } from '$app/environment';
2
2
  import { RimeError, RimeFormError } from '../../../../errors/index.js';
3
+ import { t__ } from '../../../../i18n/index.js';
3
4
  import { Hooks } from '../../../../operations/hooks/index.server.js';
4
5
  import { access } from '../../../../../util/access/index.js';
5
6
  import { cases } from '../../../../../util/cases.js';
@@ -149,8 +150,8 @@ export const createBetterAuthUser = Hooks.beforeCreate(async (args) => {
149
150
  // Send api key by email to the user
150
151
  await rime.mailer.sendMail({
151
152
  to: event.locals.user.email,
152
- subject: 'Your API Key',
153
- text: `Your ${args.data.name} apiKey : ${apiKey}, keep it safe, only server-side`
153
+ subject: t__(`mail.api_key_created_subject`, args.data.name),
154
+ text: t__(`mail.api_key_created_text`, args.data.name, apiKey.key)
154
155
  });
155
156
  return {
156
157
  ...args,
@@ -0,0 +1,5 @@
1
+ declare namespace _default {
2
+ let api_key_created_subject: string;
3
+ let api_key_created_text: string;
4
+ }
5
+ export default _default;
@@ -0,0 +1,4 @@
1
+ export default {
2
+ api_key_created_subject: 'Your API Key',
3
+ api_key_created_text: `Your $1 apiKey : $2, keep it safe, only server-side`
4
+ };
@@ -0,0 +1,5 @@
1
+ declare namespace _default {
2
+ let api_key_created_subject: string;
3
+ let api_key_created_text: string;
4
+ }
5
+ export default _default;
@@ -0,0 +1,4 @@
1
+ export default {
2
+ api_key_created_subject: 'Votre clé API',
3
+ api_key_created_text: `Votre clé API $1 : $2, gardez-la en sécurité, uniquement côté serveur`
4
+ };
@@ -1,5 +1,5 @@
1
1
  export declare const languages: readonly ["fr", "en"];
2
- export declare const namespaces: readonly ["errors", "fields", "common"];
2
+ export declare const namespaces: readonly ["errors", "fields", "common", "mail"];
3
3
  export declare const DEFAULT_LOCALE = "en";
4
4
  export type PanelLanguage = (typeof languages)[number];
5
5
  export type Namespace = (typeof namespaces)[number];
@@ -1,5 +1,5 @@
1
1
  export const languages = ['fr', 'en'];
2
- export const namespaces = ['errors', 'fields', 'common'];
2
+ export const namespaces = ['errors', 'fields', 'common', 'mail'];
3
3
  export const DEFAULT_LOCALE = 'en';
4
4
  function createI18n() {
5
5
  let dictionaries = {};
@@ -5,12 +5,14 @@ const translationModules = {
5
5
  en: {
6
6
  common: () => import('./en/common.js'),
7
7
  errors: () => import('./en/errors.js'),
8
- fields: () => import('./en/fields.js')
8
+ fields: () => import('./en/fields.js'),
9
+ mail: () => import('./en/mail.js')
9
10
  },
10
11
  fr: {
11
12
  common: () => import('./fr/common.js'),
12
13
  errors: () => import('./fr/errors.js'),
13
- fields: () => import('./fr/fields.js')
14
+ fields: () => import('./fr/fields.js'),
15
+ mail: () => import('./fr/mail.js')
14
16
  }
15
17
  };
16
18
  export async function registerTranslation(locale) {
@@ -106,8 +106,8 @@
106
106
 
107
107
  {#if inputFocused}
108
108
  <Command.List>
109
- {#each availableItems as item (item.documentId)}
110
- <Command.Item value={item.title} onSelect={() => onSelect(item)}>
109
+ {#each availableItems as item, index (item.documentId)}
110
+ <Command.Item value="{item.title}-{index}" onSelect={() => onSelect(item)}>
111
111
  <span>{item.title}</span>
112
112
  </Command.Item>
113
113
  {/each}
@@ -155,8 +155,13 @@
155
155
  {/if}
156
156
  </div>
157
157
 
158
- <style>
159
- .rz-relation {
158
+ <style>/**************************************/
159
+
160
+ /* Font */
161
+
162
+ /**************************************/
163
+
164
+ .rz-relation {
160
165
  position: relative;
161
166
 
162
167
  :global {
@@ -3,6 +3,7 @@
3
3
  import type { Directory } from '../../../../core/collections/upload/upload';
4
4
  import { t__ } from '../../../../core/i18n/index.js';
5
5
  import { withDirectoriesSuffix } from '../../../../core/naming.js';
6
+ import Empty from '../../../../panel/components/sections/collection/Empty.svelte';
6
7
  import Folder from '../../../../panel/components/sections/collection/folder/Folder.svelte';
7
8
  import Button from '../../../../panel/components/ui/button/button.svelte';
8
9
  import CardDocument from '../../../../panel/components/ui/card-document/card-document.svelte';
@@ -119,60 +120,107 @@
119
120
  </div>
120
121
 
121
122
  <!-- Grid -->
122
- <div class="rz-relation-browse__grid">
123
- {#if parentPath}
124
- <button class="rz-browse__folder" onclick={() => (path = parentPath)}>
125
- <Folder>...</Folder>
126
- </button>
127
- {/if}
128
-
129
- {#if !isFiltered}
130
- {#if folders.data}
131
- {#each folders.data.docs as doc (doc.id)}
132
- <button class="rz-browse__folder" onclick={() => (path = doc.id)}>
133
- <Folder>{doc.name}</Folder>
134
- </button>
135
- {/each}
123
+ <div class="rz-relation-browse__grid-wrapper">
124
+ <div class="rz-relation-browse__grid">
125
+ {#if isFiltered && files.data?.docs.length === 0}
126
+ <Empty {config} />
136
127
  {/if}
137
- {/if}
138
128
 
139
- {#if files.data}
140
- {#each files.data.docs as doc (doc.id)}
141
- <button onclick={() => addValue(doc.id)}>
142
- <CardDocument {doc} />
129
+ {#if parentPath}
130
+ <button class="rz-browse__folder" onclick={() => (path = parentPath)}>
131
+ <Folder>...</Folder>
143
132
  </button>
144
- {/each}
145
- {/if}
133
+ {/if}
134
+
135
+ {#if !isFiltered}
136
+ {#if folders.data}
137
+ {#each folders.data.docs as doc (doc.id)}
138
+ <button class="rz-browse__folder" onclick={() => (path = doc.id)}>
139
+ <Folder>{doc.name}</Folder>
140
+ </button>
141
+ {/each}
142
+ {/if}
143
+ {/if}
144
+
145
+ {#if files.data}
146
+ {#each files.data.docs as doc (doc.id)}
147
+ <button onclick={() => addValue(doc.id)}>
148
+ <CardDocument {doc} />
149
+ </button>
150
+ {/each}
151
+ {/if}
152
+ </div>
146
153
  </div>
147
154
  </div>
148
155
  </Dialog.Content>
149
156
  </Dialog.Root>
150
157
 
151
158
  <style>
159
+ .rz-relation-browse {
160
+ display: flex;
161
+ flex-direction: column;
162
+ height: 100%;
163
+ min-height: 0;
164
+
165
+ :global {
166
+ .rz-no-document {
167
+ grid-column: 1 / -1;
168
+ }
169
+ }
170
+ }
171
+
152
172
  .rz-relation-browse__header {
173
+ flex-shrink: 0;
153
174
  display: flex;
154
175
  align-items: center;
155
176
  gap: var(--rz-size-4);
156
177
  padding-bottom: var(--rz-size-4);
157
178
  border-bottom: var(--rz-border);
158
- margin-bottom: var(--rz-size-4);
179
+
180
+ padding-right: var(--rz-size-8);
181
+ position: sticky;
182
+ top: 0;
183
+ background-color: hsl(var(--rz-color-bg));
184
+ z-index: 10;
159
185
 
160
186
  :global {
161
187
  .rz-dropdown-item {
162
188
  padding: var(--rz-size-3) var(--rz-size-3);
163
189
  }
190
+ .rz-input-wrapper {
191
+ max-width: 50%;
192
+ }
164
193
  .rz-input {
165
194
  height: var(--rz-size-9);
166
195
  }
167
196
  }
168
197
  }
169
198
 
199
+ .rz-relation-browse__grid-wrapper {
200
+ overflow-y: scroll;
201
+ flex: 1;
202
+ min-height: 0;
203
+ }
204
+
170
205
  .rz-relation-browse__grid {
206
+ padding-top: var(--rz-size-4);
171
207
  display: grid;
172
208
  align-self: start;
173
209
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
174
210
  grid-auto-rows: auto;
175
211
  gap: var(--rz-size-4);
212
+ width: 100%;
213
+
214
+ &::after {
215
+ content: '';
216
+ height: 5rem;
217
+ position: absolute;
218
+ left: 0;
219
+ bottom: 0;
220
+ right: 0;
221
+ z-index: 2;
222
+ background-image: linear-gradient(to top, hsl(var(--rz-color-bg)), transparent);
223
+ }
176
224
  }
177
225
 
178
226
  .rz-browse__folder {
@@ -1,21 +1,21 @@
1
1
  <script lang="ts">
2
- import type { CollectionContext } from '../../../context/collection.svelte.js';
2
+ import type { BuiltCollection } from '../../../../types';
3
3
  import { t__ } from '../../../../core/i18n/index.js';
4
4
 
5
- type Props = { collection: CollectionContext };
6
- const { collection }: Props = $props();
5
+ type Props = { config: BuiltCollection };
6
+ const { config }: Props = $props();
7
7
  </script>
8
8
 
9
- <div class="rz-page-collection__empty">
9
+ <div class="rz-no-document">
10
10
  <div>
11
11
  <span>
12
- {collection.config.label.none || t__(`common.no_document`, collection.config.label.singular)}
12
+ {config.label.none || t__(`common.no_document`, config.label.singular)}
13
13
  </span>
14
14
  </div>
15
15
  </div>
16
16
 
17
17
  <style>
18
- .rz-page-collection__empty {
18
+ .rz-no-document {
19
19
  height: var(--rz-input-height);
20
20
  background-color: light-dark(hsl(var(--rz-gray-16)), hsl(var(--rz-gray-3)));
21
21
  display: flex;
@@ -1,6 +1,6 @@
1
- import type { CollectionContext } from '../../../context/collection.svelte.js';
1
+ import type { BuiltCollection } from '../../../../types';
2
2
  type Props = {
3
- collection: CollectionContext;
3
+ config: BuiltCollection;
4
4
  };
5
5
  declare const Empty: import("svelte").Component<Props, {}, "">;
6
6
  type Empty = ReturnType<typeof Empty>;
@@ -99,7 +99,7 @@
99
99
  </div>
100
100
  </div>
101
101
  {:else}
102
- <Empty {collection} />
102
+ <Empty config={collection.config} />
103
103
  {/if}
104
104
 
105
105
  <style>
@@ -48,7 +48,7 @@
48
48
  {/each}
49
49
  </div>
50
50
  {:else}
51
- <Empty {collection} />
51
+ <Empty config={collection.config} />
52
52
  {/if}
53
53
 
54
54
  <style>
@@ -104,7 +104,7 @@
104
104
  {/each}
105
105
  </div>
106
106
  {:else}
107
- <Empty {collection} />
107
+ <Empty config={collection.config} />
108
108
  {/if}
109
109
  {/key}
110
110
 
@@ -54,10 +54,13 @@
54
54
  const user = getUserContext();
55
55
 
56
56
  let formElement = $state<HTMLFormElement>();
57
-
57
+ // This is used to intercept navigation when there are unsaved changes in the form
58
+ // It stores the URL the user is trying to navigate to, so we can redirect them there after they confirm they want to leave
58
59
  let interceptedLeave = $state<{ url: string } | null>(null);
60
+ // This is used to prevent the beforeNavigate from triggering when we programmatically navigate
59
61
  let isRedirect = $state(false);
60
62
 
63
+ // Intercept navigation when there are unsaved changes in the form
61
64
  beforeNavigate(async ({ cancel, to }) => {
62
65
  const hasCHanges = Object.keys(form.changes).length > 0;
63
66
  if (!hasCHanges) return;
@@ -108,6 +111,7 @@
108
111
  return new Promise<boolean>((resolve) => {
109
112
  function checkAndResolve() {
110
113
  if (!apiKey) {
114
+ isRedirect = !!data?.redirectUrl;
111
115
  resolve(true);
112
116
  clearInterval(intervalId);
113
117
  }
@@ -150,7 +154,8 @@
150
154
  <UploadHeader accept={config.upload.accept} create={operation === 'create'} {form} />
151
155
  {/if}
152
156
  <RenderFields fields={config.fields} {form} />
153
- {#if config.type === 'collection' && isAuthConfig(config)}
157
+ <!-- -->
158
+ {#if config.type === 'collection' && isAuthConfig(config) && config.auth.type === 'password'}
154
159
  <AuthFooter collection={config} {operation} {form} />
155
160
  {/if}
156
161
  </div>
@@ -192,13 +197,14 @@
192
197
  {t__('common.leave_confirm_title')}
193
198
  </Dialog.Header>
194
199
  <p>{t__('common.leave_confirm_text')}</p>
200
+ <!-- -->
195
201
  <Dialog.Footer --rz-justify-content="space-between">
196
- <Button onclick={() => interceptedLeave && goto(interceptedLeave.url)}
197
- >{t__('common.confirm')}</Button
198
- >
199
- <Button onclick={() => (interceptedLeave = null)} variant="secondary"
200
- >{t__('common.cancel')}</Button
201
- >
202
+ <Button onclick={() => interceptedLeave && goto(interceptedLeave.url)}>
203
+ {t__('common.confirm')}
204
+ </Button>
205
+ <Button onclick={() => (interceptedLeave = null)} variant="secondary">
206
+ {t__('common.cancel')}
207
+ </Button>
202
208
  </Dialog.Footer>
203
209
  </Dialog.Content>
204
210
  </Dialog.Root>
@@ -18,9 +18,11 @@
18
18
 
19
19
  const isUpload = $derived('mimeType' in resource);
20
20
  const isImage = $derived(
21
- resource._thumbnail || (isUpload && resource.mimeType!.includes('image'))
21
+ resource._thumbnail || (isUpload && resource.mimeType?.includes('image'))
22
+ );
23
+ const Icon = $derived(
24
+ isUpload && resource.mimeType ? mimeTypeToIcon(resource.mimeType) : FileIcon
22
25
  );
23
- const Icon = $derived(isUpload ? mimeTypeToIcon(resource.mimeType!) : FileIcon);
24
26
  </script>
25
27
 
26
28
  <div class="rz-card-resource">
@@ -51,14 +53,19 @@
51
53
  </button>
52
54
  </div>
53
55
 
54
- <style>
55
- :root {
56
+ <style>/**************************************/
57
+
58
+ /* Font */
59
+
60
+ /**************************************/
61
+
62
+ :root {
56
63
  --rz-ressource-card-bg: light-dark(hsl(var(--rz-gray-18)), hsl(var(--rz-gray-4)));
57
64
  --rz-ressource-card-thumbnail-bg: light-dark(hsl(var(--rz-gray-15)), hsl(var(--rz-gray-2)));
58
65
  --rz-border-radius: var(--rz-radius-md);
59
66
  }
60
67
 
61
- .rz-card-resource {
68
+ .rz-card-resource {
62
69
  --padding: var(--rz-card-padding, var(--rz-size-2));
63
70
  --rz-size: var(--rz-thumbnail-size, var(--rz-size-20));
64
71
  background-color: var(--rz-ressource-card-bg);
@@ -71,7 +78,7 @@
71
78
  max-width: 400px;
72
79
  }
73
80
 
74
- .rz-card-resource__thumbnail {
81
+ .rz-card-resource__thumbnail {
75
82
  width: var(--rz-size);
76
83
  height: var(--rz-size);
77
84
  flex-shrink: 0;
@@ -83,19 +90,19 @@
83
90
  border-radius: var(--rz-radius-md);
84
91
  }
85
92
 
86
- .rz-card-resource__image {
93
+ .rz-card-resource__image {
87
94
  height: 100%;
88
95
  width: 100%;
89
96
  -o-object-fit: cover;
90
97
  object-fit: cover;
91
98
  }
92
99
 
93
- .rz-card-resource__info {
100
+ .rz-card-resource__info {
94
101
  margin-top: var(--rz-size-3);
95
102
  padding-right: 2rem;
96
103
  }
97
104
 
98
- .rz-card-resource__title {
105
+ .rz-card-resource__title {
99
106
  font-variation-settings: 'wght' 600;
100
107
  font-weight: 600;
101
108
  display: flex;
@@ -109,11 +116,11 @@
109
116
  }
110
117
  }
111
118
 
112
- .rz-card-resource__info-text {
119
+ .rz-card-resource__info-text {
113
120
  font-size: var(--rz-text-sm);
114
121
  }
115
122
 
116
- .rz-card-resource__remove {
123
+ .rz-card-resource__remove {
117
124
  position: absolute;
118
125
  color: hsl(var(--rz-color-fg));
119
126
  right: var(--rz-size-2);
@@ -17,8 +17,8 @@
17
17
 
18
18
  &.rz-dialog-content--xl {
19
19
  height: calc(100vh - 4 * var(--gutter));
20
- overflow-y: auto;
21
20
  top: 50%;
21
+ grid-template-rows: 1fr;
22
22
  }
23
23
  }
24
24
 
@@ -45,11 +45,11 @@
45
45
  border-radius: var(--rz-radius-sm);
46
46
  opacity: 0.7;
47
47
  transition: opacity 0.2s;
48
+ z-index: 20;
48
49
  }
49
50
 
50
51
  [data-dialog-close]:focus {
51
52
  outline: none;
52
- /* --rz-ring-offset-2: 2px; */
53
53
  box-shadow:
54
54
  0 0 0 var(--rz-ring-offset, 0px) hsl(var(--rz-ring-offset-bg, var(--rz-gray-6)) / 1),
55
55
  0 0 0 calc(var(--rz-ring-offset, 0px) + 1px) hsl(var(--rz-color-ring) / var(--rz-ring-opacity, 1));
@@ -1,5 +1,4 @@
1
1
  <script lang="ts">
2
- import { X } from '@lucide/svelte';
3
2
  import {
4
3
  Dialog as DialogPrimitive,
5
4
  type DialogContentSnippetProps,
@@ -35,9 +34,6 @@
35
34
  class="rz-dialog-content rz-dialog-content--{size} {className}"
36
35
  {...restProps}
37
36
  >
38
- <Dialog.Close>
39
- <X size="18" />
40
- </Dialog.Close>
41
37
  {@render children?.()}
42
38
  </DialogPrimitive.Content>
43
39
  </Dialog.Portal>
@@ -99,7 +99,6 @@ input.rz-input:is(:-webkit-autofill, :autofill):focus {
99
99
 
100
100
  .rz-input:focus-visible {
101
101
  outline: none;
102
- /* --rz-ring-offset: 1px; */
103
102
  box-shadow:
104
103
  0 0 0 var(--rz-ring-offset, 0px) hsl(var(--rz-ring-offset-bg, var(--rz-gray-6)) / 1),
105
104
  0 0 0 calc(var(--rz-ring-offset, 0px) + 1px) hsl(var(--rz-color-ring) / var(--rz-ring-opacity, 1));
@@ -83,9 +83,9 @@
83
83
  --rz-nav-button-height: var(--rz-size-12);
84
84
  --rz-nav-bg: light-dark(hsl(var(--rz-gray-16)), hsl(var(--rz-gray-0)));
85
85
 
86
- --rz-nav-button-bg: light-dark(hsl(var(--rz-gray-19)), hsl(var(--rz-gray-3)));
87
- --rz-nav-group-bg: light-dark(hsl(var(--rz-gray-17)), hsl(var(--rz-gray-2)));
88
- --rz-nav-group-border-color: light-dark(hsl(var(--rz-gray-16)), hsl(var(--rz-gray-3)));
86
+ --rz-nav-button-bg: light-dark(hsl(var(--rz-gray-19)), hsl(var(--rz-gray-2)));
87
+ --rz-nav-group-bg: light-dark(hsl(var(--rz-gray-17)), hsl(var(--rz-gray-1)));
88
+ --rz-nav-group-border-color: light-dark(hsl(var(--rz-gray-16)), hsl(var(--rz-gray-2)));
89
89
  }
90
90
 
91
91
  .rz-nav {
@@ -1,4 +1,4 @@
1
- .rz-toaster__toast {
1
+ .rz-toaster__toast[data-sonner-toast][data-sonner-toast][data-styled='true'] {
2
2
  background-color: hsl(var(--rz-color-bg));
3
3
  border: var(--rz-border);
4
4
  color: var(--rz-gray-2);
@@ -506,15 +506,14 @@ function createDocumentFormState({ initial, element, config, readOnly, key, onNe
506
506
  }
507
507
  return applyAction({ type: 'redirect', location: redirect, status: 301 });
508
508
  }
509
- toast.success(message);
510
509
  // Assign document
511
510
  doc = (data?.document || doc);
511
+ toast.success(message);
512
512
  if (nestedLevel === 0) {
513
513
  await invalidateAll();
514
514
  initialDoc = doc;
515
515
  }
516
516
  else {
517
- toast.success(t__('common.doc_created'));
518
517
  apiProxy.invalidateAll();
519
518
  // Do not redirect on creation if it's a nested form
520
519
  // the form will auto close and we are back to the parent
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rimecms",
3
- "version": "0.26.1",
3
+ "version": "0.26.3",
4
4
  "homepage": "https://github.com/bienbiendev/rime",
5
5
  "scripts": {
6
6
  "dev": "vite dev",
@@ -26,7 +26,7 @@
26
26
  "license": "MIT",
27
27
  "type": "module",
28
28
  "bin": {
29
- "rime": "./dist/core/dev/cli/index.js"
29
+ "rime": "dist/core/dev/cli/index.js"
30
30
  },
31
31
  "svelte": "./dist/index.js",
32
32
  "types": "./dist/index.d.ts",