@tekkare/romulus 0.1.232 → 0.1.234
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/CopyCode.vue +79 -0
- package/dist/runtime/components/ResultCard.vue +4 -1
- package/dist/runtime/components/SidebarLeft.vue +21 -0
- package/dist/runtime/components/SourceCard.vue +2 -30
- package/dist/runtime/utils/clipboard.d.ts +8 -0
- package/dist/runtime/utils/clipboard.js +21 -0
- package/package.json +1 -1
package/dist/module.json
CHANGED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
RmCopyCode - un identifiant (RPPS, FINESS, NCT, EUCT, PMID...) qu'on copie
|
|
3
|
+
d'un clic.
|
|
4
|
+
|
|
5
|
+
Ces codes sont ce que l'utilisateur recopie dans un autre outil : les
|
|
6
|
+
laisser en pastille grise au bout de la ligne de tags les noyait parmi les
|
|
7
|
+
statuts. Le composant les sort avec leur libelle (« RPPS · 1000... ») pour
|
|
8
|
+
qu'on sache quel code on lit sans connaitre son format.
|
|
9
|
+
|
|
10
|
+
Le clic ne remonte pas : posee dans une carte cliquable (RmResultCard), la
|
|
11
|
+
copie ouvrirait la fiche. Retour visuel obligatoire : coche verte deux
|
|
12
|
+
secondes, annoncee aux lecteurs d'ecran, sinon l'utilisateur reclique. Pas
|
|
13
|
+
de coche si la copie a echoue, elle mentirait.
|
|
14
|
+
|
|
15
|
+
Pas d'equivalent Auriga : nouveau composant.
|
|
16
|
+
-->
|
|
17
|
+
<script setup lang="ts">
|
|
18
|
+
import { computed, ref, onUnmounted } from 'vue'
|
|
19
|
+
import { rmCopyText } from '../utils/clipboard'
|
|
20
|
+
|
|
21
|
+
const props = withDefaults(defineProps<{
|
|
22
|
+
/** Le code copie, tel quel. */
|
|
23
|
+
value: string
|
|
24
|
+
/** Nom du code (« RPPS », « FINESS »). Affiche, jamais copie. */
|
|
25
|
+
label?: string
|
|
26
|
+
lang?: 'fr' | 'en'
|
|
27
|
+
}>(), {
|
|
28
|
+
label: '',
|
|
29
|
+
lang: 'fr',
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
const emit = defineEmits<{ copy: [value: string] }>()
|
|
33
|
+
|
|
34
|
+
const WORDS = {
|
|
35
|
+
fr: { copy: 'Copier', copied: 'Copié' },
|
|
36
|
+
en: { copy: 'Copy', copied: 'Copied' },
|
|
37
|
+
}
|
|
38
|
+
const t = computed(() => WORDS[props.lang] ?? WORDS.fr)
|
|
39
|
+
|
|
40
|
+
const copied = ref(false)
|
|
41
|
+
let timer: ReturnType<typeof setTimeout> | undefined
|
|
42
|
+
|
|
43
|
+
async function copy() {
|
|
44
|
+
if (!(await rmCopyText(props.value))) return
|
|
45
|
+
copied.value = true
|
|
46
|
+
clearTimeout(timer)
|
|
47
|
+
timer = setTimeout(() => (copied.value = false), 2000)
|
|
48
|
+
emit('copy', props.value)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
onUnmounted(() => clearTimeout(timer))
|
|
52
|
+
|
|
53
|
+
const name = computed(() => [props.label, props.value].filter(Boolean).join(' '))
|
|
54
|
+
</script>
|
|
55
|
+
|
|
56
|
+
<template>
|
|
57
|
+
<button
|
|
58
|
+
type="button"
|
|
59
|
+
class="rm-copy-code"
|
|
60
|
+
:class="{ 'rm-copy-code--copied': copied }"
|
|
61
|
+
:aria-label="`${copied ? t.copied : t.copy} ${name}`"
|
|
62
|
+
:title="copied ? t.copied : t.copy"
|
|
63
|
+
@click.stop="copy"
|
|
64
|
+
>
|
|
65
|
+
<span v-if="label" class="rm-copy-code__label">{{ label }}</span>
|
|
66
|
+
<span class="rm-copy-code__value">{{ value }}</span>
|
|
67
|
+
<RmIcon
|
|
68
|
+
class="rm-copy-code__icon"
|
|
69
|
+
:name="copied ? 'Check' : 'Copy'"
|
|
70
|
+
:size="14"
|
|
71
|
+
:weight="copied ? 'bold' : 'regular'"
|
|
72
|
+
/>
|
|
73
|
+
<span class="rm-copy-code__live" aria-live="polite">{{ copied ? t.copied : '' }}</span>
|
|
74
|
+
</button>
|
|
75
|
+
</template>
|
|
76
|
+
|
|
77
|
+
<style scoped>
|
|
78
|
+
.rm-copy-code{align-items:center;background:var(--bg-surface-2);border:1px solid var(--border-default);border-radius:var(--radius-sm);color:var(--text-primary);cursor:pointer;display:inline-flex;font-family:var(--font-sans);font-size:var(--font-13);gap:var(--space-8);line-height:1.4;max-width:100%;padding:var(--space-2) var(--space-8);transition:background var(--duration-fast) var(--ease-out),border-color var(--duration-fast) var(--ease-out),color var(--duration-fast) var(--ease-out)}.rm-copy-code:hover{background:var(--bg-hover);border-color:var(--color-violet-pale)}.rm-copy-code:focus-visible{outline:2px solid var(--color-primary);outline-offset:2px}.rm-copy-code__label{color:var(--text-muted);font-size:var(--font-11);font-weight:var(--weight-bold);letter-spacing:.08em;text-transform:uppercase}.rm-copy-code__value{font-family:Courier New,monospace;font-weight:var(--weight-bold);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rm-copy-code__icon{color:var(--text-muted);flex-shrink:0}.rm-copy-code--copied,.rm-copy-code--copied:hover{background:var(--color-success-bg);border-color:var(--color-success)}.rm-copy-code--copied .rm-copy-code__icon{color:var(--color-success)}.rm-copy-code__live{clip-path:inset(50%);height:1px;overflow:hidden;position:absolute;white-space:nowrap;width:1px}
|
|
79
|
+
</style>
|
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
internes (export, liens).
|
|
15
15
|
|
|
16
16
|
Slots :
|
|
17
|
+
code — identifiant au-dessus du titre (RmCopyCode : RPPS, NCT...).
|
|
18
|
+
Hors de la ligne meta, ou il se perdait parmi les tags.
|
|
17
19
|
title — identite, une ligne en gras
|
|
18
20
|
subtitle — complement gris sous le titre
|
|
19
21
|
actions — a droite du titre (export...). Le clic doit y etre stoppe.
|
|
@@ -97,6 +99,7 @@ withDefaults(
|
|
|
97
99
|
<div class="rm-rcard" :class="{ 'rm-rcard--clickable': clickable, 'rm-rcard--dense': dense }">
|
|
98
100
|
<div class="rm-rcard__head">
|
|
99
101
|
<div class="rm-rcard__identity">
|
|
102
|
+
<div v-if="$slots.code" class="rm-rcard__code"><slot name="code" /></div>
|
|
100
103
|
<h3 class="rm-rcard__title"><slot name="title" /></h3>
|
|
101
104
|
<div v-if="$slots.subtitle" class="rm-rcard__subtitle"><slot name="subtitle" /></div>
|
|
102
105
|
</div>
|
|
@@ -135,5 +138,5 @@ withDefaults(
|
|
|
135
138
|
</template>
|
|
136
139
|
|
|
137
140
|
<style scoped>
|
|
138
|
-
.rm-rcard{background:var(--bg-surface);border:1px solid var(--border-default);border-radius:var(--radius-2xl);color:var(--text-primary);display:block;font-family:var(--font-sans);overflow:hidden;transition:border-color var(--duration-fast) linear,box-shadow var(--duration-fast) linear;width:100%}.rm-rcard--clickable{cursor:pointer}.rm-rcard--clickable:hover{border-color:color-mix(in srgb,var(--color-primary) 35%,var(--border-default));box-shadow:var(--shadow-md)}.rm-rcard__head{align-items:flex-start;display:flex;gap:var(--space-16);padding:var(--space-20) var(--space-24) 0}.rm-rcard__identity{flex:1 1 auto;min-width:0}.rm-rcard__title{font-size:var(--font-18);font-weight:800;letter-spacing:.01em;margin:0}.rm-rcard__subtitle{color:var(--text-muted);font-size:var(--font-14);margin-top:4px;overflow-wrap:anywhere}.rm-rcard__actions{align-items:center;display:flex;flex:0 0 auto;gap:var(--space-8)}.rm-rcard__body{align-items:stretch;display:flex;gap:var(--space-24);padding:var(--space-16) var(--space-24) var(--space-20)}.rm-rcard__count{align-items:center;border-right:1px solid var(--border-default);display:flex;flex:0 0 128px;flex-direction:column;gap:var(--space-2);justify-content:center;padding-right:var(--space-24);text-align:center}.rm-rcard__count-value{color:var(--color-primary);font-size:var(--font-32);font-weight:var(--weight-bold);line-height:1.1;margin:0}.rm-rcard__count-label{color:var(--text-muted);font-size:var(--font-12);font-weight:var(--weight-semibold);letter-spacing:.08em;margin:0;text-transform:uppercase}.rm-rcard__count-extra{color:var(--color-primary);font-size:var(--font-13);line-height:1.3;margin:6px 0 0}.rm-rcard__count-extra+.rm-rcard__count-extra{margin-top:2px}.rm-rcard__main{display:flex;flex:1 1 auto;flex-direction:column;gap:var(--space-6);min-width:0}.rm-rcard__meta{align-items:center;display:flex;flex-wrap:wrap;gap:var(--space-8)}.rm-rcard__more{color:var(--color-primary);font-size:var(--font-14);font-weight:var(--weight-semibold);margin:4px 0 0}.rm-rcard__footer{background:var(--bg-surface-2);border-top:1px solid var(--border-default);font-size:var(--font-13);padding:var(--space-12) var(--space-24)}.rm-rcard :deep(.rm-rcard__label){align-items:center;color:var(--text-secondary);display:inline-flex;font-size:var(--font-12);font-weight:var(--weight-bold);gap:var(--space-4);letter-spacing:.06em;text-transform:uppercase}.rm-rcard :deep(.rm-rcard__ref){background:var(--bg-surface-3);border-radius:var(--radius-sm);color:var(--text-secondary);font-family:Courier New,monospace;font-size:var(--font-12);padding:var(--space-2) var(--space-8)}.rm-rcard :deep(.rm-rcard__lead){font-size:15px;font-weight:var(--weight-bold);margin:0}.rm-rcard :deep(.rm-rcard__text){font-size:15px;margin:0}.rm-rcard :deep(.rm-rcard__desc){color:var(--text-secondary);font-size:var(--font-14);line-height:1.5;margin:0}.rm-rcard :deep(.rm-rcard__clamp){display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}.rm-rcard--dense{border-radius:var(--radius-xl)}.rm-rcard--dense .rm-rcard__head{gap:var(--space-12);padding:var(--space-12) var(--space-16) 0}.rm-rcard--dense .rm-rcard__title{font-size:15px}.rm-rcard--dense .rm-rcard__subtitle{font-size:var(--font-13);margin-top:2px}.rm-rcard--dense .rm-rcard__body{gap:var(--space-16);padding:var(--space-8) var(--space-16) var(--space-12)}.rm-rcard--dense .rm-rcard__count{flex-basis:104px;padding-right:var(--space-16)}.rm-rcard--dense .rm-rcard__count-value{font-size:var(--font-24)}.rm-rcard--dense .rm-rcard__count-label{font-size:var(--font-11)}.rm-rcard--dense .rm-rcard__main{gap:var(--space-4)}.rm-rcard--dense .rm-rcard__count-extra,.rm-rcard--dense .rm-rcard__footer,.rm-rcard--dense .rm-rcard__main>:not(.rm-rcard__meta,.rm-rcard__lead){display:none}.rm-rcard--dense :deep(.rm-rcard__lead){display:-webkit-box;-webkit-line-clamp:1;-webkit-box-orient:vertical;overflow:hidden}.rm-rcard--dense .rm-rcard__body{align-items:center}.rm-rcard--dense :deep(.rm-rcard__lead),.rm-rcard--dense :deep(.rm-rcard__text){font-size:var(--font-14)}.rm-rcard--dense :deep(.rm-rcard__desc){font-size:var(--font-13)}
|
|
141
|
+
.rm-rcard{background:var(--bg-surface);border:1px solid var(--border-default);border-radius:var(--radius-2xl);color:var(--text-primary);display:block;font-family:var(--font-sans);overflow:hidden;transition:border-color var(--duration-fast) linear,box-shadow var(--duration-fast) linear;width:100%}.rm-rcard--clickable{cursor:pointer}.rm-rcard--clickable:hover{border-color:color-mix(in srgb,var(--color-primary) 35%,var(--border-default));box-shadow:var(--shadow-md)}.rm-rcard__head{align-items:flex-start;display:flex;gap:var(--space-16);padding:var(--space-20) var(--space-24) 0}.rm-rcard__identity{flex:1 1 auto;min-width:0}.rm-rcard__code{display:flex;flex-wrap:wrap;gap:var(--space-8);margin-bottom:var(--space-8)}.rm-rcard__title{font-size:var(--font-18);font-weight:800;letter-spacing:.01em;margin:0}.rm-rcard__subtitle{color:var(--text-muted);font-size:var(--font-14);margin-top:4px;overflow-wrap:anywhere}.rm-rcard__actions{align-items:center;display:flex;flex:0 0 auto;gap:var(--space-8)}.rm-rcard__body{align-items:stretch;display:flex;gap:var(--space-24);padding:var(--space-16) var(--space-24) var(--space-20)}.rm-rcard__count{align-items:center;border-right:1px solid var(--border-default);display:flex;flex:0 0 128px;flex-direction:column;gap:var(--space-2);justify-content:center;padding-right:var(--space-24);text-align:center}.rm-rcard__count-value{color:var(--color-primary);font-size:var(--font-32);font-weight:var(--weight-bold);line-height:1.1;margin:0}.rm-rcard__count-label{color:var(--text-muted);font-size:var(--font-12);font-weight:var(--weight-semibold);letter-spacing:.08em;margin:0;text-transform:uppercase}.rm-rcard__count-extra{color:var(--color-primary);font-size:var(--font-13);line-height:1.3;margin:6px 0 0}.rm-rcard__count-extra+.rm-rcard__count-extra{margin-top:2px}.rm-rcard__main{display:flex;flex:1 1 auto;flex-direction:column;gap:var(--space-6);min-width:0}.rm-rcard__meta{align-items:center;display:flex;flex-wrap:wrap;gap:var(--space-8)}.rm-rcard__more{color:var(--color-primary);font-size:var(--font-14);font-weight:var(--weight-semibold);margin:4px 0 0}.rm-rcard__footer{background:var(--bg-surface-2);border-top:1px solid var(--border-default);font-size:var(--font-13);padding:var(--space-12) var(--space-24)}.rm-rcard :deep(.rm-rcard__label){align-items:center;color:var(--text-secondary);display:inline-flex;font-size:var(--font-12);font-weight:var(--weight-bold);gap:var(--space-4);letter-spacing:.06em;text-transform:uppercase}.rm-rcard :deep(.rm-rcard__ref){background:var(--bg-surface-3);border-radius:var(--radius-sm);color:var(--text-secondary);font-family:Courier New,monospace;font-size:var(--font-12);padding:var(--space-2) var(--space-8)}.rm-rcard :deep(.rm-rcard__lead){font-size:15px;font-weight:var(--weight-bold);margin:0}.rm-rcard :deep(.rm-rcard__text){font-size:15px;margin:0}.rm-rcard :deep(.rm-rcard__desc){color:var(--text-secondary);font-size:var(--font-14);line-height:1.5;margin:0}.rm-rcard :deep(.rm-rcard__clamp){display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}.rm-rcard--dense{border-radius:var(--radius-xl)}.rm-rcard--dense .rm-rcard__head{gap:var(--space-12);padding:var(--space-12) var(--space-16) 0}.rm-rcard--dense .rm-rcard__title{font-size:15px}.rm-rcard--dense .rm-rcard__subtitle{font-size:var(--font-13);margin-top:2px}.rm-rcard--dense .rm-rcard__body{gap:var(--space-16);padding:var(--space-8) var(--space-16) var(--space-12)}.rm-rcard--dense .rm-rcard__count{flex-basis:104px;padding-right:var(--space-16)}.rm-rcard--dense .rm-rcard__count-value{font-size:var(--font-24)}.rm-rcard--dense .rm-rcard__count-label{font-size:var(--font-11)}.rm-rcard--dense .rm-rcard__main{gap:var(--space-4)}.rm-rcard--dense .rm-rcard__count-extra,.rm-rcard--dense .rm-rcard__footer,.rm-rcard--dense .rm-rcard__main>:not(.rm-rcard__meta,.rm-rcard__lead){display:none}.rm-rcard--dense :deep(.rm-rcard__lead){display:-webkit-box;-webkit-line-clamp:1;-webkit-box-orient:vertical;overflow:hidden}.rm-rcard--dense .rm-rcard__body{align-items:center}.rm-rcard--dense :deep(.rm-rcard__lead),.rm-rcard--dense :deep(.rm-rcard__text){font-size:var(--font-14)}.rm-rcard--dense :deep(.rm-rcard__desc){font-size:var(--font-13)}
|
|
139
142
|
</style>
|
|
@@ -59,6 +59,13 @@ const props = withDefaults(defineProps<{
|
|
|
59
59
|
open?: boolean
|
|
60
60
|
collapseLabel?: string
|
|
61
61
|
expandLabel?: string
|
|
62
|
+
/**
|
|
63
|
+
* true : toutes les sections repliables arrivent deployees, et restent
|
|
64
|
+
* repliables au clic. Pour une app dont la navigation est courte, ou le
|
|
65
|
+
* lecteur veut voir d'emblee tout ce qui est offert sans deplier groupe par
|
|
66
|
+
* groupe.
|
|
67
|
+
*/
|
|
68
|
+
defaultOpen?: boolean
|
|
62
69
|
}>(), {
|
|
63
70
|
flag: undefined,
|
|
64
71
|
titleTo: undefined,
|
|
@@ -67,6 +74,7 @@ const props = withDefaults(defineProps<{
|
|
|
67
74
|
open: false,
|
|
68
75
|
collapseLabel: 'Replier la barre latérale',
|
|
69
76
|
expandLabel: 'Déplier la barre latérale',
|
|
77
|
+
defaultOpen: false,
|
|
70
78
|
})
|
|
71
79
|
|
|
72
80
|
const emit = defineEmits<{
|
|
@@ -128,6 +136,19 @@ watch(() => sectionOf(props.activePath), (key) => {
|
|
|
128
136
|
if (key && !openSections.value.has(key)) openSections.value.add(key)
|
|
129
137
|
})
|
|
130
138
|
|
|
139
|
+
// `defaultOpen` : chaque section deployee a sa premiere apparition, y compris
|
|
140
|
+
// celles que les droits d'acces ajoutent en cours de route. Une section deja
|
|
141
|
+
// vue garde l'etat que le lecteur lui a donne.
|
|
142
|
+
const seenSections = new Set<string>()
|
|
143
|
+
watch(() => collapsibles.value.map(s => s.key), (keys) => {
|
|
144
|
+
if (!props.defaultOpen) return
|
|
145
|
+
for (const key of keys) {
|
|
146
|
+
if (seenSections.has(key)) continue
|
|
147
|
+
seenSections.add(key)
|
|
148
|
+
openSections.value.add(key)
|
|
149
|
+
}
|
|
150
|
+
}, { immediate: true })
|
|
151
|
+
|
|
131
152
|
function toggleSection(key: string) {
|
|
132
153
|
if (openSections.value.has(key)) openSections.value.delete(key)
|
|
133
154
|
else openSections.value.add(key)
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
partent jamais dans le navigateur.
|
|
20
20
|
-->
|
|
21
21
|
<script setup lang="ts">
|
|
22
|
+
import { rmCopyText } from '../utils/clipboard'
|
|
22
23
|
import { computed, ref, useId, onUnmounted } from 'vue'
|
|
23
24
|
|
|
24
25
|
const props = withDefaults(defineProps<{
|
|
@@ -193,37 +194,8 @@ const teaserHtml = computed(() => (props.summary ? '' : props.aboutSource || '')
|
|
|
193
194
|
const copied = ref(false)
|
|
194
195
|
let copyTimer: ReturnType<typeof setTimeout> | undefined
|
|
195
196
|
|
|
196
|
-
/**
|
|
197
|
-
* Le presse-papiers moderne demande un contexte sur, et refuse dans un iframe
|
|
198
|
-
* sans permission : la selection cachee reste le seul chemin qui marche partout.
|
|
199
|
-
* En cas d'echec des deux, pas de coche : elle mentirait.
|
|
200
|
-
*/
|
|
201
|
-
async function writeToClipboard(text: string) {
|
|
202
|
-
try {
|
|
203
|
-
await navigator.clipboard.writeText(text)
|
|
204
|
-
return true
|
|
205
|
-
}
|
|
206
|
-
catch {
|
|
207
|
-
try {
|
|
208
|
-
const field = document.createElement('textarea')
|
|
209
|
-
field.value = text
|
|
210
|
-
field.setAttribute('readonly', '')
|
|
211
|
-
field.style.position = 'fixed'
|
|
212
|
-
field.style.opacity = '0'
|
|
213
|
-
document.body.appendChild(field)
|
|
214
|
-
field.select()
|
|
215
|
-
const done = document.execCommand('copy')
|
|
216
|
-
document.body.removeChild(field)
|
|
217
|
-
return done
|
|
218
|
-
}
|
|
219
|
-
catch {
|
|
220
|
-
return false
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
197
|
async function copyTitle() {
|
|
226
|
-
if (!(await
|
|
198
|
+
if (!(await rmCopyText(props.title))) return
|
|
227
199
|
copied.value = true
|
|
228
200
|
clearTimeout(copyTimer)
|
|
229
201
|
copyTimer = setTimeout(() => (copied.value = false), 2000)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copie un texte dans le presse-papiers. Rend `false` si rien n'a ete copie,
|
|
3
|
+
* pour qu'un composant n'affiche pas une coche qui mentirait.
|
|
4
|
+
*
|
|
5
|
+
* Le presse-papiers moderne demande un contexte sur, et refuse dans un iframe
|
|
6
|
+
* sans permission : la selection cachee reste le seul chemin qui marche partout.
|
|
7
|
+
*/
|
|
8
|
+
export declare function rmCopyText(text: string): Promise<boolean>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export async function rmCopyText(text) {
|
|
2
|
+
try {
|
|
3
|
+
await navigator.clipboard.writeText(text);
|
|
4
|
+
return true;
|
|
5
|
+
} catch {
|
|
6
|
+
try {
|
|
7
|
+
const field = document.createElement("textarea");
|
|
8
|
+
field.value = text;
|
|
9
|
+
field.setAttribute("readonly", "");
|
|
10
|
+
field.style.position = "fixed";
|
|
11
|
+
field.style.opacity = "0";
|
|
12
|
+
document.body.appendChild(field);
|
|
13
|
+
field.select();
|
|
14
|
+
const done = document.execCommand("copy");
|
|
15
|
+
document.body.removeChild(field);
|
|
16
|
+
return done;
|
|
17
|
+
} catch {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|