Ghost CMS
Handlebars
Web

Desarrollando un Ghost CMS Theme [Parte 4]

Aquí explico sobre Content y Editor Cards en un Ghost CMS Theme.

Ivan Robles

14 min read
Desarrollando un Ghost CMS Theme [Parte 4]

Vamos a continuar con algunos conceptos que dejamos pendientes en el post anterior.

Contenido

Al crear una pagina o un post, podemos agregar contenido variado como es imagen, markdown, html card, galería de imágenes, bookmark, email content, botón, callout, toggle, video, audio, archivo, producto así como contenido embebido como un tweet o video de youtube.

Inicialmente lo sentí como si fuese una caja negra. Esto es porque al momento del manejo de templates lo único que vi fue {{content}}. Los estilos dentro de content están predefinidos, si queremos hacer cambios hay que “sobre escribir” sus clases.

A continuación voy a explicar algunos elementos dentro de {{content}}. Por cierto que decidí de ultimo momento agregar en algunos casos esos elementos en el post solo como demostración, dicho esto, no va a concordar el ejemplo de lo que es con el código (en el caso de nombres de archivos y contenido). Avisado está aquel que lea este post.

Image

En HTML van dentro de <figure> y dentro existe un <figcaption>.

Algo que vamos a notar es que las clases que se manejan dentro de content comienzan con el prefijo kg.

{{/*  Output  */}}
<figure class="kg-image-card">
    <img class="kg-image" src="<https://casper.ghost.org/v1.25.0/images/koenig-demo-1.jpg>" width="1600" height="2400" loading="lazy" srcset="..." sizes="...">
    <figcaption>An example image</figcaption>
</figure>

Cuenta con helpers de tamaño de imagen con las clases kg-width-wide y kg-width-full:

{{/*  Output  */}}
<figure class="kg-image-card kg-width-wide">
    <img class="kg-image" src="<https://casper.ghost.org/v1.25.0/images/koenig-demo-1.jpg>" width="1600" height="2400" loading="lazy" srcset="..." sizes="...">
</figure>

Para tamaños de imágenes responsivas se usa srcset y sizes:

{{/*  Output  */}}
<figure class="kg-card kg-image-card">
    <img src="<https://myghostsite.com/content/images/2021/03/coastline.jpg>"
        class="kg-image"
        alt="A rugged coastline with small groups of people walking around rock pools"
        loading="lazy"
        width="2000"
        height="3000"
        srcset="<https://myghostsite.com/content/images/size/w600/2021/03/coastline.jpg> 600w,
                <https://myghostsite.com/content/images/size/w1000/2021/03/coastline.jpg> 1000w,
                <https://myghostsite.com/content/images/size/w1600/2021/03/coastline.jpg> 1600w,
                <https://myghostsite.com/content/images/size/w2400/2021/03/coastline.jpg> 2400w"
        sizes="(min-width: 720px) 720px">
</figure>

Editor Cards

Hay un montón de cards disponibles en el editor, las cuales requieren de CSS y Javascript para mostrarse y funcionar correctamente. Por default ghost nos viene con los archivos card.min.css y cards.min.js dentro del helper de {{ghost_head}}.

Para hacer override (sobre escribir) de estos estilos y comportamiento para cada card individual hay que excluirlos en el package.json:

"card_assets": {
    "exclude": ["bookmark", "gallery"]
}
{{/*  Output  */}}
<figure class="kg-card kg-gallery-card kg-width-wide">
    <div class="kg-gallery-container">
        <div class="kg-gallery-row">
            <div class="kg-gallery-image">
                <img src="/content/images/1.jpg" width="6720" height="4480" loading="lazy" srcset="..." sizes="...">
            </div>
            <div class="kg-gallery-image">
                <img src="/content/images/2.jpg" width="4946" height="3220" loading="lazy" srcset="..." sizes="...">
            </div>
            <div class="kg-gallery-image">
                <img src="/content/images/3.jpg" width="5560" height="3492" loading="lazy" srcset="..." sizes="...">
            </div>
        </div>
        <div class="kg-gallery-row">
            <div class="kg-gallery-image">
                <img src="/content/images/4.jpg" width="3654" height="5473" loading="lazy" srcset="..." sizes="...">
            </div>
            <div class="kg-gallery-image">
                <img src="/content/images/5.jpg" width="4160" height="6240" loading="lazy" srcset="..." sizes="...">
            </div>
            <div class="kg-gallery-image">
                <img src="/content/images/6.jpg" width="2645" height="3967" loading="lazy" srcset="..." sizes="...">
            </div>
        </div>
        <div class="kg-gallery-row">
            <div class="kg-gallery-image">
                <img src="/content/images/7.jpg" width="3840" height="5760" loading="lazy" srcset="..." sizes="...">
            </div>
            <div class="kg-gallery-image">
                <img src="/content/images/8.jpg" width="3456" height="5184" loading="lazy" srcset="..." sizes="...">
            </div>
        </div>
    </div>
</figure>

Bookmark Card

Ivan Robles
Ivan Robles specializes in technologies related to React, with several years of experience helping to create solid and scalable digital products and making elegant code.
{{/*  Output  */}}
<figure class="kg-card kg-bookmark-card">
    <a href="/" class="kg-bookmark-container">
        <div class="kg-bookmark-content">
            <div class="kg-bookmark-title">The bookmark card</div>
            <div class="kg-bookmark-description">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce at interdum ipsum.</div>
            <div class="kg-bookmark-metadata">
                <img src="/content/images/author-icon.jpg" class="kg-bookmark-icon">
                <span class="kg-bookmark-author">David Darnes</span>
                <span class="kg-bookmark-publisher">Ghost</span>
            </div>
        </div>
        <div class="kg-bookmark-thumbnail">
            <img src="/content/images/article-image.jpg">
        </div>
    </a>
</figure>

Embed Card

En caso de ser un video, hay que manejar el aspect ratio con CSS.

<figure class="kg-card kg-embed-card">
    <iframe ...></iframe> <!-- <iframe> represents card content -->
</figure>

NFT Card

Estos vienen por OpenSea una de las plataformas mas famosas de NFTs (al parecer ya no se puede embeber por cosas de OpenSea... me quedé con las ganas):

<figure class="kg-card kg-embed-card kg-nft-card">
    <a class="kg-nft-card"> <!-- Link to NFT on OpenSea -->
        <img class="kg-nft-image"> <!-- Image of NFT -->
        <div class="kg-nft-metadata">
            <div class="kg-nft-header">
                <h4 class="kg-nft-title"> NFT Name </h4>
            </div>
            <div class="kg-nft-creator">
                Created by <span class="kg-nft-creator-name"> Creator Name </span>
                &bull; Collection
            </div>
        </div>
    </a>
</figure>

Button Card

Inserta un link y el estilo se ve como un botón, su color depende de la configuración del sitio y se puede alinear a la izquierda o al centro.

<div class="kg-card kg-button-card kg-align-center">
    <a href="<https://example.com/signup/>" class="kg-btn kg-btn-accent">Sign up now</a>
</div>

Callout Card

Muestra un texto resaltado con un emoji, algo parecido a un quote.

🌎
Hola Mundo!!
<div class="kg-card kg-callout-card kg-callout-card-accent">
    <div class="kg-callout-emoji">💡</div>
    <div class="kg-callout-text">Did you know about the callout card?</div>
</div>

Toggle Card

Este es un elemento que muestra contenido desplegable o colapsable.

Titulo del Toggle

Contenido del Toggle

<div class="kg-card kg-toggle-card" data-kg-toggle-state="close">
    <div class="kg-toggle-heading">
        <h4 class="kg-toggle-heading-text">Do you give any discounts ?</h4>
        <button class="kg-toggle-card-icon">
            <svg id="Regular" xmlns="<http://www.w3.org/2000/svg>" viewBox="0 0 24 24"><path class="cls-1" d="M23.25,7.311,12.53,18.03a.749.749,0,0,1-1.06,0L.75,7.311"/></svg>
        </button>
    </div>
    <div class="kg-toggle-content">Yes, we give 20% off on annual subscriptions.</div>
</div>

Audio upload card

Pa’ los audio files.

audio-thumbnail
Snowflake Ethereal Space
0:00
/1:16
<div class="kg-card kg-audio-card">
    <img src="<https://example.com/blog/content/media/2021/12/file_example_MP3_thumb.png?v=1639412501826>" alt="audio-thumbnail" class="kg-audio-thumbnail">
    <div class="kg-audio-thumbnail placeholder kg-audio-hide">
        <svg width="24" height="24" fill="none" xmlns="<http://www.w3.org/2000/svg>"><path fill-rule="evenodd" clip-rule="evenodd" d="M7.5 15.33a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Zm-2.25.75a2.25 2.25 0 1 1 4.5 0 2.25 2.25 0 0 1-4.5 0ZM15 13.83a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Zm-2.25.75a2.25 2.25 0 1 1 4.5 0 2.25 2.25 0 0 1-4.5 0Z"></path><path fill-rule="evenodd" clip-rule="evenodd" d="M14.486 6.81A2.25 2.25 0 0 1 17.25 9v5.579a.75.75 0 0 1-1.5 0v-5.58a.75.75 0 0 0-.932-.727.755.755 0 0 1-.059.013l-4.465.744a.75.75 0 0 0-.544.72v6.33a.75.75 0 0 1-1.5 0v-6.33a2.25 2.25 0 0 1 1.763-2.194l4.473-.746Z"></path><path fill-rule="evenodd" clip-rule="evenodd" d="M3 1.5a.75.75 0 0 0-.75.75v19.5a.75.75 0 0 0 .75.75h18a.75.75 0 0 0 .75-.75V5.133a.75.75 0 0 0-.225-.535l-.002-.002-3-2.883A.75.75 0 0 0 18 1.5H3ZM1.409.659A2.25 2.25 0 0 1 3 0h15a2.25 2.25 0 0 1 1.568.637l.003.002 3 2.883a2.25 2.25 0 0 1 .679 1.61V21.75A2.25 2.25 0 0 1 21 24H3a2.25 2.25 0 0 1-2.25-2.25V2.25c0-.597.237-1.169.659-1.591Z"></path></svg>
    </div>
    <div class="kg-audio-player-container" style="--buffered-width:0.757576%;">
        <audio src="<https://example.com/content/media/2021/12/file_example_MP3.mp3>" preload="metadata"></audio>
        <div class="kg-audio-title">File example MP3</div><div class="kg-audio-player">
            <button class="kg-audio-play-icon">
                <svg xmlns="<http://www.w3.org/2000/svg>" viewBox="0 0 24 24"><path d="M23.14 10.608 2.253.164A1.559 1.559 0 0 0 0 1.557v20.887a1.558 1.558 0 0 0 2.253 1.392L23.14 13.393a1.557 1.557 0 0 0 0-2.785Z"></path></svg>
            </button>
            <button class="kg-audio-pause-icon kg-audio-hide">
                <svg xmlns="<http://www.w3.org/2000/svg>" viewBox="0 0 24 24"><rect x="3" y="1" width="7" height="22" rx="1.5" ry="1.5"></rect><rect x="14" y="1" width="7" height="22" rx="1.5" ry="1.5"></rect></svg>
            </button>
            <span class="kg-audio-current-time">0:00</span>
            <div class="kg-audio-time">
                /<span class="kg-audio-duration">2:12</span>
            </div>
            <input type="range" class="kg-audio-seek-slider" max="132" value="0">
            <button class="kg-audio-playback-rate">1×</button>
            <button class="kg-audio-unmute-icon">
                <svg xmlns="<http://www.w3.org/2000/svg>" viewBox="0 0 24 24"><path d="M15.189 2.021a9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h1.794a.249.249 0 0 1 .221.133 9.73 9.73 0 0 0 7.924 4.85h.06a1 1 0 0 0 1-1V3.02a1 1 0 0 0-1.06-.998Z"></path></svg>
            </button>
            <button class="kg-audio-mute-icon kg-audio-hide">
                <svg xmlns="<http://www.w3.org/2000/svg>" viewBox="0 0 24 24"><path d="M16.177 4.3a.248.248 0 0 0 .073-.176v-1.1a1 1 0 0 0-1.061-1 9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h.114a.251.251 0 0 0 .177-.073ZM23.707 1.706A1 1 0 0 0 22.293.292l-22 22a1 1 0 0 0 0 1.414l.009.009a1 1 0 0 0 1.405-.009l6.63-6.631A.251.251 0 0 1 8.515 17a.245.245 0 0 1 .177.075 10.081 10.081 0 0 0 6.5 2.92 1 1 0 0 0 1.061-1V9.266a.247.247 0 0 1 .073-.176Z"></path></svg>
            </button>
            <input type="range" class="kg-audio-volume-slider" max="100" value="100">
        </div>
    </div>
</div>

Video upload card

Pa’ los archivos de video.

0:00
/
<figure class="kg-card kg-video-card">
  <div class="kg-video-container">
    <video src="<https://example.com/video.mp4>" poster="<https://img.spacergif.org/v1/640x480/0a/spacer.png>" width="640" height="480" playsinline preload="metadata" style="background: transparent url('<https://example.com/video.png>') 50% 50% / cover no-repeat;" />
    </video>
  <div class="kg-video-overlay">
    <button class="kg-video-large-play-icon">
      <svg xmlns="<http://www.w3.org/2000/svg>" viewBox="0 0 24 24"><path d="M23.14 10.608 2.253.164A1.559 1.559 0 0 0 0 1.557v20.887a1.558 1.558 0 0 0 2.253 1.392L23.14 13.393a1.557 1.557 0 0 0 0-2.785Z"/></svg>
    </button>
  </div>
  <div class="kg-video-player-container">
    <div class="kg-video-player">
      <button class="kg-video-play-icon">
        <svg xmlns="<http://www.w3.org/2000/svg>" viewBox="0 0 24 24"><path d="M23.14 10.608 2.253.164A1.559 1.559 0 0 0 0 1.557v20.887a1.558 1.558 0 0 0 2.253 1.392L23.14 13.393a1.557 1.557 0 0 0 0-2.785Z"/></svg>
      </button>
      <button class="kg-video-pause-icon kg-video-hide">
        <svg xmlns="<http://www.w3.org/2000/svg>" viewBox="0 0 24 24"><rect x="3" y="1" width="7" height="22" rx="1.5" ry="1.5"/><rect x="14" y="1" width="7" height="22" rx="1.5" ry="1.5"/></svg>
      </button>
      <span class="kg-video-current-time">0:00</span>
      <div class="kg-video-time">/<span class="kg-video-duration"></span></div>
      <input type="range" class="kg-video-seek-slider" max="100" value="0">
        <button class="kg-video-playback-rate">1&#215;</button>
        <button class="kg-video-unmute-icon">
          <svg xmlns="<http://www.w3.org/2000/svg>" viewBox="0 0 24 24"><path d="M15.189 2.021a9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h1.794a.249.249 0 0 1 .221.133 9.73 9.73 0 0 0 7.924 4.85h.06a1 1 0 0 0 1-1V3.02a1 1 0 0 0-1.06-.998Z"/></svg>
        </button>
        <button class="kg-video-mute-icon kg-video-hide">
          <svg xmlns="<http://www.w3.org/2000/svg>" viewBox="0 0 24 24"><path d="M16.177 4.3a.248.248 0 0 0 .073-.176v-1.1a1 1 0 0 0-1.061-1 9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h.114a.251.251 0 0 0 .177-.073ZM23.707 1.706A1 1 0 0 0 22.293.292l-22 22a1 1 0 0 0 0 1.414l.009.009a1 1 0 0 0 1.405-.009l6.63-6.631A.251.251 0 0 1 8.515 17a.245.245 0 0 1 .177.075 10.081 10.081 0 0 0 6.5 2.92 1 1 0 0 0 1.061-1V9.266a.247.247 0 0 1 .073-.176Z"/></svg>
        </button>
      <input type="range" class="kg-video-volume-slider" max="100" value="100">
      </div>
    </div>
  </div>
</figure>

File upload card

Pa’ cualquier tipo de archivo.

<div class="kg-card kg-file-card ">
    <a class="kg-file-card-container" href="<https://ghost.org/uploads/2017/11/file_example_PDF.pdf>" title="Download">
        <div class="kg-file-card-contents">
            <div class="kg-file-card-title">Sample File</div>
            <div class="kg-file-card-caption">Sample file caption</div>
            <div class="kg-file-card-metadata">
                <div class="kg-file-card-filename">file_example_PDF.pdf</div>
                <div class="kg-file-card-filesize">488 KB</div>
            </div>
        </div>
        <div class="kg-file-card-icon">
            <svg xmlns="<http://www.w3.org/2000/svg>" viewBox="0 0 24 24"><defs><style>.a{fill:none;stroke:currentColor;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5px;}</style></defs><title>download-circle</title><polyline class="a" points="8.25 14.25 12 18 15.75 14.25"/><line class="a" x1="12" y1="6.75" x2="12" y2="18"/><circle class="a" cx="12" cy="12" r="11.25"/></svg>
        </div>
    </a>
</div>

Header card

Agrega headers (aunque yo lo veo mas como un Hero) a los post y paginas.

Header Title

Subtitle

Call To Action
<div class="kg-card kg-header-card kg-width-full kg-size-<size> kg-style-<style>" style="" data-kg-background-image="<https://example.com/image.jpg>">
    <h2 class="kg-header-card-header">Header</h2>
    <h3 class="kg-header-card-subheader">Subheader</h3>
    <a href="" class="kg-header-card-button">
        Button Text
    </a>
</div>

También hay otros elementos como signup card, product, divider, email content, email call to action y alternative blockquote style, pero la verdad no es algo que vaya a utilizar en este blog.

Conclusión

Existen otros conceptos utilizados en el CMS de Ghost, pero no voy a entrar en detalles porque  no voy a hacer uso de ellos, cual es el caso de Members, Routing y Search.

Ghost para mi es solo una alternativa al aburrido Wordpress. Y estoy seguro de que este es el único proyecto en el cual lo voy a estar utilizando, el cual es un blog sin monetizar. No miembros, no cookies, no publicidad. Solo escribir y leer.

Nos leemos pronto!! Bye!!