Compare commits
30 Commits
v1.0.0-RC.
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
37a7864727 | ||
| 9ab551f335 | |||
| 30e2b890cf | |||
| dfabac5c2f | |||
| 292b660138 | |||
| 88b08e3fc9 | |||
| 2c0be36e02 | |||
| eeb73fb393 | |||
| 1b137868fb | |||
| 05deb182e8 | |||
| c076620755 | |||
| 388790bbae | |||
| 7de12ffe92 | |||
|
|
7dcbf060c8 | ||
| c8e8bc5d80 | |||
| 05d72f6d03 | |||
| f008ea821f | |||
| 590340e4da | |||
| 8144c99ceb | |||
| 4e0082b3f3 | |||
| 2248cf1c8d | |||
| 1d819d6ea6 | |||
| a09b549219 | |||
| b321fb4698 | |||
| 01c639ceca | |||
| 2598314c2b | |||
| a81cb7cce8 | |||
| 734860a334 | |||
| 5da894af13 | |||
| f5198270f2 |
@@ -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
3
.prettierrc
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"trailingComma": "all"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -90,6 +104,7 @@
|
||||
|
||||
.content .twitter-tweet {
|
||||
width: auto !important;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.content .wp-caption {
|
||||
@@ -113,8 +128,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 +137,29 @@
|
||||
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 .wp-block-table {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.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;
|
||||
@@ -141,12 +178,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
.content th,
|
||||
.content tr,
|
||||
.content td {
|
||||
border-width: 0 0 1px;
|
||||
border-style: solid;
|
||||
border-color: var(--color-dark-blue);
|
||||
border: 1px solid var(--color-dark-blue);
|
||||
padding: var(--space-xs);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
17
css/nav.css
17
css/nav.css
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
10
css/page.css
10
css/page.css
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import windy from './windy';
|
||||
import windy from "./windy";
|
||||
|
||||
document.addEventListener('DOMContentLoaded', windy);
|
||||
document.addEventListener("DOMContentLoaded", windy);
|
||||
|
||||
29
js/menu.js
29
js/menu.js
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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("");
|
||||
});
|
||||
});
|
||||
|
||||
21
js/windy.js
21
js/windy.js
@@ -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"),
|
||||
});
|
||||
}
|
||||
|
||||
17105
package-lock.json
generated
17105
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
82
package.json
82
package.json
@@ -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",
|
||||
|
||||
@@ -31,3 +31,5 @@ window.twttr = (function(d, s, id) {
|
||||
};
|
||||
return t;
|
||||
}(document, "script", "twitter-wjs"));</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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 ' « '; } ?> <?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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user