2
1
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2023-12-13 21:00:40 +01:00

Updated casper test fixture to latest version

closes: https://github.com/TryGhost/Toolbox/issues/193

- Our Casper fixture was several years out of date.
- I'd already updated the ghost-api to point to the latest API version, which was the main difference
- This makes sure the full fixture is up to date and correct, and therefore that we're truly testing if Ghost right now works
- It also adds instructions for how to update it in future

GOTCHA: the mock-express-style tests are failing if the API difference between test-theme and casper are different
- I've tried to look into why this is - it's something to do with the overridden route settings not working properly if the API version changes
- Given that we may not keep this style of testing AND we are definitely not keeping API versions, I'm ignoreing this for now
- To get around it, I'm setting both themes to be v4 API, so that Casper is exactly as in main
This commit is contained in:
Hannah Wolfe 2022-01-25 11:38:03 +00:00
parent a5655c4a22
commit 2ea7d5432b
No known key found for this signature in database
GPG key ID: AB586C3B5AE5C037
26 changed files with 630 additions and 470 deletions

View file

@ -1 +1,2 @@
test/coverage/**
test/utils/fixtures/themes/casper/assets/**

View file

@ -307,7 +307,7 @@ describe('Default Frontend routing', function () {
describe('Static assets', function () {
it('should retrieve theme assets', async function () {
await request.get('/assets/css/screen.css')
await request.get('/assets/built/screen.css')
.expect('Cache-Control', testUtils.cacheRules.year)
.expect(200)
.expect(assertCorrectFrontendHeaders);

View file

@ -141,12 +141,12 @@ describe('Integration - Web - Site canary', function () {
return localUtils.mockExpress.invoke(app, req)
.then(function (response) {
const $ = cheerio.load(response.body);
response.statusCode.should.eql(200);
response.template.should.eql('author');
$('.author-bio').length.should.equal(1);
const bodyClasses = response.body.match(/<body[^>]*class="([^"]*?)">/)[1].split(' ');
bodyClasses.should.containEql('author-template');
bodyClasses.should.containEql('author-joe-bloggs');
});
});
@ -235,7 +235,7 @@ describe('Integration - Web - Site canary', function () {
const req = {
secure: true,
method: 'GET',
url: '/assets/css/screen.css',
url: '/assets/built/screen.css',
host: 'example.com'
};

View file

@ -1,3 +1,15 @@
# Theme Fixtures
These files are used throughout our tests to mock themes in various states.
## Updating the Casper theme fixture
The casper fixture is a partial copy of the content/themes/casper folder.
It should not include any files that aren't needed to run the theme.
To update it, make sure you are in side this folder (the fixtures/themes directory) and run:
`rsync -rv --exclude '.git*' --exclude 'assets/css*' --exclude 'assets/js*' --exclude 'gulpfile.js' --exclude 'yarn.lock' --exlcude 'README.md' ../../../../content/themes/casper .`
### Modifying theme fixtures
When a new rule is introduced in gscan one of these fixture files might break and you'll have to update a "zip" which isn't as easy as opening a text editor... It could become that one day but for now here are some commands to help out with the edit process

View file

@ -1,4 +1,4 @@
Copyright (c) 2013-2020 Ghost Foundation
Copyright (c) 2013-2021 Ghost Foundation
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation

View file

@ -0,0 +1,2 @@
!function(o){"use strict";o.fn.fitVids=function(e){var t,i,n={customSelector:null,ignore:null};return document.getElementById("fit-vids-style")||(t=document.head||document.getElementsByTagName("head")[0],(i=document.createElement("div")).innerHTML='<p>x</p><style id="fit-vids-style">.fluid-width-video-container{flex-grow: 1;width:100%;}.fluid-width-video-wrapper{width:100%;position:relative;padding:0;}.fluid-width-video-wrapper iframe,.fluid-width-video-wrapper object,.fluid-width-video-wrapper embed {position:absolute;top:0;left:0;width:100%;height:100%;}</style>',t.appendChild(i.childNodes[1])),e&&o.extend(n,e),this.each(function(){var e=['iframe[src*="player.vimeo.com"]','iframe[src*="youtube.com"]','iframe[src*="youtube-nocookie.com"]','iframe[src*="kickstarter.com"][src*="video.html"]',"object","embed"];n.customSelector&&e.push(n.customSelector);var r=".fitvidsignore";n.ignore&&(r=r+", "+n.ignore);e=o(this).find(e.join(","));(e=(e=e.not("object object")).not(r)).each(function(){var e,t,i=o(this);0<i.parents(r).length||"embed"===this.tagName.toLowerCase()&&i.parent("object").length||i.parent(".fluid-width-video-wrapper").length||(i.css("height")||i.css("width")||!isNaN(i.attr("height"))&&!isNaN(i.attr("width"))||(i.attr("height",9),i.attr("width",16)),e=("object"===this.tagName.toLowerCase()||i.attr("height")&&!isNaN(parseInt(i.attr("height"),10))?parseInt(i.attr("height"),10):i.height())/(isNaN(parseInt(i.attr("width"),10))?i.width():parseInt(i.attr("width"),10)),i.attr("name")||(t="fitvid"+o.fn.fitVids._count,i.attr("name",t),o.fn.fitVids._count++),i.wrap('<div class="fluid-width-video-container"><div class="fluid-width-video-wrapper"></div></div>').parent(".fluid-width-video-wrapper").css("padding-top",100*e+"%"),i.removeAttr("height").removeAttr("width"))})})},o.fn.fitVids._count=0}(window.jQuery||window.Zepto),function(t,i){var r,n,o,d,s,a,c,l=i.querySelector("link[rel=next]");function h(){if(404===this.status)return t.removeEventListener("scroll",p),void t.removeEventListener("resize",f);this.response.querySelectorAll("article.post-card").forEach(function(e){r.appendChild(i.importNode(e,!0))});var e=this.response.querySelector("link[rel=next]");e?l.href=e.href:(t.removeEventListener("scroll",p),t.removeEventListener("resize",f)),c=i.documentElement.scrollHeight,d=o=!1}function e(){var e;d||(s+a<=c-n?o=!1:(d=!0,(e=new t.XMLHttpRequest).responseType="document",e.addEventListener("load",h),e.open("GET",l.href),e.send(null)))}function u(){o||t.requestAnimationFrame(e),o=!0}function p(){s=t.scrollY,u()}function f(){a=t.innerHeight,c=i.documentElement.scrollHeight,u()}!l||(r=i.querySelector(".post-feed"))&&(d=o=!(n=300),s=t.scrollY,a=t.innerHeight,c=i.documentElement.scrollHeight,t.addEventListener("scroll",p,{passive:!0}),t.addEventListener("resize",f),u())}(window,document);
//# sourceMappingURL=casper.js.map

File diff suppressed because one or more lines are too long

View file

@ -1,2 +1,2 @@
a,abbr,acronym,address,applet,article,aside,audio,big,blockquote,body,canvas,caption,cite,code,dd,del,details,dfn,div,dl,dt,em,embed,fieldset,figcaption,figure,footer,form,h1,h2,h3,h4,h5,h6,header,hgroup,html,iframe,img,ins,kbd,label,legend,li,mark,menu,nav,object,ol,output,p,pre,q,ruby,s,samp,section,small,span,strike,strong,sub,summary,sup,table,tbody,td,tfoot,th,thead,time,tr,tt,ul,var,video{margin:0;padding:0;border:0;font:inherit;font-size:100%;vertical-align:baseline}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:after,blockquote:before,q:after,q:before{content:"";content:none}img{max-width:100%}html{box-sizing:border-box;font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}*,:after,:before{box-sizing:inherit}a{background-color:transparent}a:active,a:hover{outline:0}b,strong{font-weight:700}dfn,em,i{font-style:italic}h1{margin:.67em 0;font-size:2em}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}mark{background-color:#fdffb6}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;color:inherit;font:inherit}button{overflow:visible;border:none}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{cursor:pointer;-webkit-appearance:button}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input:focus{outline:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{box-sizing:content-box;-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}legend{padding:0;border:0}textarea{overflow:auto}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}html{overflow-y:scroll;font-size:62.5%;-webkit-tap-highlight-color:rgba(0,0,0,0)}body,html{overflow-x:hidden}body{color:#3c484e;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Open Sans,Helvetica Neue,sans-serif;font-size:1.5rem;line-height:1.6em;font-weight:400;font-style:normal;letter-spacing:0;text-rendering:optimizeLegibility;background:#fff;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;-moz-font-feature-settings:"liga" on}::-moz-selection{text-shadow:none;background:#cbeafb}::selection{text-shadow:none;background:#cbeafb}hr{position:relative;display:block;width:100%;margin:2.5em 0 3.5em;padding:0;height:1px;border:0;border-top:1px solid #e3e9ed}audio,canvas,iframe,img,svg,video{vertical-align:middle}fieldset{margin:0;padding:0;border:0}textarea{resize:vertical}blockquote,dl,ol,p,ul{margin:0 0 1.5em}ol,ul{padding-left:1.3em;padding-right:1.5em}ol ol,ol ul,ul ol,ul ul{margin:.5em 0 1em}ul{list-style:disc}ol{list-style:decimal}ol,ul{max-width:100%}li{margin:.5em 0;padding-left:.3em;line-height:1.6em}dt{float:left;margin:0 20px 0 0;width:120px;color:#15171a;font-weight:500;text-align:right}dd{margin:0 0 5px;text-align:left}blockquote{margin:1.5em 0;padding:0 1.6em;border-left:.5em solid #e5eff5}blockquote p{margin:.8em 0;font-size:1.2em;font-weight:300}blockquote small{display:inline-block;margin:.8em 0 .8em 1.5em;font-size:.9em;opacity:.8}blockquote small:before{content:"\2014 \00A0"}blockquote cite{font-weight:700}blockquote cite a{font-weight:400}a{color:#26a8ed;text-decoration:none}a:hover{text-decoration:underline}h1,h2,h3,h4,h5,h6{margin-top:0;line-height:1.15;font-weight:700;text-rendering:optimizeLegibility}h1{margin:0 0 .5em;font-size:5rem;font-weight:700}@media (max-width:500px){h1{font-size:2.2rem}}h2{margin:1.5em 0 .5em;font-size:2rem}@media (max-width:500px){h2{font-size:1.8rem}}h3{margin:1.5em 0 .5em;font-size:1.8rem;font-weight:500}@media (max-width:500px){h3{font-size:1.7rem}}h4{margin:1.5em 0 .5em;font-size:1.6rem;font-weight:500}h5,h6{margin:1.5em 0 .5em;font-size:1.4rem;font-weight:500}
/*# sourceMappingURL=global.css.map */
a,abbr,acronym,address,applet,article,aside,audio,big,blockquote,body,canvas,caption,cite,code,dd,del,details,dfn,div,dl,dt,em,embed,fieldset,figcaption,figure,footer,form,h1,h2,h3,h4,h5,h6,header,hgroup,html,iframe,img,ins,kbd,label,legend,li,mark,menu,nav,object,ol,output,p,pre,q,ruby,s,samp,section,small,span,strike,strong,sub,summary,sup,table,tbody,td,tfoot,th,thead,time,tr,tt,ul,var,video{margin:0;padding:0;border:0;font:inherit;font-size:100%;vertical-align:baseline}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:after,blockquote:before,q:after,q:before{content:"";content:none}img{display:block;max-width:100%;height:auto}html{box-sizing:border-box;font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}*,:after,:before{box-sizing:inherit}a{background-color:transparent}a:active,a:hover{outline:0}b,strong{font-weight:700}dfn,em,i{font-style:italic}h1{margin:.67em 0;font-size:2em}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}mark{background-color:#fdffb6}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;color:inherit;font:inherit}button{overflow:visible;border:none}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{cursor:pointer;-webkit-appearance:button}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input:focus{outline:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{box-sizing:content-box;-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}legend{padding:0;border:0}textarea{overflow:auto}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}html{font-size:62.5%;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{color:#35373a;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Open Sans,Helvetica Neue,sans-serif;font-size:1.6rem;line-height:1.6em;font-weight:400;font-style:normal;letter-spacing:0;text-rendering:optimizeLegibility;background:#fff;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;-moz-font-feature-settings:"liga" on}::-moz-selection{text-shadow:none;background:#daf2fd}::selection{text-shadow:none;background:#daf2fd}hr{position:relative;display:block;width:100%;margin:2.5em 0 3.5em;padding:0;height:1px;border:0;border-top:1px solid #f0f0f0}audio,canvas,iframe,img,svg,video{vertical-align:middle}fieldset{margin:0;padding:0;border:0}textarea{resize:vertical}::not(.gh-content) blockquote,::not(.gh-content) dl,::not(.gh-content) ol,::not(.gh-content) p,::not(.gh-content) ul{margin:0 0 1.5em}ol,ul{padding-left:1.3em;padding-right:1.5em}ol ol,ol ul,ul ol,ul ul{margin:.5em 0 1em}ul{list-style:disc}ol{list-style:decimal}ol,ul{max-width:100%}li{padding-left:.3em;line-height:1.6em}li+li{margin-top:.5em}dt{float:left;margin:0 20px 0 0;width:120px;color:#daf2fd;font-weight:500;text-align:right}dd{margin:0 0 5px;text-align:left}blockquote{margin:1.5em 0;padding:0 1.6em;border-left:#daf2fd}blockquote p{margin:.8em 0;font-size:1.2em;font-weight:300}blockquote small{display:inline-block;margin:.8em 0 .8em 1.5em;font-size:.9em;opacity:.8}blockquote small:before{content:"\2014 \00A0"}blockquote cite{font-weight:700}blockquote cite a{font-weight:400}a{color:#15171a;text-decoration:none}h1,h2,h3,h4,h5,h6{margin-top:0;line-height:1.15;font-weight:600;text-rendering:optimizeLegibility;letter-spacing:-.01em}h1{margin:0 0 .5em;font-size:4.8rem;font-weight:700;letter-spacing:-.015em}@media (max-width:600px){h1{font-size:2.8rem}}h2{margin:1.5em 0 .5em;font-size:2.8rem;font-weight:700}@media (max-width:600px){h2{font-size:2.3rem}}h3{margin:1.5em 0 .5em;font-size:2.4rem;font-weight:600}@media (max-width:600px){h3{font-size:1.7rem}}h4{margin:1.5em 0 .5em;font-size:2rem}@media (max-width:600px){h4{font-size:1.7rem}}h5{font-size:2rem}h5,h6{margin:1.5em 0 .5em}h6{font-size:1.8rem}
/*# sourceMappingURL=global.css.map */

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 60 KiB

View file

@ -1,54 +1,57 @@
{{!< default}}
{{!-- The tag above means - insert everything in this file into the {body} of the default.hbs template --}}
{{#author}}
{{!-- Everything inside the #author tags pulls data from the author --}}
<header class="site-header outer {{#if cover_image}}" style="background-image: url({{cover_image}}){{else}}no-cover{{/if}}">
<div class="inner">
{{> "site-nav"}}
<div class="site-header-content">
{{#if profile_image}}
<img class="author-profile-image" src="{{profile_image}}" alt="{{name}}" />
{{/if}}
<h1 class="site-title">{{name}}</h1>
{{#if bio}}
<h2 class="author-bio">{{bio}}</h2>
{{/if}}
<div class="author-meta">
{{#if location}}
<div class="author-location">{{location}} <span class="bull">&bull;</span></div>
{{/if}}
<div class="author-stats">
{{plural ../pagination.total empty='No posts' singular='% post' plural='% posts'}} <span class="bull">&bull;</span>
</div>
{{#if website}}
<a class="social-link social-link-wb" href="{{website}}" target="_blank" rel="noopener">{{> "icons/website"}}</a>
{{/if}}
{{#if twitter}}
<a class="social-link social-link-tw" href="{{twitter_url}}" target="_blank" rel="noopener">{{> "icons/twitter"}}</a>
{{/if}}
{{#if facebook}}
<a class="social-link social-link-fb" href="{{facebook_url}}" target="_blank" rel="noopener">{{> "icons/facebook"}}</a>
{{/if}}
<a class="social-link social-link-rss" href="https://feedly.com/i/subscription/feed/{{url absolute="true"}}rss/" target="_blank" rel="noopener">{{> "icons/rss"}}</a>
</div>
</div>
</div>
</header>
{{/author}}
{{!-- The main content area --}}
<main id="site-main" class="site-main outer">
<div class="inner">
<section class="outer">
<div class="inner posts">
<header class="author-profile">
{{#author}}
{{!-- Everything inside the #author tags pulls data from the author --}}
<div class="author-profile-content">
{{#if profile_image}}
<img class="author-profile-pic" src="{{profile_image}}" alt="{{name}}" />
{{else}}
<span class="author-profile-pic">{{> "icons/avatar"}}</span>
{{/if}}
<h1>{{name}}</h1>
<p>
{{#if bio}}
{{bio}}
{{else}}
{{plural ../pagination.total empty='No posts' singular='% post' plural='% posts'}} published
{{/if}}
</p>
<div class="author-profile-meta">
{{#if location}}
<div class="author-profile-location">📍 {{location}}</div>
{{/if}}
{{#if website}}
<span><a class="author-profile-social-link" href="{{website}}" target="_blank" rel="noopener">{{website}}</a></span>
{{/if}}
{{#if twitter}}
<span><a class="author-profile-social-link" href="{{twitter_url}}" target="_blank" rel="noopener">{{twitter_url}}</a></span>
{{/if}}
{{#if facebook}}
<span><a class="author-profile-social-link" href="{{facebook_url}}" target="_blank" rel="noopener">{{facebook_url}}</a></span>
{{/if}}
</div>
</div>
{{/author}}
</header>
<div class="post-feed">
{{#foreach posts}}
{{!-- The tag below includes the markup for each post - partials/post-card.hbs --}}
{{> "post-card"}}
{{/foreach}}
</div>
</div>
</main>
</section>

View file

@ -1,83 +1,114 @@
<!DOCTYPE html>
<html lang="{{lang}}">
<html lang="{{@site.locale}}"{{#match @custom.color_scheme "Dark"}} class="dark-mode"{{/match}}{{#match @custom.color_scheme "Auto"}} class="auto-color"{{/match}}>
<head>
{{!-- Document Settings --}}
{{!-- Basic meta - advanced meta is output with {ghost_head} below --}}
<title>{{meta_title}}</title>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
{{!-- Base Meta --}}
<title>{{meta_title}}</title>
<meta name="HandheldFriendly" content="True" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
{{!-- Styles'n'Scripts --}}
<style>
:root {
{{#if @custom.header_button_background}}
--button-bg-color: {{@custom.header_button_background}};
{{/if}}
--button-text-color: {{#match @custom.header_button_text_color "Light"}}#fff{{else}}var(--color-darkgrey){{/match}};
}
</style>
{{!-- Theme assets - use the {asset} helper to reference styles & scripts,
this will take care of caching and cache-busting automatically --}}
<link rel="stylesheet" type="text/css" href="{{asset "built/screen.css"}}" />
{{!-- This tag outputs SEO meta+structured data and other important settings --}}
{{!-- This tag outputs all your advanced SEO meta, structured data, and other important settings,
it should always be the last tag before the closing head tag --}}
{{ghost_head}}
</head>
<body class="{{body_class}}">
<body class="{{body_class}}{{#match @custom.title_font "=" "Elegant serif"}} has-serif-title{{/match}}{{#match @custom.body_font "=" "Modern sans-serif"}} has-sans-body{{/match}}">
<div class="viewport">
<div class="site-wrapper">
<header id="gh-head" class="gh-head{{#match @custom.publication_cover_style "!=" "None"}} has-cover{{/match}}">
<nav class="gh-head-inner inner gh-container">
{{!-- All the main content gets inserted here, index.hbs, post.hbs, etc --}}
{{{body}}}
{{!-- The footer at the very bottom of the screen --}}
<footer class="site-footer outer">
<div class="site-footer-content inner">
<section class="copyright"><a href="{{@blog.url}}">{{@blog.title}}</a> &copy; {{date format="YYYY"}}</section>
<nav class="site-footer-nav">
<a href="{{@blog.url}}">Latest Posts</a>
{{#if @blog.facebook}}<a href="{{facebook_url @blog.facebook}}" target="_blank" rel="noopener">Facebook</a>{{/if}}
{{#if @blog.twitter}}<a href="{{twitter_url @blog.twitter}}" target="_blank" rel="noopener">Twitter</a>{{/if}}
<a href="https://ghost.org" target="_blank" rel="noopener">Ghost</a>
</nav>
<div class="gh-head-brand">
<a class="gh-head-logo" href="{{@site.url}}">
{{#if @site.logo}}
<img src="{{@site.logo}}" alt="{{@site.title}}" />
{{else}}
{{@site.title}}
{{/if}}
</a>
<a class="gh-burger" role="button">
<div class="gh-burger-box">
<div class="gh-burger-inner"></div>
</div>
</a>
</div>
</footer>
<div class="gh-head-menu">
{{navigation}}
</div>
<div class="gh-head-actions">
<div class="gh-social">
{{#if @site.facebook}}
<a class="gh-social-facebook" href="{{facebook_url @site.facebook}}" title="Facebook" target="_blank" rel="noopener">{{> "icons/facebook"}}</a>
{{/if}}
{{#if @site.twitter}}
<a class="gh-social-twitter" href="{{twitter_url @site.twitter}}" title="Twitter" target="_blank" rel="noopener">{{> "icons/twitter"}}</a>
{{/if}}
</div>
{{#unless @member}}
<a class="gh-head-button" href="#/portal/signup" data-portal="signup">Subscribe</a>
{{else}}
<a class="gh-head-button" href="#/portal/account" data-portal="account">Account</a>
{{/unless}}
</div>
</nav>
</header>
<div class="site-content">
{{!-- All other templates get inserted here, index.hbs, post.hbs, etc --}}
{{{body}}}
</div>
{{!-- The big email subscribe modal content --}}
{{#if @labs.subscribers}}
<div id="subscribe" class="subscribe-overlay">
<a class="subscribe-overlay-close" href="#"></a>
<div class="subscribe-overlay-content">
{{#if @blog.logo}}
<img class="subscribe-overlay-logo" src="{{@blog.logo}}" alt="{{@blog.title}}" />
{{/if}}
<h1 class="subscribe-overlay-title">Subscribe to {{@blog.title}}</h1>
<p class="subscribe-overlay-description">Stay up to date! Get all the latest &amp; greatest posts delivered straight to your inbox</p>
{{subscribe_form placeholder="youremail@example.com"}}
{{!-- The global footer at the very bottom of the screen --}}
<footer class="site-footer outer">
<div class="inner">
<section class="copyright"><a href="{{@site.url}}">{{@site.title}}</a> &copy; {{date format="YYYY"}}</section>
<nav class="site-footer-nav">
{{navigation type="secondary"}}
</nav>
<div><a href="https://ghost.org/" target="_blank" rel="noopener">Powered by Ghost</a></div>
</div>
</div>
{{/if}}
</footer>
{{!-- jQuery + Fitvids, which makes all video embeds responsive --}}
<script
src="https://code.jquery.com/jquery-3.2.1.min.js"
integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4="
crossorigin="anonymous">
</script>
<script type="text/javascript" src="{{asset "js/jquery.fitvids.js"}}"></script>
</div>
{{!-- /.viewport --}}
{{#if pagination.pages}}
<script>
// maxPages is a global variable that is needed to determine
// if we need to load more pages for the infinitescroll, or if
// we reached the last page already.
var maxPages = parseInt('{{pagination.pages}}');
</script>
<script src="{{asset "js/infinitescroll.js"}}"></script>
{{/if}}
{{!-- The #block helper will pull in data from the #contentFor other template files. In this case, there's some JavaScript which we only want to use in post.hbs, but it needs to be included down here, after jQuery has already loaded. --}}
{{{block "scripts"}}}
{{!-- Scripts - handle member signups, responsive videos, infinite scroll, floating headers, and galleries --}}
<script
src="https://code.jquery.com/jquery-3.5.1.min.js"
integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0="
crossorigin="anonymous">
</script>
<script src="{{asset "built/casper.js"}}"></script>
<script>
$(document).ready(function () {
// Mobile Menu Trigger
$('.gh-burger').click(function () {
$('body').toggleClass('gh-head-open');
});
// FitVids - Makes video embeds responsive
$(".gh-content").fitVids();
});
</script>
{{!-- Ghost outputs important scripts and data with this tag - it should always be the very last thing before the closing body tag --}}
{{ghost_foot}}
{{!-- Ghost outputs required functional scripts with this tag - it should always be the last thing before the closing body tag --}}
{{ghost_foot}}
</body>
</html>

View file

@ -1,56 +1,37 @@
{{!< default}}
{{!--
This error template is used for all 404 errors, which might occur on your site.
It's a good idea to keep this template as minimal as possible in terms of both file size and complexity.
There are two error files in this theme, one for 404s and one for all other errors.
This file is the former, and handles all 404 Page Not Found errors.
The 404 error is the most common error that a visitor might see, for example when
following a broken link
Keep this template as lightweight as you can!
--}}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>{{meta_title}}</title>
<meta name="HandheldFriendly" content="True" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" type="text/css" href="{{asset "built/screen.css"}}" />
</head>
<body class="error-template">
<div class="site-wrapper">
<header class="site-header outer {{#if feature_image}}" style="background-image: url({{feature_image}}){{else}}no-cover{{/if}}">
<div class="inner">
<nav class="site-nav-center">
{{#if @blog.logo}}
<a class="site-nav-logo" href="{{@blog.url}}"><img src="{{@blog.logo}}" alt="{{@blog.title}}" /></a>
{{else}}
<a class="site-nav-logo" href="{{@blog.url}}">{{@blog.title}}</a>
{{/if}}
</nav>
</div>
</header>
<main id="site-main" class="site-main outer">
<div class="inner">
<section class="error-message">
<h1 class="error-code">{{code}}</h1>
<p class="error-description">{{message}}</p>
<a class="error-link" href="{{@blog.url}}">Go to the front page →</a>
</section>
</div>
</main>
{{#get "posts" limit="3"}}
<aside class="outer">
<div class="inner">
<div class="post-feed">
{{#foreach posts}}
{{> "post-card"}}
{{/foreach}}
</div>
</div>
</aside>
{{/get}}
<section class="outer error-content">
<div class="inner">
<section class="error-message">
<h1 class="error-code">{{statusCode}}</h1>
<p class="error-description">{{message}}</p>
<a class="error-link" href="{{@site.url}}">Go to the front page →</a>
</section>
</div>
</body>
</html>
</section>
{{!-- Given that people landing on this page didn't find what they
were looking for, let's give them some alternative stuff to read. --}}
<aside class="read-more-wrap">
<div class="read-more inner">
{{#get "posts" include="authors" limit="3" as |more_posts|}}
{{#if more_posts}}
{{#foreach more_posts}}
{{> "post-card"}}
{{/foreach}}
{{/if}}
{{/get}}
</div>
</aside>

View file

@ -1,7 +1,15 @@
{{!--
This error template is used for all 400/500 errors, except 404, which might occur on your site.
It's a good idea to keep this template as minimal as possible in terms of both file size and complexity.
You'll notice that we *don't* use any JavsScript, or ghost_head / ghost_foot in this file.
There are two error files in this theme, one for 404s and one for all other errors.
This file is the latter, and handle all 400/500 errors that might occur.
Because 500 errors in particular usuall happen when a server is struggling, this
template is as simple as possible. No template dependencies, no JS, no API calls.
This is to prevent rendering the error-page itself compounding the issue causing
the error in the first place.
Keep this template as lightweight as you can!
--}}
<!DOCTYPE html>
@ -14,44 +22,47 @@ You'll notice that we *don't* use any JavsScript, or ghost_head / ghost_foot in
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" type="text/css" href="{{asset "built/screen.css"}}" />
</head>
<body class="error-template">
<body>
<div class="site-wrapper">
<header class="site-header outer {{#if feature_image}}" style="background-image: url({{feature_image}}){{else}}no-cover{{/if}}">
<div class="inner">
<nav class="site-nav-center">
{{#if @blog.logo}}
<a class="site-nav-logo" href="{{@blog.url}}"><img src="{{@blog.logo}}" alt="{{@blog.title}}" /></a>
{{else}}
<a class="site-nav-logo" href="{{@blog.url}}">{{@blog.title}}</a>
{{/if}}
</nav>
<header class="site-header no-image">
<div class="site-nav-main outer">
<div class="inner">
<nav class="site-nav-center">
{{#if @site.logo}}
<a class="site-nav-logo" href="{{@site.url}}"><img src="{{img_url @site.logo size="xs"}}"
alt="{{@site.title}}" /></a>
{{else}}
<a class="site-nav-logo" href="{{@site.url}}">{{@site.title}}</a>
{{/if}}
</nav>
</div>
</div>
</header>
<main id="site-main" class="site-main outer">
<main class="outer error-content">
<div class="inner">
<section class="error-message">
<h1 class="error-code">{{code}}</h1>
<h1 class="error-code">{{statusCode}}</h1>
<p class="error-description">{{message}}</p>
<a class="error-link" href="{{@blog.url}}">Go to the front page →</a>
<a class="error-link" href="{{@site.url}}">Go to the front page →</a>
</section>
{{#if errorDetails}}
<section class="error-stack">
<h3>Theme errors</h3>
<ul class="error-stack-list">
{{#each errorDetails}}
{{#foreach errorDetails}}
<li>
<em class="error-stack-function">{{{rule}}}</em>
{{#each failures}}
{{#foreach failures}}
<p><span class="error-stack-file">Ref: {{ref}}</span></p>
<p><span class="error-stack-file">Message: {{message}}</span></p>
{{/each}}
{{/foreach}}
</li>
{{/each}}
{{/foreach}}
</ul>
</section>
{{/if}}

View file

@ -2,28 +2,37 @@
{{!-- The tag above means: insert everything in this file
into the {body} of the default.hbs template --}}
{{!-- The big featured header, it uses blog cover image as a BG if available --}}
<header class="site-header outer {{#if @blog.cover_image}}" style="background-image: url({{@blog.cover_image}}){{else}}no-cover{{/if}}">
<div class="inner">
<div class="site-header-content">
<h1 class="site-title">
{{#if @blog.logo}}
<img class="site-logo" src="{{@blog.logo}}" alt="{{@blog.title}}" />
{{else}}
{{@blog.title}}
{{/if}}
</h1>
<h2 class="site-description">{{@blog.description}}</h2>
</div>
{{> "site-nav"}}
{{#match @custom.publication_cover_style "!=" "None"}}
<div class="site-header-content{{#match @custom.publication_cover_style "Large"}} large{{/match}}">
{{#if @site.cover_image}}
{{!-- This is a responsive image, it loads different sizes depending on device
https://medium.freecodecamp.org/a-guide-to-responsive-images-with-ready-to-use-templates-c400bd65c433 --}}
<img class="site-header-cover"
srcset="{{img_url @site.cover_image size="s"}} 300w,
{{img_url @site.cover_image size="m"}} 600w,
{{img_url @site.cover_image size="l"}} 1000w,
{{img_url @site.cover_image size="xl"}} 2000w"
sizes="100vw"
src="{{img_url @site.cover_image size="xl"}}"
alt=""
/>
{{/if}}
<h1 class="site-title">
{{#if @site.logo}}
<img class="site-logo" src="{{img_url @site.logo size="m"}}" alt="{{@site.title}}" />
{{else}}
{{@site.title}}
{{/if}}
</h1>
<p>{{@site.description}}</p>
</div>
</header>
{{/match}}
{{!-- The main content area --}}
<main id="site-main" class="site-main outer">
<div class="inner">
<div class="inner posts">
<div class="post-feed">
<div class="post-feed{{#match @custom.feed_layout "List"}} list{{/match}}">
{{#foreach posts}}
{{!-- The tag below includes the markup for each post - partials/post-card.hbs --}}

View file

@ -1,10 +1,11 @@
{
"name": "casper",
"description": "The default personal blogging theme for Ghost. Beautiful, minimal and responsive.",
"description": "A clean, minimal default theme for the Ghost publishing platform",
"demo": "https://demo.ghost.io",
"version": "2.4.2",
"version": "4.7.2",
"engines": {
"ghost-api": "canary"
"ghost": ">=4.0.0",
"ghost-api": "v4"
},
"license": "MIT",
"screenshots": {
@ -12,13 +13,19 @@
"mobile": "assets/screenshot-mobile.jpg"
},
"scripts": {
"dev": "gulp",
"zip": "gulp zip"
"dev": "gulp",
"zip": "gulp zip",
"test": "gscan .",
"test:ci": "gscan --fatal --verbose .",
"pretest": "gulp build",
"preship": "yarn test",
"ship": "STATUS=$(git status --porcelain); echo $STATUS; if [ -z \"$STATUS\" ]; then yarn version && git push --follow-tags; else echo \"Uncomitted changes found.\" && exit 1; fi",
"postship": "git fetch && gulp release"
},
"author": {
"name": "Ghost Foundation",
"email": "hello@ghost.org",
"url": "https://ghost.org"
"url": "https://ghost.org/"
},
"gpm": {
"type": "theme",
@ -29,7 +36,8 @@
},
"keywords": [
"ghost",
"theme"
"theme",
"ghost-theme"
],
"repository": {
"type": "git",
@ -38,20 +46,142 @@
"bugs": "https://github.com/TryGhost/Casper/issues",
"contributors": "https://github.com/TryGhost/Casper/graphs/contributors",
"devDependencies": {
"autoprefixer": "6.3.6",
"cssnano": "3.7.1",
"gulp": "3.9.1",
"gulp-livereload": "3.8.1",
"gulp-postcss": "6.1.1",
"gulp-sourcemaps": "1.6.0",
"gulp-util": "3.0.7",
"gulp-watch": "4.3.8",
"gulp-zip": "4.0.0",
"postcss-color-function": "2.0.1",
"postcss-custom-properties": "5.0.1",
"postcss-easy-import": "1.0.1"
"@tryghost/release-utils": "0.6.12",
"autoprefixer": "10.2.5",
"beeper": "2.1.0",
"cssnano": "4.1.10",
"gscan": "4.11.1",
"gulp": "4.0.2",
"gulp-concat": "2.6.1",
"gulp-livereload": "4.0.2",
"gulp-postcss": "9.0.0",
"gulp-uglify": "3.0.2",
"gulp-zip": "5.1.0",
"inquirer": "8.0.0",
"postcss": "8.2.8",
"postcss-color-mod-function": "3.0.3",
"postcss-easy-import": "3.0.0",
"pump": "3.0.0"
},
"browserslist": [
"defaults"
],
"config": {
"posts_per_page": 25
"posts_per_page": 25,
"image_sizes": {
"xxs": {
"width": 30
},
"xs": {
"width": 100
},
"s": {
"width": 300
},
"m": {
"width": 600
},
"l": {
"width": 1000
},
"xl": {
"width": 2000
}
},
"card_assets": true,
"custom": {
"title_font": {
"type": "select",
"options": [
"Modern sans-serif",
"Elegant serif"
],
"default": "Modern sans-serif"
},
"body_font": {
"type": "select",
"options": [
"Modern sans-serif",
"Elegant serif"
],
"default": "Elegant serif"
},
"publication_cover_style": {
"type": "select",
"options": [
"None",
"Medium",
"Large"
],
"default": "Medium",
"group": "homepage"
},
"feed_layout": {
"type": "select",
"options": [
"Dynamic grid",
"Simple grid",
"List"
],
"default": "Dynamic grid",
"group": "homepage"
},
"color_scheme": {
"type": "select",
"options": [
"Light",
"Dark",
"Auto"
],
"default": "Light"
},
"header_button_background": {
"type": "color",
"default": "#ffffff"
},
"header_button_text_color": {
"type": "select",
"options": [
"Light",
"Dark"
],
"default": "Dark"
},
"post_image_width": {
"type": "select",
"options": [
"Wide",
"Full",
"Small"
],
"default": "Wide",
"group": "post"
},
"email_signup_for_logged_out_visitors": {
"type": "select",
"options": [
"Footer",
"Bottom of post",
"None"
],
"default": "Footer",
"group": "post"
},
"email_signup_text": {
"type": "text",
"default": "Sign up for more like this.",
"group": "post"
},
"show_recent_posts": {
"type": "boolean",
"default": true,
"group": "post"
}
}
},
"renovate": {
"extends": [
"@tryghost:theme"
]
}
}

View file

@ -1,48 +1,45 @@
{{!< default}}
{{!-- The tag above means: insert everything in this file
into the {body} of the default.hbs template --}}
into the {body} tag of the default.hbs template --}}
{{!-- The big featured header, it uses blog cover image as a BG if available --}}
<header class="site-header outer">
<div class="inner">
{{> "site-nav"}}
</div>
</header>
{{!-- Everything inside the #post tags pulls data from the post --}}
{{#post}}
{{!-- Everything inside the #post block pulls data from the page --}}
<main id="site-main" class="site-main outer">
<div class="inner">
<article class="post-full {{post_class}} {{#unless feature_image}}no-image{{/unless}}">
<header class="post-full-header">
<h1 class="post-full-title">{{title}}</h1>
</header>
<main id="site-main" class="site-main">
<article class="article {{post_class}}">
<header class="article-header gh-canvas">
{{#if feature_image}}
<figure class="post-full-image" style="background-image: url({{feature_image}})">
<figure class="article-image">
{{!-- This is a responsive image, it loads different sizes depending on device
https://medium.freecodecamp.org/a-guide-to-responsive-images-with-ready-to-use-templates-c400bd65c433 --}}
<img
srcset="{{img_url feature_image size="s"}} 300w,
{{img_url feature_image size="m"}} 600w,
{{img_url feature_image size="l"}} 1000w,
{{img_url feature_image size="xl"}} 2000w"
sizes="(min-width: 1400px) 1400px, 92vw"
src="{{img_url feature_image size="xl"}}"
alt="{{#if feature_image_alt}}{{feature_image_alt}}{{else}}{{title}}{{/if}}"
/>
{{#if feature_image_caption}}
<figcaption>{{feature_image_caption}}</figcaption>
{{/if}}
</figure>
{{/if}}
</header>
<section class="post-full-content">
{{content}}
</section>
<section class="gh-content gh-canvas">
</article>
<h1 class="article-title">{{title}}</h1>
</div>
{{content}}
</section>
</article>
</main>
{{/post}}
{{!-- The #contentFor helper here will send everything inside it up to the matching #block helper found in default.hbs --}}
{{#contentFor "scripts"}}
<script>
$(function() {
var $postContent = $(".post-full-content");
$postContent.fitVids();
});
</script>
{{/contentFor}}
{{/post}}

View file

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path d="M19 6h5V0h-5c-3.86 0-7 3.14-7 7v3H8v6h4v16h6V16h5l1-6h-6V7c0-.542.458-1 1-1z"/></svg>
<svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"><path d="M16 0c8.837 0 16 7.163 16 16s-7.163 16-16 16S0 24.837 0 16 7.163 0 16 0zm5.204 4.911h-3.546c-2.103 0-4.443.885-4.443 3.934.01 1.062 0 2.08 0 3.225h-2.433v3.872h2.509v11.147h4.61v-11.22h3.042l.275-3.81h-3.397s.007-1.695 0-2.187c0-1.205 1.253-1.136 1.329-1.136h2.054V4.911z" /></svg>

Before

Width:  |  Height:  |  Size: 155 B

After

Width:  |  Height:  |  Size: 350 B

View file

@ -0,0 +1,11 @@
<svg version="1.1" id="loader-1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px"
y="0px" width="40px" height="40px" viewBox="0 0 40 40" enable-background="new 0 0 40 40" xml:space="preserve">
<path opacity="0.2" fill="#000" d="M20.201,5.169c-8.254,0-14.946,6.692-14.946,14.946c0,8.255,6.692,14.946,14.946,14.946
s14.946-6.691,14.946-14.946C35.146,11.861,28.455,5.169,20.201,5.169z M20.201,31.749c-6.425,0-11.634-5.208-11.634-11.634
c0-6.425,5.209-11.634,11.634-11.634c6.425,0,11.633,5.209,11.633,11.634C31.834,26.541,26.626,31.749,20.201,31.749z" />
<path fill="#000" d="M26.013,10.047l1.654-2.866c-2.198-1.272-4.743-2.012-7.466-2.012h0v3.312h0
C22.32,8.481,24.301,9.057,26.013,10.047z">
<animateTransform attributeType="xml" attributeName="transform" type="rotate" from="0 20 20" to="360 20 20"
dur="0.5s" repeatCount="indefinite" />
</path>
</svg>

After

Width:  |  Height:  |  Size: 923 B

View file

@ -1,42 +1,61 @@
<article class="post-card {{post_class}}{{#unless feature_image}} no-image{{/unless}}">
{{!-- This is a partial file used to generate a post "card"
which templates loop over to generate a list of posts. --}}
<article class="post-card {{post_class}} {{#match @custom.feed_layout "!=" "Simple grid"}}{{#is "home"}}{{#match @custom.feed_layout "Dynamic grid"}}{{#has index="0"}}{{#if feature_image}}post-card-large{{/if}}{{/has}}{{else}}post-card-large{{/match}}{{/is}}{{/match}}">
{{#if feature_image}}
<a class="post-card-image-link" href="{{url}}">
<div class="post-card-image" style="background-image: url({{feature_image}})"></div>
</a>
<a class="post-card-image-link" href="{{url}}">
{{!-- This is a responsive image, it loads different sizes depending on device
https://medium.freecodecamp.org/a-guide-to-responsive-images-with-ready-to-use-templates-c400bd65c433 --}}
<img class="post-card-image"
srcset="{{img_url feature_image size="s"}} 300w,
{{img_url feature_image size="m"}} 600w,
{{img_url feature_image size="l"}} 1000w,
{{img_url feature_image size="xl"}} 2000w"
sizes="(max-width: 1000px) 400px, 800px"
src="{{img_url feature_image size="m"}}"
alt="{{#if feature_image_alt}}{{feature_image_alt}}{{else}}{{title}}{{/if}}"
loading="lazy"
/>
</a>
{{/if}}
<div class="post-card-content">
<a class="post-card-content-link" href="{{url}}">
<header class="post-card-header">
{{#if primary_tag}}
<span class="post-card-tags">{{primary_tag.name}}</span>
{{#primary_tag}}
<div class="post-card-primary-tag">{{name}}</div>
{{/primary_tag}}
{{/if}}
<h2 class="post-card-title">{{title}}</h2>
</header>
<section class="post-card-excerpt">
<p>{{excerpt words="33"}}</p>
</section>
<div class="post-card-excerpt">
<p>{{excerpt}}</p>
</div>
</a>
<footer class="post-card-meta">
<ul class="author-list">
{{#foreach authors}}
{{#foreach authors}}
<li class="author-list-item">
<div class="author-name-tooltip">
{{name}}
</div>
{{#if profile_image}}
<a href="{{url}}" class="static-avatar"><img class="author-profile-image" src="{{profile_image}}" alt="{{name}}" /></a>
<a href="{{url}}" class="static-avatar">
<img class="author-profile-image" src="{{img_url profile_image size="xs"}}" alt="{{name}}" loading="lazy" />
</a>
{{else}}
<a href="{{url}}" class="static-avatar author-profile-image">{{> "icons/avatar"}}</a>
<a href="{{url}}" class="static-avatar author-profile-image">{{> "icons/avatar"}}</a>
{{/if}}
</li>
{{/foreach}}
{{/foreach}}
</ul>
<span class="reading-time">{{reading_time}}</span>
<div class="post-card-byline-content">
<span>{{#has author="count:>2"}}Multiple authors{{else}}{{authors}}{{/has}}</span>
<span class="post-card-byline-date"><time datetime="{{date format="YYYY-MM-DD"}}">{{date}}</time> <span class="bull">&bull;</span> {{reading_time}}</span>
</div>
</footer>
</div>
</div>{{!--/.post-card-content--}}
</article>

View file

@ -1,198 +1,126 @@
{{!< default}}
{{!-- The tag above means: insert everything in this file
into the {body} of the default.hbs template --}}
into the {body} tag of the default.hbs template --}}
<header class="site-header outer">
<div class="inner">
{{> "site-nav"}}
</div>
</header>
{{!-- Everything inside the #post tags pulls data from the post --}}
{{#post}}
{{!-- Everything inside the #post block pulls data from the post --}}
<main id="site-main" class="site-main outer">
<div class="inner">
<main id="site-main" class="site-main">
<article class="article {{post_class}} {{#match @custom.post_image_width "Full"}}image-full{{else match @custom.post_image_width "=" "Small"}}image-small{{/match}}">
<article class="post-full {{post_class}} {{#unless feature_image}}no-image{{/unless}}">
<header class="article-header gh-canvas">
<header class="post-full-header">
<section class="post-full-meta">
<time class="post-full-meta-date" datetime="{{date format="YYYY-MM-DD"}}">{{date format="D MMMM YYYY"}}</time>
{{#primary_tag}}
<span class="date-divider">/</span> <a href="{{url}}">{{name}}</a>
{{/primary_tag}}
{{#if primary_tag}}
<section class="article-tag">
<a href="{{primary_tag.url}}">{{primary_tag.name}}</a>
</section>
{{/if}}
<h1 class="article-title">{{title}}</h1>
{{#if custom_excerpt}}
<p class="article-excerpt">{{custom_excerpt}}</p>
{{/if}}
<div class="article-byline">
<section class="article-byline-content">
<ul class="author-list">
{{#foreach authors}}
<li class="author-list-item">
{{#if profile_image}}
<a href="{{url}}" class="author-avatar">
<img class="author-profile-image" src="{{img_url profile_image size="xs"}}" alt="{{name}}" />
</a>
{{else}}
<a href="{{url}}" class="author-avatar author-profile-image">{{> "icons/avatar"}}</a>
{{/if}}
</li>
{{/foreach}}
</ul>
<div class="article-byline-meta">
<h4 class="author-name">{{authors}}</h4>
<div class="byline-meta-content">
<time class="byline-meta-date" datetime="{{date format="YYYY-MM-DD"}}">{{date}}</time>
<span class="byline-reading-time"><span class="bull">&bull;</span> {{reading_time}}</span>
</div>
</div>
</section>
<h1 class="post-full-title">{{title}}</h1>
</header>
</div>
{{#if feature_image}}
<figure class="post-full-image" style="background-image: url({{feature_image}})">
<figure class="article-image">
{{!-- This is a responsive image, it loads different sizes depending on device
https://medium.freecodecamp.org/a-guide-to-responsive-images-with-ready-to-use-templates-c400bd65c433 --}}
<img
srcset="{{img_url feature_image size="s"}} 300w,
{{img_url feature_image size="m"}} 600w,
{{img_url feature_image size="l"}} 1000w,
{{img_url feature_image size="xl"}} 2000w"
sizes="(min-width: 1400px) 1400px, 92vw"
src="{{img_url feature_image size="xl"}}"
alt="{{#if feature_image_alt}}{{feature_image_alt}}{{else}}{{title}}{{/if}}"
/>
{{#if feature_image_caption}}
<figcaption>{{feature_image_caption}}</figcaption>
{{/if}}
</figure>
{{/if}}
</header>
<section class="post-full-content">
<div class="post-content">
{{content}}
</div>
</section>
<section class="gh-content gh-canvas">
{{content}}
</section>
{{!-- Email subscribe form at the bottom of the page --}}
{{#if @labs.subscribers}}
<section class="subscribe-form">
<h3 class="subscribe-form-title">Subscribe to {{@blog.title}}</h3>
<p>Get the latest posts delivered right to your inbox</p>
{{subscribe_form placeholder="youremail@example.com"}}
</section>
{{/if}}
{{!--
<section class="article-comments gh-canvas">
If you want to embed comments, this is a good place to paste your code!
</section>
--}}
<footer class="post-full-footer">
{{!-- There are two options for how we display the byline/author-info.
If the post has more than one author, we load a specific template
from includes/byline-multiple.hbs, otherwise, we just use the
default byline. --}}
{{#has author="count:>1"}}
{{> "byline-multiple"}}
{{else}}
{{> "byline-single"}}
{{/has}}
</footer>
{{!--
<section class="post-full-comments">
If you want to embed comments, this is a good place to do it!
</section>
--}}
</article>
</div>
</article>
</main>
{{!-- Links to Previous/Next posts --}}
<aside class="read-next outer">
<div class="inner">
<div class="read-next-feed">
{{#if primary_tag}}
{{#get "posts" filter="tags:{{primary_tag.slug}}+id:-{{id}}" limit="3" as |related_posts|}}
{{#if related_posts}}
<article class="read-next-card"
{{#if ../primary_tag.feature_image}}
style="background-image: url({{../primary_tag.feature_image}})"
{{else}}
{{#if @blog.cover_image}}
style="background-image: url({{@blog.cover_image}})"{{/if}}
{{/if}}
>
<header class="read-next-card-header">
<small class="read-next-card-header-sitetitle">&mdash; {{@blog.title}} &mdash;</small>
{{#../primary_tag}}
<h3 class="read-next-card-header-title"><a href="{{url}}">{{name}}</a></h3>
{{/../primary_tag}}
</header>
<div class="read-next-divider">{{> "icons/infinity"}}</div>
<div class="read-next-card-content">
<ul>
{{#foreach related_posts}}
<li><a href="{{url}}">{{title}}</a></li>
{{/foreach}}
</ul>
</div>
<footer class="read-next-card-footer">
<a href="{{#../primary_tag}}{{url}}{{/../primary_tag}}">{{plural meta.pagination.total empty='No posts' singular='% post' plural='See all % posts'}} →</a>
</footer>
</article>
{{/if}}
{{/get}}
{{/if}}
{{!-- If there's a next post, display it using the same markup included from - partials/post-card.hbs --}}
{{#next_post}}
{{> "post-card"}}
{{/next_post}}
{{!-- If there's a previous post, display it using the same markup included from - partials/post-card.hbs --}}
{{#prev_post}}
{{> "post-card"}}
{{/prev_post}}
{{!-- A signup call to action is displayed here, unless viewed as a logged-in member --}}
{{#match @custom.email_signup_for_logged_out_visitors "!=" "None"}}
{{#unless @member}}{{#if access}}
<section class="footer-cta {{#match @custom.email_signup_for_logged_out_visitors "Bottom of post"}}cta-alt{{/match}}">
<div class="inner">
{{#if @custom.email_signup_text}}<h2>{{@custom.email_signup_text}}</h2>{{/if}}
<a class="footer-cta-button" href="#/portal" data-portal>
<div class="footer-cta-input">Enter your email</div>
<span>Subscribe</span>
</a>
{{!-- ^ This looks like a form element, but it's just a link to Portal,
making the form validation and submission much simpler. --}}
</div>
</section>
{{/if}}{{/unless}}
{{/match}}
{{!-- Read more links, just above the footer --}}
{{#if @custom.show_recent_posts}}
<aside class="read-more-wrap">
<div class="read-more inner">
{{!-- The {#get} helper below fetches some of the latest posts here
so that people have something else to read when they finish this one.
This query gets the latest 3 posts on the site, but adds a filter to
exclude the post we're currently on from being included. --}}
{{#get "posts" filter="id:-{{id}}" include="authors" limit="3" as |more_posts|}}
{{#if more_posts}}
{{#foreach more_posts}}
{{> "post-card"}}
{{/foreach}}
{{/if}}
{{/get}}
</div>
</aside>
{{/if}}
{{!-- Floating header which appears on-scroll, included from includes/floating-header.hbs --}}
{{> floating-header}}
{{/post}}
{{!-- The #contentFor helper here will send everything inside it up to the matching #block helper found in default.hbs --}}
{{#contentFor "scripts"}}
<script>
// NOTE: Scroll performance is poor in Safari
// - this appears to be due to the events firing much more slowly in Safari.
// Dropping the scroll event and using only a raf loop results in smoother
// scrolling but continuous processing even when not scrolling
$(document).ready(function () {
// Start fitVids
var $postContent = $(".post-full-content");
$postContent.fitVids();
// End fitVids
var progressBar = document.querySelector('#reading-progress');
var header = document.querySelector('.floating-header');
var title = document.querySelector('.post-full-title');
var lastScrollY = window.scrollY;
var lastWindowHeight = window.innerHeight;
var lastDocumentHeight = $(document).height();
var ticking = false;
function onScroll() {
lastScrollY = window.scrollY;
requestTick();
}
function onResize() {
lastWindowHeight = window.innerHeight;
lastDocumentHeight = $(document).height();
requestTick();
}
function requestTick() {
if (!ticking) {
requestAnimationFrame(update);
}
ticking = true;
}
function update() {
var trigger = title.getBoundingClientRect().top + window.scrollY;
var triggerOffset = title.offsetHeight + 35;
var progressMax = lastDocumentHeight - lastWindowHeight;
// show/hide floating header
if (lastScrollY >= trigger + triggerOffset) {
header.classList.add('floating-active');
} else {
header.classList.remove('floating-active');
}
progressBar.setAttribute('max', progressMax);
progressBar.setAttribute('value', lastScrollY);
ticking = false;
}
window.addEventListener('scroll', onScroll, {passive: true});
window.addEventListener('resize', onResize, false);
update();
});
</script>
{{/contentFor}}
{{/post}}

View file

@ -1,33 +1,57 @@
{{!< default}}
{{!-- The tag above means - insert everything in this file into the {body} of the default.hbs template --}}
{{!-- The big featured header, it uses blog cover image as a BG if available --}}
{{#tag}}
<header class="site-header outer {{#if feature_image}}" style="background-image: url({{feature_image}}){{else}}no-cover{{/if}}">
<div class="inner">
{{> "site-nav"}}
<div class="site-header-content">
<h1 class="site-title">{{name}}</h1>
<h2 class="site-description">
{{#if description}}
{{description}}
{{else}}
A collection of {{plural ../pagination.total empty='posts' singular='% post' plural='% posts'}}
{{/if}}
</h2>
</div>
</div>
</header>
{{/tag}}
{{!-- The main content area --}}
<main id="site-main" class="site-main outer">
<div class="inner">
<div class="inner posts">
<div class="post-feed">
{{#tag}}
<section class="post-card post-card-large">
{{#if feature_image}}
<div class="post-card-image-link">
{{!-- This is a responsive image, it loads different sizes depending on device
https://medium.freecodecamp.org/a-guide-to-responsive-images-with-ready-to-use-templates-c400bd65c433 --}}
<img class="post-card-image"
srcset="{{img_url feature_image size="s"}} 300w,
{{img_url feature_image size="m"}} 600w,
{{img_url feature_image size="l"}} 1000w,
{{img_url feature_image size="xl"}} 2000w"
sizes="(max-width: 1000px) 400px, 800px"
src="{{img_url feature_image size="m"}}"
alt="{{title}}"
loading="lazy"
/>
</div>
{{/if}}
<div class="post-card-content">
<div class="post-card-content-link">
<header class="post-card-header">
<div class="post-card-primary-tag">Tagged</div>
<h2 class="post-card-title">{{name}}</h2>
</header>
<div class="post-card-excerpt">
<p>
{{#if description}}
{{description}}
{{else}}
A collection of {{plural ../pagination.total empty='zero posts' singular='% post' plural='% posts'}}
{{/if}}
</p>
</div>
</div>
</div>{{!--/.post-card-content--}}
</section>
{{/tag}}
{{#foreach posts}}
{{!-- The tag below includes the markup for each post - partials/post-card.hbs --}}
{{> "post-card"}}
{{/foreach}}
</div>
</div>
</main>

View file

@ -4,7 +4,7 @@
"demo": "https://demo.ghost.io",
"version": "2.4.2",
"engines": {
"ghost-api": "canary"
"ghost-api": "v4"
},
"license": "MIT",
"screenshots": {