feat: Add download attribute based on file extension

This commit is contained in:
Gauthier Daniels 2025-04-13 20:03:10 +02:00
parent a7f874c64f
commit 9427cb7b5d
8 changed files with 312 additions and 30 deletions

View File

@ -5,13 +5,15 @@ export function Link(props: React.AnchorHTMLAttributes<HTMLAnchorElement> & { hr
const { urlPathname } = usePageContext();
const isActive = props.href === "/" ? urlPathname === props.href : urlPathname.startsWith(props.href);
const isSameDomain = !(props.href.startsWith("http") || props.href.startsWith("mailto"));
const isDownload = props.href.endsWith(".pdf") || props.href.endsWith(".zip");
return (
<a
{...props}
href={props.href}
className={clsx(isActive && "is-active", props.className)}
{...(!isSameDomain ? { target: "_blank", rel: "noopener noreferrer" } : {})}
{...(isDownload ? { download: true } : {})}
{...(!isSameDomain || isDownload ? { target: "_blank", rel: "noopener noreferrer" } : {})}
>
{props.children}
</a>

View File

@ -1,8 +1,105 @@
import { ChevronDownIcon, ChevronUpIcon } from "@heroicons/react/24/solid";
import { usePageContext } from "vike-react/usePageContext";
import { Link } from "@/components/common/Link";
import { navigation } from "@/lib/navigation";
import { useState } from "react";
import clsx from "clsx";
type NavigationItemProps = {
section: (typeof navigation)[number];
onLinkClick?: React.MouseEventHandler<HTMLAnchorElement>;
};
function NavigationItem(props: NavigationItemProps) {
const { urlPathname } = usePageContext();
const [isOpened, setIsOpened] = useState(() => {
return props.section.links.some(
(link) => link.href === urlPathname || link.subitems?.some((subitem) => subitem.href === urlPathname),
);
});
return (
<>
<h2
role="button"
tabIndex={0}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
setIsOpened((prev) => !prev);
e.preventDefault();
}
}}
className={clsx(
"font-display font-medium cursor-pointer",
isOpened ? "text-violet-600 dark:text-violet-200" : "text-slate-900 dark:text-white ",
)}
onClick={() => setIsOpened((prev) => !prev)}
>
{isOpened ? (
<ChevronUpIcon className="inline-block mr-2 h-5 w-5 text-slate-400" />
) : (
<ChevronDownIcon className="inline-block mr-2 h-5 w-5 text-slate-400" />
)}
<span className="sr-only">{isOpened ? "Masquer" : "Afficher"}</span>
{props.section.title}
<span className="text-slate-400 dark:text-slate-500"> ({props.section.links.length})</span>
</h2>
{isOpened && (
<ul
role="list"
className="mt-2 space-y-2 border-l-2 border-slate-100 lg:mt-4 lg:space-y-4 lg:border-slate-200 dark:border-slate-800 mb-9"
>
{props.section.links.map((link) => (
<li key={link.href} className="relative">
<Link
href={link.href}
onClick={props.onLinkClick}
className={clsx(
"block w-full pl-3.5 before:pointer-events-none before:absolute before:-left-1 before:h-1.5 before:w-1.5 before:rounded-full",
{ "before:top-1/2 before:-translate-y-1/2": !link.subitems },
{ "before:top-3 before:-translate-y-1/2": link.subitems },
link.href === urlPathname || link.subitems?.some((subitem) => subitem.href === urlPathname)
? "font-semibold text-violet-500 before:bg-violet-500"
: "text-slate-500 before:hidden before:bg-slate-300 hover:text-slate-600 hover:before:block dark:text-slate-400 dark:before:bg-slate-700 dark:hover:text-slate-300",
)}
>
{link.title}
{link.subitems && <span className="text-slate-400 dark:text-slate-500"> ({link.subitems.length})</span>}
</Link>
{link.subitems && (
<ul
role="list"
className="ml-4 border-l-2 border-slate-100 lg:space-y-1 lg:border-slate-200 dark:border-slate-800 mb-4"
>
{link.subitems.map((subitem) => (
<li key={subitem.href} className="relative">
<Link
href={subitem.href}
onClick={props.onLinkClick}
className={clsx(
"block w-full pl-3.5 before:pointer-events-none before:absolute before:top-1/2 before:-left-1 before:h-1.5 before:w-1.5 before:-translate-y-1/2 before:rounded-full",
subitem.href === urlPathname
? "font-semibold text-violet-500 before:bg-violet-500"
: "text-slate-500 before:hidden before:bg-slate-300 hover:text-slate-600 hover:before:block dark:text-slate-400 dark:before:bg-slate-700 dark:hover:text-slate-300",
)}
>
{subitem.title}
</Link>
</li>
))}
</ul>
)}
</li>
))}
</ul>
)}
</>
);
}
export function Navigation({
className,
onLinkClick,
@ -10,35 +107,35 @@ export function Navigation({
className?: string;
onLinkClick?: React.MouseEventHandler<HTMLAnchorElement>;
}) {
const { urlPathname } = usePageContext();
const firstSections = navigation.filter((section) => section.position === "start");
const filteredSections = navigation
.filter((section) => section.position === "auto" || section.position === undefined)
.reduce(
(acc, section) => {
if (!acc[section.type]) {
acc[section.type] = [];
}
acc[section.type].push(section);
return acc;
},
{} as Record<string, typeof navigation>,
);
return (
<nav className={clsx("text-base lg:text-sm", className)}>
<ul role="list" className="space-y-9">
{navigation.map((section) => (
<ul role="list" className="space-y-4">
{firstSections.map((section) => (
<li key={section.title}>
<h2 className="font-display font-medium text-slate-900 dark:text-white">{section.title}</h2>
<ul
role="list"
className="mt-2 space-y-2 border-l-2 border-slate-100 lg:mt-4 lg:space-y-4 lg:border-slate-200 dark:border-slate-800"
>
{section.links.map((link) => (
<li key={link.href} className="relative">
<Link
href={link.href}
onClick={onLinkClick}
className={clsx(
"block w-full pl-3.5 before:pointer-events-none before:absolute before:top-1/2 before:-left-1 before:h-1.5 before:w-1.5 before:-translate-y-1/2 before:rounded-full",
link.href === urlPathname
? "font-semibold text-violet-500 before:bg-violet-500"
: "text-slate-500 before:hidden before:bg-slate-300 hover:text-slate-600 hover:before:block dark:text-slate-400 dark:before:bg-slate-700 dark:hover:text-slate-300",
)}
>
{link.title}
</Link>
</li>
))}
</ul>
<NavigationItem section={section} onLinkClick={onLinkClick} />
</li>
))}
{Object.entries(filteredSections).map(([type, sections]) => (
<li key={type}>
<h2 className="font-display font-bold text-base text-slate-900 dark:text-white">{type}</h2>
{sections.map((section) => (
<NavigationItem key={section.title} section={section} onLinkClick={onLinkClick} />
))}
</li>
))}
</ul>

View File

@ -0,0 +1,63 @@
---
title: DWWM CP 1 - Installer et configurer son environnement de travail en fonction du projet web ou web mobile
description: Synthèse et explications des attentes relatives à la compétence professionnelle 1 du titre professionnel DWWM (01280m04).
tags: [DWWM]
---
## 📚 Références
- REAC _(mise à jour du 02/07/2024)_, pages 15 et 16
- RE _(mise à jour du 02/07/2024)_, page 9
## 📋 En résumé
Ce qui est attendu de ta part, c'est d'expliquer **comment** on peut installer et configurer les prérequis pour exécuter ton projet.
Tu as utilisé un framework PHP et React en front ?
Tu devras alors expliquer comment installer PHP, Composer, Node.js, npm _(ou autre gestionnaire de dépendances Node)_ et les autres dépendances nécessaires à ton projet comme la base de données !
Et pour te donner un ordre d'idée, voici ce que ça peut donner :
- Versionning _(Git, SVN, ...)_
- IDE ou éditeur de code _(Visual Studio Code, PhpStorm, ...)_
- Langages/runtimes _(PHP, Node.js, ...)_
- Gestionnaires de dépendances _(Composer, npm, ...)_
- Serveurs web _(Apache, Nginx, ...)_
- Base de données _(MySQL, PostgreSQL, ...)_
- DevOps _(Docker, Vagrant, ...)_
- etc.
Tu l'as compris, c'est vaste !
Mais heureusement, tu dois uniquement expliquer comment installer et configurer les outils que tu as utilisés pour ton projet.
Si tu fais un projet Laravel et React, pas besoin d'expliquer comment installer et configurer Ruby et Java, par exemple 😉
{% callout type="note" title="Utilisation de XAMPP, WAMP, MAMP, LAMP, Laragon etc." %}
Si tu utilises un logiciel comme XAMPP, WAMP, MAMP, LAMP, Laragal etc., tu as évidemment le droit de le mentionner dans ta présentation et dossier de projet.
Toutefois, il est préférable que tu saches expliquer comment installer et configurer les éléments nécessaires de manières individuelles.
{% /callout %}
### Informations complémentaires
{% callout type="warning" title="Versions des outils et dépendances" %}
Même si le choix des outils que tu utilises est libre, il est important de préciser les versions que tu as utilisées pour ton projet.
Étant donné que chaque version corrige probablement diverses failles de sécurité et/ou ajoute des fonctionnalités, c'est le bon moment pour montrer que tu prends la veille technologique au sérieux.
{% /callout %}
### 🛠️ Ressources conseillées
TODO
### 🎯 Critères d'évaluation
- Les outils de développement nécessaires sont installés et configurés
- Les outils de gestion de versions et de collaboration sont installés
- Les conteneurs implémentes les services requis pour l'environnement de développement
- La documentation technique de l'environnement de travail est comprise, en langue française ou anglaise (niveau B1 CECRL pour l'anglais)
- Le système de veille permet de suivre les évolutions technologies et les problématiques de sécurité en lien avec l'installation et la configuration d'un environnement de travail

View File

@ -0,0 +1,23 @@
---
title: Activité Type 1 - Développer la partie front-end d'une application web ou web mobile sécurisée
description: Synthèse et explications des attentes relatives à l'activité type 1 du titre professionnel DWWM (01280m04).
tags: [DWWM]
---
## 📚 Références
- REAC _(mise à jour du 02/07/2024)_, pages 15 et 16
- RE _(mise à jour du 02/07/2024)_, page 9
## 📋 En résumé
Cette activité type concerne tout ce qui est relatif à la conception _(maquettes, arborescence etc.)_ et à la création de l'interface.
Voyons un peu plus en détail ce qui est attendu pour chacune de ces compétences professionnelles ! 🚀
Elle est divisée en 4 **compétences professionnelles** _(CP)_ :
- **CP 1** : Installer et configurer son environnement de travail en fonction du projet web ou web mobile
- **CP 2** : Maquetter des interfaces utilisateur web ou web mobile
- **CP 3** : Réaliser des interfaces utilisateur statiques web ou web mobile
- **CP 4** : Développer la partie dynamique des interfaces utilisateur web ou web mobile

View File

@ -0,0 +1,59 @@
---
title: Résumé du titre professionnel DWWM
description: Découvre le résumé du titre professionnel DWWM (TP-01280m04), qui te permettra de te préparer au mieux à l'examen !
tags: [DWWM]
---
## Informations administratives
- Nom complet du titre : **Développeur Web et Web Mobile**
- Sigle : **DWWM**
- Code RNCP : **37674**
- Code titre : **01280m04**
### Documentations officielles
- [REAC - Référentiel Emploi Activités Compétences _(02/07/2024)_](/downloads/dwwm/REAC_DWWM_V04_02072024.pdf)
- [RE - Référentiel d'Évaluation _(02/07/2024)_](/downloads/dwwm/REV2_DWWM_V04_02072024.pdf)
> Provenance des documentations : [Site DGEFP Grand public](https://www.banque.di.afpa.fr/EspaceEmployeursCandidatsActeurs/titre-professionnel/01280m04)
## Activités types et compétences professionnelles
## 📚 Activité type 1 - Développer la partie front-end d'une application web ou web mobile sécurisée
- CP 1 - Installer et configurer son environnement de travail en fonction du projet web ou web mobile
- CP 2 - Maquetter des interfaces utilisateur web ou web mobile
- CP 3 - Réaliser des interfaces utilisateur statiques web ou web mobile
- CP 4 - Développer la partie dynamique des interfaces utilisateur web ou web mobile
## 📚 Activité type 2 - Développer la partie back-end d'une application web ou web mobile sécurisée
- CP 5 - Mettre en place une base de données relationnelle
- CP 6 - Développer des composants d'accès aux données SQL et NoSQL
- CP 7 - Développer des composants métier coté serveur
- CP 8 - Documenter le déploiement d'une application dynamique web ou web mobile
## Compétences transverses
- Communiquer en français et en anglais
- Mettre en oeuvre une démarche de résolution de problème
- Apprendre en continu
## Déroulé de l'examen
{% callout type="note" title="Déroulé relatif au passage de l'épreuve dans sa globalité" %}
En cas de repassage d'un CCP, se référer au Référentiel d'Évaluation pour connaître les modalités de l'épreuve :
- Pages 17 et 18 pour l'AT 1
- Pages 19 et 20 pour l'AT 2
{% /callout %}
**Durée totale de l'examen** : 2h _(dont 1h30 de soutenance face au jury)_
- Questionnaire professionnel _(30 minutes, sans présence du jury)_
- Présentation d'un projet réalisé en amont de la session _(35 minutes, face au jury)_
- Entretien technique _(40 minutes, face au jury)_
- Entretien final _(15 minutes, face au jury)_

View File

@ -1,16 +1,52 @@
const navigationsTypes = {
GLOBAL: "👋 Général",
CERTIFICATIONS: "🎓 Certifications",
DOCUMENTATIONS: "📚 Documentations",
};
export const navigation = [
{
title: "Préambule",
type: "global",
type: navigationsTypes.GLOBAL,
position: "start",
links: [
{ title: "Memento Dev", href: "/" },
{ title: "Certifications", href: "/certifications" },
{ title: "Documentations", href: "/docs" },
],
},
{
title: "Développeur Web et Web Mobile",
type: navigationsTypes.CERTIFICATIONS,
position: "auto",
links: [
{ title: "Résumé", href: "/certifications/dwwm" },
{
title: "Activité Type 1",
href: "/certifications/dwwm/at1",
subitems: [
{ title: "CP 1", href: "/certifications/dwwm/at1/cp1" },
{ title: "CP 2", href: "/certifications/dwwm/at1/cp2" },
{ title: "CP 3", href: "/certifications/dwwm/at1/cp3" },
{ title: "CP 4", href: "/certifications/dwwm/at1/cp4" },
],
},
{
title: "Activité Type 2",
href: "/certifications/dwwm/at2",
subitems: [
{ title: "CP 5", href: "/certifications/dwwm/at2/cp5" },
{ title: "CP 6", href: "/certifications/dwwm/at2/cp6" },
{ title: "CP 7", href: "/certifications/dwwm/at2/cp7" },
{ title: "CP 8", href: "/certifications/dwwm/at2/cp8" },
],
},
],
},
{
title: "React",
type: "documentation",
type: navigationsTypes.DOCUMENTATIONS,
position: "auto",
links: [
{ title: "Introduction", href: "/docs/react" },
{ title: "Initialisation", href: "/docs/react/initialisation" },
@ -24,7 +60,8 @@ export const navigation = [
},
{
title: "Merise",
type: "documentation",
type: navigationsTypes.DOCUMENTATIONS,
position: "auto",
links: [
{ title: "Introduction", href: "/docs/merise" },
{ title: "Dictionnaire de données", href: "/docs/merise/dictionnaire-de-donnees" },
@ -33,7 +70,8 @@ export const navigation = [
},
{
title: "Communauté",
type: "global",
type: navigationsTypes.GLOBAL,
position: "start",
links: [
{ title: "Influenceurs", href: "/docs/communaute/influenceurs" },
{ title: "Partages et réutilisations", href: "/docs/communaute/partages" },

Binary file not shown.

Binary file not shown.