@tekkare/romulus 0.1.251 → 0.1.253

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.
Files changed (267) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/runtime/utils/projects.d.ts +8 -0
  3. package/dist/runtime/utils/projects.js +8 -5
  4. package/layer/app/app.config.ts +6 -0
  5. package/layer/app/components/OipPubmedSummary.vue +223 -0
  6. package/layer/app/components/OipResourceDrawer.vue +331 -0
  7. package/layer/app/components/OipResourceDrawerLink.vue +40 -0
  8. package/layer/app/components/OipResourceField.vue +41 -0
  9. package/layer/app/components/OipResourceGenericCard.vue +54 -0
  10. package/layer/app/components/OipResourceSource.vue +50 -0
  11. package/layer/app/components/OipResourceTags.vue +65 -0
  12. package/layer/app/components/OipResourceView.vue +107 -0
  13. package/layer/app/components/oip-drugs/fiches/AssociationFiche.vue +775 -0
  14. package/layer/app/components/oip-drugs/fiches/BrandFiche.vue +1111 -0
  15. package/layer/app/components/oip-drugs/fiches/IndicationFiche.vue +182 -0
  16. package/layer/app/components/oip-drugs/fiches/JoFiche.vue +101 -0
  17. package/layer/app/components/oip-drugs/fiches/PndsFiche.vue +279 -0
  18. package/layer/app/components/oip-drugs/heart/JoCard.vue +314 -0
  19. package/layer/app/components/oip-drugs/heart/TextHighlight.vue +326 -0
  20. package/layer/app/components/oip-drugs/heart/ansm/BrandCard.vue +187 -0
  21. package/layer/app/components/oip-drugs/heart/ansm/CisDetail.vue +363 -0
  22. package/layer/app/components/oip-drugs/heart/ansm/CisTable.vue +167 -0
  23. package/layer/app/components/oip-drugs/heart/associations/NationalAssemblyCard.vue +373 -0
  24. package/layer/app/components/oip-drugs/heart/associations/NationalAssemblyFiche.vue +372 -0
  25. package/layer/app/components/oip-drugs/heart/associations/TranscriptionCard.vue +55 -0
  26. package/layer/app/components/oip-drugs/heart/elastic/SourceContainerElastic.vue +103 -0
  27. package/layer/app/components/oip-drugs/heart/france/BrandGroupCard.vue +450 -0
  28. package/layer/app/components/oip-drugs/heart/mitm/Card.vue +199 -0
  29. package/layer/app/components/oip-drugs/heart/mitm/Source.vue +23 -0
  30. package/layer/app/components/oip-drugs/heart/mitm/details/Overview.vue +381 -0
  31. package/layer/app/components/oip-drugs/heart/put/Card.vue +212 -0
  32. package/layer/app/components/oip-drugs/heart/results/AccesCard.vue +696 -0
  33. package/layer/app/components/oip-drugs/heart/results/AlertCard.vue +342 -0
  34. package/layer/app/components/oip-drugs/heart/results/AsmrCursor.vue +229 -0
  35. package/layer/app/components/oip-drugs/heart/results/EvalCard.vue +370 -0
  36. package/layer/app/components/oip-drugs/heart/results/IndicCard.vue +743 -0
  37. package/layer/app/components/oip-drugs/heart/results/MatchExplain.vue +243 -0
  38. package/layer/app/components/oip-drugs/heart/results/PrefRows.vue +105 -0
  39. package/layer/app/components/oip-drugs/heart/results/details/HasHistory.vue +487 -0
  40. package/layer/app/components/oip-drugs/heart/results/details/InternationalCountryCard.vue +461 -0
  41. package/layer/app/components/oip-drugs/heart/results/details/InternationalOverview.vue +200 -0
  42. package/layer/app/components/oip-drugs/heart/results/details/InternationalPriceDrawer.vue +647 -0
  43. package/layer/app/components/oip-drugs/heart/results/details/Monograph.vue +128 -0
  44. package/layer/app/components/oip-drugs/heart/results/details/OpinionDrawer.vue +535 -0
  45. package/layer/app/components/oip-drugs/heart/results/details/PresentationDrawer.vue +738 -0
  46. package/layer/app/components/oip-drugs/heart/results/details/Price.vue +227 -0
  47. package/layer/app/components/oip-drugs/heart/results/details/PriceOverview.vue +1261 -0
  48. package/layer/app/components/oip-drugs/heart/results/details/RecordDrawer.vue +494 -0
  49. package/layer/app/components/oip-drugs/heart/results/details/RecordList.vue +426 -0
  50. package/layer/app/components/oip-drugs/heart/results/les/Card.vue +340 -0
  51. package/layer/app/components/oip-drugs/heart/results/les/Drawer.vue +67 -0
  52. package/layer/app/components/oip-drugs/heart/results/les/IndicationDetail.vue +149 -0
  53. package/layer/app/components/oip-drugs/heart/results/les/Source.vue +20 -0
  54. package/layer/app/components/oip-drugs/heart/results/les/SourceTag.vue +39 -0
  55. package/layer/app/components/oip-drugs/heart/searchPage/DynamicSource.vue +29 -0
  56. package/layer/app/components/oip-drugs/medicam/market/Chart.vue +121 -0
  57. package/layer/app/components/oip-drugs/outpatient/ProductEvolution.vue +236 -0
  58. package/layer/app/components/oip-drugs/resources/Ansm.vue +75 -0
  59. package/layer/app/components/oip-drugs/resources/Association.vue +30 -0
  60. package/layer/app/components/oip-drugs/resources/Brand.vue +65 -0
  61. package/layer/app/components/oip-drugs/resources/Contribution.vue +216 -0
  62. package/layer/app/components/oip-drugs/resources/Hospital.vue +151 -0
  63. package/layer/app/components/oip-drugs/resources/Indication.vue +54 -0
  64. package/layer/app/components/oip-drugs/resources/Jo.vue +57 -0
  65. package/layer/app/components/oip-drugs/resources/Medicam.vue +140 -0
  66. package/layer/app/components/oip-drugs/resources/Mitm.vue +84 -0
  67. package/layer/app/components/oip-drugs/resources/NationalAssembly.vue +93 -0
  68. package/layer/app/components/oip-drugs/resources/Outpatient.vue +97 -0
  69. package/layer/app/components/oip-drugs/resources/Pnds.vue +24 -0
  70. package/layer/app/components/oip-drugs/resources/Price.vue +106 -0
  71. package/layer/app/components/oip-drugs/resources/Put.vue +143 -0
  72. package/layer/app/components/oip-science/appel-a-projet/Cards.vue +162 -0
  73. package/layer/app/components/oip-science/clinical-trials/Cards.vue +382 -0
  74. package/layer/app/components/oip-science/clinical-trials/CardsGlobal.vue +273 -0
  75. package/layer/app/components/oip-science/clinical-trials/LinkedList.vue +366 -0
  76. package/layer/app/components/oip-science/clinical-trials/PairBadge.vue +25 -0
  77. package/layer/app/components/oip-science/clinical-trials/PairBody.vue +182 -0
  78. package/layer/app/components/oip-science/clinical-trials/SitesDrawer.vue +95 -0
  79. package/layer/app/components/oip-science/common/Form.vue +73 -0
  80. package/layer/app/components/oip-science/common/MatchAvgTag.vue +283 -0
  81. package/layer/app/components/oip-science/common/NoData.vue +16 -0
  82. package/layer/app/components/oip-science/common/ScoreBadge.vue +35 -0
  83. package/layer/app/components/oip-science/common/Source.vue +31 -0
  84. package/layer/app/components/oip-science/common/TableTags.vue +279 -0
  85. package/layer/app/components/oip-science/common/Timeline.vue +104 -0
  86. package/layer/app/components/oip-science/common/ToolTips.vue +89 -0
  87. package/layer/app/components/oip-science/cross/publicationPubmed.vue +484 -0
  88. package/layer/app/components/oip-science/crossData/CrossDataLocations.vue +71 -0
  89. package/layer/app/components/oip-science/ctgov/Investigators.vue +148 -0
  90. package/layer/app/components/oip-science/ctgov/Locations.vue +102 -0
  91. package/layer/app/components/oip-science/ctgov/Publications.vue +52 -0
  92. package/layer/app/components/oip-science/ctgov/Sponsors.vue +66 -0
  93. package/layer/app/components/oip-science/ctgov/StudyDescription.vue +1136 -0
  94. package/layer/app/components/oip-science/ctgov-centers/ClinicalTrials.vue +40 -0
  95. package/layer/app/components/oip-science/ctgov-centers/Publications.vue +50 -0
  96. package/layer/app/components/oip-science/ctgov-investigators/ClinicalTrials.vue +43 -0
  97. package/layer/app/components/oip-science/ctgov-investigators/Informations.vue +171 -0
  98. package/layer/app/components/oip-science/ctgov-investigators/Publications.vue +44 -0
  99. package/layer/app/components/oip-science/elastic/DateRangeFilter.vue +209 -0
  100. package/layer/app/components/oip-science/elastic/Filter.vue +719 -0
  101. package/layer/app/components/oip-science/elastic/MatchExplain.vue +152 -0
  102. package/layer/app/components/oip-science/establishments/Cards.vue +183 -0
  103. package/layer/app/components/oip-science/establishments/ClinicalTrials.vue +62 -0
  104. package/layer/app/components/oip-science/establishments/Hcps.vue +227 -0
  105. package/layer/app/components/oip-science/establishments/Informations.vue +263 -0
  106. package/layer/app/components/oip-science/establishments/LinkedList.vue +153 -0
  107. package/layer/app/components/oip-science/establishments/MatchedLabels.vue +194 -0
  108. package/layer/app/components/oip-science/establishments/PublicFinance.vue +62 -0
  109. package/layer/app/components/oip-science/euct/Investigators.vue +125 -0
  110. package/layer/app/components/oip-science/euct/Locations.vue +102 -0
  111. package/layer/app/components/oip-science/euct/Products.vue +101 -0
  112. package/layer/app/components/oip-science/euct/Publications.vue +89 -0
  113. package/layer/app/components/oip-science/euct/Sponsors.vue +86 -0
  114. package/layer/app/components/oip-science/euct/StudyDescription.vue +1075 -0
  115. package/layer/app/components/oip-science/euct-centers/ClinicalTrials.vue +40 -0
  116. package/layer/app/components/oip-science/euct-centers/Publications.vue +55 -0
  117. package/layer/app/components/oip-science/euct-investigators/ClinicalTrials.vue +41 -0
  118. package/layer/app/components/oip-science/euct-investigators/Informations.vue +171 -0
  119. package/layer/app/components/oip-science/euct-investigators/Publications.vue +51 -0
  120. package/layer/app/components/oip-science/hcps/AppelsAProjet.vue +62 -0
  121. package/layer/app/components/oip-science/hcps/Cards.vue +237 -0
  122. package/layer/app/components/oip-science/hcps/ClinicalTrials.vue +78 -0
  123. package/layer/app/components/oip-science/hcps/Informations.vue +296 -0
  124. package/layer/app/components/oip-science/hcps/LinkedList.vue +153 -0
  125. package/layer/app/components/oip-science/hcps/MatchedLabels.vue +207 -0
  126. package/layer/app/components/oip-science/hcps/Pubmed.vue +128 -0
  127. package/layer/app/components/oip-science/local/SliderTabs.vue +99 -0
  128. package/layer/app/components/oip-science/local/SliderTabsFull.vue +135 -0
  129. package/layer/app/components/oip-science/pubmed/Authors.vue +128 -0
  130. package/layer/app/components/oip-science/pubmed/Cards.vue +417 -0
  131. package/layer/app/components/oip-science/pubmed/ClinicalTrials.vue +71 -0
  132. package/layer/app/components/oip-science/resources/Phrc.vue +39 -0
  133. package/layer/app/composables/useOipChapterScroll.ts +43 -0
  134. package/layer/app/composables/useOipResourceHooks.ts +27 -0
  135. package/layer/app/composables/useOipResourceNav.ts +66 -0
  136. package/layer/app/composables/useOipResourceViews.ts +117 -0
  137. package/layer/app/composables/useOipResources.ts +83 -0
  138. package/layer/app/oip-drugs/PORTING.md +120 -0
  139. package/layer/app/oip-drugs/composables/useAssociationDetail.ts +220 -0
  140. package/layer/app/oip-drugs/composables/useAssociationExport.ts +63 -0
  141. package/layer/app/oip-drugs/composables/useAssociationLookup.ts +105 -0
  142. package/layer/app/oip-drugs/composables/useCardPrefOptions.ts +201 -0
  143. package/layer/app/oip-drugs/composables/useCurrency.ts +121 -0
  144. package/layer/app/oip-drugs/composables/useDirectusSources.ts +224 -0
  145. package/layer/app/oip-drugs/composables/useHeartBrandMatches.ts +112 -0
  146. package/layer/app/oip-drugs/composables/useHeartDetailSearches.ts +479 -0
  147. package/layer/app/oip-drugs/composables/useHeartIntlApprovals.ts +661 -0
  148. package/layer/app/oip-drugs/composables/useHeartLesIndication.ts +168 -0
  149. package/layer/app/oip-drugs/composables/useHeartMatchExplain.ts +231 -0
  150. package/layer/app/oip-drugs/composables/useHeartReports.ts +239 -0
  151. package/layer/app/oip-drugs/composables/useHeartSearchFields.ts +233 -0
  152. package/layer/app/oip-drugs/composables/useMergeByKey.ts +23 -0
  153. package/layer/app/oip-drugs/composables/useOipDrugsElastic.ts +621 -0
  154. package/layer/app/oip-drugs/composables/usePageEmpty.ts +58 -0
  155. package/layer/app/oip-drugs/composables/v-useSalesSource.ts +64 -0
  156. package/layer/app/oip-drugs/drugs.css +500 -0
  157. package/layer/app/oip-drugs/esIndices.ts +322 -0
  158. package/layer/app/oip-drugs/kinds.ts +264 -0
  159. package/layer/app/oip-drugs/locales/en.json +1571 -0
  160. package/layer/app/oip-drugs/locales/fr.json +1571 -0
  161. package/layer/app/oip-drugs/monographs/blincyto-has.vue +289 -0
  162. package/layer/app/oip-drugs/monographs/darzalex-has.vue +276 -0
  163. package/layer/app/oip-drugs/monographs/dupixent-has.vue +216 -0
  164. package/layer/app/oip-drugs/monographs/enhertu-has.vue +280 -0
  165. package/layer/app/oip-drugs/monographs/has-sein-2023.vue +1649 -0
  166. package/layer/app/oip-drugs/monographs/ibrance-has.vue +277 -0
  167. package/layer/app/oip-drugs/monographs/imfinzi-has.vue +293 -0
  168. package/layer/app/oip-drugs/monographs/keytruda-has.vue +334 -0
  169. package/layer/app/oip-drugs/monographs/kisqali-has.vue +201 -0
  170. package/layer/app/oip-drugs/monographs/opdivo-has.vue +231 -0
  171. package/layer/app/oip-drugs/monographs/ozempic-has.vue +186 -0
  172. package/layer/app/oip-drugs/monographs/tecentriq-has.vue +312 -0
  173. package/layer/app/oip-drugs/monographs/yescarta-has.vue +272 -0
  174. package/layer/app/oip-drugs/types/elastic.ts +113 -0
  175. package/layer/app/oip-drugs/types/es/agencies.ts +152 -0
  176. package/layer/app/oip-drugs/types/es/directory.ts +54 -0
  177. package/layer/app/oip-drugs/types/es/docByIndex.ts +151 -0
  178. package/layer/app/oip-drugs/types/es/openmedic.ts +67 -0
  179. package/layer/app/oip-drugs/types/es/outpatient.ts +71 -0
  180. package/layer/app/oip-drugs/types/es/portal.ts +130 -0
  181. package/layer/app/oip-drugs/types/es/retail.ts +107 -0
  182. package/layer/app/oip-drugs/types/es/statistics.ts +29 -0
  183. package/layer/app/oip-drugs/types/heart/CurrencyRate.ts +16 -0
  184. package/layer/app/oip-drugs/types/heart/ansm.ts +106 -0
  185. package/layer/app/oip-drugs/types/heart/tag.ts +29 -0
  186. package/layer/app/oip-drugs/utils/associationFields.ts +118 -0
  187. package/layer/app/oip-drugs/utils/contributions.ts +36 -0
  188. package/layer/app/oip-drugs/utils/countryMapping.ts +387 -0
  189. package/layer/app/oip-drugs/utils/esHighlight.ts +21 -0
  190. package/layer/app/oip-drugs/utils/exportSheets.ts +57 -0
  191. package/layer/app/oip-drugs/utils/heartIntlPrice.ts +228 -0
  192. package/layer/app/oip-drugs/utils/heartStyleAndFormat.ts +262 -0
  193. package/layer/app/oip-drugs/utils/heartStyleAnsm.ts +71 -0
  194. package/layer/app/oip-drugs/utils/latestDate.ts +31 -0
  195. package/layer/app/oip-drugs/utils/p-heartCardTags.ts +53 -0
  196. package/layer/app/oip-drugs/utils/priceExport.ts +269 -0
  197. package/layer/app/oip-drugs/utils/q-hasRatings.ts +43 -0
  198. package/layer/app/oip-drugs/utils/s-openCard.ts +20 -0
  199. package/layer/app/oip-drugs/utils/sourceUuids.ts +155 -0
  200. package/layer/app/oip-drugs/utils/styleAndFormat.ts +40 -0
  201. package/layer/app/oip-drugs/utils/v-salesSeries.ts +156 -0
  202. package/layer/app/oip-drugs/utils/v-scopeTags.ts +23 -0
  203. package/layer/app/oip-science/PORTING.md +93 -0
  204. package/layer/app/oip-science/assets/smartSelection.json +428 -0
  205. package/layer/app/oip-science/composables/useAuthorMatching.ts +342 -0
  206. package/layer/app/oip-science/composables/useCTCenter.ts +107 -0
  207. package/layer/app/oip-science/composables/useCTInvestigators.ts +107 -0
  208. package/layer/app/oip-science/composables/useEstablishmentCrossData.ts +139 -0
  209. package/layer/app/oip-science/composables/useEstablishmentMatching.ts +176 -0
  210. package/layer/app/oip-science/composables/useEuct.ts +418 -0
  211. package/layer/app/oip-science/composables/useEuctCenter.ts +107 -0
  212. package/layer/app/oip-science/composables/useEuctInvestigators.ts +107 -0
  213. package/layer/app/oip-science/composables/useGlobalClinicalTrials.ts +624 -0
  214. package/layer/app/oip-science/composables/useLinkedTrials.ts +83 -0
  215. package/layer/app/oip-science/composables/useOipElastic.ts +658 -0
  216. package/layer/app/oip-science/composables/useOipSearchExplain.ts +166 -0
  217. package/layer/app/oip-science/composables/usePubmed.ts +487 -0
  218. package/layer/app/oip-science/composables/useSearchFields.ts +153 -0
  219. package/layer/app/oip-science/indexConfig.json +794 -0
  220. package/layer/app/oip-science/locales/en.json +483 -0
  221. package/layer/app/oip-science/locales/fr.json +483 -0
  222. package/layer/app/oip-science/science.css +1182 -0
  223. package/layer/app/oip-science/utils/a-openCard.ts +23 -0
  224. package/layer/app/oip-science/utils/b-openCard.ts +35 -0
  225. package/layer/app/oip-science/utils/c-filterContext.ts +81 -0
  226. package/layer/app/oip-science/utils/f-types.ts +241 -0
  227. package/layer/app/oip-science/utils/frDiseaseTerms.ts +269 -0
  228. package/layer/app/oip-science/utils/matchMethod.ts +53 -0
  229. package/layer/app/oip-science/utils/sliderTabs.ts +13 -0
  230. package/layer/app/oip-science/utils/sources.ts +15 -0
  231. package/layer/app/oip-science/utils/styleAndFormat.ts +608 -0
  232. package/layer/app/oip-science/utils/toPresets.ts +7 -0
  233. package/layer/app/oip-science/utils/valueTable.ts +126 -0
  234. package/layer/app/plugins/oip-science-i18n.ts +17 -0
  235. package/layer/app/utils/oipFicheRoutes.ts +118 -0
  236. package/layer/app/utils/oipResources.ts +440 -0
  237. package/layer/nuxt.config.ts +22 -2
  238. package/layer/public/reports/blincyto-has.html +292 -0
  239. package/layer/public/reports/blincyto-has.pdf +0 -0
  240. package/layer/public/reports/darzalex-has.html +279 -0
  241. package/layer/public/reports/darzalex-has.pdf +0 -0
  242. package/layer/public/reports/dupixent-has.html +217 -0
  243. package/layer/public/reports/dupixent-has.pdf +0 -0
  244. package/layer/public/reports/enhertu-has.html +283 -0
  245. package/layer/public/reports/enhertu-has.pdf +0 -0
  246. package/layer/public/reports/has-sein-2023.html +1639 -0
  247. package/layer/public/reports/ibrance-has.html +280 -0
  248. package/layer/public/reports/ibrance-has.pdf +0 -0
  249. package/layer/public/reports/imfinzi-has.html +297 -0
  250. package/layer/public/reports/imfinzi-has.pdf +0 -0
  251. package/layer/public/reports/keytruda-has.html +337 -0
  252. package/layer/public/reports/keytruda-has.pdf +0 -0
  253. package/layer/public/reports/kisqali-has.html +202 -0
  254. package/layer/public/reports/kisqali-has.pdf +0 -0
  255. package/layer/public/reports/opdivo-has.html +232 -0
  256. package/layer/public/reports/opdivo-has.pdf +0 -0
  257. package/layer/public/reports/ozempic-has.html +188 -0
  258. package/layer/public/reports/ozempic-has.pdf +0 -0
  259. package/layer/public/reports/tecentriq-has.html +315 -0
  260. package/layer/public/reports/tecentriq-has.pdf +0 -0
  261. package/layer/public/reports/yescarta-has.html +275 -0
  262. package/layer/public/reports/yescarta-has.pdf +0 -0
  263. package/layer/server/api/oip/es.post.ts +99 -0
  264. package/layer/server/api/oip/pdf.get.ts +70 -0
  265. package/layer/server/api/oip/reports.get.ts +98 -0
  266. package/layer/server/api/oip/resource-types.get.ts +22 -0
  267. package/package.json +1 -1
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": ">=3.16.0"
6
6
  },
7
- "version": "0.1.250",
7
+ "version": "0.1.252",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "0.8.4",
10
10
  "unbuild": "2.0.0"
@@ -49,6 +49,12 @@ export interface RmProjectGroup {
49
49
  export interface RmProjectKind<S = any> {
50
50
  /** Valeur de `resourceType` cote API. */
51
51
  type: string;
52
+ /**
53
+ * La famille sous laquelle la ligne s'affiche, quand deux types designent
54
+ * les memes documents (un essai range depuis deux apps, sous deux cles) :
55
+ * une seule puce, un seul filtre. Absent : le type lui-meme.
56
+ */
57
+ group?: string;
52
58
  index: string;
53
59
  label: Record<RmProjectLang, string>;
54
60
  /** Rubrique de l'app d'ou vient la famille (Essais, Publications...). */
@@ -82,6 +88,8 @@ export interface RmProjectResource {
82
88
  path?: string;
83
89
  /** Libelle de la famille. */
84
90
  family: string;
91
+ /** Cle de la famille affichee (cf. RmProjectKind.group) ; `type` reste celui de l'API. */
92
+ group?: string;
85
93
  section?: string;
86
94
  icon: string;
87
95
  createdAt?: string;
@@ -74,8 +74,9 @@ export async function rmProjectHydrate(items, kinds, search) {
74
74
  return out;
75
75
  }
76
76
  export function rmProjectRows(items, documents, kinds, lang = "fr") {
77
+ const groupOf = (type) => kinds.find((kind) => kind.type === type)?.group ?? type;
77
78
  const rank = (type) => {
78
- const index = kinds.findIndex((kind) => kind.type === type);
79
+ const index = kinds.findIndex((kind) => kind.type === groupOf(type));
79
80
  return index < 0 ? kinds.length : index;
80
81
  };
81
82
  return [...items].sort((a, b) => rank(a.resourceType) - rank(b.resourceType)).map((item) => {
@@ -88,6 +89,7 @@ export function rmProjectRows(items, documents, kinds, lang = "fr") {
88
89
  subtitle: kind && source ? kind.subtitle?.(source, lang) ?? "" : "",
89
90
  path: kind && source ? kind.path?.(source, item.resourceId) ?? "" : "",
90
91
  family: kind ? kind.label[lang] : item.resourceType,
92
+ group: kind?.group ?? item.resourceType,
91
93
  section: kind?.section?.[lang],
92
94
  icon: kind?.icon ?? "Question",
93
95
  createdAt: item.createdAt,
@@ -100,9 +102,10 @@ export function rmProjectRows(items, documents, kinds, lang = "fr") {
100
102
  export function rmProjectFamilies(rows) {
101
103
  const out = [];
102
104
  for (const row of rows) {
103
- const found = out.find((family) => family.type === row.type);
105
+ const key = row.group ?? row.type;
106
+ const found = out.find((family) => family.type === key);
104
107
  if (found) found.count++;
105
- else out.push({ type: row.type, label: row.family, icon: row.icon, count: 1 });
108
+ else out.push({ type: key, label: row.family, icon: row.icon, count: 1 });
106
109
  }
107
110
  return out;
108
111
  }
@@ -110,14 +113,14 @@ export function rmProjectFilterRows(rows, filter, lang = "fr") {
110
113
  const words = fold(filter.search).split(/\s+/).filter(Boolean);
111
114
  const family = filter.family && filter.family !== "all" ? filter.family : "";
112
115
  const order = rmProjectFamilies(rows).map((f) => f.type);
113
- const list = rows.filter((row) => !family || row.type === family).filter((row) => !filter.onlyCommented || (row.comments ?? 0) > 0).filter((row) => !words.length || words.every((word) => fold([row.title, row.subtitle, row.family, row.section, row.id].join(" ")).includes(word)));
116
+ const list = rows.filter((row) => !family || (row.group ?? row.type) === family).filter((row) => !filter.onlyCommented || (row.comments ?? 0) > 0).filter((row) => !words.length || words.every((word) => fold([row.title, row.subtitle, row.family, row.section, row.id].join(" ")).includes(word)));
114
117
  const byDate = (a, b) => String(a.createdAt ?? "").localeCompare(String(b.createdAt ?? ""));
115
118
  const byTitle = (a, b) => (a.title || a.id).localeCompare(b.title || b.id, lang);
116
119
  const sort = filter.sort ?? "date-desc";
117
120
  return [...list].sort((a, b) => {
118
121
  if (sort === "date-asc") return byDate(a, b);
119
122
  if (sort === "title") return byTitle(a, b);
120
- if (sort === "family") return order.indexOf(a.type) - order.indexOf(b.type) || byTitle(a, b);
123
+ if (sort === "family") return order.indexOf(a.group ?? a.type) - order.indexOf(b.group ?? b.type) || byTitle(a, b);
121
124
  return byDate(b, a);
122
125
  });
123
126
  }
@@ -39,6 +39,12 @@ export default defineAppConfig({
39
39
  * pointe la route qui nourrit le panneau : une app peut la detourner vers
40
40
  * la sienne (ou une autre API) sans toucher au panneau.
41
41
  */
42
+ /**
43
+ * L'app, pour les vues de ressources (cf. utils/oipResources) : une fiche
44
+ * dont c'est l'app d'origine s'ouvre sur place, les autres dans leur app.
45
+ * « france_augmented_science_app », « fr_drugs_app »... Vide : aucune.
46
+ */
47
+ resourceApp: "",
42
48
  /**
43
49
  * Registre des index Elasticsearch (collection Directus ElasticIndices).
44
50
  * `preload` : charge les noms au demarrage, pour les apps qui designent
@@ -0,0 +1,223 @@
1
+ <!--
2
+ Resume d'une publication PubMed : l'onglet « Summary » de la fiche de France
3
+ Augmented Science, son app d'origine.
4
+
5
+ Une seule vue pour toutes les apps : la page /pubmed/[id]/summary de France
6
+ Augmented Science la monte, et le tiroir d'un projet la monte dans
7
+ n'importe quelle app du portail. Elle ne lit ni route ni store : l'id et
8
+ les termes a surligner lui sont donnes.
9
+ -->
10
+ <template>
11
+ <div ref="root" class="oip-pm">
12
+ <RmLoadingState v-if="pending" :lines="3" :chart-height="240" />
13
+ <RmEmptyState
14
+ v-else-if="!doc"
15
+ icon="FileX"
16
+ :title="en ? 'Record unavailable' : 'Fiche indisponible'"
17
+ :text="en ? 'The record is no longer in the data.' : 'La fiche n\'est plus dans la donnée.'"
18
+ />
19
+ <div v-else class="oip-pm__layout">
20
+ <div class="oip-pm__nav">
21
+ <RmChaptersNav
22
+ class="oip-pm__chapters"
23
+ :chapters="chapters"
24
+ :title="en ? 'Chapters' : 'Chapitres'"
25
+ :offset="chaptersOffset"
26
+ :style="{ top: `${stickyTop}px` }"
27
+ @select="scrollToChapter"
28
+ />
29
+ </div>
30
+
31
+ <div class="oip-pm__content">
32
+ <section id="introduction" class="oip-pm__card">
33
+ <div class="oip-pm__head">
34
+ <RmTag v-if="doc.delete" variant="rose">
35
+ {{ en ? "Article has been deleted" : "L'article a été supprimé" }}
36
+ </RmTag>
37
+ <h2 class="oip-pm__title">Introduction</h2>
38
+ <OipResourceSource :uuid="PUBMED_SOURCE_UUID" :lang="lang" />
39
+ </div>
40
+
41
+ <OipResourceField :label="en ? 'Full Title' : 'Titre complet'" :text="orNA(doc.title)" :highlight="highlight" :lang="lang" />
42
+ <OipResourceField
43
+ :label="en ? 'First Published' : 'Première publication'"
44
+ :text="doc.article_date ? longDate(doc.article_date) : 'N/A'"
45
+ :lang="lang"
46
+ />
47
+ <OipResourceTags v-for="group in tagGroups" :key="group.label" v-bind="group" :highlight="highlight" :lang="lang" />
48
+ </section>
49
+
50
+ <section v-for="section in sections" :id="anchor(section.label)" :key="section.label" class="oip-pm__card">
51
+ <h2 class="oip-pm__title">{{ titleCase(section.label) }}</h2>
52
+ <RmTextHighlight class="oip-pm__body" :queries="highlight" :text="section.text" :lang="lang" />
53
+ </section>
54
+
55
+ <section v-if="hasLinks" id="links" class="oip-pm__card">
56
+ <h2 class="oip-pm__title">{{ en ? "Links" : "Liens" }}</h2>
57
+ <div v-if="!doc.delete" class="oip-pm__link">
58
+ <p class="oip-pm__link-label">
59
+ <RmIcon name="Link" :size="12" color="var(--text-muted)" />
60
+ {{ en ? "Pubmed article" : "Article PubMed" }}
61
+ </p>
62
+ <a :href="`https://pubmed.ncbi.nlm.nih.gov/${id}/`" target="_blank" rel="noopener">
63
+ https://pubmed.ncbi.nlm.nih.gov/<b>{{ id }}</b>/
64
+ </a>
65
+ </div>
66
+ <div v-if="doc.doi" class="oip-pm__link">
67
+ <p class="oip-pm__link-label">
68
+ <RmIcon name="Link" :size="12" color="var(--text-muted)" />
69
+ {{ en ? "Complete article" : "Article complet" }}
70
+ </p>
71
+ <a :href="`https://www.doi.org/${doc.doi}`" target="_blank" rel="noopener">
72
+ https://www.doi.org/<b>{{ doc.doi }}</b>
73
+ </a>
74
+ </div>
75
+ </section>
76
+ </div>
77
+ </div>
78
+ </div>
79
+ </template>
80
+
81
+ <script setup lang="ts">
82
+ import { oipResourceSpec } from "../utils/oipResources";
83
+ import { oipEsGet } from "../composables/useOipResources";
84
+
85
+ const props = withDefaults(
86
+ defineProps<{
87
+ /** `_id` de la publication (son PMID). */
88
+ id: string;
89
+ /** Termes de la recherche en cours, surlignes dans la fiche. */
90
+ highlight?: string[];
91
+ lang?: "fr" | "en";
92
+ }>(),
93
+ { highlight: () => [], lang: "fr" },
94
+ );
95
+
96
+ /** `source` : l'uuid Directus d'une source citee, pour ouvrir le panneau Sources. */
97
+ const emit = defineEmits<{ source: [uuid: string] }>();
98
+
99
+ /** La fiche Directus « PubMed » (cf. useSources de France Augmented Science). */
100
+ const PUBMED_SOURCE_UUID = "63805e97-48ac-4c04-afd6-8a15055dec97";
101
+
102
+ const en = computed(() => props.lang === "en");
103
+ const root = ref<HTMLElement | null>(null);
104
+ const { chaptersOffset, stickyTop, scrollToChapter } = useOipChapterScroll(root);
105
+
106
+ const spec = oipResourceSpec("PubMed")!;
107
+ const { data: doc, pending } = useAsyncData(
108
+ () => `oip-pubmed-${props.id}`,
109
+ async () => (await oipEsGet(spec.cluster, spec.kind.index, props.id))?._source ?? null,
110
+ { server: false, watch: [() => props.id] },
111
+ );
112
+
113
+ const orNA = (value: unknown) => (value == null || value === "" ? "N/A" : String(value));
114
+ const unique = (values: unknown[]) => [...new Set(values.filter((v): v is string => typeof v === "string" && v !== ""))];
115
+ const titleCase = (value: string) => (value || "").toLowerCase().replace(/(^|\s)\S/g, (c) => c.toUpperCase());
116
+ const anchor = (label: string) => (label || "").toLowerCase();
117
+ const longDate = (value: string) =>
118
+ new Date(value).toLocaleDateString(en.value ? "en-US" : "fr-FR", { year: "numeric", month: "long", day: "numeric" });
119
+
120
+ const tagGroups = computed(() => {
121
+ const d = doc.value ?? {};
122
+ const diseases: any[] = d.diseases ?? [];
123
+ return [
124
+ { icon: "Article", label: en.value ? "Article type" : "Type d'article", values: unique(d.publication_types ?? []) },
125
+ { icon: "BookOpen", label: en.value ? "Journal name" : "Nom de la revue", values: [orNA(d.journal)] },
126
+ { icon: "Molecule", label: en.value ? "Molecules" : "Molécules", values: unique(d.chemical_list ?? []) },
127
+ { icon: "TextAa", label: en.value ? "Keywords" : "Mots-clés", values: unique(d.keywords ?? []) },
128
+ { icon: "Tag", label: en.value ? "Mesh Terms" : "Termes MeSH", values: unique(d.mesh_terms ?? []) },
129
+ {
130
+ icon: "Bandaids",
131
+ label: en.value ? "Rare diseases" : "Maladies rares",
132
+ values: unique((d.rare_diseases ?? []).map((r: any) => r.concat_disease_name_orpha_code)),
133
+ },
134
+ {
135
+ icon: "FaceMask",
136
+ label: "ICD 10",
137
+ values: unique(diseases.flatMap((x) => x.icd10?.map((i: any) => i.combined_label) ?? [])),
138
+ },
139
+ {
140
+ icon: "ListMagnifyingGlass",
141
+ label: en.value ? "Oncology Terms" : "Termes oncologiques",
142
+ values: unique(diseases.flatMap((x) => x.section?.map((s: any) => s.label) ?? [])),
143
+ },
144
+ ];
145
+ });
146
+
147
+ const sections = computed<{ label: string; text: string; order: number }[]>(() =>
148
+ [...(doc.value?.sections ?? [])].sort((a, b) => a?.order - b?.order),
149
+ );
150
+
151
+ const hasLinks = computed(() => !doc.value?.delete || !!doc.value?.doi);
152
+
153
+ const chapters = computed(() => [
154
+ { label: "Introduction", value: "introduction" },
155
+ ...sections.value.map((s) => ({ label: titleCase(s.label), value: anchor(s.label) })),
156
+ ...(hasLinks.value ? [{ label: en.value ? "Links" : "Liens", value: "links" }] : []),
157
+ ]);
158
+ </script>
159
+
160
+ <style scoped>
161
+ .oip-pm__layout {
162
+ display: flex;
163
+ gap: var(--space-16);
164
+ align-items: flex-start;
165
+ }
166
+ .oip-pm__nav {
167
+ flex: 0 0 auto;
168
+ align-self: stretch;
169
+ }
170
+ .oip-pm__chapters {
171
+ position: sticky;
172
+ }
173
+ .oip-pm__content {
174
+ flex: 1 1 auto;
175
+ min-width: 0;
176
+ display: grid;
177
+ gap: var(--space-16);
178
+ }
179
+ .oip-pm__card {
180
+ display: grid;
181
+ gap: var(--space-24);
182
+ padding: var(--space-24);
183
+ border: 1px solid var(--border-default);
184
+ border-radius: 12px;
185
+ background: var(--bg-surface);
186
+ box-shadow: var(--shadow-sm);
187
+ }
188
+ .oip-pm__head {
189
+ display: grid;
190
+ gap: var(--space-4);
191
+ justify-items: start;
192
+ }
193
+ .oip-pm__title {
194
+ margin: 0;
195
+ color: var(--text-primary);
196
+ font-size: var(--font-18);
197
+ font-weight: 800;
198
+ line-height: normal;
199
+ }
200
+ .oip-pm__body {
201
+ color: var(--text-primary);
202
+ font-size: var(--font-14);
203
+ line-height: 160%;
204
+ }
205
+ .oip-pm__link {
206
+ display: grid;
207
+ gap: var(--space-4);
208
+ }
209
+ .oip-pm__link-label {
210
+ display: flex;
211
+ align-items: center;
212
+ gap: var(--space-4);
213
+ margin: 0;
214
+ color: var(--text-primary);
215
+ font-size: var(--font-12);
216
+ }
217
+ .oip-pm__link a {
218
+ color: var(--color-primary);
219
+ font-size: var(--font-14);
220
+ text-decoration: underline;
221
+ word-break: break-all;
222
+ }
223
+ </style>
@@ -0,0 +1,331 @@
1
+ <!--
2
+ Le detail d'une ressource de projet, ouvert sans quitter la page, dans
3
+ n'importe quelle app du portail.
4
+
5
+ - Un type que le layer sait montrer (cf. OIP_RESOURCE_SPECS) s'affiche avec
6
+ la fiche de son app d'origine, ses onglets compris : une publication comme
7
+ dans France Augmented Science, une marque comme dans fr_drugs_app, dans
8
+ l'une comme dans l'autre.
9
+ - Les autres passent par la carte generique, decrite dans le registre des
10
+ types de l'API : un type ajoute la-bas s'affiche ici sans deploiement.
11
+
12
+ Sous le titre, un lien mene a la fiche dans son app d'origine. Dans l'app
13
+ d'origine elle-meme, il navigue sur place ; ailleurs, il ouvre l'autre app
14
+ dans un onglet.
15
+
16
+ Une fiche ouverte depuis la fiche du tiroir (l'auteur d'une publication, la
17
+ marque d'une contribution...) s'empile dans un tiroir de plus ; celui de
18
+ dessous reste ou il etait. Un lien vers une page qui n'est pas une fiche
19
+ quitte le tiroir.
20
+ -->
21
+ <template>
22
+ <RmDrawer
23
+ v-for="(entry, index) in entries"
24
+ :key="`${index}-${entry.type ?? entry.fiche?.view}-${entry.id ?? entry.fiche?.path}`"
25
+ :model-value="index === 0 ? open : true"
26
+ :size="index === 0 ? size : `min(${1180 - index * 40}px, calc(100vw - ${96 + index * 24}px))`"
27
+ :z-index="index === 0 ? undefined : 100 + index * 2"
28
+ :label="entry.title || entry.id || ''"
29
+ @update:model-value="(value: boolean) => !value && close(index)"
30
+ >
31
+ <!-- Carte generique : l'en-tete du tiroir porte le titre et le lien. -->
32
+ <template v-if="!isPage(entry)" #header>
33
+ <div class="oip-rd__head">
34
+ <p class="oip-rd__title">{{ entry.title || entry.id }}</p>
35
+ <OipResourceDrawerLink :origin="originOf(entry)" :lang="lang" @leave="close(0)" />
36
+ </div>
37
+ </template>
38
+
39
+ <div v-if="index > 0 || open" class="oip-rd__body" :class="{ 'oip-rd__body--page': isPage(entry) }">
40
+ <!-- Une fiche porte son bandeau (titre, lien dessous, onglets) : l'en-tete
41
+ du tiroir s'efface, sa croix se pose au bout du bandeau. -->
42
+ <RmButton
43
+ v-if="isPage(entry)"
44
+ class="oip-rd__close"
45
+ variant="tertiary"
46
+ size="md"
47
+ icon-only
48
+ ghost
49
+ prefix-icon="X"
50
+ :aria-label="en ? 'Close' : 'Fermer'"
51
+ @click="close(index)"
52
+ />
53
+ <!-- Une fiche visee par un lien, montee par sa vue. -->
54
+ <div v-if="entry.fiche && !entry.fiche.view.startsWith('resource:')" class="oip-rv oip-rv--whole">
55
+ <component :is="oipResourceViewComponent(entry.fiche.view)" v-bind="entry.fiche.props" :lang="lang">
56
+ <template #title-link>
57
+ <OipResourceDrawerLink :origin="originOf(entry)" :lang="lang" @leave="close(0)" />
58
+ </template>
59
+ </component>
60
+ </div>
61
+ <OipResourceView
62
+ v-else-if="entry.type && oipResourceViewSpec(entry.type)"
63
+ :type="entry.type"
64
+ :id="entry.id ?? ''"
65
+ :title="entry.title"
66
+ :tab="entry.tab"
67
+ :highlight="index === 0 ? highlight : []"
68
+ :lang="lang"
69
+ @source="emit('source', $event)"
70
+ >
71
+ <template v-if="isPage(entry)" #title-link>
72
+ <OipResourceDrawerLink :origin="originOf(entry)" :lang="lang" @leave="close(0)" />
73
+ </template>
74
+ </OipResourceView>
75
+ <OipResourceGenericCard
76
+ v-else
77
+ :type="entry.type ?? ''"
78
+ :id="entry.id ?? ''"
79
+ :title="entry.title"
80
+ :source="source"
81
+ :lang="lang"
82
+ />
83
+ </div>
84
+ </RmDrawer>
85
+ </template>
86
+
87
+ <script setup lang="ts">
88
+ import {
89
+ OIP_RESOURCE_APPS,
90
+ oipFillTemplate,
91
+ oipResourceAppUrl,
92
+ oipResourceViewSpec,
93
+ type OipResourceApp,
94
+ } from "../utils/oipResources";
95
+ import { oipFicheOf, type OipFicheTarget } from "../utils/oipFicheRoutes";
96
+ import { oipHydrate, useOipResourceTypes } from "../composables/useOipResources";
97
+ import { OIP_RESOURCE_NAV, useOipResourceApp } from "../composables/useOipResourceNav";
98
+ import { oipResourceViewComponent } from "../composables/useOipResourceViews";
99
+
100
+ const props = withDefaults(
101
+ defineProps<{
102
+ modelValue: boolean;
103
+ /** `resourceType` de l'API. */
104
+ type: string;
105
+ /** `resourceId` de l'API : l'`_id` du document. */
106
+ id: string;
107
+ title?: string;
108
+ /** L'app qui ouvre le tiroir. Absente : `oip.resourceApp` de l'app.config. */
109
+ app?: OipResourceApp | "";
110
+ /** Le document deja relu par l'app, pour la carte generique. */
111
+ source?: Record<string, unknown> | null;
112
+ highlight?: string[];
113
+ lang?: "fr" | "en";
114
+ size?: string;
115
+ }>(),
116
+ {
117
+ title: "",
118
+ app: "",
119
+ source: null,
120
+ highlight: () => [],
121
+ lang: "fr",
122
+ size: "min(1280px, calc(100vw - 48px))",
123
+ },
124
+ );
125
+
126
+ const emit = defineEmits<{
127
+ "update:modelValue": [value: boolean];
128
+ /** Une source citee dans la fiche : l'app ouvre son panneau Sources. */
129
+ source: [uuid: string];
130
+ }>();
131
+
132
+ const open = computed({
133
+ get: () => props.modelValue,
134
+ set: (value: boolean) => emit("update:modelValue", value),
135
+ });
136
+
137
+ const en = computed(() => props.lang === "en");
138
+ const configuredApp = useOipResourceApp();
139
+ const app = computed(() => props.app || configuredApp);
140
+ const localePath = useLocalePath();
141
+
142
+ /**
143
+ * Une fiche ouverte : une ressource (`type`, `id`, l'onglet et le document
144
+ * relu pour son lien) ou une fiche visee par un lien (`fiche`).
145
+ */
146
+ interface Entry {
147
+ type?: string;
148
+ id?: string;
149
+ tab?: string;
150
+ title: string;
151
+ doc?: Record<string, any> | null;
152
+ fiche?: OipFicheTarget & { app: OipResourceApp; path: string };
153
+ }
154
+
155
+ /** Les fiches ouvertes au-dessus de celle du projet, de bas en haut. */
156
+ const stacked = ref<Entry[]>([]);
157
+ /** Le document de la ressource du projet : certaines fiches s'adressent par un de ses champs. */
158
+ const rootDoc = ref<Record<string, any> | null>(null);
159
+ const entries = computed<Entry[]>(() => [
160
+ { type: props.type, id: props.id, title: props.title, doc: rootDoc.value },
161
+ ...stacked.value,
162
+ ]);
163
+
164
+ watch(
165
+ () => [props.modelValue, props.type, props.id] as const,
166
+ async ([isOpen, type, id]) => {
167
+ rootDoc.value = null;
168
+ const spec = oipResourceViewSpec(type);
169
+ if (!isOpen || !spec) return;
170
+ rootDoc.value = (await oipHydrate(spec.type, [id]).catch(() => ({})))[id] ?? null;
171
+ },
172
+ { immediate: true },
173
+ );
174
+
175
+ /** Fermer un tiroir ferme aussi ceux qui sont au-dessus de lui. */
176
+ function close(index: number) {
177
+ if (index === 0) open.value = false;
178
+ stacked.value = stacked.value.slice(0, Math.max(0, index - 1));
179
+ }
180
+
181
+ watch(open, (isOpen) => {
182
+ if (!isOpen) stacked.value = [];
183
+ });
184
+
185
+ /** Empile une ressource, titree par la description de son type des que son document arrive. */
186
+ async function pushResource(type: string, id: string, tab?: string) {
187
+ const spec = oipResourceViewSpec(type);
188
+ if (!spec) return;
189
+ const entry = reactive<Entry>({ type, id, tab, title: "", doc: null });
190
+ stacked.value = [...stacked.value, entry];
191
+ const doc = (await oipHydrate(spec.type, [id]).catch(() => ({})))[id];
192
+ if (doc) {
193
+ entry.doc = doc;
194
+ entry.title = spec.kind.title(doc, props.lang);
195
+ }
196
+ }
197
+
198
+ const appHref = (target: OipResourceApp, path: string) =>
199
+ target === app.value ? localePath(path) : oipResourceAppUrl(target, path, props.lang);
200
+
201
+ // Les liens des fiches du tiroir empilent un tiroir de plus ; un lien vers une
202
+ // page qui n'est pas une fiche quitte le tiroir (sur place dans l'app, dans un
203
+ // onglet ailleurs).
204
+ provide(OIP_RESOURCE_NAV, {
205
+ href: (type, id, tab) => urlOf(type, id, tab)?.url ?? "",
206
+ open: (type, id, tab) => pushResource(type, id, tab),
207
+ appHref,
208
+ openApp(target, path) {
209
+ const fiche = oipFicheOf(target, path);
210
+ if (fiche?.view.startsWith("resource:")) {
211
+ pushResource(fiche.props.type!, fiche.props.id!, fiche.props.tab || undefined);
212
+ return;
213
+ }
214
+ if (fiche) {
215
+ stacked.value = [...stacked.value, { title: fiche.title, fiche: { ...fiche, app: target, path } }];
216
+ return;
217
+ }
218
+ const url = appHref(target, path);
219
+ if (target === app.value) {
220
+ close(0);
221
+ navigateTo(url);
222
+ } else window.open(url, "_blank", "noopener");
223
+ },
224
+ });
225
+
226
+ /** L'URL de la fiche : locale dans son app d'origine, absolue ailleurs. */
227
+ function urlOf(type: string, id: string, tab?: string, doc?: Record<string, any> | null) {
228
+ const spec = oipResourceViewSpec(type);
229
+ if (!spec) return null;
230
+ const path = spec.detailPath(id, tab, doc);
231
+ if (!path) return null;
232
+ if (spec.app === app.value) return { local: true, url: localePath(path), name: "" };
233
+ return { local: false, url: oipResourceAppUrl(spec.app, path, props.lang), name: OIP_RESOURCE_APPS[spec.app].name };
234
+ }
235
+
236
+ /**
237
+ * Les vues sans bandeau a elles (une carte de resultat) : le tiroir garde son
238
+ * en-tete, titre et lien.
239
+ */
240
+ const CARD_VIEWS = new Set(["drugs-national-assembly", "science-phrc"]);
241
+
242
+ /** Une fiche du layer : elle porte son bandeau, le tiroir efface le sien. */
243
+ function isPage(entry: Entry) {
244
+ if (entry.fiche) return true;
245
+ const spec = entry.type ? oipResourceViewSpec(entry.type) : undefined;
246
+ return !!spec && !CARD_VIEWS.has(spec.view ?? "");
247
+ }
248
+
249
+ function originOf(entry: Entry) {
250
+ if (entry.fiche) {
251
+ const local = entry.fiche.app === app.value;
252
+ return { local, url: appHref(entry.fiche.app, entry.fiche.path), name: OIP_RESOURCE_APPS[entry.fiche.app].name };
253
+ }
254
+ if (entry.type && oipResourceViewSpec(entry.type)) return urlOf(entry.type, entry.id ?? "", entry.tab, entry.doc);
255
+ return registryUrl.value ? { local: false, url: registryUrl.value, name: registryName.value } : null;
256
+ }
257
+
258
+ // Un type sans vue : son lien vient du registre de l'API.
259
+ const registryUrl = ref("");
260
+ const registryName = ref("");
261
+ watch(
262
+ () => [props.modelValue, props.type, props.id] as const,
263
+ async ([isOpen, type, id]) => {
264
+ registryUrl.value = "";
265
+ registryName.value = "";
266
+ if (!isOpen || oipResourceViewSpec(type)) return;
267
+ const entry = (await useOipResourceTypes()).find((e) => e.key === type);
268
+ registryUrl.value = entry?.display?.detailUrl ? oipFillTemplate(entry.display.detailUrl, props.source ?? {}, id) : "";
269
+ const owner = entry?.ownerApp ?? "";
270
+ registryName.value = OIP_RESOURCE_APPS[owner as OipResourceApp]?.name ?? owner;
271
+ },
272
+ { immediate: true },
273
+ );
274
+ </script>
275
+
276
+ <style scoped>
277
+ .oip-rd__head {
278
+ display: grid;
279
+ gap: var(--space-4);
280
+ min-width: 0;
281
+ }
282
+ .oip-rd__title {
283
+ margin: 0;
284
+ color: var(--text-primary);
285
+ font-size: var(--font-18);
286
+ font-weight: 700;
287
+ line-height: 1.3;
288
+ }
289
+ .oip-rd__body {
290
+ padding: var(--space-16) 0;
291
+ }
292
+ .oip-rd__body.oip-rd__body--page {
293
+ padding: 0 0 var(--space-32);
294
+ }
295
+ .oip-rd__close {
296
+ position: absolute;
297
+ top: var(--space-16);
298
+ right: var(--space-24);
299
+ z-index: 96;
300
+ }
301
+ /* La place de la croix, au bout de la rangee titre + actions du bandeau. */
302
+ .oip-rd__body--page :deep(.rm-page-header__row) {
303
+ padding-right: 72px;
304
+ }
305
+ /* La fleche de retour des pages remonterait l'historique de l'ecran dessous. */
306
+ .oip-rd__body--page :deep(.rm-page-header__back) {
307
+ display: none;
308
+ }
309
+ /* Le contenu d'une fiche d'un seul tenant recoit les gouttieres de la colonne
310
+ de contenu ; son bandeau reste bord a bord. On vise l'element qui porte le
311
+ bandeau, a quelque profondeur qu'il soit : une vue de ressource enveloppe
312
+ souvent sa fiche, et la marge posee sur l'enveloppe decalerait le bandeau. */
313
+ .oip-rd__body--page :deep(.oip-rv--whole :has(> .rm-page-header) > :not(.rm-page-header)) {
314
+ box-sizing: border-box;
315
+ padding-inline: var(--space-24);
316
+ }
317
+ .oip-rd__body--page :deep(.oip-rv--whole :has(> .rm-page-header) > .rm-page-header + *) {
318
+ margin-top: var(--space-24);
319
+ }
320
+ </style>
321
+
322
+ <style>
323
+ /* Hors portee : l'en-tete et le corps appartiennent au tiroir (teleporte).
324
+ Sans marge interne, le bandeau colle de la fiche tient le haut du tiroir. */
325
+ .rm-drawer:has(> .rm-drawer__body .oip-rd__body--page) > .rm-drawer__header {
326
+ display: none;
327
+ }
328
+ .rm-drawer:has(> .rm-drawer__body .oip-rd__body--page) > .rm-drawer__body {
329
+ padding: 0;
330
+ }
331
+ </style>