@tekkare/romulus 0.1.246 → 0.1.247
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/dist/module.json +1 -1
- package/dist/runtime/components/AlertForm.vue +78 -46
- package/dist/runtime/components/Modal.vue +1 -1
- package/dist/runtime/components/RichTextEditor.vue +3 -3
- package/dist/runtime/components/SavedViewCard.vue +12 -2
- package/dist/runtime/components/ViewPill.vue +11 -1
- package/dist/runtime/composables/useRmDialog.js +6 -0
- package/dist/runtime/utils/alerts.d.ts +21 -0
- package/package.json +1 -1
package/dist/module.json
CHANGED
|
@@ -26,6 +26,7 @@ import type {
|
|
|
26
26
|
RmAlertKind,
|
|
27
27
|
RmAlertDraft,
|
|
28
28
|
RmAlertField,
|
|
29
|
+
RmAlertProductTexts,
|
|
29
30
|
RmAlertTrigger,
|
|
30
31
|
RmAlertViewOption,
|
|
31
32
|
} from '../utils/alerts'
|
|
@@ -66,6 +67,12 @@ const props = withDefaults(
|
|
|
66
67
|
* disparait, il ne reste que l'alerte de vue.
|
|
67
68
|
*/
|
|
68
69
|
canWatchProduct?: boolean
|
|
70
|
+
/**
|
|
71
|
+
* Textes du second genre d'alerte, quand l'app y suit autre chose que des
|
|
72
|
+
* marques (Case Atlas : des maladies rares). Les cles absentes gardent le
|
|
73
|
+
* texte des marques. `{max}` est remplace par `maxBrands`.
|
|
74
|
+
*/
|
|
75
|
+
productTexts?: Partial<RmAlertProductTexts>
|
|
69
76
|
/** Nombre d'elements que la vue tient a l'ecran, s'il est connu. */
|
|
70
77
|
scopeCount?: number | null
|
|
71
78
|
submitting?: boolean
|
|
@@ -92,6 +99,7 @@ const props = withDefaults(
|
|
|
92
99
|
maxBrands: 20,
|
|
93
100
|
canWatchView: true,
|
|
94
101
|
canWatchProduct: true,
|
|
102
|
+
productTexts: () => ({}),
|
|
95
103
|
fields: () => [],
|
|
96
104
|
scopeCount: null,
|
|
97
105
|
submitting: false,
|
|
@@ -134,6 +142,7 @@ const FR = {
|
|
|
134
142
|
brandsRequired: 'Choisissez au moins une marque.',
|
|
135
143
|
brandsTooMany: 'Une alerte suit {max} marques au maximum.',
|
|
136
144
|
productFields: 'Rien à choisir : l’alerte signale tout changement sur ces marques (nouvel élément, changement de valeur, sortie du périmètre).',
|
|
145
|
+
productTitle: 'Veille',
|
|
137
146
|
screenOption: 'Écran actuel (une vue sera créée)',
|
|
138
147
|
viewSelect: 'Vue à surveiller',
|
|
139
148
|
viewApplied: 'La vue choisie est appliquée à l’écran : c’est lui qui fournit la requête surveillée.',
|
|
@@ -143,15 +152,17 @@ const FR = {
|
|
|
143
152
|
viewHint: 'L’alerte se déclenche sur le périmètre de cette vue, tel qu’il est à l’écran.',
|
|
144
153
|
viewFields: 'Rien à choisir : l’alerte signale tout changement dans la vue (nouvel élément, changement de valeur, sortie du périmètre).',
|
|
145
154
|
channels: 'Canaux',
|
|
146
|
-
inApp: '
|
|
147
|
-
email: '
|
|
155
|
+
inApp: 'L’alerte apparaît toujours dans l’app (pastille sur la tuile Alertes).',
|
|
156
|
+
email: 'Recevoir aussi un email',
|
|
148
157
|
cadence: 'Fréquence d’envoi du mail',
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
158
|
+
checkEvery: 'Vérifier une fois par',
|
|
159
|
+
daily: 'jour',
|
|
160
|
+
weekly: 'semaine',
|
|
161
|
+
monthly: 'mois',
|
|
162
|
+
checkTest: 'Vérifier toutes les',
|
|
163
|
+
test5: '5 minutes',
|
|
164
|
+
onlyOnChange: 'et envoyer un email uniquement si un changement est détecté',
|
|
165
|
+
testMode: 'mode essai',
|
|
155
166
|
submit: 'Créer l’alerte',
|
|
156
167
|
save: 'Enregistrer',
|
|
157
168
|
reseed: 'Cette alerte suit désormais tout changement. L’enregistrer la fait repartir d’une nouvelle référence : vous ne serez prévenu que de ce qui bouge ensuite.',
|
|
@@ -173,6 +184,7 @@ const EN = {
|
|
|
173
184
|
brandsRequired: 'Pick at least one brand.',
|
|
174
185
|
brandsTooMany: 'An alert watches at most {max} brands.',
|
|
175
186
|
productFields: 'Nothing to choose: the alert reports any change on these brands (new item, value change, left the scope).',
|
|
187
|
+
productTitle: 'Watch',
|
|
176
188
|
screenOption: 'Current screen (a view will be created)',
|
|
177
189
|
viewSelect: 'View to watch',
|
|
178
190
|
viewApplied: 'The chosen view is applied to the screen: the screen is what provides the watched query.',
|
|
@@ -182,15 +194,17 @@ const EN = {
|
|
|
182
194
|
viewHint: 'The alert fires on this view’s scope, as it stands on screen.',
|
|
183
195
|
viewFields: 'Nothing to choose: the alert reports any change in the view (new item, value change, left the scope).',
|
|
184
196
|
channels: 'Channels',
|
|
185
|
-
inApp: '
|
|
186
|
-
email: '
|
|
197
|
+
inApp: 'The alert always shows in the app (badge on the Alerts tile).',
|
|
198
|
+
email: 'Also send me an email',
|
|
187
199
|
cadence: 'Mail frequency',
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
200
|
+
checkEvery: 'Check once a',
|
|
201
|
+
daily: 'day',
|
|
202
|
+
weekly: 'week',
|
|
203
|
+
monthly: 'month',
|
|
204
|
+
checkTest: 'Check every',
|
|
205
|
+
test5: '5 minutes',
|
|
206
|
+
onlyOnChange: 'and send an email only if a change is detected',
|
|
207
|
+
testMode: 'test mode',
|
|
194
208
|
submit: 'Create the alert',
|
|
195
209
|
save: 'Save',
|
|
196
210
|
reseed: 'This alert now watches every change. Saving it restarts from a new baseline: you are only told about what moves afterwards.',
|
|
@@ -200,7 +214,7 @@ const EN = {
|
|
|
200
214
|
scope: 'items in the view right now',
|
|
201
215
|
}
|
|
202
216
|
|
|
203
|
-
const T = computed(() => (props.lang === 'en' ? EN : FR))
|
|
217
|
+
const T = computed(() => ({ ...(props.lang === 'en' ? EN : FR), ...props.productTexts }))
|
|
204
218
|
|
|
205
219
|
/**
|
|
206
220
|
* Vue retenue. `null` designe l'ecran actuel, qui deviendra une vue au moment
|
|
@@ -299,7 +313,7 @@ watch(
|
|
|
299
313
|
watch([kind, brands], () => {
|
|
300
314
|
if (titleTouched.value) return
|
|
301
315
|
if (watchesProduct.value) {
|
|
302
|
-
title.value = brands.value.length ?
|
|
316
|
+
title.value = brands.value.length ? `${T.value.productTitle} ${brands.value.join(', ')}` : ''
|
|
303
317
|
return
|
|
304
318
|
}
|
|
305
319
|
const view = props.views.find((entry) => entry.id === selectedViewId.value)
|
|
@@ -313,20 +327,20 @@ watch(selectedViewId, (id) => {
|
|
|
313
327
|
if (id) emit('select-view', id)
|
|
314
328
|
})
|
|
315
329
|
|
|
316
|
-
/**
|
|
330
|
+
/**
|
|
331
|
+
* Options de cadence, du plus frequent au plus espace. L'unite se lit en gras
|
|
332
|
+
* dans la phrase (« Verifier une fois par **semaine** ») : c'est elle qui
|
|
333
|
+
* distingue les options, le reste de la ligne est commun.
|
|
334
|
+
*/
|
|
317
335
|
const cadenceOptions = computed(() => [
|
|
318
|
-
{ value: 'monthly' as const, label: T.value.monthly },
|
|
319
|
-
{ value: 'weekly' as const, label: T.value.weekly },
|
|
320
|
-
{ value: 'daily' as const, label: T.value.daily },
|
|
321
336
|
...(props.testCadence
|
|
322
|
-
? [{ value: 'test5' as const,
|
|
337
|
+
? [{ value: 'test5' as const, lead: T.value.checkTest, unit: T.value.test5, test: true }]
|
|
323
338
|
: []),
|
|
339
|
+
{ value: 'daily' as const, lead: T.value.checkEvery, unit: T.value.daily, test: false },
|
|
340
|
+
{ value: 'weekly' as const, lead: T.value.checkEvery, unit: T.value.weekly, test: false },
|
|
341
|
+
{ value: 'monthly' as const, lead: T.value.checkEvery, unit: T.value.monthly, test: false },
|
|
324
342
|
])
|
|
325
343
|
|
|
326
|
-
const cadenceLabel = computed(
|
|
327
|
-
() => cadenceOptions.value.find((option) => option.value === cadence.value)?.label ?? '',
|
|
328
|
-
)
|
|
329
|
-
|
|
330
344
|
const brandsInvalid = computed(
|
|
331
345
|
() =>
|
|
332
346
|
watchesProduct.value &&
|
|
@@ -464,26 +478,44 @@ function submit() {
|
|
|
464
478
|
|
|
465
479
|
<fieldset class="rm-alert-form__group" data-field="channels">
|
|
466
480
|
<legend class="rm-alert-form__label">{{ T.channels }}</legend>
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
481
|
+
<!-- L'app n'est pas un choix : une case grisee laissait croire qu'on
|
|
482
|
+
pouvait la decocher. Seul le mail se choisit. -->
|
|
483
|
+
<p class="rm-alert-form__in-app">
|
|
484
|
+
<RmIcon name="Bell" :size="14" />
|
|
485
|
+
{{ T.inApp }}
|
|
486
|
+
</p>
|
|
487
|
+
|
|
488
|
+
<RmCheckbox v-model="emailOn" :label="T.email" />
|
|
489
|
+
|
|
490
|
+
<!-- Une liste plutot qu'un menu : chaque option tient sur deux lignes,
|
|
491
|
+
l'unite en gras et la condition d'envoi en dessous, en grise. -->
|
|
492
|
+
<div
|
|
493
|
+
v-if="emailOn"
|
|
494
|
+
class="rm-alert-form__cadences"
|
|
495
|
+
role="radiogroup"
|
|
496
|
+
:aria-label="T.cadence"
|
|
497
|
+
>
|
|
498
|
+
<label
|
|
499
|
+
v-for="option in cadenceOptions"
|
|
500
|
+
:key="option.value"
|
|
477
501
|
class="rm-alert-form__cadence"
|
|
478
|
-
:
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
cadence
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
502
|
+
:class="{ 'rm-alert-form__cadence--on': cadence === option.value }"
|
|
503
|
+
>
|
|
504
|
+
<input
|
|
505
|
+
v-model="cadence"
|
|
506
|
+
type="radio"
|
|
507
|
+
name="rm-alert-cadence"
|
|
508
|
+
:value="option.value"
|
|
509
|
+
>
|
|
510
|
+
<span class="rm-alert-form__cadence-text">
|
|
511
|
+
<span class="rm-alert-form__cadence-main">
|
|
512
|
+
{{ option.lead }} <strong>{{ option.unit }}</strong>
|
|
513
|
+
<template v-if="option.test"> ({{ T.testMode }})</template>
|
|
514
|
+
</span>
|
|
515
|
+
<span class="rm-alert-form__cadence-sub">{{ T.onlyOnChange }}</span>
|
|
516
|
+
</span>
|
|
517
|
+
</label>
|
|
485
518
|
</div>
|
|
486
|
-
<p v-if="emailOn" class="rm-alert-form__hint">{{ T.cadenceHint }}</p>
|
|
487
519
|
</fieldset>
|
|
488
520
|
|
|
489
521
|
<p
|
|
@@ -509,5 +541,5 @@ function submit() {
|
|
|
509
541
|
</template>
|
|
510
542
|
|
|
511
543
|
<style scoped>
|
|
512
|
-
.rm-alert-form{display:flex;flex-direction:column;gap:18px}.rm-alert-form__label{color:var(--text-primary);font-size:13px;font-weight:600;margin:0 0 2px;padding:0}.rm-alert-form__group{border:0;display:flex;flex-direction:column;gap:8px;margin:0;padding:0}.rm-alert-form__view-name{align-items:center;color:var(--text-primary);display:flex;flex-wrap:wrap;font-size:14px;gap:6px;margin:0}.rm-alert-form__count{color:var(--text-muted);font-size:11.5px}.rm-alert-form__hint{color:var(--text-muted);font-size:12px;line-height:1.45;margin:0 0 4px}.rm-alert-form__error{color:var(--color-danger);font-size:12px;margin:0}.rm-alert-
|
|
544
|
+
.rm-alert-form{display:flex;flex-direction:column;gap:18px}.rm-alert-form__label{color:var(--text-primary);font-size:13px;font-weight:600;margin:0 0 2px;padding:0}.rm-alert-form__group{border:0;display:flex;flex-direction:column;gap:8px;margin:0;padding:0}.rm-alert-form__view-name{align-items:center;color:var(--text-primary);display:flex;flex-wrap:wrap;font-size:14px;gap:6px;margin:0}.rm-alert-form__count{color:var(--text-muted);font-size:11.5px}.rm-alert-form__hint{color:var(--text-muted);font-size:12px;line-height:1.45;margin:0 0 4px}.rm-alert-form__error{color:var(--color-danger);font-size:12px;margin:0}.rm-alert-form__in-app{align-items:center;color:var(--text-secondary);display:flex;font-size:13px;gap:8px;margin:0}.rm-alert-form__cadences{display:flex;flex-direction:column;gap:6px;padding-left:26px}.rm-alert-form__cadence{align-items:flex-start;border:1px solid var(--border-soft);border-radius:var(--radius-md);cursor:pointer;display:flex;gap:10px;padding:8px 12px}.rm-alert-form__cadence:hover{background:var(--bg-hover)}.rm-alert-form__cadence--on{background:rgba(var(--brand-rgb),.04);border-color:var(--color-secondary)}.rm-alert-form__cadence input{accent-color:var(--color-primary);margin:3px 0 0}.rm-alert-form__cadence-text{display:flex;flex-direction:column;gap:2px}.rm-alert-form__cadence-main{color:var(--text-primary);font-size:13px}.rm-alert-form__cadence-sub{color:var(--text-muted);font-size:12px}.rm-alert-form__nested{display:flex;flex-direction:column;gap:6px;padding-left:26px}.rm-alert-form__baseline{align-items:flex-start;background:var(--bg-surface-3);border-radius:8px;color:var(--text-secondary);display:flex;font-size:12.5px;gap:8px;line-height:1.45;margin:0;padding:10px 12px}.rm-alert-form__actions{display:flex;gap:8px;justify-content:flex-end}
|
|
513
545
|
</style>
|
|
@@ -114,5 +114,5 @@ const modalStyle = computed(() => {
|
|
|
114
114
|
</template>
|
|
115
115
|
|
|
116
116
|
<style scoped>
|
|
117
|
-
.rm-modal-overlay{align-items:center;backdrop-filter:blur(2px);background:rgba(0,0,0,.4);display:flex;font-family:var(--r-font-family);inset:0;justify-content:center;position:fixed;z-index:100}.rm-modal{background:var(--bg-surface);border-radius:12px;box-shadow:0 20px 60px rgba(0,0,0,.15);display:flex;flex-direction:column;max-height:90vh;max-width:95vw}.rm-modal__header{align-items:flex-start;display:flex;gap:var(--space-16);justify-content:space-between;padding:var(--space-20) var(--space-24) 0}.rm-modal__header-content{flex:1;min-width:0}.rm-modal__close{align-items:center;background:none;border:none;border-radius:var(--r-radius-md);color:var(--text-muted);cursor:pointer;display:flex;flex-shrink:0;height:32px;justify-content:center;transition:all var(--r-transition-fast);width:32px}.rm-modal__close:hover{background:var(--bg-surface-3);color:var(--text-primary)}.rm-modal__tabs{padding:0}.rm-modal__body{flex:1;overflow-y:auto;padding:var(--space-24)}.rm-modal__footer{border-top:1px solid var(--border-soft);padding:var(--space-16) var(--space-24)}.rm-modal-enter-active,.rm-modal-leave-active{transition:opacity .2s ease}.rm-modal-enter-active .rm-modal,.rm-modal-leave-active .rm-modal{transition:transform .2s ease}.rm-modal-enter-from,.rm-modal-leave-to{opacity:0}.rm-modal-enter-from .rm-modal,.rm-modal-leave-to .rm-modal{transform:scale(.95)}.rm-modal-leave-active,.rm-modal-leave-to{pointer-events:none}
|
|
117
|
+
.rm-modal-overlay{align-items:center;backdrop-filter:blur(2px);background:rgba(0,0,0,.4);display:flex;font-family:var(--r-font-family);inset:0;justify-content:center;position:fixed;z-index:100}.rm-modal{background:var(--bg-surface);border-radius:12px;box-shadow:0 20px 60px rgba(0,0,0,.15);display:flex;flex-direction:column;max-height:90vh;max-width:95vw}.rm-modal__header{align-items:flex-start;display:flex;gap:var(--space-16);justify-content:space-between;padding:var(--space-20) var(--space-24) 0}.rm-modal__header-content{flex:1;min-width:0}.rm-modal__close{align-items:center;background:none;border:none;border-radius:var(--r-radius-md);color:var(--text-muted);cursor:pointer;display:flex;flex-shrink:0;height:32px;justify-content:center;transition:all var(--r-transition-fast);width:32px}.rm-modal__close:hover{background:var(--bg-surface-3);color:var(--text-primary)}.rm-modal__tabs{padding:0}.rm-modal__body{flex:1;overflow-y:auto;padding:var(--space-24)}.rm-modal__footer{align-items:center;border-top:1px solid var(--border-soft);display:flex;flex-wrap:wrap;gap:var(--space-8);justify-content:flex-end;padding:var(--space-16) var(--space-24)}.rm-modal-enter-active,.rm-modal-leave-active{transition:opacity .2s ease}.rm-modal-enter-active .rm-modal,.rm-modal-leave-active .rm-modal{transition:transform .2s ease}.rm-modal-enter-from,.rm-modal-leave-to{opacity:0}.rm-modal-enter-from .rm-modal,.rm-modal-leave-to .rm-modal{transform:scale(.95)}.rm-modal-leave-active,.rm-modal-leave-to{pointer-events:none}
|
|
118
118
|
</style>
|
|
@@ -221,7 +221,7 @@ const NoteLink = Node.create({
|
|
|
221
221
|
title: node.attrs.label,
|
|
222
222
|
class: 'rm-rte-pagelink',
|
|
223
223
|
}),
|
|
224
|
-
`${node.attrs.icon ? `${node.attrs.icon} ` : ''}${node.attrs.label || '
|
|
224
|
+
`${node.attrs.icon ? `${node.attrs.icon} ` : ''}${node.attrs.label || 'Note sans titre'}`,
|
|
225
225
|
]
|
|
226
226
|
},
|
|
227
227
|
renderText({ node }) {
|
|
@@ -581,8 +581,8 @@ defineExpose({ editor })
|
|
|
581
581
|
const GROUP_LABEL: Record<RmRichTextMentionItem['kind'], string> = {
|
|
582
582
|
user: 'Personnes',
|
|
583
583
|
resource: 'Ressources',
|
|
584
|
-
page: '
|
|
585
|
-
'create-page': 'Nouvelle
|
|
584
|
+
page: 'Notes',
|
|
585
|
+
'create-page': 'Nouvelle note',
|
|
586
586
|
}
|
|
587
587
|
const MENTION_ICON: Record<RmRichTextMentionItem['kind'], string> = {
|
|
588
588
|
user: 'User',
|
|
@@ -86,6 +86,11 @@ export interface RmSavedView {
|
|
|
86
86
|
isTemplate?: boolean
|
|
87
87
|
/** Nombre d'espaces vises. `0` vaut « tous ». */
|
|
88
88
|
templateTargets?: number
|
|
89
|
+
/**
|
|
90
|
+
* Modele publie pour l'espace du lecteur nommement, et non pour tout le
|
|
91
|
+
* monde : pastille « Cree pour vous » au degrade de marque.
|
|
92
|
+
*/
|
|
93
|
+
forYou?: boolean
|
|
89
94
|
/**
|
|
90
95
|
* Menu propre a cette vue, a la place de celui de la liste. Une liste ou
|
|
91
96
|
* cohabitent mes vues et celles des autres n'a pas un seul menu : le
|
|
@@ -155,6 +160,7 @@ const T = computed(() => ({
|
|
|
155
160
|
? 'Default view, applied on opening'
|
|
156
161
|
: "Vue par défaut, s'active à l'ouverture",
|
|
157
162
|
shared: en.value ? 'Shared' : 'Partagée',
|
|
163
|
+
forYou: en.value ? 'Made for you' : 'Créé pour vous',
|
|
158
164
|
sharedBy: (name: string) =>
|
|
159
165
|
en.value ? `Shared by ${name}` : `Partagée par ${name}`,
|
|
160
166
|
template: (n: number) => {
|
|
@@ -267,9 +273,13 @@ function onCardClick() {
|
|
|
267
273
|
@click="onCardClick"
|
|
268
274
|
>
|
|
269
275
|
<div
|
|
270
|
-
v-if="view.isDefault || view.sharedBy || view.shared || view.isTemplate"
|
|
276
|
+
v-if="view.isDefault || view.sharedBy || view.shared || view.isTemplate || view.forYou"
|
|
271
277
|
class="rm-view__strip"
|
|
272
278
|
>
|
|
279
|
+
<span v-if="view.forYou" class="rm-view__for-you">
|
|
280
|
+
<RmIcon name="Sparkle" :size="12" weight="fill" />
|
|
281
|
+
{{ T.forYou }}
|
|
282
|
+
</span>
|
|
273
283
|
<span v-if="view.isDefault" class="rm-view__strip-mark">
|
|
274
284
|
<RmIcon name="PushPin" :size="14" weight="fill" />
|
|
275
285
|
{{ T.isDefault }}
|
|
@@ -427,5 +437,5 @@ function onCardClick() {
|
|
|
427
437
|
</template>
|
|
428
438
|
|
|
429
439
|
<style scoped>
|
|
430
|
-
.rm-view{background:var(--bg-surface);border:1px solid var(--border-default);border-radius:var(--radius-lg);color:var(--text-primary);cursor:pointer;font-family:var(--font-sans);padding:14px 18px;transition:border-color var(--duration-fast) var(--ease-out),box-shadow var(--duration-fast) var(--ease-out),background var(--duration-fast) var(--ease-out)}.rm-view:not(.rm-view--active):hover{background:var(--bg-hover);border-color:var(--color-accent-soft)}.rm-view--active{border-color:var(--color-secondary);box-shadow:0 0 0 3px rgba(var(--brand-rgb),.08);cursor:default}.rm-view__strip{align-items:center;background:var(--bg-hover);border-bottom:1px solid var(--border-soft);border-radius:7px 7px 0 0;color:var(--color-secondary);display:flex;flex-wrap:wrap;font-size:var(--font-11);font-weight:var(--weight-bold);gap:var(--space-8);letter-spacing:.06em;margin:-14px -18px var(--space-12);padding:var(--space-8) var(--space-16);text-transform:uppercase}.rm-view__strip-mark{align-items:center;display:inline-flex;gap:var(--space-6);white-space:nowrap}.rm-view__head{align-items:center;display:flex;gap:var(--space-10)}.rm-view__icon{align-items:center;border-radius:var(--radius-md);display:inline-flex;flex:0 0 auto;height:34px;justify-content:center;width:34px}.rm-view__icon--primary{background:var(--bg-hover);color:var(--color-secondary)}.rm-view__icon--success{background:var(--color-success-bg);color:var(--color-success)}.rm-view__icon--warning{background:var(--color-warning-bg);color:var(--color-warning)}.rm-view__icon--danger{background:var(--color-danger-bg);color:var(--color-danger)}.rm-view__icon--info{background:var(--color-info-bg);color:var(--color-info)}.rm-view__star{align-items:center;background:transparent;border:1px solid transparent;border-radius:var(--radius-md);color:var(--text-muted);cursor:pointer;display:inline-flex;flex:0 0 auto;height:28px;justify-content:center;padding:0;width:28px}.rm-view__star:hover{background:var(--bg-hover)}.rm-view__star--on,.rm-view__star:hover{color:var(--color-warning)}.rm-view__title,.rm-view__title-edit{flex:1;font-size:var(--font-16);font-weight:var(--weight-bold);min-width:0}.rm-view__title-edit{background:var(--bg-surface);border:1px solid var(--color-secondary);border-radius:var(--radius-md);color:var(--text-primary);font-family:inherit;outline:none;padding:var(--space-2) var(--space-8)}.rm-view__title-edit:focus{box-shadow:0 0 0 3px rgba(var(--brand-rgb),.12)}.rm-view__actions{align-items:center;display:flex;gap:var(--space-8)}.rm-
|
|
440
|
+
.rm-view{background:var(--bg-surface);border:1px solid var(--border-default);border-radius:var(--radius-lg);color:var(--text-primary);cursor:pointer;font-family:var(--font-sans);padding:14px 18px;transition:border-color var(--duration-fast) var(--ease-out),box-shadow var(--duration-fast) var(--ease-out),background var(--duration-fast) var(--ease-out)}.rm-view:not(.rm-view--active):hover{background:var(--bg-hover);border-color:var(--color-accent-soft)}.rm-view--active{border-color:var(--color-secondary);box-shadow:0 0 0 3px rgba(var(--brand-rgb),.08);cursor:default}.rm-view__strip{align-items:center;background:var(--bg-hover);border-bottom:1px solid var(--border-soft);border-radius:7px 7px 0 0;color:var(--color-secondary);display:flex;flex-wrap:wrap;font-size:var(--font-11);font-weight:var(--weight-bold);gap:var(--space-8);letter-spacing:.06em;margin:-14px -18px var(--space-12);padding:var(--space-8) var(--space-16);text-transform:uppercase}.rm-view__strip-mark{align-items:center;display:inline-flex;gap:var(--space-6);white-space:nowrap}.rm-view__head{align-items:center;display:flex;gap:var(--space-10)}.rm-view__icon{align-items:center;border-radius:var(--radius-md);display:inline-flex;flex:0 0 auto;height:34px;justify-content:center;width:34px}.rm-view__icon--primary{background:var(--bg-hover);color:var(--color-secondary)}.rm-view__icon--success{background:var(--color-success-bg);color:var(--color-success)}.rm-view__icon--warning{background:var(--color-warning-bg);color:var(--color-warning)}.rm-view__icon--danger{background:var(--color-danger-bg);color:var(--color-danger)}.rm-view__icon--info{background:var(--color-info-bg);color:var(--color-info)}.rm-view__star{align-items:center;background:transparent;border:1px solid transparent;border-radius:var(--radius-md);color:var(--text-muted);cursor:pointer;display:inline-flex;flex:0 0 auto;height:28px;justify-content:center;padding:0;width:28px}.rm-view__star:hover{background:var(--bg-hover)}.rm-view__star--on,.rm-view__star:hover{color:var(--color-warning)}.rm-view__title{overflow-wrap:anywhere}.rm-view__title,.rm-view__title-edit{flex:1;font-size:var(--font-16);font-weight:var(--weight-bold);min-width:0}.rm-view__title-edit{background:var(--bg-surface);border:1px solid var(--color-secondary);border-radius:var(--radius-md);color:var(--text-primary);font-family:inherit;outline:none;padding:var(--space-2) var(--space-8)}.rm-view__title-edit:focus{box-shadow:0 0 0 3px rgba(var(--brand-rgb),.12)}.rm-view__actions{align-items:center;display:flex;gap:var(--space-8)}.rm-view__for-you{background:linear-gradient(90deg,#4dccf7,#a06bf5 50%,#ff5cf0);box-shadow:0 1px 4px rgba(160,107,245,.35);color:#fff;text-shadow:0 1px 1px rgba(33,0,173,.35)}.rm-view__badge,.rm-view__for-you{align-items:center;border-radius:var(--radius-pill);display:inline-flex;font-size:var(--font-10);font-weight:var(--weight-bold);gap:var(--space-4);letter-spacing:.06em;padding:3px var(--space-8);text-transform:uppercase}.rm-view__badge{background:var(--color-success-bg);color:var(--color-success)}.rm-view__menu-wrap{position:relative}.rm-view__more{align-items:center;background:transparent;border:none;border-radius:var(--radius-md);color:var(--text-muted);cursor:pointer;display:inline-flex;height:28px;justify-content:center;padding:0;width:28px}.rm-view__more:hover{background:var(--bg-hover);color:var(--text-primary)}.rm-view__menu-shade{inset:0;position:fixed;z-index:60}.rm-view__menu{background:var(--bg-surface);border:1px solid var(--border-default);border-radius:var(--radius-lg);box-shadow:var(--shadow-lg);display:flex;flex-direction:column;min-width:200px;padding:var(--space-4);position:absolute;right:0;top:calc(100% + 6px);z-index:61}.rm-view__menu-item{align-items:center;background:transparent;border:none;border-radius:var(--radius-md);color:var(--text-primary);cursor:pointer;display:flex;font-family:inherit;font-size:var(--font-13);gap:var(--space-8);padding:var(--space-8) var(--space-12);text-align:left;width:100%}.rm-view__menu-item:hover{background:var(--bg-hover);color:var(--color-secondary)}.rm-view__menu-item--danger{color:var(--color-danger)}.rm-view__menu-item--danger:hover{background:var(--color-danger-bg);color:var(--color-danger)}.rm-view__desc{color:var(--text-secondary);font-size:var(--font-13);margin:var(--space-8) 0 var(--space-12)}.rm-view__config{background:var(--bg-surface-2);border:1px solid var(--border-soft);border-radius:var(--radius-md);display:flex;flex-direction:column;gap:var(--space-6);margin:10px 0 var(--space-12);padding:var(--space-10) 14px}.rm-view__row{align-items:flex-start;display:flex;flex-wrap:wrap;font-size:var(--font-12);gap:14px}.rm-view__row-key{color:var(--text-muted);display:inline-flex;flex-shrink:0;font-size:var(--font-10);font-weight:var(--weight-semibold);letter-spacing:.04em;min-width:96px;padding-top:var(--space-4);text-transform:uppercase}.rm-view__row-key,.rm-view__row-val{align-items:center;gap:var(--space-6)}.rm-view__row-val{color:var(--text-primary);display:flex;flex:1 1 200px;flex-wrap:wrap;padding:var(--space-2) 0}.rm-view__pill{align-items:center;background:var(--bg-surface);border:1px solid var(--border-default);border-radius:var(--radius-md);display:inline-flex;font-size:var(--font-12);gap:var(--space-4);padding:5px var(--space-10)}.rm-view__pill-k{color:var(--color-primary);font-weight:var(--weight-semibold)}.rm-view__pill-tip{min-width:0}.rm-view__pill-v{display:-webkit-box;-webkit-box-orient:vertical;color:var(--text-primary);font-weight:var(--weight-medium);-webkit-line-clamp:3;overflow:hidden}.rm-view__foot{border-top:1px solid var(--border-soft);color:var(--text-muted);display:flex;flex-wrap:wrap;font-size:var(--font-11);gap:var(--space-16);padding-top:var(--space-10)}.rm-view__foot span{align-items:center;display:inline-flex;gap:var(--space-4)}.rm-view__confirm{align-items:center;background:var(--color-danger-bg);border:1px solid var(--color-danger);border-radius:var(--radius-md);color:var(--color-danger);display:flex;flex-wrap:wrap;font-size:var(--font-13);gap:var(--space-12);margin-top:var(--space-12);padding:var(--space-8) var(--space-12)}.rm-view__confirm>span{align-items:center;display:inline-flex;flex:1 1 220px;gap:var(--space-8)}.rm-view__confirm-actions{display:flex;flex-shrink:0;gap:var(--space-8)}
|
|
431
441
|
</style>
|
|
@@ -49,6 +49,12 @@ const props = withDefaults(
|
|
|
49
49
|
viewsLabel?: string
|
|
50
50
|
/** L'ecran ne correspond plus a la vue active. */
|
|
51
51
|
dirty?: boolean
|
|
52
|
+
/**
|
|
53
|
+
* La vue n'est pas enregistree (posee par un lien, un template, une
|
|
54
|
+
* selection). « modifiee » laissait croire a une vue sauvegardee qu'on
|
|
55
|
+
* aurait retouchee : la pastille dit alors « non sauvegardee ».
|
|
56
|
+
*/
|
|
57
|
+
temporary?: boolean
|
|
52
58
|
/**
|
|
53
59
|
* Actions de bas de menu. La page y met ce qu'elle sait faire d'une vue :
|
|
54
60
|
* les ouvrir toutes, en enregistrer une nouvelle, modifier son perimetre.
|
|
@@ -61,6 +67,7 @@ const props = withDefaults(
|
|
|
61
67
|
views: () => [],
|
|
62
68
|
viewsLabel: undefined,
|
|
63
69
|
dirty: false,
|
|
70
|
+
temporary: false,
|
|
64
71
|
actions: () => [],
|
|
65
72
|
lang: 'fr',
|
|
66
73
|
},
|
|
@@ -83,6 +90,7 @@ const WORDS = {
|
|
|
83
90
|
actions: 'Actions',
|
|
84
91
|
unsaved: 'Modifications non enregistrées',
|
|
85
92
|
modified: 'modifiée',
|
|
93
|
+
temporary: 'non sauvegardée',
|
|
86
94
|
applied: 'Vue appliquée',
|
|
87
95
|
save: 'Sauvegarder',
|
|
88
96
|
aria: 'Vue affichée',
|
|
@@ -94,6 +102,7 @@ const WORDS = {
|
|
|
94
102
|
actions: 'Actions',
|
|
95
103
|
unsaved: 'Unsaved changes',
|
|
96
104
|
modified: 'edited',
|
|
105
|
+
temporary: 'unsaved',
|
|
97
106
|
applied: 'View applied',
|
|
98
107
|
save: 'Save',
|
|
99
108
|
aria: 'Current view',
|
|
@@ -108,7 +117,8 @@ const t = computed(() => WORDS[props.lang])
|
|
|
108
117
|
*/
|
|
109
118
|
const label = computed(() => {
|
|
110
119
|
if (!props.viewName) return t.value.none
|
|
111
|
-
|
|
120
|
+
if (!props.dirty) return props.viewName
|
|
121
|
+
return `${props.viewName} (${props.temporary ? t.value.temporary : t.value.modified})`
|
|
112
122
|
})
|
|
113
123
|
|
|
114
124
|
const open = ref(false)
|
|
@@ -11,9 +11,12 @@ const FOCUSABLE = [
|
|
|
11
11
|
function focusables(root) {
|
|
12
12
|
return Array.from(root.querySelectorAll(FOCUSABLE)).filter((el) => el.offsetWidth > 0 || el.offsetHeight > 0 || el === document.activeElement);
|
|
13
13
|
}
|
|
14
|
+
const stack = [];
|
|
14
15
|
export function useRmDialog({ open, surface, close }) {
|
|
15
16
|
let opener = null;
|
|
17
|
+
const token = Symbol("rm-dialog");
|
|
16
18
|
function onKeydown(event) {
|
|
19
|
+
if (stack[stack.length - 1] !== token) return;
|
|
17
20
|
if (event.key === "Escape") {
|
|
18
21
|
event.stopPropagation();
|
|
19
22
|
if (rmPanelsDismiss()) return;
|
|
@@ -41,9 +44,12 @@ export function useRmDialog({ open, surface, close }) {
|
|
|
41
44
|
}
|
|
42
45
|
}
|
|
43
46
|
function bind() {
|
|
47
|
+
if (!stack.includes(token)) stack.push(token);
|
|
44
48
|
document.addEventListener("keydown", onKeydown, true);
|
|
45
49
|
}
|
|
46
50
|
function unbind() {
|
|
51
|
+
const at = stack.indexOf(token);
|
|
52
|
+
if (at >= 0) stack.splice(at, 1);
|
|
47
53
|
document.removeEventListener("keydown", onKeydown, true);
|
|
48
54
|
}
|
|
49
55
|
watch(open, async (isOpen) => {
|
|
@@ -133,6 +133,27 @@ export interface RmAlertViewOption {
|
|
|
133
133
|
* nomme des marques et vaut pour tout le hub, sans dependre d'aucune page.
|
|
134
134
|
*/
|
|
135
135
|
export type RmAlertKind = 'view' | 'product';
|
|
136
|
+
/**
|
|
137
|
+
* Textes du genre `product` de RmAlertForm, quand l'app y suit autre chose que
|
|
138
|
+
* des marques. `{max}` est remplace par le plafond d'elements.
|
|
139
|
+
*/
|
|
140
|
+
export interface RmAlertProductTexts {
|
|
141
|
+
/** Libelle du choix dans le selecteur de genre. */
|
|
142
|
+
kindProduct: string;
|
|
143
|
+
/** Explication sous le selecteur de genre. */
|
|
144
|
+
kindProductHint: string;
|
|
145
|
+
/** Legende de la section de choix, et prefixe en modification. */
|
|
146
|
+
brands: string;
|
|
147
|
+
/** Intitule du champ de recherche. */
|
|
148
|
+
brandsSearch: string;
|
|
149
|
+
brandsHint: string;
|
|
150
|
+
brandsRequired: string;
|
|
151
|
+
brandsTooMany: string;
|
|
152
|
+
/** Ce que l'alerte signale, sous la section. */
|
|
153
|
+
productFields: string;
|
|
154
|
+
/** Debut du titre propose (« Veille Keytruda »). */
|
|
155
|
+
productTitle: string;
|
|
156
|
+
}
|
|
136
157
|
/** Ce que le formulaire de creation rend a l'appelant. */
|
|
137
158
|
export interface RmAlertDraft {
|
|
138
159
|
title: string;
|