Gatsby: Question - Comment lire Markdown depuis le frontmatter

Créé le 17 avr. 2018  ·  41Commentaires  ·  Source: gatsbyjs/gatsby

La description

Je veux lire le contenu de démarque depuis le frontmatter. Par exemple:

 demi-blocs:
 - title: Ceci est le premier titre
 contenu:> -
 ### Il s'agit du contenu réel au format ** MarkDown **.

 - Ceci est la première ligne
 - Ceci est la deuxième rangée
 - Ceci est la troisième rangée
 - title: Ceci est le deuxième titre
 contenu:> -
 ### Il s'agit du contenu réel au format ** MarkDown **.

 - Ceci est la première ligne
 - Ceci est la deuxième rangée
 - Ceci est la troisième rangée

J'utilise le graphql suivant:

 halfBlocks {
 Titre
 image
 contenu
 }

Comment lire le contenu converti en HTML ou l'afficher au format HTML?

Résultat attendu

Je m'attends à être capable de lire ceci à partir d'un fichier de démarque dit «index.md» et de le rendre au format HTML.

Résultat actuel

Markdown est affiché tel quel sans interprétation.

Environnement

  • Version Gatsby ( npm list gatsby ): gatsby@^1.9.247
  • version gatsby-cli ( gatsby --version ): 1.1.50
question or discussion

Commentaire le plus utile

Cela pourrait être totalement désactivé, mais vous ne pourriez pas simplement créer un composant de démarquage comme ci-dessous que vous pourriez ensuite utiliser dans vos modèles partout où le démarquage est nécessaire pour être converti en HTML

import React from 'react'
import PropTypes from 'prop-types'
import showdown from 'showdown'

const converter = new showdown.Converter()

const MarkdownContent = ({ content, className }) => (
  <div className={className} dangerouslySetInnerHTML={{ __html: converter.makeHtml(content) }} />
)

MarkdownContent.propTypes = {
  content: PropTypes.string,
  className: PropTypes.string,
}


export default MarkdownContent

Tous les 41 commentaires

Le didacticiel explique cela, en particulier les parties 5-7 https://www.gatsbyjs.org/tutorial/

Vous voudrez peut-être également commencer avec l'un des démarreurs - dont beaucoup ont déjà un support de démarque - https://www.gatsbyjs.org/docs/gatsby-starters/

La question est plus nuancée et n'est pas couverte par le didacticiel, je vais donc la rouvrir.

À mon avis, vous avez 2 options:

  1. Vous pouvez séparer votre contenu de démarque en fichiers séparés et utiliser des liens de chemin:
Separate file - let's call it `someContent.md`
```md
### This is the actual content in **MarkDown** format.

- This is the first row
- This is second row
- This is third row
```
and reference that file in your main file (by relative path):
```md
halfBlocks:
  - title: This is first title
    content: "./someContent.md"
```
then in query you could
```
halfBlocks {
  content {
    childMarkdownRemark {
      html
    }
}
```
  1. Une autre approche consisterait à gérer cela par programme - en créant des nœuds de démarque pour vos champs de frontmatter et en les ajoutant via createNodeField . C'est plus compliqué. Vous devrez probablement parcourir le plugin Contentful source pour voir comment créer le nœud MarkdownRemark.

Salut @KyleAMathews merci pour votre suggestion. Je les ai lus mais il m’était difficile de comprendre complètement comment procéder. J'utilise un démarreur mais c'était plus compliqué. Démarreur utilisé: https://github.com/v4iv/gatsby-starter-business

@pieh Merci beaucoup de m'avoir guidé. Vous avez raison. J'ai pensé que j'aurais trop de petits fichiers, donc je l'ai fait fonctionner avec le code ci-dessous. Je documente ceci ici afin que si quelqu'un d'autre a le même problème, il puisse également le voir.

Étape 1: lecture du contenu

Je lis le contenu normalement via Graphql. Cela me donne la démarque sous forme de chaîne. J'ai encore besoin de convertir cela.

Étape 2: Conversion en HTML

Pour cela, j'ai décidé de laisser le contenu sous forme de chaîne jusqu'à ce que nous atteignions le composant réel qui allait l'afficher. Là, je le convertis en markdown.

Ajoutez une remarque pour faire cela par programme. Vous pourrez peut-être ignorer remarque-preset-lint-recommended:

Remarque d'installation

yarn add remark remark-preset-lint-recommended remark-html

Importer

import remark from 'remark';
import recommended from 'remark-preset-lint-recommended';
import remarkHtml from 'remark-html';

Rendre
Plus tard dans la partie de rendu en supposant que content est le markdown qui a été lu sous forme de chaîne:

content = remark()
      .use(recommended)
      .use(remarkHtml)
      .processSync(content).toString();

Je suis maintenant capable de réinterpréter le contenu au format HTML.

Étape 3: Ajouter du contenu dans Markdown

Je suis tombé sur un autre piège. La mise en forme n'était pas correcte lorsque j'utilisais la multiligne avec >- :

content: >-
    ### This is the actual content in **MarkDown** format.
    - This is the first row
    - This is second row

Mais avec le symbole pipe | cela fonctionne très bien.

content: |
    ### This is the actual content in **MarkDown** format.
    - This is the first row
    - This is second row

Pour l'instant, je ferme ça. N'hésitez pas à rouvrir si vous le souhaitez.

Merci!!

Je voudrais également utiliser le markdown pour le frontmatter (titre et extrait pour être exact), et je pense qu'il devrait être pris en charge par défaut.

Ce serait génial d'avoir une convention de dénomination, de sorte que gatsby-transformer-remark puisse comprendre que par exemple title.md est un champ de démarque.

@omeid @ thorn0 cela pourrait être quelque chose que nous prenons en charge directement dans gatsby-transformer-remarque, mais en attendant, vous pouvez créer un plugin qui le fait pour vous, par exemple https://github.com/gatsbyjs/gatsby/issues/5729#issuecomment -395701042 et createNodeField

Toutes mes excuses pour avoir commenté un problème déjà résolu, mais je voulais juste partager un extrait que j'ai utilisé dans mon propre gatsby-node.js qui a fonctionné pour moi, en suivant ce que vous avez tous référencé:

// Need to `yarn add remark remark-html`, then include the following code in
// gatsby-node.js.
const remark = require('remark');
const remarkHTML = require('remark-html');

exports.onCreateNode = ({ node }) => {
  // Conditionals, etc. can be used here, but I omitted those just for example's sake.
  const markdown = node.frontmatter.my_field;
  node.frontmatter.my_field = remark()
    .use(remarkHTML)
    .processSync(markdown)
    .toString();
  return node;
};

Alors, est-ce correct de le faire sans utiliser createNodeField ? Je suis confus.

@ thorn0 mieux utiliser createNodeField au lieu de node.frontmatter.my_field = car la mutation de node peut entraîner des bogues difficiles à déboguer

@amitjindal @nshki Cela fonctionne bien mais

success delete html and css files from previous builds — 0.626 s
success open and validate gatsby-config — 0.018 s
success copy gatsby files — 0.075 s
success onPreBootstrap — 2.782 s
error UNHANDLED EXCEPTION


  TypeError: Cannot set property 'Compiler' of null

  - index.js:16 plugin
    [blog]/[remark-html]/index.js:16:17

  - index.js:271 Function.use
    [blog]/[unified]/index.js:271:25

  - gatsby-node.js:63 exports.onCreateNode.postscriptumsMarkdown.forEach.postscr    iptum
    /home/projects/blog/gatsby-node.js:63:12

  - Array.forEach

  - gatsby-node.js:61 Object.exports.onCreateNode
    /home/projects/blog/gatsby-node.js:61:29

  - api-runner-node.js:110 runAPI
    [blog]/[gatsby]/dist/utils/api-runner-node.js:110:36

  - api-runner-node.js:187 
    [blog]/[gatsby]/dist/utils/api-runner-node.js:187:33

  - map.js:27 
    [blog]/[async]/internal/map.js:27:9

  - eachOfLimit.js:66 replenish
    [blog]/[async]/internal/eachOfLimit.js:66:17

  - eachOfLimit.js:50 iterateeCallback
    [blog]/[async]/internal/eachOfLimit.js:50:17

  - onlyOnce.js:12 module.exports
    [blog]/[async]/internal/onlyOnce.js:12:16

  - map.js:29 
    [blog]/[async]/internal/map.js:29:13

  - util.js:16 tryCatcher
    [blog]/[bluebird]/js/release/util.js:16:23

  - nodeify.js:23 Promise.successAdapter
    [blog]/[bluebird]/js/release/nodeify.js:23:30

  - promise.js:566 Promise.module.exports.Promise._settlePromise
    [blog]/[bluebird]/js/release/promise.js:566:21

  - promise.js:606 Promise.module.exports.Promise._settlePromiseCtx
    [blog]/[bluebird]/js/release/promise.js:606:10


Waiting for the debugger to disconnect...

Process finished with exit code 130 (interrupted by signal 2: SIGINT)

La tentative d'exclusion de la bibliothèque de Webpack ne fonctionne pas ( @see https://github.com/gatsbyjs/gatsby/issues/7599)

Salut David (@comxd), Désolé je voyageais.
Malheureusement, je n'ai aucune idée de cela. J'ai essayé de vérifier le code. Le compilateur sur null semble provenir de la bibliothèque de remarques.

Vous semblez utiliser une boucle dans votre fichier gatsby-node.js.
Cela peut être lié à un contenu entrant qui n'est pas démarqué ou pire encore vide et que vous essayez de le traiter. Essayez de mettre des instructions console.log dedans et voyez si vous trouvez un modèle où quelque chose de vide est à l'origine de cela.

Cela pourrait être totalement désactivé, mais vous ne pourriez pas simplement créer un composant de démarquage comme ci-dessous que vous pourriez ensuite utiliser dans vos modèles partout où le démarquage est nécessaire pour être converti en HTML

import React from 'react'
import PropTypes from 'prop-types'
import showdown from 'showdown'

const converter = new showdown.Converter()

const MarkdownContent = ({ content, className }) => (
  <div className={className} dangerouslySetInnerHTML={{ __html: converter.makeHtml(content) }} />
)

MarkdownContent.propTypes = {
  content: PropTypes.string,
  className: PropTypes.string,
}


export default MarkdownContent

@blakenoll n'est certainement pas totalement éteint! C'est une approche raisonnable.

Cela dit, l'un des grands avantages de Gatsby est que vous effectuez ces opérations au moment de la construction, ce qui est bien parce que nous n'avons pas besoin d'acheter un analyseur Markdown à l'utilisateur final!

@DSchau L' analyseur Markdown ne sera-t-il pas réellement envoyé à l'utilisateur lorsque l'application est réhydratée?

@blakenoll J'adore la pensée originale!

Avons-nous quelque chose à notre disposition pour extraire tous les plugins / configuration de remarque que nous avons inclus dans gatsby-config.js afin que nous n'ayons pas besoin de dupliquer toutes les fonctionnalités effectuées dans les coulisses La mise en œuvre de la remarque par Gatsby nous fournit ? Cela rendrait la génération des champs de nœuds un peu plus facile; mais incroyablement encombrant quand on pense aux champs imbriqués et répétables et à la variation du contenu par page.

@blakenoll Merci beaucoup pour votre astuce de confrontation. Super utile et a fait ce dont j'avais besoin pour utiliser HTML en frontmatter. Cela dit, cela semble une approche maladroite de l'utilisation de Markdown pour créer une page Web qui doit transmettre différents éléments de contenu à différentes parties de la page.

Existe-t-il un moyen d'appliquer une sorte de fonction markdownParser dans la section frontmatter de notre requête graphQL (similaire à la façon dont nous pouvons manipuler les images) qui analysera la chaîne de démarque entrante dans le frontmatter et la convertira en HTML? Pas un expert en graphQL ... essayant juste de réfléchir.

C'est un problème important IMO b / c si l'on utilise Gatsby avec Netifly CMS, Netifly offre la possibilité d'avoir divers champs frontmatter acceptant Markdown comme valeurs. Mais lors de l'interrogation de ces champs, ils sont renvoyés sous forme de démarque dans une chaîne au lieu d'être analysés en HTML. Les solutions de @amitjindal et @blakenoll fonctionnent, mais comme @DSchau l'a mentionné, il n'est pas préférable d'envoyer une analyse de démarque à l'utilisateur si nous pouvons l'éviter. Des pensées de quelqu'un qui est plus familier avec Gatbsy que moi?

@skylarweaver Je suis définitivement sur la même

+1 ce que @skylarweaver a dit!

@amitjindal Peut-être une question stupide, mais que fait même le "> -"? Utiliser le démarreur netlify cms et semble ne faire absolument aucune différence dans la sortie générée, que j'aie>,> -, | ou rien du tout.

@ nol13 Voir les scalaires de bloc , il s'agit de nouvelles lignes.

Comment pourrais-je insérer un tableau? Cela ne fonctionne pas

content: |
        |   |   |   |   |   |
        |---|---|---|---|---|
        |   |   |   |   |   |
        |   |   |   |   |   |
        |   |   |   |   |   |

@ qnguyen12 J'essaierais d'utiliser un outil comme https://jmalarcon.github.io/markdowntables/ pour vous aider avec la conversion.

Oui, je veux dire qu'il est rendu comme source, pas comme table
Par exemple:

text: |
        test   
        ### This is the actual content in **MarkDown** format.  
        |Month|Savings|Spending|
        |--- |--- |--- |
        |January|$100|$900|
        |July|$750|$1000|
        |December|$250|$300|
        |April|$400|$700|

il génère:
tester

Il s'agit du contenu réel au format MarkDown .

| Mois | Économies | Dépenses | | --- | --- | --- | | Janvier | 100 $ | 900 $ | | Juillet | 750 $ | 1000 $ | | Décembre | 250 $ | 300 $ | | Avril | 400 $ | 700 $ |

@KyleAMathews Merci, ce genre de travail, mais évidemment vous devez soit répondre à la configuration et aux plugins du plugin de remarque, soit vous obtenez des résultats différents. Des plans pour prendre en charge le frontmatter de démarque? Peut-être un champ frontmattermd avec le brut frontmatter ?

@omeid Je n'ai aucun projet non - ce serait un excellent plugin que quelqu'un pourrait créer et partager avec la communauté!

J'ai créé un plugin qui devrait faire cela: gatsby-transformer-remarque-frontmatter . Cela semble fonctionner à partir de mes tests, et je prévois de l'utiliser dans un projet que je fais pour un client, mais j'apprécierais que vous jetiez un coup d'œil et me disiez s'il y a quelque chose qui semble incorrect , car c'est la première fois que j'écris un plugin gatsby. Il prend l'itinéraire suggéré par @omeid et ajoute un champ frontmattermd au nœud MarkdownRemark.

Au départ, avant de réaliser que je pouvais simplement créer de nouveaux nœuds de fichier de démarque pour que gatsby-transformer-remarque consomme, j'ai proposé une solution vraiment hacky impliquant l'appel des résolveurs exportés par la fonction setFieldsOnGraphQLNodeType de gatsby-transformer-remarque, et en passant un nouveau nœud de démarque créé dans un autre résolveur. Cela permettait d'interroger n'importe quel champ sur le nœud MarkdownRemark en utilisant une énumération de champ comme celle utilisée pour la fonction de groupe, ce que j'ai vraiment aimé, mais cela ressemblait beaucoup à un hack à utiliser pour quoi que ce soit. Je l'ai gardé ici pour la postérité.

salut @WhiteAbeLincoln j'ai essayé d'installer et de tester:
npm i gatsby-transformer-remark-frontmatter npm ERR! code ENOVERSIONS npm ERR! No valid versions available for gatsby-transformer-remark-frontmatter

Désolé, j'ai réalisé que je n'ai pas encore publié sur npm. Je le publierai une fois que je serai au travail et vous le ferai savoir.

- Abe White

Le 17 juin 2019, à 22h53, broeker [email protected] a écrit:

salut @WhiteAbeLincoln j'ai essayé d'installer et de tester:
npm i gatsby-transformer-remarque-frontmatter npm ERR! code ENOVERSIONS npm ERR! Aucune version valide disponible pour gatsby-transformer-remarque-frontmatter

-
Vous recevez cela parce que vous avez été mentionné.
Répondez directement à cet e-mail, affichez-le sur GitHub ou désactivez le fil de discussion.

@WhiteAbeLincoln J'ai essayé gatsby-transformer-remarque-frontmatter mais cela m'a donné une erreur.

ERREUR # 11325

"Gatsby-node.js" de votre site a créé une page avec un composant qui n'existe pas.

Avez-vous cette erreur?

Il a été signalé à l'origine par @obeid dans le journal des problèmes de votre

Peut-être que je ne l'utilise pas correctement. Donc, une aide appréciée.

S'appuyant sur la réponse @nshki et avec le commentaire @pieh sur la mutation des nœuds. Cela fonctionne totalement pour moi:

const remark = require("remark");
const remarkHTML = require("remark-html");

exports.onCreateNode = ({ node, actions: { createNodeField } }) => {
  const my_field = node.frontmatter.my_field;

  if (my_field) {
    const value = remark()
      .use(remarkHTML)
      .processSync(my_field)
      .toString();

    // new node at:
    // fields {
    //   my_field_html
    // }
    createNodeField({
      name: `my_field_html`,
      node,
      value
    });
  }
};

modifier: my_field => my_field_html

@aziaziazi Comment puis-je faire la même chose mais pour un champ imbriqué dans un tableau?

---
pressEventsList:
  - body: >-
      *My md content...*
    image: 'https://res.cloudinary.com/press/01.jpg'
  - body: >-
      *My md content...*
    image: 'https://res.cloudinary.com/press/02.jpg'
---

J'ai besoin de convertir chaque pressEventsList[i].body .

@alexeychikk je suppose que vous pouvez rechercher pressEventList , puis mapper le contenu pour créer un tableau de résultats:

const remark = require("remark");
const remarkHTML = require("remark-html");

exports.onCreateNode = ({ node, actions: { createNodeField } }) => {
const pressEventList = node.frontmatter.pressEventList;

if (pressEventList) {
  const value = pressEventList.map(event =>
     remark()
    .use(remarkHTML)
    .processSync(event.body)
    .toString()
  )

  createNodeField({
    name: `pressEventList`,
    node,
    value
  });
}
};

Je suis intéressé par la création d'un plugin pour analyser les balises YAML personnalisées pour atteindre ce qui précède sans utiliser createNodeField (de la même manière que sharp analyse les URL d'image).
Quelqu'un peut-il me diriger vers le code où les URL d'images sont analysées pour voir un exemple de la façon dont cela est actuellement fait avec Sharp?

👋 Pour ceux qui utilisent MDX, j'ai créé un plugin pour ajouter le support du frontmatter https://www.gatsbyjs.org/packages/gatsby-plugin-mdx-frontmatter/

@zslabs , ce n'est pas souvent que vous voyez une solution postée "il y a 9 heures"! Je vais essayer! Bon travail.

J'ai du mal avec cela depuis que je voulais utiliser une structure de données plus complexe pour l'une de mes pages.
En frontmatter, j'avais un tableau de sections, avec quelques champs comme le titre et l'image en vedette, puis sur chacun, j'avais un corps fait avec markdown.
Utiliser createNodeField ne le faisait pas pour moi car j'avais du mal à les relier logiquement car ils sont créés dans leur propre champ, pas ajoutés à la structure de frontmatter existante.
J'ai fini par utiliser createFieldExtension de sorte que lorsque ma section.body est interrogée, elle est renvoyée en HTML.
S'il vous plaît, corrigez-moi si ce n'est pas une bonne solution, cela semble fonctionner pour moi, mais j'ai le sentiment tenace que c'est la mauvaise façon de procéder.

ma structure frontmatter ressemble à ceci:

templateKey: project-entry
date: 2020-06-22T13:16:57.702Z
featuredproject: true
title: Project title
description: Description for listing the project on other pages
featuredimage: Image for listing the project on other pages
featuredpost: false
sections:
  - heading: Section heading
    standout: false
    intro: >-
      Introduction to be displayed separately to body
    body: >-
       ## section title
       * bullet point
       * bullet point
       Some other text here

Et le code que j'ai utilisé dans gatsby-node.js

exports.createSchemaCustomization = ({actions}) => {
  const { createTypes, createFieldExtension} = actions

  createFieldExtension({
    name: 'toHTML',
    extend:() => ({
        resolve(source) {
          return remark().use(remarkHTML).processSync(source.body).toString()
        }
      })
  })
  const typeDefs = `
  type MarkdownRemark implements Node {
    frontmatter: Frontmatter
  }
  type Frontmatter <strong i="14">@infer</strong> {
    sections: [section]
  }
  type section <strong i="15">@infer</strong> {
    body: String <strong i="16">@toHTML</strong>
  }
  `
  createTypes(typeDefs)
}

Pour toute autre personne intéressée, je l'ai résolu en utilisant un type YAML personnalisé pour permettre l'analyse de tout champ arbitraire en tant que markdown comme ceci:

---
title: My Page
inline: !md Some **bold** and _italic_ text
block: !md |
  ## I'm a H2 title
  [I'm an inline-style link](https://www.google.com)
---

Pour ce faire, créez un type personnalisé, puis remplacez l'analyseur YAML de la matière grise:

// custom-yaml.js
const yaml = require('js-yaml')
const remark = require('remark')
const remarkHTML = require('remark-html')

const MarkdownYamlType = new yaml.Type('!md', {
  kind: 'scalar',
  construct: data => remark().use(remarkHTML).processSync(data).toString(),
})

const MARKDOWN_SCHEMA = yaml.Schema.create(MarkdownYamlType)

module.exports = doc => yaml.safeLoad(doc, { schema: MARKDOWN_SCHEMA })
// gatsby-config.js
module.exports = {
  plugins: [
    {
      resolve: `gatsby-transformer-remark`,
      options: {
        engines: { yaml: require("path/to/custom-yaml.js") },
      },
    }
  ]
}

J'ai résolu cela un peu différemment. J'ai créé une extension de champ appelée @md et l'

exports.createSchemaCustomization = ({ actions }) => {
  actions.createFieldExtension({
    name: "md",
    args: {
      from: {
        type: "String!",
        defaultValue: true,
      },
    },

    extend() {
      return {
        args: {
          from: "String!",
        },
        resolve(source, args) {
          const fieldValue = source[args.from]
          return convertToHTML(fieldValue)
        },
      }
    },
  })
  const typeDefs = `
    type MarkdownRemark implements Node <strong i="7">@infer</strong> {
      frontmatter: Frontmatter
    }
    type Frontmatter {
      markdownField: String! <strong i="8">@md</strong>
    }
  `
  actions.createTypes(typeDefs)
}

voici un exemple d'utilisation:

...
frontmatter {
        title: markdownField(from: "title")
        subtitle: markdownField(from: "subtitle")
}

J'ai résolu cela un peu différemment.

Cela ne fonctionne pas vraiment pour moi. D'abord, j'obtiens une erreur rejetant le defaultValue: true pour l'argument from - ce doit être une chaîne. En changeant cela en defaultValue: '' , j'obtiens alors cette erreur:

Encountered an error parsing the provided GraphQL type definitions:
Argument "from" of required type "String!" was not provided.

  1 |
  2 |     type MarkdownRemark implements Node <strong i="11">@infer</strong> {
  3 |       frontmatter: Frontmatter
  4 |     }
  5 |     type Frontmatter {
> 6 |       markdownField: String! <strong i="12">@md</strong>
    |                              ^
  7 |     }

Je ne sais pas comment résoudre ce problème.

Pour toute autre personne intéressée, je l'ai résolu en utilisant un type YAML personnalisé pour permettre l'analyse de tout champ arbitraire en tant que markdown comme ceci:

---
title: My Page
inline: !md Some **bold** and _italic_ text
block: !md |
  ## I'm a H2 title
  [I'm an inline-style link](https://www.google.com)
---

Pour ce faire, créez un type personnalisé, puis remplacez l'analyseur YAML de la matière grise:

// custom-yaml.js
const yaml = require('js-yaml')
const remark = require('remark')
const remarkHTML = require('remark-html')

const MarkdownYamlType = new yaml.Type('!md', {
  kind: 'scalar',
  construct: data => remark().use(remarkHTML).processSync(data).toString(),
})

const MARKDOWN_SCHEMA = yaml.Schema.create(MarkdownYamlType)

module.exports = doc => yaml.safeLoad(doc, { schema: MARKDOWN_SCHEMA })
// gatsby-config.js
module.exports = {
  plugins: [
    {
      resolve: `gatsby-transformer-remark`,
      options: {
        engines: { yaml: require("path/to/custom-yaml.js") },
      },
    }
  ]
}

J'obtiens 'Options de plugin invalides pour "gatsby-transformer-remarque":' si j'essaye cette méthode?

Cette page vous a été utile?
0 / 5 - 0 notes

Questions connexes

hobochild picture hobochild  ·  3Commentaires

ghost picture ghost  ·  3Commentaires

ferMartz picture ferMartz  ·  3Commentaires

dustinhorton picture dustinhorton  ·  3Commentaires

theduke picture theduke  ·  3Commentaires