Compare commits

...

28 Commits

Author SHA1 Message Date
30e2b890cf 1.0.1 2021-07-30 21:12:37 +01:00
dfabac5c2f Add styles for figcaption 2021-07-30 21:12:14 +01:00
292b660138 use ems for media queries 2021-07-30 21:03:03 +01:00
88b08e3fc9 1.0.0 2021-07-30 20:53:03 +01:00
2c0be36e02 audit dependencies 2021-07-30 20:52:50 +01:00
eeb73fb393 1.0.0-RC.8 2020-06-24 19:28:19 +01:00
1b137868fb remove jquery 2020-06-24 19:27:58 +01:00
05deb182e8 add description metadata 2020-06-24 19:25:22 +01:00
c076620755 update dependencies 2020-06-24 18:04:47 +01:00
388790bbae 1.0.0-RC.7 2020-06-24 17:37:16 +01:00
7de12ffe92 Merge pull request #1 from danhead/dependabot/npm_and_yarn/acorn-5.7.4
Bump acorn from 5.7.3 to 5.7.4
2020-06-24 17:29:02 +01:00
dependabot[bot]
7dcbf060c8 Bump acorn from 5.7.3 to 5.7.4
Bumps [acorn](https://github.com/acornjs/acorn) from 5.7.3 to 5.7.4.
- [Release notes](https://github.com/acornjs/acorn/releases)
- [Commits](https://github.com/acornjs/acorn/compare/5.7.3...5.7.4)

Signed-off-by: dependabot[bot] <support@github.com>
2020-03-13 21:42:15 +00:00
c8e8bc5d80 1.0.0-RC.6 2019-10-04 22:34:43 +01:00
05d72f6d03 Bump browsersync 2019-10-04 22:33:47 +01:00
f008ea821f 1.0.0-RC.5 2019-10-04 22:11:14 +01:00
590340e4da Add donate shortcode to banner 2019-10-04 22:08:32 +01:00
8144c99ceb 1.0.0-RC.4 2019-03-09 21:16:22 +00:00
4e0082b3f3 Integrate Prettier in to Stylelint and ESLint 2019-03-09 21:10:41 +00:00
2248cf1c8d Reduce citation font-size 2019-03-09 19:38:06 +00:00
1d819d6ea6 Change comments sub-list colour 2019-03-09 18:52:44 +00:00
a09b549219 Refactor list styles
Add blockquote styles
2019-03-09 18:50:40 +00:00
b321fb4698 Fix linting errors in build scripts 2019-03-09 18:35:00 +00:00
01c639ceca Add missing body and html tags 2019-03-09 17:12:12 +00:00
2598314c2b Update dependencies
Replace pre-commit hook with Husky and lint-staged
2019-03-03 23:26:24 +00:00
a81cb7cce8 1.0.0-RC.3 2019-03-03 22:31:35 +00:00
734860a334 Indent lists in content 2019-03-03 22:29:32 +00:00
5da894af13 1.0.0-RC.2 2019-02-02 23:32:51 +00:00
f5198270f2 Replace open graph tags with Jetpack implementation 2019-02-02 23:30:14 +00:00
29 changed files with 9152 additions and 8479 deletions

View File

@@ -1,7 +1,16 @@
{
"extends": "airbnb-base",
"plugins": [
"prettier"
],
"extends": [
"airbnb-base",
"prettier"
],
"env": {
"browser": true,
"jest": true
},
"rules": {
"prettier/prettier": "error"
}
}

3
.prettierrc Normal file
View File

@@ -0,0 +1,3 @@
{
"trailingComma": "all"
}

View File

@@ -1,6 +1,13 @@
{
"extends": [ "stylelint-config-standard" ],
"plugins": [
"stylelint-prettier"
],
"extends": [
"stylelint-config-standard",
"stylelint-prettier/recommended"
],
"rules": {
"prettier/prettier": true,
"selector-pseudo-element-colon-notation": "single"
}
}

View File

@@ -1,11 +1,11 @@
const { writeFile } = require('fs');
const { resolve } = require('path');
const imagemin = require('imagemin');
const imageminPngquant = require('imagemin-pngquant');
const imageminGifsicle = require('imagemin-gifsicle');
const imageminJpegtran = require('imagemin-jpegtran');
const { writeFile } = require("fs");
const { resolve } = require("path");
const imagemin = require("imagemin");
const imageminPngquant = require("imagemin-pngquant");
const imageminGifsicle = require("imagemin-gifsicle");
const imageminJpegtran = require("imagemin-jpegtran");
imagemin(['images/*.{png,jpg,gif}'], 'theme/images', {
imagemin(["images/*.{png,jpg,gif}"], "theme/images", {
plugins: [
imageminPngquant({
quality: [0.65, 0.8],
@@ -15,18 +15,20 @@ imagemin(['images/*.{png,jpg,gif}'], 'theme/images', {
}),
imageminJpegtran(),
],
}).then((files) => {
files.forEach((file) => {
const path = resolve(__dirname, '../', file.path);
writeFile(path, file.data, (error) => {
})
.then(files => {
files.forEach(file => {
const path = resolve(__dirname, "../", file.path);
writeFile(path, file.data, error => {
if (error) {
console.error(error);
process.stdout.write(`${error}\n`);
process.exit(1);
}
console.log(`File written: ${path}`);
process.stdout.write(`File written: ${path}\n`);
});
});
}).catch((error) => {
console.error(error);
})
.catch(error => {
process.stderr.write(`${error}\n`);
process.exit(1);
});
});

View File

@@ -1,10 +1,10 @@
const { resolve } = require('path');
const { execSync } = require('child_process');
const { exec, upAll, stop } = require('docker-compose');
const browsersync = require('browser-sync');
const mysql = require('mysql');
const { resolve } = require("path");
const { execSync } = require("child_process");
const { upAll, stop } = require("docker-compose");
const browsersync = require("browser-sync");
const mysql = require("mysql");
const name = 'windycoys';
const name = "windycoys";
const dockerOptions = {
cwd: resolve(__dirname),
@@ -12,24 +12,26 @@ const dockerOptions = {
};
const browserSyncOptions = {
files: [
'theme/**/*',
],
files: ["theme/**/*"],
watchtask: true,
open: false,
};
const mysqlConfig = {
user: 'root',
password: 'password',
database: 'wordpress',
user: "root",
password: "password",
database: "wordpress",
};
process.env.COMPOSE_PROJECT_NAME = name;
// Get IP address
const getIpAddr = function getIpAddr(id) {
return execSync(`docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' ${id}`).toString('utf8').trim();
return execSync(
`docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' ${id}`,
)
.toString("utf8")
.trim();
};
// Update URIs in MYSQL database
@@ -40,9 +42,9 @@ const updateUriData = function updateUriData(wordpress, db) {
const query = `UPDATE wp_options SET option_value='http://${wordpress}' WHERE option_name in ('siteurl', 'home')`;
con.query(query, (error) => {
con.query(query, error => {
if (error) {
console.error('Error', error.sqlMessage);
process.stderr.write(`Error: ${error.sqlMessage}\n`);
process.exit(1);
}
});
@@ -54,26 +56,28 @@ const updateUriData = function updateUriData(wordpress, db) {
const exitHandler = function exitHandler() {
stop(dockerOptions).then(
() => {
console.log('\n');
process.stdout.write("\n");
process.exit();
},
(err) => {
console.error('Error:', err.message);
err => {
process.stderr.write(`Error: ${err.message}\n`);
process.exit(1);
},
);
};
process.on('beforeExit', exitHandler.bind());
process.on('SIGINT', exitHandler.bind());
process.on('SIGUSR1', exitHandler.bind());
process.on('SIGUSR2', exitHandler.bind());
process.on('uncaughtException', exitHandler.bind());
process.on("beforeExit", exitHandler.bind());
process.on("SIGINT", exitHandler.bind());
process.on("SIGUSR1", exitHandler.bind());
process.on("SIGUSR2", exitHandler.bind());
process.on("uncaughtException", exitHandler.bind());
// Start Docker containers
upAll(dockerOptions).then(
() => {
console.log('Docker containers launched, starting browser-sync...');
process.stdout.write(
"Docker containers launched, starting browser-sync...",
);
const wordpress = getIpAddr(`${name}_wordpress_1`);
const db = getIpAddr(`${name}_db_1`);
if (wordpress && db) {
@@ -82,12 +86,14 @@ upAll(dockerOptions).then(
browserSyncOptions.proxy = wordpress;
bs.init(browserSyncOptions);
} else {
console.error('Error', 'No IP address for Docker containers. Aborting.');
process.stderr.write(
"Error: No IP address for Docker containers. Aborting.\n",
);
process.exit(1);
}
},
(err) => {
console.error('Error', err.message);
err => {
process.stderr.write(`Error: ${err.message}\n`);
process.exit(1);
},
);

View File

@@ -12,7 +12,7 @@
}
.comments__list--sub-list .comments__item {
border-left: 2px solid var(--color-light-grey);
border-left: 2px solid var(--color-light-blue);
padding-left: var(--space-sm);
}

View File

@@ -39,35 +39,49 @@
.content ul,
.content ol {
counter-reset: li;
margin-bottom: var(--space-sm);
}
.content ul li,
.content ol li {
counter-increment: li;
position: relative;
padding: 0 0 var(--space-xs) var(--space-sm);
margin-bottom: var(--space-xs);
padding-left: var(--space-md);
}
.content ul li:before,
.content ol li:before {
position: absolute;
left: 0;
}
.content ul li:before {
content: '';
position: absolute;
left: calc(var(--space-xxs) - (var(--bullet-size) / 2));
content: "";
top: calc(var(--base-line-height) / 2 * 1rem);
background-color: var(--color-dark-blue);
background-color: var(--color-light-blue);
width: var(--bullet-size);
height: var(--bullet-size);
border-radius: 50%;
transform: translateY(-50%);
}
.content ol li:before {
content: counter(li) ".";
top: 0;
color: var(--color-light-blue);
font-weight: 700;
font-size: 0.9rem;
}
.content a {
color: var(--color-light-blue);
transition: box-shadow var(--transition-md) ease-out;
}
.content a img {
transition:
box-shadow var(--transition-md) ease-out,
transition: box-shadow var(--transition-md) ease-out,
filter var(--transition-md) ease-out;
}
@@ -113,8 +127,7 @@
@media (--viewport-md) {
.content .twitter-tweet,
.content .wp-caption {
margin: 0 0 var(--space-md);
padding: var(--space-md);
padding: var(--space-sm) var(--space-md);
}
}
@@ -123,6 +136,23 @@
margin: var(--space-sm) 0 0;
}
.content .wp-block-quote {
border-left: var(--space-xxs) solid var(--color-light-blue);
margin-bottom: var(--space-sm);
padding: var(--space-sm) var(--space-md);
}
.content .wp-block-quote cite {
font-size: 0.8rem;
}
.content figcaption {
font-size: var(--size-body-extra-small);
font-style: italic;
text-align: center;
color: var(--color-dark-blue);
}
.content table {
border-collapse: collapse;
table-layout: fixed;

View File

@@ -64,15 +64,15 @@
--transition-fs: 0.15s;
--transition-md: 0.3s;
--transition-sl: 0.6s;
--heading-font-family: 'Raleway', sans-serif;
--body-font-family: 'Open Sans', sans-serif;
--heading-font-family: "Raleway", sans-serif;
--body-font-family: "Open Sans", sans-serif;
--link-focus: 0 0.1rem 0 0 var(--color-light-blue);
--image-focus: 0 0 0 0.2rem #fff, 0 0 0 0.4rem var(--color-light-blue);
--bullet-size: 0.334rem;
--bullet-size: 0.45rem;
}
@custom-media --viewport-sm (min-width: 30rem);
@custom-media --viewport-md (min-width: 40rem);
@custom-media --viewport-lg (min-width: 60rem);
@custom-media --viewport-xl (min-width: 75rem);
@custom-media --viewport-sm (min-width: 30em);
@custom-media --viewport-md (min-width: 40em);
@custom-media --viewport-lg (min-width: 60em);
@custom-media --viewport-xl (min-width: 75em);
@custom-media --viewport-wordpress-admin-bar (min-width: 783px);

View File

@@ -9,8 +9,7 @@
position: fixed;
top: var(--space-xxs);
opacity: 0;
transition:
filter var(--transition-md) ease-out,
transition: filter var(--transition-md) ease-out,
opacity var(--transition-md) ease-out,
transform var(--transition-md) ease-out;
width: var(--menu-size);

View File

@@ -79,8 +79,7 @@
}
.metadata__tag-link {
transition:
box-shadow var(--transition-md) ease-out,
transition: box-shadow var(--transition-md) ease-out,
color var(--transition-md) ease-out;
}

View File

@@ -5,13 +5,12 @@
.nav:after {
background-color: rgba(0, 0, 0, 0.5);
bottom: 0;
content: '';
content: "";
left: 0;
opacity: 0;
position: fixed;
right: 0;
transition:
opacity var(--nav-transition-duration) ease-out,
transition: opacity var(--nav-transition-duration) ease-out,
visibility var(--nav-transition-duration) ease-out;
top: 0;
visibility: hidden;
@@ -90,8 +89,7 @@
position: absolute;
right: var(--space-sm);
top: var(--space-sm);
transition:
box-shadow var(--nav-transition-duration) ease-out,
transition: box-shadow var(--nav-transition-duration) ease-out,
filter var(--nav-transition-duration) ease-out;
width: var(--icon-size-sm);
}
@@ -111,7 +109,8 @@
height: 8%;
position: absolute;
left: 0;
transition: transform var(--nav-transition-duration) ease-out var(--nav-transition-duration);
transition: transform var(--nav-transition-duration) ease-out
var(--nav-transition-duration);
transform: translateY(-50%);
top: 50%;
width: 100%;
@@ -143,8 +142,7 @@
color: #fff;
display: inline-block;
margin-bottom: var(--space-xs);
transition:
box-shadow var(--nav-transition-duration) ease-out,
transition: box-shadow var(--nav-transition-duration) ease-out,
text-shadow var(--nav-transition-duration) ease-out;
}
@@ -171,8 +169,7 @@
.nav__social-link:visited {
display: inline-block;
color: #fff;
transition:
box-shadow var(--nav-transition-duration) ease-out,
transition: box-shadow var(--nav-transition-duration) ease-out,
filter var(--nav-transition-duration) ease-out;
}

View File

@@ -72,7 +72,9 @@
@media (--viewport-lg) {
.page {
grid-template-columns: var(--logo-size-lg) 1fr;
grid-template-rows: var(--icon-size-md) calc(var(--logo-size-lg) - var(--space-md) - 32px) 1fr;
grid-template-rows:
var(--icon-size-md) calc(var(--logo-size-lg) - var(--space-md) - 32px)
1fr;
}
.page__logo {
@@ -93,7 +95,11 @@
@media (--viewport-xl) {
.page {
grid-template-columns: var(--logo-size-xl) 1fr;
grid-template-rows: var(--icon-size-md) calc(var(--logo-size-xl) - var(--space-md) - var(--icon-size-md)) 1fr;
grid-template-rows:
var(--icon-size-md) calc(
var(--logo-size-xl) - var(--space-md) - var(--icon-size-md)
)
1fr;
margin: var(--space-md) auto 0;
}

View File

@@ -1,11 +1,19 @@
.social {
text-align: right;
display: flex;
justify-content: flex-end;
align-items: center;
}
.social__item {
margin-left: var(--space-xs);
}
.social__link {
display: inline-block;
transition:
filter var(--transition-fs) ease-out,
display: block;
background-color: transparent;
border: 0;
padding: 0;
transition: filter var(--transition-fs) ease-out,
transform var(--transition-fs) ease-out;
transform-origin: center bottom;
}
@@ -15,3 +23,17 @@
filter: saturate(1.5) contrast(1.5);
transform: scale(1.1);
}
/* Donate button */
.social__item input[type="image"] {
transition: filter var(--transition-fs) ease-out,
transform var(--transition-fs) ease-out;
transform-origin: center bottom;
}
.social__item input[type="image"]:focus,
.social__item input[type="image"]:hover {
filter: saturate(1.5) contrast(1.5);
transform: scale(1.1);
}

View File

@@ -9,7 +9,7 @@ export default class Image {
}
start() {
if (this.dataset.loadTransition === 'false') {
if (this.dataset.loadTransition === "false") {
return;
}
this.image.style.opacity = 0;
@@ -21,6 +21,6 @@ export default class Image {
}
attachLoadedEvent() {
this.image.addEventListener('load', () => this.fadeIn());
this.image.addEventListener("load", () => this.fadeIn());
}
}

View File

@@ -1,46 +1,46 @@
import Image from './image';
import Image from "./image";
const imageHtml = `
<img class="image" src="foo.jpg"></img>
<img class="disabled-image" data-load-transition="false" src="foo.jpg"></img>
`;
describe('Image', () => {
describe("Image", () => {
let image;
let disabledImage;
beforeEach(() => {
document.body.innerHTML = imageHtml;
image = document.querySelector('.image');
disabledImage = document.querySelector('.disabled-image');
image = document.querySelector(".image");
disabledImage = document.querySelector(".disabled-image");
});
it('Should initialize', () => {
it("Should initialize", () => {
const img = new Image(image);
expect(img).toBeInstanceOf(Image);
});
it('Should store a reference to the image element', () => {
it("Should store a reference to the image element", () => {
const img = new Image(image);
expect(img.image).toBe(image);
});
test('fadeIn should set the element opacity to 1', () => {
test("fadeIn should set the element opacity to 1", () => {
const img = new Image(image);
img.fadeIn();
expect(img.image.style.opacity).toBe('1');
expect(img.image.style.opacity).toBe("1");
});
test('start should set the element opacity to 0', () => {
test("start should set the element opacity to 0", () => {
const img = new Image(image);
img.start();
expect(img.image.style.opacity).toBe('0');
expect(img.image.style.opacity).toBe("0");
});
test('start should call fadeIn when image has already loaded', () => {
test("start should call fadeIn when image has already loaded", () => {
const img = new Image(image);
img.fadeIn = jest.fn();
Object.defineProperty(img.image, 'complete', {
Object.defineProperty(img.image, "complete", {
value: true,
writable: false,
});
@@ -48,10 +48,10 @@ describe('Image', () => {
expect(img.fadeIn).toHaveBeenCalledTimes(1);
});
test('start should call attachLoadedEvent if image has not yet loaded', () => {
test("start should call attachLoadedEvent if image has not yet loaded", () => {
const img = new Image(image);
img.attachLoadedEvent = jest.fn();
Object.defineProperty(img.image, 'complete', {
Object.defineProperty(img.image, "complete", {
value: false,
writable: false,
});
@@ -59,7 +59,7 @@ describe('Image', () => {
expect(img.attachLoadedEvent).toHaveBeenCalledTimes(1);
});
test('start should do nothing if data-load-transition=false', () => {
test("start should do nothing if data-load-transition=false", () => {
const img = new Image(disabledImage);
img.attachLoadedEvent = jest.fn();
img.start();

View File

@@ -1,3 +1,3 @@
import windy from './windy';
import windy from "./windy";
document.addEventListener('DOMContentLoaded', windy);
document.addEventListener("DOMContentLoaded", windy);

View File

@@ -1,20 +1,20 @@
const STATES = {
visible: 'is-visible',
minimized: 'is-minimized',
visible: "is-visible",
minimized: "is-minimized",
};
export default class Menu {
constructor(cfg = {}) {
if (!cfg.container) return false;
this.container = cfg.container;
this.state = 'visible';
this.state = "visible";
this.lastKnownScrollPosition = 0;
}
init() {
if (!this.state) return false;
this.container.setAttribute('aria-haspopup', true);
this.container.setAttribute('aria-expanded', false);
this.container.setAttribute("aria-haspopup", true);
this.container.setAttribute("aria-expanded", false);
this.attachEvents();
setTimeout(() => {
this.showMenu();
@@ -23,38 +23,37 @@ export default class Menu {
}
attachEvents() {
window.addEventListener('scroll', () => {
window.addEventListener("scroll", () => {
this.updateState(this.lastKnownScrollPosition, window.scrollY);
this.lastKnownScrollPosition = window.scrollY;
});
}
updateState(lastY = 0, currentY) {
if (this.state !== 'hidden') {
if (this.state !== 'visible' && currentY < lastY) {
if (this.state !== "hidden") {
if (this.state !== "visible" && currentY < lastY) {
this.showMenu();
} else if (this.state !== 'minimized' && currentY
> lastY) {
} else if (this.state !== "minimized" && currentY > lastY) {
this.minimizeMenu();
}
}
}
showMenu() {
this.state = 'visible';
this.container.setAttribute('aria-expanded', false);
this.state = "visible";
this.container.setAttribute("aria-expanded", false);
this.container.classList.add(STATES.visible);
this.container.classList.remove(STATES.minimized);
}
minimizeMenu() {
this.state = 'minimized';
this.state = "minimized";
this.container.classList.add(STATES.minimized);
}
hideMenu() {
this.state = 'hidden';
this.container.setAttribute('aria-expanded', true);
this.state = "hidden";
this.container.setAttribute("aria-expanded", true);
this.container.classList.remove(STATES.visible);
}
}

View File

@@ -1,4 +1,4 @@
import Menu from './menu';
import Menu from "./menu";
jest.useFakeTimers();
@@ -10,105 +10,105 @@ const menuHtml = `
<span class="menu__text">Open menu</span>
</button>`;
describe('Menu button', () => {
describe("Menu button", () => {
let menu;
beforeEach(() => {
document.body.innerHTML = menuHtml;
menu = new Menu({
container: document.querySelector('.menu'),
container: document.querySelector(".menu"),
});
});
it('Should initialise', () => {
it("Should initialise", () => {
menu.init();
expect(menu).toMatchObject({});
});
it('Should fail to initialise if container is missing', () => {
it("Should fail to initialise if container is missing", () => {
menu.init();
expect((new Menu()).state).toBeUndefined();
expect(new Menu().state).toBeUndefined();
});
it('Should default to visible state', () => {
expect(menu.state).toBe('visible');
it("Should default to visible state", () => {
expect(menu.state).toBe("visible");
});
it('Should be hidden initially', () => {
it("Should be hidden initially", () => {
menu.init();
expect(menu.container.classList.contains('is-visible')).toBe(false);
expect(menu.container.classList.contains("is-visible")).toBe(false);
});
it('Should be visible after 1 second', () => {
it("Should be visible after 1 second", () => {
menu.init();
jest.runAllTimers();
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 1000);
expect(menu.container.classList.contains('is-visible')).toBe(true);
expect(menu.container.classList.contains("is-visible")).toBe(true);
});
test('Menu can be shown', () => {
test("Menu can be shown", () => {
menu.init();
menu.showMenu();
expect(menu.state).toBe('visible');
expect(menu.container.classList.contains('is-visible')).toBe(true);
expect(menu.container.classList.contains('is-minimized')).toBe(false);
expect(menu.state).toBe("visible");
expect(menu.container.classList.contains("is-visible")).toBe(true);
expect(menu.container.classList.contains("is-minimized")).toBe(false);
});
test('Menu can be hidden', () => {
test("Menu can be hidden", () => {
menu.init();
menu.hideMenu();
expect(menu.state).toBe('hidden');
expect(menu.container.classList.contains('is-visible')).toBe(false);
expect(menu.container.classList.contains('is-minimized')).toBe(false);
expect(menu.state).toBe("hidden");
expect(menu.container.classList.contains("is-visible")).toBe(false);
expect(menu.container.classList.contains("is-minimized")).toBe(false);
});
test('Menu can be minimized', () => {
test("Menu can be minimized", () => {
menu.init();
menu.minimizeMenu();
expect(menu.state).toBe('minimized');
expect(menu.container.classList.contains('is-visible')).toBe(false);
expect(menu.container.classList.contains('is-minimized')).toBe(true);
expect(menu.state).toBe("minimized");
expect(menu.container.classList.contains("is-visible")).toBe(false);
expect(menu.container.classList.contains("is-minimized")).toBe(true);
});
test('The state should not change on scroll if it is hidden', () => {
test("The state should not change on scroll if it is hidden", () => {
menu.init();
menu.hideMenu();
menu.updateState(10, 0);
expect(menu.state).toBe('hidden');
expect(menu.state).toBe("hidden");
menu.updateState(0, 10);
expect(menu.state).toBe('hidden');
expect(menu.state).toBe("hidden");
});
test('The state should change to minimized when scrolling down', () => {
test("The state should change to minimized when scrolling down", () => {
menu.init();
menu.showMenu();
menu.updateState(0, 10);
expect(menu.state).toBe('minimized');
expect(menu.state).toBe("minimized");
});
test('The state should change to visible when scrolling up', () => {
test("The state should change to visible when scrolling up", () => {
menu.init();
menu.minimizeMenu();
menu.updateState(10, 0);
expect(menu.state).toBe('visible');
expect(menu.state).toBe("visible");
});
it('Should have initial ARIA attributes', () => {
it("Should have initial ARIA attributes", () => {
menu.init();
expect(menu.container.getAttribute('aria-haspopup')).toBe('true');
expect(menu.container.getAttribute('aria-expanded')).toBe('false');
expect(menu.container.getAttribute("aria-haspopup")).toBe("true");
expect(menu.container.getAttribute("aria-expanded")).toBe("false");
});
it('Should have updated ARIA attribute when menu is opened', () => {
it("Should have updated ARIA attribute when menu is opened", () => {
menu.init();
menu.showMenu();
expect(menu.container.getAttribute('aria-expanded')).toBe('false');
expect(menu.container.getAttribute("aria-expanded")).toBe("false");
});
it('Should have updated ARIA attribute when menu is closed', () => {
it("Should have updated ARIA attribute when menu is closed", () => {
menu.init();
menu.showMenu();
menu.hideMenu();
expect(menu.container.getAttribute('aria-expanded')).toBe('true');
expect(menu.container.getAttribute("aria-expanded")).toBe("true");
});
});

View File

@@ -1,53 +1,55 @@
const STATES = {
open: 'is-open',
open: "is-open",
};
export default class Navigation {
constructor(cfg = {}) {
if (!cfg.container) return false;
if (!cfg.menu) return false;
this.state = 'hidden';
this.state = "hidden";
this.container = cfg.container;
this.menu = cfg.menu;
}
init() {
if (!this.state) return false;
this.container.setAttribute('aria-hidden', true);
this.container.setAttribute("aria-hidden", true);
this.attachClickEvents();
this.attachKeyEvents();
return true;
}
attachClickEvents() {
this.container.addEventListener('click', (e) => {
this.container.addEventListener("click", e => {
if (this.container === e.target) {
this.closeNav();
}
});
this.menu.container.addEventListener('click', () => {
this.menu.container.addEventListener("click", () => {
this.openNav();
});
this.container.querySelector('.nav__close').addEventListener('click', () => {
this.container
.querySelector(".nav__close")
.addEventListener("click", () => {
this.closeNav();
});
}
attachKeyEvents() {
const first = this.container.querySelector('.nav__link');
const last = this.container.querySelector('.nav__close');
this.container.addEventListener('keydown', (e) => {
const first = this.container.querySelector(".nav__link");
const last = this.container.querySelector(".nav__close");
this.container.addEventListener("keydown", e => {
if (e.keyCode === 27) {
this.closeNav();
}
});
first.addEventListener('keydown', (e) => {
first.addEventListener("keydown", e => {
if (e.shiftKey && e.keyCode === 9) {
e.preventDefault();
last.focus();
}
});
last.addEventListener('keydown', (e) => {
last.addEventListener("keydown", e => {
if (!e.shiftKey && e.keyCode === 9) {
e.preventDefault();
first.focus();
@@ -57,18 +59,18 @@ export default class Navigation {
openNav() {
this.lastActiveElement = document.activeElement;
this.state = 'visible';
this.state = "visible";
this.menu.hideMenu();
this.container.classList.add(STATES.open);
this.container.setAttribute('aria-hidden', false);
this.container.querySelector('.nav__title-link').focus();
this.container.setAttribute("aria-hidden", false);
this.container.querySelector(".nav__title-link").focus();
}
closeNav() {
this.state = 'hidden';
this.state = "hidden";
this.menu.showMenu();
this.container.classList.remove(STATES.open);
this.container.setAttribute('aria-hidden', true);
this.container.setAttribute("aria-hidden", true);
if (this.lastActiveElement) {
this.lastActiveElement.focus();
}

View File

@@ -1,4 +1,4 @@
import Navigation from './navigation';
import Navigation from "./navigation";
const navHtml = `<a href="/test">Test link</a>
<nav class="nav">
@@ -26,140 +26,140 @@ beforeEach(() => {
document.body.innerHTML = navHtml;
// Mock Menu
const menu = {
container: document.querySelector('.menu'),
container: document.querySelector(".menu"),
showMenu: jest.fn(),
hideMenu: jest.fn(),
};
nav = new Navigation({
container: document.querySelector('.nav'),
container: document.querySelector(".nav"),
menu,
});
});
describe('Navigation', () => {
it('Should initialise', () => {
describe("Navigation", () => {
it("Should initialise", () => {
nav.init();
expect(nav).toMatchObject({});
});
it('Should fail to initialise if container is missing', () => {
it("Should fail to initialise if container is missing", () => {
nav.init();
expect((new Navigation()).state).toBeUndefined();
expect(new Navigation().state).toBeUndefined();
});
it('Should fail to initialise if the menu is not provided', () => {
it("Should fail to initialise if the menu is not provided", () => {
const noMenu = new Navigation({
container: document.querySelector('.nav'),
container: document.querySelector(".nav"),
});
noMenu.init();
expect(noMenu.state).toBeUndefined();
});
it('Should be hidden by default', () => {
it("Should be hidden by default", () => {
nav.init();
expect(nav.container.classList.contains('is-open')).toBe(false);
expect(nav.container.classList.contains("is-open")).toBe(false);
});
it('Should close when the .nav element is clicked', () => {
it("Should close when the .nav element is clicked", () => {
nav.init();
nav.openNav();
const event = new Event('click');
const event = new Event("click");
nav.container.dispatchEvent(event);
expect(nav.state).toBe('hidden');
expect(nav.state).toBe("hidden");
});
it('Should close when the user presses ESC', () => {
it("Should close when the user presses ESC", () => {
nav.init();
nav.openNav();
const event = new Event('keydown');
const event = new Event("keydown");
event.keyCode = 27;
nav.container.dispatchEvent(event);
expect(nav.state).toBe('hidden');
expect(nav.state).toBe("hidden");
});
it('Should not close when the .nav__content element is clicked', () => {
it("Should not close when the .nav__content element is clicked", () => {
nav.init();
nav.openNav();
const event = new Event('click');
nav.container.querySelector('.nav__content').dispatchEvent(event);
expect(nav.state).toBe('visible');
const event = new Event("click");
nav.container.querySelector(".nav__content").dispatchEvent(event);
expect(nav.state).toBe("visible");
});
it('Should have initial ARIA attributes', () => {
it("Should have initial ARIA attributes", () => {
nav.init();
expect(nav.container.getAttribute('aria-hidden')).toBe('true');
expect(nav.container.getAttribute("aria-hidden")).toBe("true");
});
it('Should update the ARIA attributes when visible', () => {
it("Should update the ARIA attributes when visible", () => {
nav.init();
nav.openNav();
expect(nav.container.getAttribute('aria-hidden')).toBe('false');
expect(nav.container.getAttribute("aria-hidden")).toBe("false");
});
it('Should update the ARIA attributes when hidden', () => {
it("Should update the ARIA attributes when hidden", () => {
nav.init();
nav.openNav();
nav.closeNav();
expect(nav.container.getAttribute('aria-hidden')).toBe('true');
expect(nav.container.getAttribute("aria-hidden")).toBe("true");
});
});
describe('Navigation menu button', () => {
it('Should open the Navigation when clicked', () => {
describe("Navigation menu button", () => {
it("Should open the Navigation when clicked", () => {
nav.init();
const event = new Event('click');
const event = new Event("click");
nav.menu.container.dispatchEvent(event);
expect(nav.state).toBe('visible');
expect(nav.state).toBe("visible");
});
});
describe('Navigation close button', () => {
it('Should close the Navigation when clicked', () => {
describe("Navigation close button", () => {
it("Should close the Navigation when clicked", () => {
nav.init();
nav.openNav();
const event = new Event('click');
nav.container.querySelector('.nav__close').dispatchEvent(event);
expect(nav.state).toBe('hidden');
const event = new Event("click");
nav.container.querySelector(".nav__close").dispatchEvent(event);
expect(nav.state).toBe("hidden");
});
});
describe('Navigation focus behaviour', () => {
describe("Navigation focus behaviour", () => {
let first;
let last;
let menu;
beforeEach(() => {
menu = nav.container.querySelector('.menu');
first = nav.container.querySelector('.nav__link');
last = nav.container.querySelector('.nav__close');
menu = nav.container.querySelector(".menu");
first = nav.container.querySelector(".nav__link");
last = nav.container.querySelector(".nav__close");
menu.focus();
nav.init();
nav.openNav();
});
it('Should apply focus to .nav__title-link when opened', () => {
const link = nav.container.querySelector('.nav__title-link');
it("Should apply focus to .nav__title-link when opened", () => {
const link = nav.container.querySelector(".nav__title-link");
expect(document.activeElement).toBe(link);
});
test('Focus moves to first element when TAB pressed on last', () => {
test("Focus moves to first element when TAB pressed on last", () => {
last.focus();
const event = new Event('keydown');
const event = new Event("keydown");
event.keyCode = 9;
last.dispatchEvent(event);
expect(document.activeElement).toBe(first);
});
test('Focus moves to last element when SHIFT + TAB pressed on first', () => {
test("Focus moves to last element when SHIFT + TAB pressed on first", () => {
first.focus();
const event = new Event('keydown');
const event = new Event("keydown");
event.shiftKey = true;
event.keyCode = 9;
first.dispatchEvent(event);
expect(document.activeElement).toBe(last);
});
test('Focus is restored to last active element when Navigation is closed', () => {
test("Focus is restored to last active element when Navigation is closed", () => {
first.focus();
nav.closeNav();
expect(document.activeElement).toBe(menu);

View File

@@ -1,33 +1,37 @@
export default class ReplyToComment {
constructor(cfg = {}) {
if (!cfg.form || cfg.form.className !== 'comments__foot') {
if (!cfg.form || cfg.form.className !== "comments__foot") {
return false;
}
if (!cfg.links || cfg.links.length === 0) {
return false;
}
this.form = cfg.form;
this.backupForm = cfg.form.querySelector('#respond').cloneNode(true);
this.backupForm = cfg.form.querySelector("#respond").cloneNode(true);
this.links = Array.prototype.slice.call(cfg.links);
this.links.forEach((link) => {
link.addEventListener('click', e => this.handleLinkClick(e, link));
this.links.forEach(link => {
link.addEventListener("click", e => this.handleLinkClick(e, link));
});
}
insertForm(container, id, author) {
const form = this.backupForm.cloneNode(true);
const cancel = form.querySelector('#cancel-comment-reply-link');
form.querySelector('.comment-respond__title-text').innerHTML = `Reply to ${author}`;
form.querySelector('#submit').value = 'Post reply';
form.querySelector('#comment_parent').value = id;
cancel.style.display = 'inline-block';
cancel.addEventListener('click', e => this.handleCancelClick(e, container));
const cancel = form.querySelector("#cancel-comment-reply-link");
form.querySelector(
".comment-respond__title-text",
).innerHTML = `Reply to ${author}`;
form.querySelector("#submit").value = "Post reply";
form.querySelector("#comment_parent").value = id;
cancel.style.display = "inline-block";
cancel.addEventListener("click", e => this.handleCancelClick(e, container));
container.appendChild(form);
}
handleCancelClick(e, container) {
e.preventDefault();
container.parentNode.querySelector('.comment__reply').classList.remove('is-hidden');
container.parentNode
.querySelector(".comment__reply")
.classList.remove("is-hidden");
while (container.firstChild) {
container.removeChild(container.firstChild);
}
@@ -36,11 +40,11 @@ export default class ReplyToComment {
handleLinkClick(e, link) {
e.preventDefault();
link.classList.add('is-hidden');
link.classList.add("is-hidden");
while (this.form.firstChild) {
this.form.removeChild(this.form.firstChild);
}
const container = link.parentNode.querySelector('.comment__inline-reply');
const container = link.parentNode.querySelector(".comment__inline-reply");
const { commentId, commentAuthor } = link.dataset;
this.insertForm(container, commentId, commentAuthor);
}

View File

@@ -1,4 +1,4 @@
import ReplyToComment from './reply-to-comment';
import ReplyToComment from "./reply-to-comment";
const commentHtml = `
<div class="comment">
@@ -66,73 +66,77 @@ let inlineReply;
beforeEach(() => {
document.body.innerHTML = `${commentHtml}${formHtml}`;
form = document.querySelector('.comments__foot');
links = document.querySelectorAll('.comment__reply');
inlineReply = document.querySelector('.comment__inline-reply');
form = document.querySelector(".comments__foot");
links = document.querySelectorAll(".comment__reply");
inlineReply = document.querySelector(".comment__inline-reply");
comments = new ReplyToComment({ form, links });
});
describe('ReplyToComment', () => {
it('Should initialise', () => {
describe("ReplyToComment", () => {
it("Should initialise", () => {
expect(comments.form).toBeInstanceOf(HTMLDivElement);
expect(comments.links).toBeInstanceOf(Array);
});
it('Should fail to initialise if form is missing', () => {
it("Should fail to initialise if form is missing", () => {
expect(new ReplyToComment()).toEqual({});
});
it('Should fail to initialise if there are no links', () => {
expect(new ReplyToComment({
form: document.querySelector('#respond'),
})).toEqual({});
expect(new ReplyToComment({
form: document.querySelector('#respond'),
links: document.querySelectorAll('.no-links-here'),
})).toEqual({});
it("Should fail to initialise if there are no links", () => {
expect(
new ReplyToComment({
form: document.querySelector("#respond"),
}),
).toEqual({});
expect(
new ReplyToComment({
form: document.querySelector("#respond"),
links: document.querySelectorAll(".no-links-here"),
}),
).toEqual({});
});
it('Should remove the form from the DOM when a reply link is clicked', () => {
const event = new Event('click');
it("Should remove the form from the DOM when a reply link is clicked", () => {
const event = new Event("click");
comments.handleLinkClick(event, links[0]);
expect(form.innerHTML).toEqual('');
expect(form.innerHTML).toEqual("");
});
it('Should hide the link when it is clicked', () => {
const event = new Event('click');
it("Should hide the link when it is clicked", () => {
const event = new Event("click");
comments.handleLinkClick(event, links[0]);
expect(links[0].classList.contains('is-hidden')).toBe(true);
expect(links[0].classList.contains("is-hidden")).toBe(true);
});
it('Should insert the form into the inline reply container', () => {
it("Should insert the form into the inline reply container", () => {
comments.insertForm(inlineReply);
const inlineForm = inlineReply.querySelectorAll('form');
const inlineForm = inlineReply.querySelectorAll("form");
expect(inlineForm.length).toEqual(1);
});
it('Should update the comment_parent hidden element', () => {
comments.insertForm(inlineReply, '123', 'Mr Smith');
const { value } = inlineReply.querySelector('#comment_parent');
expect(value).toEqual('123');
it("Should update the comment_parent hidden element", () => {
comments.insertForm(inlineReply, "123", "Mr Smith");
const { value } = inlineReply.querySelector("#comment_parent");
expect(value).toEqual("123");
});
it('Should add a cancel link to the inline form', () => {
it("Should add a cancel link to the inline form", () => {
comments.insertForm(inlineReply);
const el = inlineReply.querySelector('#cancel-comment-reply-link');
expect(el.style.display).toEqual('inline-block');
const el = inlineReply.querySelector("#cancel-comment-reply-link");
expect(el.style.display).toEqual("inline-block");
});
it('Should restore the reply link when the cancel link is clicked', () => {
const event = new Event('click');
it("Should restore the reply link when the cancel link is clicked", () => {
const event = new Event("click");
comments.insertForm(inlineReply);
comments.handleCancelClick(event, inlineReply);
expect(links[0].classList.contains('is-hidden')).toBe(false);
expect(links[0].classList.contains("is-hidden")).toBe(false);
});
it('Should remove the inline form when the cancel link is clicked', () => {
const event = new Event('click');
it("Should remove the inline form when the cancel link is clicked", () => {
const event = new Event("click");
comments.insertForm(inlineReply);
comments.handleCancelClick(event, inlineReply);
expect(inlineReply.innerHTML).toEqual('');
expect(inlineReply.innerHTML).toEqual("");
});
});

View File

@@ -1,30 +1,31 @@
import Menu from './menu';
import Navigation from './navigation';
import Image from './image';
import ReplyToComment from './reply-to-comment';
import Menu from "./menu";
import Navigation from "./navigation";
import Image from "./image";
import ReplyToComment from "./reply-to-comment";
const windy = {
images: [],
};
export default function init() {
Array.prototype.slice.call(document.querySelectorAll('img'))
.forEach((image) => {
Array.prototype.slice
.call(document.querySelectorAll("img"))
.forEach(image => {
const img = new Image(image);
img.start();
windy.images.push(img);
});
windy.menu = new Menu({
container: document.querySelector('.menu'),
container: document.querySelector(".menu"),
});
windy.navigation = new Navigation({
container: document.querySelector('.nav'),
container: document.querySelector(".nav"),
menu: windy.menu,
});
windy.menu.init();
windy.navigation.init();
windy.comments = new ReplyToComment({
form: document.querySelector('.comments__foot'),
links: document.querySelectorAll('.comment__reply'),
form: document.querySelector(".comments__foot"),
links: document.querySelectorAll(".comment__reply"),
});
}

16777
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "windycoys",
"version": "1.0.0-RC.1",
"version": "1.0.1",
"description": "Wordpress theme for windycoys.com",
"author": "Dan Head <me@danielhead.com>",
"license": "MIT",
@@ -12,13 +12,13 @@
"build": "run-s images css:compile js:compile",
"css": "run-p -c css:*",
"css:compile": "postcss -o theme/style.css css/index.css",
"css:lint": "stylelint --config .stylelintrc \"css/**/*.css\"",
"css:lint": "stylelint css/*.css",
"images": "node build/imagemin.js",
"jest": "jest",
"jest:watch": "jest --watch",
"js": "run-s js:*",
"js:compile": "rollup --config rollup.config.js",
"js:lint": "eslint js/",
"js:lint": "eslint build js",
"serve": "node build/serve.js",
"start": "run-p watch serve",
"test": "run-s css:lint js:lint jest",
@@ -27,43 +27,67 @@
"watch:images": "nodemon -q --watch images -e png,jpg,gif --exec npm run images",
"watch:js": "nodemon -q --watch js -e js --exec npm run js"
},
"devDependencies": {
"browser-sync": "^2.26.3",
"docker-compose": "^0.9.3",
"mysql": "^2.16.0",
"nodemon": "^1.18.7",
"pre-commit": "^1.2.2"
},
"dependencies": {
"@babel/cli": "^7.2.3",
"@babel/core": "^7.2.0",
"@babel/preset-env": "^7.2.3",
"@babel/cli": "^7.14.8",
"@babel/core": "^7.10.3",
"@babel/preset-env": "^7.10.3",
"babel-core": "^7.0.0-bridge.0",
"babel-jest": "^23.6.0",
"cssnano": "^4.1.8",
"eslint": "^5.12.0",
"eslint-config-airbnb-base": "^13.1.0",
"eslint-plugin-import": "^2.14.0",
"babel-jest": "^26.1.0",
"browser-sync": "^2.27.5",
"cssnano": "^4.1.10",
"docker-compose": "^0.12.0",
"eslint": "^5.16.0",
"eslint-config-airbnb-base": "^13.2.0",
"eslint-config-prettier": "^4.3.0",
"eslint-plugin-import": "^2.21.2",
"eslint-plugin-prettier": "^3.1.4",
"husky": "^1.3.1",
"imagemin": "^6.1.0",
"imagemin-gifsicle": "^6.0.1",
"imagemin-jpegtran": "^6.0.0",
"imagemin-pngquant": "^7.0.0",
"jest": "^23.6.0",
"jest": "^26.1.0",
"lint-staged": "^8.2.1",
"mysql": "^2.18.1",
"nodemon": "^2.0.4",
"npm-run-all": "^4.1.5",
"postcss-banner": "^3.0.0",
"postcss-cli": "^6.1.1",
"postcss-banner": "^3.0.2",
"postcss-cli": "^7.1.1",
"postcss-import": "^12.0.1",
"postcss-normalize": "^7.0.1",
"postcss-preset-env": "^6.4.0",
"rollup": "^1.1.0",
"rollup-plugin-babel": "^4.3.0",
"rollup-plugin-uglify": "^6.0.1",
"stylelint": "^9.9.0",
"stylelint-config-standard": "^18.0.0"
"postcss-preset-env": "^6.7.0",
"prettier": "^1.19.1",
"rollup": "^1.32.1",
"rollup-plugin-babel": "^4.4.0",
"rollup-plugin-uglify": "^6.0.4",
"stylelint": "^13.6.1",
"stylelint-config-prettier": "^5.3.0",
"stylelint-config-standard": "^18.3.0",
"stylelint-prettier": "^1.1.2"
},
"pre-commit": [
"test"
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"build/*.js": [
"eslint --fix",
"prettier --write",
"git add"
],
"css/*.css": [
"stylelint --fix",
"prettier --write",
"git add"
],
"js/*.js": [
"eslint --fix",
"prettier --write",
"jest",
"git add"
]
},
"browserslist": [
"last 1 version",
"not dead",

View File

@@ -31,3 +31,5 @@ window.twttr = (function(d, s, id) {
};
return t;
}(document, "script", "twitter-wjs"));</script>
</body>
</html>

View File

@@ -150,3 +150,47 @@ function windycoys_is_ie() {
}
return false;
}
function windycoys_first_sentence($string) {
$sentence = preg_split( '/(\.|!|\?)\s/', $string, 2, PREG_SPLIT_DELIM_CAPTURE );
return $sentence['0'] . $sentence['1'];
}
function windycoys_get_description($post_id) {
if (get_post_type() == "post") {
$post = get_post($post_id);
$content = (strlen($post->post_excerpt) === 0) ?
strip_tags(windycoys_first_sentence($post->post_content)) :
$post->post_excerpt;
return $content;
} else {
return get_bloginfo('description');
}
}
function windycoys_jetpack_custom_image($media, $post_id, $args) {
if ($media) {
return $media;
} else {
$url = get_template_directory_uri() . '/images/icon.png';
$permalink = get_permalink($post_id);
return array(
array(
'type' => 'image',
'from' => 'custom_fallback',
'src' => esc_url($url),
'href' => $permalink,
)
);
}
}
add_filter('jetpack_images_get_images', 'windycoys_jetpack_custom_image', 10, 3);
function windycoys_strip_scripts(&$scripts) {
if(!is_admin()){
$scripts->remove('jquery');
}
}
add_filter('wp_default_scripts', 'windycoys_strip_scripts');

View File

@@ -11,16 +11,9 @@ $manifest = get_template_directory_uri() . '/manifest.json';
<head>
<meta charset="<?php bloginfo('charset'); ?>">
<title><?php wp_title(''); ?><?php if(wp_title('', false)) { echo ' &laquo; '; } ?> <?php bloginfo('name'); ?></title>
<meta name="description" content="<?php echo windycoys_get_description(get_the_id()) ?>">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#201f42">
<meta name="description" content="<?php bloginfo('description'); ?>">
<meta property="og:title" content="<?php if(is_single()) { single_post_title(); } else { bloginfo("name"); } ?>"/>
<meta property="og:type" content="blog"/>
<meta property="og:url" content="https://<?php echo $_SERVER["HTTP_HOST"].$_SERVER["REQUEST_URI"]; ?>"/>
<meta property="og:image" content="<?php echo get_template_directory_uri() ?>/images/icon.png"/>
<meta property="og:site_name" content="WindyCOYS"/>
<meta property="fb:admins" content="Windy84"/>
<meta property="og:description" content="<?php bloginfo('description'); ?>"/>
<?php wp_head(); ?>
<link rel="me" href="https://twitter.com/WindyCOYS">
<link rel="icon" type="image/png" href="<?php echo get_template_directory_uri() ?>/images/icon_48.png">

View File

@@ -1,17 +1,26 @@
<div class="social">
<div class="social__item">
<a class="social__link" href="https://www.facebook.com/WindyCOYS/" aria-label="WindyCOYS on Facebook">
<svg class="icon icon--facebook" aria-hidden="true">
<use xlink:href="#icon-facebook"></use>
</svg>
</a>
</div>
<div class="social__item">
<a class="social__link" href="https://twitter.com/windycoys/" aria-label="WindyCOYS on Twitter">
<svg class="icon icon--twitter" aria-hidden="true">
<use xlink:href="#icon-twitter"></use>
</svg>
</a>
</div>
<div class="social__item">
<a class="social__link" href="/feed" aria-label="WindyCOYS RSS Feed">
<svg class="icon icon--rss" aria-hidden="true">
<use xlink:href="#icon-rss"></use>
</svg>
</a>
</div>
<div class="social__item">
<?php echo do_shortcode('[paypal-donation]'); ?>
</div>
</div>