diff --git a/.github/backup.sql b/.github/backup.sql index af6248f..5eae50f 100644 --- a/.github/backup.sql +++ b/.github/backup.sql @@ -19,7 +19,7 @@ INSERT INTO `functions` (`id`, `title`, `slug`, `description`, `image`, `type`, (9, 'Conversion d\'un nombre arabe en nombre romain', 'convertRomanArabicNumbers', 'Convertis un nombre arabe en nombre romain (et l\'inverse aussi).', '/images/functions/convertRomanArabicNumbers.png', 'form', '

Nous allons créer 2 fonctions, une qui convertis un nombre arabe en nombre romain et l\"autre qui fera dans l\"inverse.

Pour cela nous avons besoin d\"une variables de référence, qu\"on utilisera dans les deux fonctions :

const correspondancesRomainArabe = [
    [1000\"M\"],
    [900\"CM\"],
    [500\"D\"],
    [400\"CD\"],
    [100\"C\"],
    [90\"XC\"],
    [50\"L\"],
    [40\"XL\"],
    [10\"X\"],
    [9\"IX\"],
    [5\"V\"],
    [4\"IV\"],
    [1\"I\"],
];

- Commençons par la fonction qui convertis un nombre arabe en nombre romain :

/** 
 * @description Convertis un nombre arabe en nombre romain.
 * @param {number} nombre - Le nombre arabe à convertir
 * @returns {string}
 * @examples convertArabicToRoman(24) → \"XXIV\"
 */
function convertArabicToRoman(nombre) {
    // Initialisation de la variable qui va contenir le résultat de la conversion
    let chiffresRomains = \"\";
    function extraireChiffreRomain(valeurLettrelettre) {
        while (nombre >= valeurLettre) {
            chiffresRomains = chiffresRomains + lettre;
            nombre = nombre - valeurLettre;
        }
    }
    correspondancesRomainArabe.forEach(correspondance => {
        extraireChiffreRomain(correspondance[0], correspondance[1]);
    });
    return chiffresRomains;

Au début on initialise le chiffresRomains à une chaîne de caractère vide, puis on créer la fonction extraireChiffreRomain qui prend en argument le nombre qui correspond à la lettre par exemple si la lettre c\"est \"V\" alors valeurLettre vaudra 5 puis la lettre romaine en question. Et tant qu\"on peut enlever la valeur de la lettre au nombre on continue la boucle while par exemple si on exécute la fonction extraireChiffreRomain(1000, \"M\"); avec nombre = 3000 alors la boucle se fera 3 fois, 1er tour de boucle nombre vaudra 2000 puis 1000 puis 0.

Et donc on fait une boucle forEach sur le tableau de référence et on exécute la fonction que j\"ai exécutée plus haut.

- Convertis un nombre romain en nombre arabe :

/** 
 * @description Convertis un nombre romain en nombre arabe.
 * @param {string} string - Le nombre romain à convertir
 * @returns {number}
 * @examples convertRomanToArabic(\'XXIV\') → 24
 */
function convertRomanToArabic(string) {
    let result = 0;
    correspondancesRomainArabe.forEach((correspondance=> {        
        while (string.indexOf(correspondance[1]) === 0) {
            // Ajout de la valeur décimale au résultat
            result += correspondance[0];
            // Supprimer la lettre romaine correspondante du début
            string = string.replace(correspondance[1], \'\');
        }
    });
    if (string != \'\') {
        result = 0;
    }
    return result;

Au début le résultat = 0, puis on fait une boucle forEach sur le tableau de référence et on vérifie si la lettre en cours de l\'itération est dans la chaîne de caractère au 1er index (0) passée en argument, puis on rajoute la valeur de lettre au résultat exemple X c\'est 10 et on enlève la lettre de la chaîne de caractère. A la fin la chaîne de caractère doit être vide si ce n\'est pas le cas, c\'est que ce n\'est pas un chffre romain valide donc on retourne 0.

', '[{\"name\": \"value\", \"type\": \"text\", \"label\": \"Entrez votre nombre :\", \"placeholder\": \"(e.g : 50 ou L)\"}, {\"name\": \"functionName\", \"type\": \"select\", \"label\": \"Convertir en :\", \"options\": [{\"name\": \"Nombre Romain\", \"value\": \"convertArabicToRomanOutput\"}, {\"name\": \"Nombre Arabe\", \"value\": \"convertRomanToArabicOutput\"}], \"placeholder\": \"\"}]', 1, '2019-09-21 00:00:00', '2020-05-11 14:52:19', 2), (10, 'Nombre d\'Armstrong', 'armstrongNumber', 'Permet de savoir si un nombre fait partie des nombres d\'Armstrong.', '/images/functions/armstrongNumber.png', 'form', '

Un nombre d\"Armstrong est un nombre entier positif qui est égal à la somme de ses chiffres portés à la puissance du nombre de chiffres le composant. Cette fonction permet de savoir si un nombre fait partie des nombres d\"Armstrong (avec l\"explication).
\n
\n\n\n\nExemple : 153 est un nombre d\"Armstrong car 13 + 53 + 33 = 153.

/** 
 * @description Vérifie si un nombre fait partie des nombres d\'Armstrong.
 * @param {number} number - Le nombre à tester
 * @return {boolean} 
 * @example isArmstrongNumber(153) → true
 */ 
function isArmstrongNumber(number) {
    let numberString = number.toString();
    let result = 0;

    for (let index = 0index < numberString.lengthindex++) {
        result += parseInt(numberString[index]) ** numberString.length;
    }

    return result === number
}

Pour cela, nous devons parcourir chaque chiffre du nombre individuellement, alors il faut transformer le nombre en string grâce à \".toString()\" et ensuite on peut initialiser la variable de résultat et faire les tours de boucle en incrémentant la variable de résultat et à la fin on regarde si le résultat est égale au nombre de base, si c\"est le cas c\"est que c\"est un nombre d\"Armstrong.

', '[{\"name\": \"number\", \"type\": \"integer\", \"label\": \"Entrez votre nombre :\", \"placeholder\": \"(e.g : 153)\"}]', 1, '2019-09-21 00:00:00', '2020-05-12 10:53:23', 2), (11, 'Heap\'s algorithm', 'heapAlgorithm', 'Génère toutes les permutations uniques possibles d\'une chaîne de caractère.', '/images/functions/heapAlgorithm.png', 'form', NULL, '[{\"name\": \"string\", \"type\": \"text\", \"label\": \"Entrez un mot :\", \"placeholder\": \"(e.g : Mot)\"}]', 1, '2019-10-11 00:01:00', '2020-04-22 23:06:15', 2), -(12, 'Raccourcisseurs de liens', 'linkShortener', 'Une URL trop longue ? Raccourcissez-là !', '/images/functions/linkShortener.png', 'form', '

Pour faire son propre \"link shortener\" comme Bitly etc. Il faut une simple base de donnée (J\'utilise MySQL), avec 3 champs : id, url et shortcut. Le principe c\'est que vous avez une url prédéfinie (grâce à Express et NodeJS c\'est très simple), qui peut ressembler à ça : nomdedomaine.fr/?q=shortcut et à la place de \"shortcut\" c\'est le shortcut enregistré dans la base de donnée pour accéder à l\'url raccourci, donc l\'algo va se contenter de chercher dans sa base de donnée si le shortcut existe si oui il va prendre le champ url et faire un simple redirect. Pour que l\'algo fonctionne correctement, il faudrait que le shortcut soit unique.

', '[{\"name\": \"url\", \"type\": \"text\", \"label\": \"Entrez le lien à raccourcir :\", \"placeholder\": \"(e.g : https://divlo.fr)\"}, {\"name\": \"shortcutName\", \"type\": \"text\", \"label\": \"Entrez le nom du raccourci :\", \"placeholder\": \"(e.g : divlo)\"}]', 1, '2019-12-11 00:00:00', '2020-05-12 07:49:46', 1), +(12, 'Raccourcisseurs de liens', 'linkShortener', 'Une URL trop longue ? Raccourcissez-là !', '/images/functions/linkShortener.png', 'page', '

Pour faire son propre \"link shortener\" comme Bitly etc. Il faut une simple base de donnée (J\'utilise MySQL), avec 3 champs : id, url et shortcut. Le principe c\'est que vous avez une url prédéfinie (grâce à Express et NodeJS c\'est très simple), qui peut ressembler à ça : nomdedomaine.fr/?q=shortcut et à la place de \"shortcut\" c\'est le shortcut enregistré dans la base de donnée pour accéder à l\'url raccourci, donc l\'algo va se contenter de chercher dans sa base de donnée si le shortcut existe si oui il va prendre le champ url et faire un simple redirect. Pour que l\'algo fonctionne correctement, il faudrait que le shortcut soit unique.

', '[{\"name\": \"url\", \"type\": \"text\", \"label\": \"Entrez le lien à raccourcir :\", \"placeholder\": \"(e.g : https://divlo.fr)\"}, {\"name\": \"shortcutName\", \"type\": \"text\", \"label\": \"Entrez le nom du raccourci :\", \"placeholder\": \"(e.g : divlo)\"}]', 1, '2019-12-11 00:00:00', '2020-05-12 07:49:46', 1), (13, 'Liste de choses à faire', 'toDoList', 'Prévoyez la liste de choses que vous devez faire.', '/images/functions/toDoList.png', 'page', '

La To do list c\'est l\'applications que tout les débutants font en programmation, c\'est le meilleur moyen de mettre en pratique les connaissances pour faire des applications CRUD (Create. Read. Update. Delete). Surtout qu\'avec un peu d\'imagination c\'est une app qui peut-être très grandement amélioré. Pour sauvegarder vos données vous pouvez utilisez le localStorage du navigateur ou encore mieux (ce que j\'utilise ici), un système de connexion/inscription des utilisateurs et sauvegarde des données en base de donnée (MySQL par exemple). Ici, l\'utilisateur peut ajouter une tâche à faire, voir les tâches ajoutées précédemment, déclarer comme fait et supprimer. Libre à vous d\'ajouter des fonctionnalités comme par exemple la possibilté de modifier une tâche ou encore de séparer les tâches faîtes des tâches en cours. Si vous apprennez le développement web, c\'est la 1ère application \"fullstack\" que vous pouvez faire simplement : HTML, CSS, JS et un langage côté serveur (PHP ou NodeJS par exemple).

', NULL, 1, '2019-12-26 00:00:00', '2020-05-12 10:49:48', 1), (14, 'Juste Prix', 'rightPrice', 'Arriverez-vous à deviner le prix d\'un objet ?', '/images/functions/rightPrice.png', 'page', '

Faire un juste prix ce n\"est pas très compliqué, ça se résume en 3 conditions if, pour vérifier si le prix est inférieur/supérieur ou égale, donc très simple mais pour rendre le jeu un peu plus fun surtout à développer. J\"ai imaginé de prendre un produit d\"Amazon au hasard, d\"afficher le titre et l\"image et de faire deviner le prix du produit. Problème : Amazon ne possède pas d\"API (avec documentation etc.), donc pas moyen de récupérer les 3 informations que je veux d\"un produit. 

Donc pas le choix, on va faire ce qu\"on appelle du \"Web scraping\", c\"est à dire qu\"on va faire un script qui va aller sur la page Amazon et on va prendre tout le HTML de la page, le stocké dans une variable et faire un DOM virtuel pour pouvoir accéder aux mêmes fonctions/méthodes en JavaScript comme le document.getElementById, document.querySelector etc. et donc pouvoir récupérer les valeurs voulues.

Pour cela j\"utilise NodeJS avec la librairie Nightmare qui permet justement de faire du Web Scraping.
\n\nDonc l\"idée c\"est que nous allons faire une fonction qui va nous retourner un Tableau de produits d\"Amazon avec chaque produit c\"est un objet contenant comme information : le nom, l\"image et le prix.\n

async function getAmazonProductList(subject) {
    const url       = `https://www.amazon.fr/s?k=${subject}`;
    const Nightmare = require(\"nightmare\")();
    return await Nightmare.goto(url)
        .wait(\".s-result-item\")
        .evaluate(() => {
            const amazonProductList = document.querySelectorAll(\".s-result-item\");
            const productsList = [];
            for (let indexProduct in amazonProductList) {
                try {   
                    const elementProduct = amazonProductList[indexProduct];
                    const productImage   = elementProduct.querySelector(\".s-image\");
                    const originalPrice  = elementProduct.querySelector(\".a-price-whole\").innerHTML;
                    productsList.push({
                        name: productImage[\"alt\"],
                        image: productImage[\"src\"],
                        price: Number(originalPrice.replace(\",\"\".\").replace(\" \"\"\"))
                    });
                } catch (_error) {
                    continue;
                }
            }
            return productsList;
        })
        .end();
}

Je vous invite à voir la documentation de Nightmare mais là ce qu\"il faut comprendre c\"est qu\"on va à l\"url : `https://www.amazon.fr/s?k=${subject}`, où le subject c\"est la recherche sur amazon qui va s\"effectuer, donc par exemple si vous voulez faire deviner le prix d\"un smartphone vous appellerez cette fonction comme ça : getAmazonProductList(\"smartphone\"). On attend que tous les éléments qui ont la classe \".s-result-item\" ont fini de charger, puis on sélectionne tout éléments qui ont cette classe avec querySelectorAll on fait une boucle et on parcourt cette liste et on construit notre tableau qui contient les informations qu\"on veut.

Maintenant comme dans la fonction randomQuote, à la place de sélectionner une citation dans un tableau, on sélectionne un produit, exemple :\n

function randomNumber(minmax) {
    return Math.floor(Math.random() * (max - min +1)) + min;
}

getAmazonProductList(\'smartphone\')
    .then((productsList=> {
        console.log(productsList[randomNumber(0productsList.length - 1)]);
    });

Puis ici je l\'affiche mais ensuite on peut en faire ce qu\'on veut de cette information est faire notre Juste Prix en manipulant le DOM etc.

', NULL, 1, '2020-04-27 20:17:05', '2020-05-12 12:46:28', 3), (15, 'Chronomètre', 'chronometerTimer', 'Gérer votre temps facilement (et adopter la technique Pomodoro).', '/images/functions/chronometerTimer.png', 'page', '

Pour faire un chronomètre il faut utiliser la fonction globale setInterval, qui va exécuter une callback toutes les x nombres de millisecondes. Nous dans notre cas, chaque seconde on va incrémenter la variable de 1 pour connaître les secondes.

let timeLength = 0;
setInterval(() => {
    timeLength++;
}, 1000);

Puis pour afficher le nombre de minutes on va créer une fonction convertSeconds :

function convertSeconds(seconds) {
    return {
        minutes: Math.floor(seconds / 60),
        seconds: seconds % 60 
    };
}

Exemple : On exécute la fonction convertSeconds(90) qui va nous retourner l\'objet où minutes vaudra 1 et seconds vaudra 30, 

Et globalement le reste c\'est de la manipulation du DOM en JavaScript.

', NULL, 1, '2020-04-29 09:28:08', '2020-05-12 13:11:48', 1); diff --git a/website/pages/functions/linkShortener.jsx b/website/pages/functions/linkShortener.jsx new file mode 100644 index 0000000..de5ccfa --- /dev/null +++ b/website/pages/functions/linkShortener.jsx @@ -0,0 +1,438 @@ +import { useState, useEffect, useContext, useRef, useCallback } from 'react' +import Link from 'next/link' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { faPen, faTrash, faTimes } from '@fortawesome/free-solid-svg-icons' +import { UserContext } from '../../contexts/UserContext' +import redirect from '../../utils/redirect' +import htmlParser from 'html-react-parser' +import Loader from '../../components/Loader' +import FunctionPage from '../../components/FunctionPage/FunctionPage' +import FunctionTabs from '../../components/FunctionPage/FunctionTabs' +import FunctionArticle from '../../components/FunctionPage/FunctionArticle' +import FunctionComments from '../../components/FunctionPage/FunctionComments/FunctionComments' +import Modal from '../../components/Modal' +import api from '../../utils/api' +import 'notyf/notyf.min.css' +import '../../public/css/pages/FunctionComponent.css' +import '../../public/css/pages/admin.css' + +const CreateLink = () => { + const { isAuth, user } = useContext(UserContext) + const [inputState, setInputState] = useState({}) + const [message, setMessage] = useState('') + const [isLoading, setIsLoading] = useState(false) + + const handleSubmit = async event => { + setIsLoading(true) + event.preventDefault() + try { + const response = await api.post('/links', inputState, { + headers: { Authorization: user.token } + }) + setMessage(response.data.resultHTML) + setIsLoading(false) + setInputState({ url: '', shortcutName: '' }) + } catch (error) { + setIsLoading(false) + setMessage(error.response.data.message) + } + } + + const handleChange = event => { + const inputStateNew = { ...inputState } + inputStateNew[event.target.name] = event.target.value + setInputState(inputStateNew) + } + + if (!isAuth) { + return ( +

+ Vous devez être{' '} + + connecté + {' '} + pour gérer des liens raccourcis. +

+ ) + } + + return ( +
+
+
+
+
+ + +
+ +
+ + +
+ +
+ +
+
+
+
+
+ {isLoading ? : htmlParser(message)} +
+
+ ) +} + +let pageLinks = 1 +const LinksList = () => { + const { isAuth, user } = useContext(UserContext) + const [linksData, setLinksData] = useState({ + hasMore: true, + rows: [], + totalItems: 0 + }) + const [isLoadingLinks, setLoadingLinks] = useState(true) + const [isEditing, setIsEditing] = useState(false) + + const [defaultInputState, setDefaultInputState] = useState({ + shortcutName: '', + url: '', + id: 1 + }) + const [message, setMessage] = useState('') + const [isLoading, setIsLoading] = useState(false) + + const toggleModal = () => { + if (isEditing) { + setIsLoading(false) + setMessage('') + setDefaultInputState({ shortcutName: '', url: '', id: 1 }) + } + setIsEditing(!isEditing) + } + + // Récupère les liens initiales + useEffect(() => { + getLinksData().then(data => setLinksData(data)) + }, []) + + const handleChange = event => { + const inputStateNew = { ...defaultInputState } + inputStateNew[event.target.name] = event.target.value + setDefaultInputState(inputStateNew) + } + + const handleRemoveLink = async linkId => { + try { + await api.delete(`/links/${linkId}`, { + headers: { Authorization: user.token } + }) + const linksDataState = { ...linksData } + const deletedLinkIndex = linksData.rows.findIndex(link => { + return link.id === linkId + }) + linksDataState.rows.splice(deletedLinkIndex, 1) + setLinksData(linksDataState) + + let Notyf + if (typeof window !== 'undefined') { + Notyf = require('notyf') + } + const notyf = new Notyf.Notyf({ + duration: 5000 + }) + notyf.success('Succès: lien raccourci supprimé!') + } catch {} + } + + const handleEditLink = linkInfo => { + toggleModal() + setDefaultInputState({ + shortcutName: linkInfo.shortcut, + url: linkInfo.url, + id: linkInfo.id + }) + } + + const handleEditSubmit = async event => { + setIsLoading(true) + event.preventDefault() + try { + const response = await api.put( + `/links/${defaultInputState.id}`, + defaultInputState, + { + headers: { Authorization: user.token } + } + ) + const linksDataState = { ...linksData } + const editedLinkIndex = linksData.rows.findIndex(link => { + return link.id === defaultInputState.id + }) + if (editedLinkIndex != null) { + linksDataState.rows[editedLinkIndex].shortcut = + defaultInputState.shortcutName + linksDataState.rows[editedLinkIndex].url = defaultInputState.url + setLinksData(linksDataState) + } + setMessage(response.data.resultHTML) + setIsLoading(false) + } catch (error) { + setIsLoading(false) + setMessage(error.response.data.message) + } + } + + const getLinksData = async () => { + setLoadingLinks(true) + const { data } = await api.get(`/links?page=${pageLinks}&limit=20`, { + headers: { Authorization: user.token } + }) + setLoadingLinks(false) + return data + } + + // Permet la pagination au scroll + const observer = useRef() + const lastLinkRef = useCallback( + node => { + if (isLoadingLinks) return + if (observer.current) observer.current.disconnect() + observer.current = new window.IntersectionObserver( + entries => { + if (entries[0].isIntersecting && linksData.hasMore) { + pageLinks += 1 + getLinksData().then(data => { + setLinksData(oldData => { + return { + hasMore: data.hasMore, + rows: [...oldData.rows, ...data.rows], + totalItems: data.totalItems + } + }) + }) + } + }, + { threshold: 1 } + ) + if (node) observer.current.observe(node) + }, + [isLoadingLinks, linksData.hasMore] + ) + + if (!isAuth) { + return ( +

+ Vous devez être{' '} + + connecté + {' '} + pour gérer des liens raccourcis. +

+ ) + } + + return ( +
+
+
+

Gérer les liens

+
+
+
+
+ {!isEditing ? ( +
+ + + + + + + + + + + {linksData.rows.map((link, index) => { + const linkJSX = ( + <> + + + + + + ) + // Si c'est le dernier élément + if (linksData.rows.length === index + 1) { + return ( + + {linkJSX} + + ) + } + return {linkJSX} + })} + +
+ Liens + + Nom + + Modifier + + Supprimer +
+ {link.url} + {link.shortcut} handleEditLink(link)} + > + + handleRemoveLink(link.id)} + > + +
+
+ ) : ( + +
+
+
+
+
+ + + +
+
+
+ +
+
+
+ + +
+ +
+ + +
+ +
+ +
+
+
+ {isLoading ? : htmlParser(message)} +
+
+
+
+
+ )} +
+
+
+ ) +} + +const FunctionTabManager = props => { + return ( + +
+ +
+
+ +
+
+ +
+
+ +
+
+ ) +} + +const linkShortener = props => ( + +) + +export async function getServerSideProps (context) { + return api + .get('/functions/linkShortener') + .then(response => ({ props: response.data })) + .catch(() => redirect(context, '/404')) +} + +export default linkShortener