@tekkare/romulus 0.1.232 → 0.1.233
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/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>
|
|
@@ -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
|
+
}
|