Compare commits
30 Commits
v1.0.0-RC.
...
next
| Author | SHA1 | Date | |
|---|---|---|---|
| a293155787 | |||
| 7bd647a7a0 | |||
| 33f604f5cb | |||
| 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 |
33
.babelrc
@@ -1,33 +0,0 @@
|
|||||||
{
|
|
||||||
"presets": [
|
|
||||||
[
|
|
||||||
"@babel/preset-env", {
|
|
||||||
"targets": {
|
|
||||||
"browsers": [
|
|
||||||
"last 2 versions",
|
|
||||||
"ie >= 11"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"loose": true,
|
|
||||||
"modules": false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
],
|
|
||||||
"env": {
|
|
||||||
"test": {
|
|
||||||
"presets": [
|
|
||||||
[
|
|
||||||
"@babel/preset-env", {
|
|
||||||
"targets": {
|
|
||||||
"browsers": [
|
|
||||||
"last 2 versions",
|
|
||||||
"ie >= 11"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"loose": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,128 +1,99 @@
|
|||||||
defaults: &defaults
|
version: 2.1
|
||||||
working_directory: ~/tmp/windycoys
|
orbs:
|
||||||
docker:
|
node: circleci/node@5.1
|
||||||
- image: circleci/node:10.15
|
aws-s3: circleci/aws-s3@3.1
|
||||||
|
|
||||||
semver-tags: &semver-tags
|
|
||||||
filters:
|
|
||||||
tags:
|
|
||||||
only: /^v.*/
|
|
||||||
branches:
|
|
||||||
ignore: /.*/
|
|
||||||
develop: &develop
|
|
||||||
filters:
|
|
||||||
branches:
|
|
||||||
only: develop
|
|
||||||
other-branches: &other-branches
|
|
||||||
filters:
|
|
||||||
branches:
|
|
||||||
ignore:
|
|
||||||
- master
|
|
||||||
- develop
|
|
||||||
|
|
||||||
version: 2
|
|
||||||
jobs:
|
jobs:
|
||||||
install:
|
build_and_test:
|
||||||
<<: *defaults
|
executor: node/default
|
||||||
steps:
|
steps:
|
||||||
- attach_workspace:
|
|
||||||
at: ~/tmp/windycoys
|
|
||||||
- checkout
|
- checkout
|
||||||
- run:
|
- node/install-packages:
|
||||||
name: Install dependencies
|
pkg-manager: npm
|
||||||
command: npm install
|
|
||||||
- persist_to_workspace:
|
|
||||||
root: .
|
|
||||||
paths: .
|
|
||||||
test:
|
|
||||||
<<: *defaults
|
|
||||||
steps:
|
|
||||||
- attach_workspace:
|
|
||||||
at: ~/tmp/windycoys
|
|
||||||
- run:
|
- run:
|
||||||
name: Run tests
|
name: Run tests
|
||||||
command: npm test
|
command: npm run lint
|
||||||
build:
|
|
||||||
<<: *defaults
|
|
||||||
environment:
|
|
||||||
NODE_ENV: production
|
|
||||||
steps:
|
|
||||||
- attach_workspace:
|
|
||||||
at: ~/tmp/windycoys
|
|
||||||
- run:
|
- run:
|
||||||
name: Build CSS and JavaScript
|
name: Build app
|
||||||
command: npm run build
|
command: npm run build
|
||||||
- run:
|
|
||||||
name: Generate timestamp
|
|
||||||
command: bash build/generate_timestamp.sh
|
|
||||||
- persist_to_workspace:
|
- persist_to_workspace:
|
||||||
root: .
|
root: ~/project
|
||||||
paths: .
|
paths:
|
||||||
deploy-prod:
|
- .
|
||||||
<<: *defaults
|
deploy:
|
||||||
|
executor: node/default
|
||||||
steps:
|
steps:
|
||||||
- attach_workspace:
|
- attach_workspace:
|
||||||
at: ~/tmp/windycoys
|
at: ~/project
|
||||||
- run:
|
- run:
|
||||||
name: Install rsync
|
name: Known Hosts
|
||||||
command: sudo apt install -y rsync
|
command: |
|
||||||
|
mkdir ~/.ssh || :
|
||||||
|
echo -e $KNOWN_HOSTS > ~/.ssh/known_hosts
|
||||||
- run:
|
- run:
|
||||||
name: Deploy theme
|
name: Clean
|
||||||
command: bash build/deploy.sh prod
|
command: sh build/clean.sh
|
||||||
- run:
|
- run:
|
||||||
name: Purge Cloudflare cache
|
name: Deploy
|
||||||
command: bash build/purge_cache.sh
|
command: sh build/deploy.sh
|
||||||
deploy-develop:
|
backup_to_s3:
|
||||||
<<: *defaults
|
docker:
|
||||||
|
- image: 'cimg/python:3.10'
|
||||||
steps:
|
steps:
|
||||||
- attach_workspace:
|
- checkout
|
||||||
at: ~/tmp/windycoys
|
|
||||||
- run:
|
- run:
|
||||||
name: Install rsync
|
name: Known Hosts
|
||||||
command: sudo apt install -y rsync
|
command: |
|
||||||
|
mkdir ~/.ssh || :
|
||||||
|
echo -e $KNOWN_HOSTS > ~/.ssh/known_hosts
|
||||||
- run:
|
- run:
|
||||||
name: Deploy theme
|
name: Prepare filename
|
||||||
command: bash build/deploy.sh develop
|
command: |
|
||||||
|
filename=windycoys-$(date +'%Y-%m-%dT%H-%M-%S').tgz
|
||||||
|
echo "export filename='$filename'" >> $BASH_ENV
|
||||||
- run:
|
- run:
|
||||||
name: Purge Cloudflare cache
|
name: Prepare backups
|
||||||
command: bash build/purge_cache.sh
|
command: |
|
||||||
|
ssh $SSH_USER@$SSH_IP "bash -c '\
|
||||||
|
|
||||||
|
source windycoys/.env
|
||||||
|
rm -rf windycoys/backup
|
||||||
|
mkdir -p windycoys/backup/data
|
||||||
|
scp -r windycoys/wp-content windycoys/backup/data/
|
||||||
|
docker exec -t $DOCKER_DB_NAME /usr/bin/mysqldump \
|
||||||
|
-uroot --password=$DB_ROOT_PASSWORD \
|
||||||
|
$DB_NAME | gzip -9 > windycoys/backup/data/database.sql.gz
|
||||||
|
/usr/bin/tar zcf windycoys/backup/windycoys.tgz windycoys/backup/data'"
|
||||||
|
- run:
|
||||||
|
name: Copy backup
|
||||||
|
command: |
|
||||||
|
scp $SSH_USER@$SSH_IP:~/windycoys/backup/windycoys.tgz ./
|
||||||
|
mv windycoys.tgz $filename
|
||||||
|
- aws-s3/copy:
|
||||||
|
aws-region: AWS_REGION
|
||||||
|
aws-access-key-id: AWS_ACCESS_KEY_ID
|
||||||
|
aws-secret-access-key: AWS_SECRET_ACCESS_KEY
|
||||||
|
from: '$filename'
|
||||||
|
to: 's3://$AWS_S3_BUCKET_NAME'
|
||||||
workflows:
|
workflows:
|
||||||
version: 2
|
test_my_app:
|
||||||
test:
|
|
||||||
jobs:
|
jobs:
|
||||||
- install: *other-branches
|
- build_and_test:
|
||||||
- test:
|
filters:
|
||||||
<<: *other-branches
|
branches:
|
||||||
|
only: main
|
||||||
|
- deploy:
|
||||||
requires:
|
requires:
|
||||||
- install
|
- build_and_test
|
||||||
develop:
|
filters:
|
||||||
|
branches:
|
||||||
|
only: main
|
||||||
|
backup:
|
||||||
|
triggers:
|
||||||
|
- schedule:
|
||||||
|
cron: "0 2 * * 1"
|
||||||
|
filters:
|
||||||
|
branches:
|
||||||
|
only:
|
||||||
|
- next
|
||||||
jobs:
|
jobs:
|
||||||
- install: *develop
|
- backup_to_s3
|
||||||
- test:
|
|
||||||
<<: *develop
|
|
||||||
requires:
|
|
||||||
- install
|
|
||||||
- build:
|
|
||||||
<<: *develop
|
|
||||||
requires:
|
|
||||||
- test
|
|
||||||
- deploy-develop:
|
|
||||||
<<: *develop
|
|
||||||
requires:
|
|
||||||
- build
|
|
||||||
prod:
|
|
||||||
jobs:
|
|
||||||
- install: *semver-tags
|
|
||||||
- test:
|
|
||||||
<<: *semver-tags
|
|
||||||
requires:
|
|
||||||
- install
|
|
||||||
- build:
|
|
||||||
<<: *semver-tags
|
|
||||||
requires:
|
|
||||||
- test
|
|
||||||
- deploy-prod:
|
|
||||||
<<: *semver-tags
|
|
||||||
requires:
|
|
||||||
- build
|
|
||||||
|
|||||||
10
.eslintrc.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
parser: "@typescript-eslint/parser",
|
||||||
|
plugins: ["@typescript-eslint"],
|
||||||
|
extends: [
|
||||||
|
"eslint:recommended",
|
||||||
|
"plugin:@typescript-eslint/recommended",
|
||||||
|
"prettier",
|
||||||
|
],
|
||||||
|
};
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "airbnb-base",
|
|
||||||
"env": {
|
|
||||||
"browser": true,
|
|
||||||
"jest": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
46
.gitignore
vendored
@@ -1,43 +1,5 @@
|
|||||||
# Temporary
|
.tmp
|
||||||
.tmp/
|
.old
|
||||||
|
|
||||||
# Logs
|
|
||||||
logs
|
|
||||||
*.log
|
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
|
|
||||||
# Runtime data
|
|
||||||
pids
|
|
||||||
*.pid
|
|
||||||
*.seed
|
|
||||||
*.pid.lock
|
|
||||||
|
|
||||||
# node-waf configuration
|
|
||||||
.lock-wscript
|
|
||||||
|
|
||||||
# Dependency directories
|
|
||||||
node_modules/
|
|
||||||
jspm_packages/
|
|
||||||
|
|
||||||
# Optional npm cache directory
|
|
||||||
.npm
|
|
||||||
|
|
||||||
# Optional eslint cache
|
|
||||||
.eslintcache
|
|
||||||
|
|
||||||
# Yarn Integrity file
|
|
||||||
.yarn-integrity
|
|
||||||
|
|
||||||
# Test
|
|
||||||
coverage/
|
|
||||||
|
|
||||||
# dotenv environment variables file
|
|
||||||
.env
|
.env
|
||||||
|
dist
|
||||||
# Distribution assets
|
node_modules
|
||||||
theme/images
|
|
||||||
theme/bundle.js
|
|
||||||
theme/style.css
|
|
||||||
theme/.timestamp
|
|
||||||
0
.prettierrc
Normal file
@@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": [ "stylelint-config-standard" ],
|
|
||||||
"rules": {
|
|
||||||
"selector-pseudo-element-colon-notation": "single"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
91
README.md
@@ -1,91 +0,0 @@
|
|||||||
<p align="center">
|
|
||||||
<img src="https://github.com/danhead/windycoys/raw/master/images/icon_144.png" title="WindyCOYS Logo">
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h1 align="center">Wordpress Theme</h1>
|
|
||||||
|
|
||||||
<p align="center">
|
|
||||||
<a href="https://windycoys.com" title="WindyCOYS Blog">
|
|
||||||
<img src="https://img.shields.io/website-up-down-green-red/https/windycoys.com.svg?label=windycoys.com"></a>
|
|
||||||
<a href="https://github.com/danhead/windycoys/releases" title="Theme releases">
|
|
||||||
<img src="https://img.shields.io/github/tag/danhead/windycoys.svg?label=version" alt="Latest version"></a>
|
|
||||||
<a href="https://circleci.com/gh/danhead/windycoys" title="CircleCI jobs">
|
|
||||||
<img src="https://img.shields.io/circleci/project/github/danhead/windycoys.svg?logo=circleci" alt="Latest build status"></a>
|
|
||||||
<img src="https://img.shields.io/david/danhead/windycoys.svg" alt="Theme dependencies">
|
|
||||||
<a href="https://github.com/danhead/windycoys/issues" title="Github issues">
|
|
||||||
<img src="https://img.shields.io/github/issues/danhead/windycoys.svg?logo=github" alt="Theme issues"></a>
|
|
||||||
<a href="https://github.com/danhead/windycoys/blob/master/LICENSE" title="Theme license">
|
|
||||||
<img src="https://img.shields.io/github/license/danhead/windycoys.svg" alt="Theme license"></a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p align="center">
|
|
||||||
<a href="https://twitter.com/intent/follow?screen_name=windycoys" title="Follow WindyCOYS on Twitter">
|
|
||||||
<img src="https://img.shields.io/twitter/follow/windycoys.svg?style=social&logo=twitter" alt="Follow WindyCOYS on Twitter"></a>
|
|
||||||
<a href="https://twitter.com/intent/follow?screen_name=danhead" title="Follow Dan Head on Twitter">
|
|
||||||
<img src="https://img.shields.io/twitter/follow/danhead.svg?style=social&logo=twitter" alt="Follow Dan Head on Twitter"></a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
## About
|
|
||||||
|
|
||||||
A modern Wordpress theme written for [WindyCOYS.com](https://windycoys.com).
|
|
||||||
|
|
||||||
Built using:
|
|
||||||
|
|
||||||
* [PostCSS](https://postcss.org)
|
|
||||||
* [Rollup.js](https://rollupjs.org) & [Babel](https://babeljs.io/)
|
|
||||||
|
|
||||||
## Requirements
|
|
||||||
|
|
||||||
* [Docker](https://www.docker.com/products/docker-desktop/)
|
|
||||||
* [Docker Compose](https://docs.docker.com/compose/install/)
|
|
||||||
* [Node.js](https://nodejs.org/)
|
|
||||||
|
|
||||||
## Building the theme
|
|
||||||
|
|
||||||
The build script runs the following tasks:
|
|
||||||
|
|
||||||
* **css:compile** - Process the CSS
|
|
||||||
* **js:compile** - Compile the JavaScript to ES5
|
|
||||||
* **images** - Minify images
|
|
||||||
|
|
||||||
The CSS and JavaScript will be minified if the environment variable `NODE_ENV='production'`.
|
|
||||||
|
|
||||||
```
|
|
||||||
npm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
## Developing
|
|
||||||
|
|
||||||
The `start` script will monitor CSS, JavaScript and images for changes and automatically compile them.
|
|
||||||
|
|
||||||
It will also start Docker containers and proxy the connection through browser-sync.
|
|
||||||
|
|
||||||
```
|
|
||||||
npm start
|
|
||||||
```
|
|
||||||
|
|
||||||
### CSS
|
|
||||||
|
|
||||||
The CSS is compiled to `theme/style.css` using [postcss-preset-env](https://github.com/csstools/postcss-preset-env) which primarily used to resolve custom properties and add vendor prefixes. It is linted with [stylelint](https://stylelint.io) using [stylelint-selector-bem-pattern](https://github.com/simonsmith/stylelint-selector-bem-pattern).
|
|
||||||
|
|
||||||
### JavaScript
|
|
||||||
|
|
||||||
The JavaScript is transformed to ES5 and bundled to `theme/bundle.js`. It is linted with [eslint](https://eslint.org/) using [Airbnb's base config](https://github.com/airbnb/javascript/tree/master/packages/eslint-config-airbnb-base).
|
|
||||||
|
|
||||||
Tests are written with [Jest](https://jestjs.io/) and run with:
|
|
||||||
|
|
||||||
```
|
|
||||||
npm run jest
|
|
||||||
```
|
|
||||||
|
|
||||||
The tests are written in `*.spec.js` files along side the source code.
|
|
||||||
|
|
||||||
To watch the files and automatically re-run the tests, use:
|
|
||||||
|
|
||||||
```
|
|
||||||
npm run jest:watch
|
|
||||||
```
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
The WindyCOYS theme is licensed under the [MIT license](http://opensource.org/licenses/MIT)
|
|
||||||
1
browser-sync/.dockerignore
Normal file
@@ -0,0 +1 @@
|
|||||||
|
node_modules
|
||||||
6
browser-sync/Dockerfile
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
FROM node:18
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
COPY . .
|
||||||
|
RUN npm ci
|
||||||
|
EXPOSE 3000
|
||||||
|
CMD ["node", "index.js"]
|
||||||
20
browser-sync/index.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
const bs = require("browser-sync").create();
|
||||||
|
|
||||||
|
bs.watch("dist/styles.css", (e) => {
|
||||||
|
if (e === "change") {
|
||||||
|
bs.reload("styles.css");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
bs.watch("dist/bundle.js", (e) => {
|
||||||
|
if (e === "change") {
|
||||||
|
bs.reload("bundle.js");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
bs.watch("dist/**/*.php", () => bs.reload());
|
||||||
|
|
||||||
|
bs.init({
|
||||||
|
open: false,
|
||||||
|
proxy: "http://windycoys_wordpress",
|
||||||
|
});
|
||||||
2970
browser-sync/package-lock.json
generated
Normal file
11
browser-sync/package.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"name": "windycoys-browser-sync",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"private": true,
|
||||||
|
"description": "Browsersync Docker container for local developement",
|
||||||
|
"main": "index.js",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"browser-sync": "^2.27.9"
|
||||||
|
}
|
||||||
|
}
|
||||||
17
build/backup.sh
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
source .env
|
||||||
|
|
||||||
|
rm -rf ~/windycoys/backup
|
||||||
|
rm -f ~/windycoys/windycoys-*.tgz
|
||||||
|
mkdir -p ~/windycoys/backup
|
||||||
|
|
||||||
|
scp -r ~/windycoys/wp-content ~/windycoys/backup/
|
||||||
|
docker exec -it $DOCKER_DB_NAME /usr/bin/mysqldump -uroot -p"$DB_ROOT_PASSWORD" $DB_NAME | gzip -9 > ~/windycoys/backup/database.sql.gz
|
||||||
|
ls -l ~/windycoys/backup/
|
||||||
|
|
||||||
|
datetime=$(date +'%Y-%m-%dT%H-%M-%S')
|
||||||
|
file=windycoys-$datetime.tgz
|
||||||
|
/usr/bin/tar zcf ~/windycoys/$file ~/windycoys/backup
|
||||||
|
|
||||||
|
rm -rf ~/windycoys/backup
|
||||||
8
build/clean.sh
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
# Test environment variables are set
|
||||||
|
[ -z "$SSH_IP" ] && { echo >&2 "Error: SSH_IP is missing. Aborting."; exit 1; }
|
||||||
|
[ -z "$SSH_USER" ] && { echo >&2 "Error: SSH_USER is missing. Aborting."; exit 1; }
|
||||||
|
|
||||||
|
# Deploy theme
|
||||||
|
ssh $SSH_USER@$SSH_IP rm -r ~/windycoys/dist/*
|
||||||
14
build/deploy.sh
Executable file → Normal file
@@ -1,13 +1,9 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
# Ensure target is defined
|
# Test environment variables are set
|
||||||
[ -z "$1" ] && { echo >&2 "Error: Build target not defined. Aborting."; exit 1; }
|
|
||||||
|
|
||||||
# Test rsync is installed and ENV variables are set
|
|
||||||
hash rsync 2>/dev/null || { echo >&2 "Error: rsync is not installed. Aborting."; exit 1; }
|
|
||||||
[ -z "$SSH_IP" ] && { echo >&2 "Error: SSH_IP is missing. Aborting."; exit 1; }
|
[ -z "$SSH_IP" ] && { echo >&2 "Error: SSH_IP is missing. Aborting."; exit 1; }
|
||||||
[ -z "$SSH_USER" ] && { echo >&2 "Error: SSH_USER is missing. Aborting."; exit 1; }
|
[ -z "$SSH_USER" ] && { echo >&2 "Error: SSH_USER is missing. Aborting."; exit 1; }
|
||||||
|
|
||||||
# Deploy theme with rsync
|
# Deploy theme
|
||||||
rsync -e "ssh -o StrictHostKeyChecking=no" -avzuh --progress \
|
scp ./build/docker-compose.yml $SSH_USER@$SSH_IP:~/windycoys/
|
||||||
theme/. $SSH_USER@$SSH_IP:~/windycoys_theme_$1
|
scp -r ./dist $SSH_USER@$SSH_IP:~/windycoys/
|
||||||
|
|||||||
36
build/docker-compose.dev.yml
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
version: "3.8"
|
||||||
|
services:
|
||||||
|
db:
|
||||||
|
image: mariadb:10.8
|
||||||
|
container_name: windycoys_db
|
||||||
|
volumes:
|
||||||
|
- ../.tmp/db:/var/lib/mysql
|
||||||
|
environment:
|
||||||
|
- MYSQL_ROOT_PASSWORD=${dbRootPassword}
|
||||||
|
- MYSQL_PASSWORD=${dbPassword}
|
||||||
|
- MYSQL_DATABASE=${dbDatabase}
|
||||||
|
- MYSQL_USER=${dbUser}
|
||||||
|
restart: unless-stopped
|
||||||
|
wordpress:
|
||||||
|
image: wordpress:latest
|
||||||
|
container_name: windycoys_wordpress
|
||||||
|
environment:
|
||||||
|
- WORDPRESS_DB_HOST=windycoys_db
|
||||||
|
- WORDPRESS_DB_USER=${dbUser}
|
||||||
|
- WORDPRESS_DB_PASSWORD=${dbPassword}
|
||||||
|
- WORDPRESS_DB_NAME=wordpress
|
||||||
|
- WORDPRESS_TABLE_PREFIX=wp_
|
||||||
|
- WORDPRESS_DEBUG=1
|
||||||
|
volumes:
|
||||||
|
- ../.tmp/wp-content:/var/www/html/wp-content
|
||||||
|
- ../dist:/var/www/html/wp-content/themes/windycoys
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
browser_sync:
|
||||||
|
image: windycoys-browser-sync:latest
|
||||||
|
container_name: windycoys_browser_sync
|
||||||
|
volumes:
|
||||||
|
- ../dist:/usr/src/app/dist
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
@@ -1,22 +1,28 @@
|
|||||||
wordpress:
|
version: "3.8"
|
||||||
|
services:
|
||||||
|
db:
|
||||||
|
image: mariadb:10.8
|
||||||
|
container_name: windycoys_db
|
||||||
|
volumes:
|
||||||
|
- ./.tmp/db:/var/lib/mysql
|
||||||
environment:
|
environment:
|
||||||
- WORDPRESS_DB_HOST=db
|
- MYSQL_ROOT_PASSWORD=${dbRootPassword}
|
||||||
- WORDPRESS_DB_PASSWORD=password
|
- MYSQL_PASSWORD=${dbPassword}
|
||||||
- WORDPRESS_DB_USER=windycoys
|
- MYSQL_DATABASE=${dbDatabase}
|
||||||
image: 'wordpress:php7.0-apache'
|
- MYSQL_USER=${dbUser}
|
||||||
links:
|
restart: unless-stopped
|
||||||
|
wordpress:
|
||||||
|
image: wordpress:latest
|
||||||
|
container_name: windycoys_wordpress
|
||||||
|
environment:
|
||||||
|
- WORDPRESS_DB_HOST=windycoys_db
|
||||||
|
- WORDPRESS_DB_USER=${dbUser}
|
||||||
|
- WORDPRESS_DB_PASSWORD=${dbPassword}
|
||||||
|
- WORDPRESS_DB_NAME=wordpress
|
||||||
|
- WORDPRESS_TABLE_PREFIX=wp_
|
||||||
|
volumes:
|
||||||
|
- ./.tmp/wp-content:/var/www/html/wp-content
|
||||||
|
- ./dist:/var/www/html/wp-content/themes/windycoys
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
- db
|
- db
|
||||||
ports:
|
|
||||||
- "80:80"
|
|
||||||
volumes:
|
|
||||||
- ../.tmp/wp-content:/var/www/html/wp-content
|
|
||||||
- ../theme:/var/www/html/wp-content/themes/windycoys
|
|
||||||
db:
|
|
||||||
environment:
|
|
||||||
- MYSQL_DATABASE=wordpress
|
|
||||||
- MYSQL_PASSWORD=password
|
|
||||||
- MYSQL_ROOT_PASSWORD=password
|
|
||||||
- MYSQL_USER=windycoys
|
|
||||||
image: 'mariadb:latest'
|
|
||||||
volumes:
|
|
||||||
- ../.tmp/mysql:/var/lib/mysql
|
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
# Write deploy timestamp to disk
|
|
||||||
timestamp=$(date +%s)
|
|
||||||
echo $timestamp > theme/.timestamp
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
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', {
|
|
||||||
plugins: [
|
|
||||||
imageminPngquant({
|
|
||||||
quality: [0.65, 0.8],
|
|
||||||
}),
|
|
||||||
imageminGifsicle({
|
|
||||||
optimizationLevel: 3,
|
|
||||||
}),
|
|
||||||
imageminJpegtran(),
|
|
||||||
],
|
|
||||||
}).then((files) => {
|
|
||||||
files.forEach((file) => {
|
|
||||||
const path = resolve(__dirname, '../', file.path);
|
|
||||||
writeFile(path, file.data, (error) => {
|
|
||||||
if (error) {
|
|
||||||
console.error(error);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
console.log(`File written: ${path}`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}).catch((error) => {
|
|
||||||
console.error(error);
|
|
||||||
process.exit(1);
|
|
||||||
});
|
|
||||||
1
build/purge_cache.sh
Executable file → Normal file
@@ -12,4 +12,3 @@ curl -X DELETE \
|
|||||||
-H "X-Auth-Email: $CLOUDFLARE_EMAIL" \
|
-H "X-Auth-Email: $CLOUDFLARE_EMAIL" \
|
||||||
--data '{"purge_everything":true}' \
|
--data '{"purge_everything":true}' \
|
||||||
"https://api.cloudflare.com/client/v4/zones/$CLOUDFLARE_IDENTIFIER/purge_cache"
|
"https://api.cloudflare.com/client/v4/zones/$CLOUDFLARE_IDENTIFIER/purge_cache"
|
||||||
|
|
||||||
|
|||||||
@@ -1,93 +0,0 @@
|
|||||||
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 name = 'windycoys';
|
|
||||||
|
|
||||||
const dockerOptions = {
|
|
||||||
cwd: resolve(__dirname),
|
|
||||||
log: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
const browserSyncOptions = {
|
|
||||||
files: [
|
|
||||||
'theme/**/*',
|
|
||||||
],
|
|
||||||
watchtask: true,
|
|
||||||
open: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
const mysqlConfig = {
|
|
||||||
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();
|
|
||||||
};
|
|
||||||
|
|
||||||
// Update URIs in MYSQL database
|
|
||||||
const updateUriData = function updateUriData(wordpress, db) {
|
|
||||||
mysqlConfig.host = db;
|
|
||||||
const con = mysql.createConnection(mysqlConfig);
|
|
||||||
con.connect();
|
|
||||||
|
|
||||||
const query = `UPDATE wp_options SET option_value='http://${wordpress}' WHERE option_name in ('siteurl', 'home')`;
|
|
||||||
|
|
||||||
con.query(query, (error) => {
|
|
||||||
if (error) {
|
|
||||||
console.error('Error', error.sqlMessage);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
con.end();
|
|
||||||
};
|
|
||||||
|
|
||||||
// Register Exit handler to stop Docker containers on exit
|
|
||||||
const exitHandler = function exitHandler() {
|
|
||||||
stop(dockerOptions).then(
|
|
||||||
() => {
|
|
||||||
console.log('\n');
|
|
||||||
process.exit();
|
|
||||||
},
|
|
||||||
(err) => {
|
|
||||||
console.error('Error:', err.message);
|
|
||||||
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());
|
|
||||||
|
|
||||||
// Start Docker containers
|
|
||||||
upAll(dockerOptions).then(
|
|
||||||
() => {
|
|
||||||
console.log('Docker containers launched, starting browser-sync...');
|
|
||||||
const wordpress = getIpAddr(`${name}_wordpress_1`);
|
|
||||||
const db = getIpAddr(`${name}_db_1`);
|
|
||||||
if (wordpress && db) {
|
|
||||||
updateUriData(wordpress, db);
|
|
||||||
const bs = browsersync.create();
|
|
||||||
browserSyncOptions.proxy = wordpress;
|
|
||||||
bs.init(browserSyncOptions);
|
|
||||||
} else {
|
|
||||||
console.error('Error', 'No IP address for Docker containers. Aborting.');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
(err) => {
|
|
||||||
console.error('Error', err.message);
|
|
||||||
process.exit(1);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
2
css/normalize.css
vendored
@@ -1,2 +0,0 @@
|
|||||||
/* stylelint-disable at-rule-no-unknown */
|
|
||||||
@import-normalize;
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
.social {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.social__link {
|
|
||||||
display: inline-block;
|
|
||||||
transition:
|
|
||||||
filter var(--transition-fs) ease-out,
|
|
||||||
transform var(--transition-fs) ease-out;
|
|
||||||
transform-origin: center bottom;
|
|
||||||
}
|
|
||||||
|
|
||||||
.social__link:focus,
|
|
||||||
.social__link:hover {
|
|
||||||
filter: saturate(1.5) contrast(1.5);
|
|
||||||
transform: scale(1.1);
|
|
||||||
}
|
|
||||||
7
jest.config.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
|
||||||
|
module.exports = {
|
||||||
|
preset: "ts-jest",
|
||||||
|
testEnvironment: "node",
|
||||||
|
roots: ["src/ts"],
|
||||||
|
};
|
||||||
|
|
||||||
26
js/image.js
@@ -1,26 +0,0 @@
|
|||||||
export default class Image {
|
|
||||||
constructor(image) {
|
|
||||||
this.image = image;
|
|
||||||
this.dataset = image.dataset;
|
|
||||||
}
|
|
||||||
|
|
||||||
fadeIn() {
|
|
||||||
this.image.style.opacity = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
start() {
|
|
||||||
if (this.dataset.loadTransition === 'false') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.image.style.opacity = 0;
|
|
||||||
if (this.image.complete) {
|
|
||||||
this.fadeIn();
|
|
||||||
} else {
|
|
||||||
this.attachLoadedEvent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
attachLoadedEvent() {
|
|
||||||
this.image.addEventListener('load', () => this.fadeIn());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
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', () => {
|
|
||||||
let image;
|
|
||||||
let disabledImage;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
document.body.innerHTML = imageHtml;
|
|
||||||
image = document.querySelector('.image');
|
|
||||||
disabledImage = document.querySelector('.disabled-image');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should initialize', () => {
|
|
||||||
const img = new Image(image);
|
|
||||||
expect(img).toBeInstanceOf(Image);
|
|
||||||
});
|
|
||||||
|
|
||||||
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', () => {
|
|
||||||
const img = new Image(image);
|
|
||||||
img.fadeIn();
|
|
||||||
expect(img.image.style.opacity).toBe('1');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('start should set the element opacity to 0', () => {
|
|
||||||
const img = new Image(image);
|
|
||||||
img.start();
|
|
||||||
expect(img.image.style.opacity).toBe('0');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('start should call fadeIn when image has already loaded', () => {
|
|
||||||
const img = new Image(image);
|
|
||||||
img.fadeIn = jest.fn();
|
|
||||||
Object.defineProperty(img.image, 'complete', {
|
|
||||||
value: true,
|
|
||||||
writable: false,
|
|
||||||
});
|
|
||||||
img.start();
|
|
||||||
expect(img.fadeIn).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
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', {
|
|
||||||
value: false,
|
|
||||||
writable: false,
|
|
||||||
});
|
|
||||||
img.start();
|
|
||||||
expect(img.attachLoadedEvent).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('start should do nothing if data-load-transition=false', () => {
|
|
||||||
const img = new Image(disabledImage);
|
|
||||||
img.attachLoadedEvent = jest.fn();
|
|
||||||
img.start();
|
|
||||||
expect(img.attachLoadedEvent).toHaveBeenCalledTimes(0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import windy from './windy';
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', windy);
|
|
||||||
60
js/menu.js
@@ -1,60 +0,0 @@
|
|||||||
const STATES = {
|
|
||||||
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.lastKnownScrollPosition = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
init() {
|
|
||||||
if (!this.state) return false;
|
|
||||||
this.container.setAttribute('aria-haspopup', true);
|
|
||||||
this.container.setAttribute('aria-expanded', false);
|
|
||||||
this.attachEvents();
|
|
||||||
setTimeout(() => {
|
|
||||||
this.showMenu();
|
|
||||||
}, 1000);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
attachEvents() {
|
|
||||||
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) {
|
|
||||||
this.showMenu();
|
|
||||||
} else if (this.state !== 'minimized' && currentY
|
|
||||||
> lastY) {
|
|
||||||
this.minimizeMenu();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
showMenu() {
|
|
||||||
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.container.classList.add(STATES.minimized);
|
|
||||||
}
|
|
||||||
|
|
||||||
hideMenu() {
|
|
||||||
this.state = 'hidden';
|
|
||||||
this.container.setAttribute('aria-expanded', true);
|
|
||||||
this.container.classList.remove(STATES.visible);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
114
js/menu.spec.js
@@ -1,114 +0,0 @@
|
|||||||
import Menu from './menu';
|
|
||||||
|
|
||||||
jest.useFakeTimers();
|
|
||||||
|
|
||||||
const menuHtml = `
|
|
||||||
<button class="menu">
|
|
||||||
<span class="menu__line"></span>
|
|
||||||
<span class="menu__line"></span>
|
|
||||||
<span class="menu__line"></span>
|
|
||||||
<span class="menu__text">Open menu</span>
|
|
||||||
</button>`;
|
|
||||||
|
|
||||||
describe('Menu button', () => {
|
|
||||||
let menu;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
document.body.innerHTML = menuHtml;
|
|
||||||
menu = new Menu({
|
|
||||||
container: document.querySelector('.menu'),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should initialise', () => {
|
|
||||||
menu.init();
|
|
||||||
expect(menu).toMatchObject({});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should fail to initialise if container is missing', () => {
|
|
||||||
menu.init();
|
|
||||||
expect((new Menu()).state).toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should default to visible state', () => {
|
|
||||||
expect(menu.state).toBe('visible');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should be hidden initially', () => {
|
|
||||||
menu.init();
|
|
||||||
expect(menu.container.classList.contains('is-visible')).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
|
|
||||||
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');
|
|
||||||
menu.updateState(0, 10);
|
|
||||||
expect(menu.state).toBe('hidden');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('The state should change to minimized when scrolling down', () => {
|
|
||||||
menu.init();
|
|
||||||
menu.showMenu();
|
|
||||||
menu.updateState(0, 10);
|
|
||||||
expect(menu.state).toBe('minimized');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('The state should change to visible when scrolling up', () => {
|
|
||||||
menu.init();
|
|
||||||
menu.minimizeMenu();
|
|
||||||
menu.updateState(10, 0);
|
|
||||||
expect(menu.state).toBe('visible');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should have initial ARIA attributes', () => {
|
|
||||||
menu.init();
|
|
||||||
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', () => {
|
|
||||||
menu.init();
|
|
||||||
menu.showMenu();
|
|
||||||
expect(menu.container.getAttribute('aria-expanded')).toBe('false');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should have updated ARIA attribute when menu is closed', () => {
|
|
||||||
menu.init();
|
|
||||||
menu.showMenu();
|
|
||||||
menu.hideMenu();
|
|
||||||
expect(menu.container.getAttribute('aria-expanded')).toBe('true');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
const STATES = {
|
|
||||||
open: 'is-open',
|
|
||||||
};
|
|
||||||
|
|
||||||
export default class Navigation {
|
|
||||||
constructor(cfg = {}) {
|
|
||||||
if (!cfg.container) return false;
|
|
||||||
if (!cfg.menu) return false;
|
|
||||||
this.state = 'hidden';
|
|
||||||
this.container = cfg.container;
|
|
||||||
this.menu = cfg.menu;
|
|
||||||
}
|
|
||||||
|
|
||||||
init() {
|
|
||||||
if (!this.state) return false;
|
|
||||||
this.container.setAttribute('aria-hidden', true);
|
|
||||||
this.attachClickEvents();
|
|
||||||
this.attachKeyEvents();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
attachClickEvents() {
|
|
||||||
this.container.addEventListener('click', (e) => {
|
|
||||||
if (this.container === e.target) {
|
|
||||||
this.closeNav();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.menu.container.addEventListener('click', () => {
|
|
||||||
this.openNav();
|
|
||||||
});
|
|
||||||
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) => {
|
|
||||||
if (e.keyCode === 27) {
|
|
||||||
this.closeNav();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
first.addEventListener('keydown', (e) => {
|
|
||||||
if (e.shiftKey && e.keyCode === 9) {
|
|
||||||
e.preventDefault();
|
|
||||||
last.focus();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
last.addEventListener('keydown', (e) => {
|
|
||||||
if (!e.shiftKey && e.keyCode === 9) {
|
|
||||||
e.preventDefault();
|
|
||||||
first.focus();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
openNav() {
|
|
||||||
this.lastActiveElement = document.activeElement;
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
closeNav() {
|
|
||||||
this.state = 'hidden';
|
|
||||||
this.menu.showMenu();
|
|
||||||
this.container.classList.remove(STATES.open);
|
|
||||||
this.container.setAttribute('aria-hidden', true);
|
|
||||||
if (this.lastActiveElement) {
|
|
||||||
this.lastActiveElement.focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,167 +0,0 @@
|
|||||||
import Navigation from './navigation';
|
|
||||||
|
|
||||||
const navHtml = `<a href="/test">Test link</a>
|
|
||||||
<nav class="nav">
|
|
||||||
<button class="menu">Menu</button>
|
|
||||||
<div class="nav__content">
|
|
||||||
<h2 class="nav__title">
|
|
||||||
<a class="nav__title-link" href="/">WindyCOYS</a>
|
|
||||||
</h2>
|
|
||||||
<ul class="nav__list">
|
|
||||||
<li class="nav__list-item">
|
|
||||||
<a class="nav__link" href="/">Home</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav__list-item">
|
|
||||||
<a class="nav__link" href="/about">About</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<button class="nav__close">close</button>
|
|
||||||
</nav>
|
|
||||||
<a href="/another-test">Another test link</a>`;
|
|
||||||
|
|
||||||
let nav;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
document.body.innerHTML = navHtml;
|
|
||||||
// Mock Menu
|
|
||||||
const menu = {
|
|
||||||
container: document.querySelector('.menu'),
|
|
||||||
showMenu: jest.fn(),
|
|
||||||
hideMenu: jest.fn(),
|
|
||||||
};
|
|
||||||
nav = new Navigation({
|
|
||||||
container: document.querySelector('.nav'),
|
|
||||||
menu,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Navigation', () => {
|
|
||||||
it('Should initialise', () => {
|
|
||||||
nav.init();
|
|
||||||
expect(nav).toMatchObject({});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should fail to initialise if container is missing', () => {
|
|
||||||
nav.init();
|
|
||||||
expect((new Navigation()).state).toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should fail to initialise if the menu is not provided', () => {
|
|
||||||
const noMenu = new Navigation({
|
|
||||||
container: document.querySelector('.nav'),
|
|
||||||
});
|
|
||||||
noMenu.init();
|
|
||||||
expect(noMenu.state).toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should be hidden by default', () => {
|
|
||||||
nav.init();
|
|
||||||
expect(nav.container.classList.contains('is-open')).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should close when the .nav element is clicked', () => {
|
|
||||||
nav.init();
|
|
||||||
nav.openNav();
|
|
||||||
const event = new Event('click');
|
|
||||||
nav.container.dispatchEvent(event);
|
|
||||||
expect(nav.state).toBe('hidden');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should close when the user presses ESC', () => {
|
|
||||||
nav.init();
|
|
||||||
nav.openNav();
|
|
||||||
const event = new Event('keydown');
|
|
||||||
event.keyCode = 27;
|
|
||||||
nav.container.dispatchEvent(event);
|
|
||||||
expect(nav.state).toBe('hidden');
|
|
||||||
});
|
|
||||||
|
|
||||||
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');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should have initial ARIA attributes', () => {
|
|
||||||
nav.init();
|
|
||||||
expect(nav.container.getAttribute('aria-hidden')).toBe('true');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should update the ARIA attributes when visible', () => {
|
|
||||||
nav.init();
|
|
||||||
nav.openNav();
|
|
||||||
expect(nav.container.getAttribute('aria-hidden')).toBe('false');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should update the ARIA attributes when hidden', () => {
|
|
||||||
nav.init();
|
|
||||||
nav.openNav();
|
|
||||||
nav.closeNav();
|
|
||||||
expect(nav.container.getAttribute('aria-hidden')).toBe('true');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Navigation menu button', () => {
|
|
||||||
it('Should open the Navigation when clicked', () => {
|
|
||||||
nav.init();
|
|
||||||
const event = new Event('click');
|
|
||||||
nav.menu.container.dispatchEvent(event);
|
|
||||||
expect(nav.state).toBe('visible');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
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');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
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.focus();
|
|
||||||
nav.init();
|
|
||||||
nav.openNav();
|
|
||||||
});
|
|
||||||
|
|
||||||
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', () => {
|
|
||||||
last.focus();
|
|
||||||
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', () => {
|
|
||||||
first.focus();
|
|
||||||
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', () => {
|
|
||||||
first.focus();
|
|
||||||
nav.closeNav();
|
|
||||||
expect(document.activeElement).toBe(menu);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
export default class ReplyToComment {
|
|
||||||
constructor(cfg = {}) {
|
|
||||||
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.links = Array.prototype.slice.call(cfg.links);
|
|
||||||
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));
|
|
||||||
container.appendChild(form);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleCancelClick(e, container) {
|
|
||||||
e.preventDefault();
|
|
||||||
container.parentNode.querySelector('.comment__reply').classList.remove('is-hidden');
|
|
||||||
while (container.firstChild) {
|
|
||||||
container.removeChild(container.firstChild);
|
|
||||||
}
|
|
||||||
this.form.appendChild(this.backupForm);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleLinkClick(e, link) {
|
|
||||||
e.preventDefault();
|
|
||||||
link.classList.add('is-hidden');
|
|
||||||
while (this.form.firstChild) {
|
|
||||||
this.form.removeChild(this.form.firstChild);
|
|
||||||
}
|
|
||||||
const container = link.parentNode.querySelector('.comment__inline-reply');
|
|
||||||
const { commentId, commentAuthor } = link.dataset;
|
|
||||||
this.insertForm(container, commentId, commentAuthor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,138 +0,0 @@
|
|||||||
import ReplyToComment from './reply-to-comment';
|
|
||||||
|
|
||||||
const commentHtml = `
|
|
||||||
<div class="comment">
|
|
||||||
<div class="comment__head">
|
|
||||||
<div class="comment__avatar"></div>
|
|
||||||
<div class="comment__metadata">
|
|
||||||
<span class="comment__author">Mr Smith</span>
|
|
||||||
<span class="comment__date">21st September, 2018 at 15:36</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="comment__body">This is a comment.</div>
|
|
||||||
<div class="comment__foot">
|
|
||||||
<a class="comment__reply" data-comment-author="Mr Smith" data-comment-id="1234" href="?replytocom=1234#respond">
|
|
||||||
<span class="comment__reply-text">Reply</span>
|
|
||||||
</a>
|
|
||||||
<div class="comment__inline-reply"></div>
|
|
||||||
</div>
|
|
||||||
</div>`;
|
|
||||||
|
|
||||||
const formHtml = `
|
|
||||||
<div class="comments__foot">
|
|
||||||
<div id="respond" class="comment-respond">
|
|
||||||
<h3 id="reply-title" class="comment-respond__title">
|
|
||||||
<span class="comment-respond__title-text">
|
|
||||||
Leave a Reply
|
|
||||||
</span>
|
|
||||||
<div class="comment-respond__cancel">
|
|
||||||
<a rel="nofollow" id="cancel-comment-reply-link" href="#respond">Cancel reply</a>
|
|
||||||
</div>
|
|
||||||
</h3>
|
|
||||||
<form action="/wp-comments-post.php" method="post" id="commentform" class="comment-respond__form">
|
|
||||||
<p class="comment-respond__notes">Your email address will not be published.</p>
|
|
||||||
<label class="comment-respond__label" for="comment">Comment</label>
|
|
||||||
<textarea class="comment-respond__input" id="comment" name="comment" cols="25" rows="8" aria-required="true"></textarea>
|
|
||||||
<div class="comment-respond__item">
|
|
||||||
<label class="comment-respond__label" for="author">Name</label>
|
|
||||||
<input class="comment-respond__input" id="author" name="author" type="text" value="" size="30">
|
|
||||||
</div>
|
|
||||||
<div class="comment-respond__item">
|
|
||||||
<label class="comment-respond__label" for="email">Email</label>
|
|
||||||
<input class="comment-respond__input id="email" name="email" type="text" value="" size="30">
|
|
||||||
</div>
|
|
||||||
<div class="comment-respond__item">
|
|
||||||
<label class="comment-respond__label" for="url">Website</label>
|
|
||||||
<input class="comment-respond__input" id="url" name="url" type="text" value="" size="30">
|
|
||||||
</div>
|
|
||||||
<p class="form-submit">
|
|
||||||
<input name="submit" type="submit" id="submit" class="comment-respond__submit" value="Post Comment">
|
|
||||||
<input type="hidden" name="comment_post_ID" value="2683" id="comment_post_ID">
|
|
||||||
<input type="hidden" name="comment_parent" id="comment_parent" value="0">
|
|
||||||
</p>
|
|
||||||
<p style="display: none;">
|
|
||||||
<input type="hidden" id="akismet_comment_nonce" name="akismet_comment_nonce" value="123456789">
|
|
||||||
</p>
|
|
||||||
<input type="hidden" id="ak_js" name="ak_js" value="987654321">
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
let comments;
|
|
||||||
let form;
|
|
||||||
let links;
|
|
||||||
let inlineReply;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
document.body.innerHTML = `${commentHtml}${formHtml}`;
|
|
||||||
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', () => {
|
|
||||||
expect(comments.form).toBeInstanceOf(HTMLDivElement);
|
|
||||||
expect(comments.links).toBeInstanceOf(Array);
|
|
||||||
});
|
|
||||||
|
|
||||||
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 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('');
|
|
||||||
});
|
|
||||||
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should insert the form into the inline reply container', () => {
|
|
||||||
comments.insertForm(inlineReply);
|
|
||||||
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 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');
|
|
||||||
});
|
|
||||||
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
|
|
||||||
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('');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
30
js/windy.js
@@ -1,30 +0,0 @@
|
|||||||
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) => {
|
|
||||||
const img = new Image(image);
|
|
||||||
img.start();
|
|
||||||
windy.images.push(img);
|
|
||||||
});
|
|
||||||
windy.menu = new Menu({
|
|
||||||
container: document.querySelector('.menu'),
|
|
||||||
});
|
|
||||||
windy.navigation = new Navigation({
|
|
||||||
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'),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
29975
package-lock.json
generated
107
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "windycoys",
|
"name": "windycoys",
|
||||||
"version": "1.0.0-RC.2",
|
"version": "2.0.0-alpha1",
|
||||||
"description": "Wordpress theme for windycoys.com",
|
"description": "Wordpress theme for windycoys.com",
|
||||||
"author": "Dan Head <me@danielhead.com>",
|
"author": "Dan Head <me@danielhead.com>",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -9,74 +9,55 @@
|
|||||||
"url": "https://github.com/danhead/windycoys"
|
"url": "https://github.com/danhead/windycoys"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "run-s images css:compile js:compile",
|
"browser-sync": "docker build -t windycoys-browser-sync browser-sync",
|
||||||
"css": "run-p -c css:*",
|
"build": "run-p build:*",
|
||||||
"css:compile": "postcss -o theme/style.css css/index.css",
|
"build:css": "cross-env NODE_ENV=production postcss src/index.css -o dist/style.css",
|
||||||
"css:lint": "stylelint --config .stylelintrc \"css/**/*.css\"",
|
"build:files": "copyfiles -u 1 'src/**/*.{json,php,jpg,png,gif}' dist",
|
||||||
"images": "node build/imagemin.js",
|
"build:ts": "cross-env NODE_ENV=production webpack",
|
||||||
"jest": "jest",
|
"lint": "run-p lint:*",
|
||||||
"jest:watch": "jest --watch",
|
"lint:eslint": "eslint src --ext .ts",
|
||||||
"js": "run-s js:*",
|
"postinstallf": "run-p browser-sync",
|
||||||
"js:compile": "rollup --config rollup.config.js",
|
"serve": "docker-compose -f build/docker-compose.dev.yml up --force-recreate 2>/dev/null",
|
||||||
"js:lint": "eslint js/",
|
"start": "run-p serve watch",
|
||||||
"serve": "node build/serve.js",
|
"test": "jest",
|
||||||
"start": "run-p watch serve",
|
|
||||||
"test": "run-s css:lint js:lint jest",
|
|
||||||
"watch": "run-p watch:*",
|
"watch": "run-p watch:*",
|
||||||
"watch:css": "nodemon -q --watch css -e css --exec npm run css",
|
"watch:css": "postcss src/index.css -o dist/styles.css -w",
|
||||||
"watch:images": "nodemon -q --watch images -e png,jpg,gif --exec npm run images",
|
"watch:ts": "webpack -w",
|
||||||
"watch:js": "nodemon -q --watch js -e js --exec npm run js"
|
"watch:files": "copy-and-watch --watch 'src/**/*.{json,php,jpg,png,gif}' dist/"
|
||||||
},
|
},
|
||||||
"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-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",
|
|
||||||
"imagemin": "^6.1.0",
|
|
||||||
"imagemin-gifsicle": "^6.0.1",
|
|
||||||
"imagemin-jpegtran": "^6.0.0",
|
|
||||||
"imagemin-pngquant": "^7.0.0",
|
|
||||||
"jest": "^23.6.0",
|
|
||||||
"npm-run-all": "^4.1.5",
|
|
||||||
"postcss-banner": "^3.0.0",
|
|
||||||
"postcss-cli": "^6.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"
|
|
||||||
},
|
|
||||||
"pre-commit": [
|
|
||||||
"test"
|
|
||||||
],
|
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
"last 1 version",
|
"last 1 version",
|
||||||
"not dead",
|
"not dead",
|
||||||
"> 2% in GB"
|
"> 2% in GB"
|
||||||
],
|
],
|
||||||
"jest": {
|
"devDependencies": {
|
||||||
"testURL": "http://localhost:3000",
|
"@prettier/plugin-php": "^0.19.3",
|
||||||
"collectCoverage": true,
|
"@types/jest": "^27.5.2",
|
||||||
"collectCoverageFrom": [
|
"@types/jsdom": "^16.2.15",
|
||||||
"js/**/*.js"
|
"@typescript-eslint/eslint-plugin": "^5.15.0",
|
||||||
],
|
"@typescript-eslint/parser": "^5.15.0",
|
||||||
"roots": [
|
"eslint": "^8.11.0",
|
||||||
"js"
|
"eslint-config-prettier": "^8.5.0",
|
||||||
]
|
"jest": "^27.5.1",
|
||||||
|
"jsdom": "^19.0.0",
|
||||||
|
"prettier": "^2.8.3",
|
||||||
|
"ts-jest": "^27.1.3"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"copy-and-watch": "^0.1.6",
|
||||||
|
"copyfiles": "^2.4.1",
|
||||||
|
"cross-env": "^7.0.3",
|
||||||
|
"cssnano": "^5.1.14",
|
||||||
|
"npm-run-all": "^4.1.5",
|
||||||
|
"postcss": "^8.4.21",
|
||||||
|
"postcss-banner": "^4.0.1",
|
||||||
|
"postcss-cli": "^9.1.0",
|
||||||
|
"postcss-import": "^14.1.0",
|
||||||
|
"postcss-preset-env": "^7.8.3",
|
||||||
|
"rimraf": "^3.0.2",
|
||||||
|
"ts-loader": "^9.4.2",
|
||||||
|
"typescript": "^4.9.4",
|
||||||
|
"webpack": "^5.75.0",
|
||||||
|
"webpack-cli": "^4.10.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
const { resolve } = require('path');
|
const { resolve } = require("path");
|
||||||
|
|
||||||
const pkg = require(resolve(__dirname, 'package.json'));
|
const pkg = require(resolve(__dirname, "package.json"));
|
||||||
const year = new Date().getFullYear();
|
const year = new Date().getFullYear();
|
||||||
|
|
||||||
const banner = `Theme Name: WindyCOYS
|
const banner = `Theme Name: WindyCOYS
|
||||||
@@ -13,22 +13,15 @@ Copyright: (c) ${year} ${pkg.author}
|
|||||||
License: MIT
|
License: MIT
|
||||||
License URI: https://choosealicense.com/licenses/mit/`;
|
License URI: https://choosealicense.com/licenses/mit/`;
|
||||||
|
|
||||||
|
module.exports = (ctx) => ({
|
||||||
module.exports = ctx => ({
|
map: ctx.env !== "production",
|
||||||
map: ctx.env !== 'production',
|
|
||||||
plugins: {
|
plugins: {
|
||||||
'postcss-import': {},
|
"postcss-import": true,
|
||||||
'postcss-normalize': {},
|
"postcss-preset-env": {
|
||||||
'postcss-preset-env': {
|
"custom-media-queries": true,
|
||||||
stage: 0,
|
|
||||||
features: {
|
|
||||||
'custom-properties': {
|
|
||||||
preserve: false,
|
|
||||||
},
|
},
|
||||||
},
|
cssnano: ctx.env === "production" ? {} : false,
|
||||||
},
|
"postcss-banner": {
|
||||||
cssnano: ctx.env === 'production' ? {} : false,
|
|
||||||
'postcss-banner': {
|
|
||||||
banner,
|
banner,
|
||||||
important: true,
|
important: true,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,41 +0,0 @@
|
|||||||
const { resolve } = require('path');
|
|
||||||
const { uglify } = require('rollup-plugin-uglify');
|
|
||||||
const babel = require('rollup-plugin-babel');
|
|
||||||
const pkg = require('./package.json');
|
|
||||||
|
|
||||||
const year = new Date().getFullYear();
|
|
||||||
|
|
||||||
const production = process.env.NODE_ENV === 'production';
|
|
||||||
|
|
||||||
const banner = `/**!
|
|
||||||
* Theme Name: WindyCOYS
|
|
||||||
* Theme URI: https://github.com/danhead/windycoys/
|
|
||||||
* Author: ${pkg.author}
|
|
||||||
* Author URI: https://danielhead.com
|
|
||||||
* Description: Theme for windycoys.com
|
|
||||||
* Version: ${pkg.version}
|
|
||||||
* Copyright: (c) ${year} ${pkg.author}
|
|
||||||
* License: MIT
|
|
||||||
* License URI: https://choosealicense.com/licenses/mit/
|
|
||||||
*/`;
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
input: resolve(__dirname, 'js/index.js'),
|
|
||||||
output: {
|
|
||||||
file: resolve(__dirname, 'theme/bundle.js'),
|
|
||||||
format: 'umd',
|
|
||||||
name: 'windy',
|
|
||||||
sourcemap: !production && 'inline',
|
|
||||||
banner,
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
babel({
|
|
||||||
exclude: 'node_modules/**',
|
|
||||||
}),
|
|
||||||
production && uglify({
|
|
||||||
output: {
|
|
||||||
preamble: banner,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
].filter(Boolean),
|
|
||||||
};
|
|
||||||
18
src/404.php
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?php get_header(); ?>
|
||||||
|
<main class="page">
|
||||||
|
<section class="page__logo">
|
||||||
|
<?php get_template_part("partials/logo"); ?>
|
||||||
|
</section>
|
||||||
|
<section class="page__content">
|
||||||
|
<h1 class="title">404 - Page not found</h1>
|
||||||
|
<a class="link" href="<?php echo get_site_url(); ?>/">Go back home</a>
|
||||||
|
</section>
|
||||||
|
<section class="page__banner">
|
||||||
|
<?php get_template_part("partials/banner"); ?>
|
||||||
|
</section>
|
||||||
|
<section class="page__footer">
|
||||||
|
<?php get_template_part("partials/footer"); ?>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
<?php get_template_part("partials/navigation"); ?>
|
||||||
|
<?php get_footer(); ?>
|
||||||
@@ -1,48 +1,61 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
class Windycoys_Walker_Comments extends Walker_Comment {
|
class Windycoys_Walker_Comments extends Walker_Comment
|
||||||
function start_lvl(&$output, $depth = 0, $args = Array()) {
|
{
|
||||||
|
function start_lvl(&$output, $depth = 0, $args = [])
|
||||||
|
{
|
||||||
$output .= '<ol class="comments__list comments__list--sub-list">';
|
$output .= '<ol class="comments__list comments__list--sub-list">';
|
||||||
}
|
}
|
||||||
|
|
||||||
function end_lvl(&$output, $depth = 0, $args = Array()) {
|
function end_lvl(&$output, $depth = 0, $args = [])
|
||||||
$output .= '</ol>';
|
{
|
||||||
|
$output .= "</ol>";
|
||||||
}
|
}
|
||||||
|
|
||||||
function start_el(&$output, $comment, $depth = 0 , $args = Array(), $id = 0) {
|
function start_el(&$output, $comment, $depth = 0, $args = [], $id = 0)
|
||||||
|
{
|
||||||
$id = $comment->comment_ID;
|
$id = $comment->comment_ID;
|
||||||
$post_id = $comment->comment_post_ID;
|
$post_id = $comment->comment_post_ID;
|
||||||
$reply_link = get_permalink($post_id) . '?replytocom='.$id.'#respond';
|
$reply_link = get_permalink($post_id) . "?replytocom=" . $id . "#respond";
|
||||||
$author_email = $comment->comment_author_email;
|
$author_email = $comment->comment_author_email;
|
||||||
$author = $comment->comment_author;
|
$author = $comment->comment_author;
|
||||||
$avatar = get_avatar($comment, 128);
|
$avatar = get_avatar($comment, 128);
|
||||||
$date = get_comment_date(get_option('date_format'), $id).' at '.get_comment_date(get_option('time_format'), $id);
|
$date =
|
||||||
|
get_comment_date(get_option("date_format"), $id) .
|
||||||
|
" at " .
|
||||||
|
get_comment_date(get_option("time_format"), $id);
|
||||||
$content = $comment->comment_content;
|
$content = $comment->comment_content;
|
||||||
$output .= '<li class="comments__item">';
|
$output .= '<li class="comments__item">';
|
||||||
$output .= '<div class="comment">';
|
$output .= '<div class="comment">';
|
||||||
$output .= '<div class="comment__head">';
|
$output .= '<div class="comment__head">';
|
||||||
$output .= sprintf('<div class="comment__avatar has-silhouette">%s</div>', $avatar);
|
$output .= sprintf(
|
||||||
|
'<div class="comment__avatar has-silhouette">%s</div>',
|
||||||
|
$avatar
|
||||||
|
);
|
||||||
$output .= '<div class="comment__metadata">';
|
$output .= '<div class="comment__metadata">';
|
||||||
$output .= sprintf('<span class="comment__author">%s</span>', $author);
|
$output .= sprintf('<span class="comment__author">%s</span>', $author);
|
||||||
$output .= sprintf('<span class="comment__date">%s</span>', $date);
|
$output .= sprintf('<span class="comment__date">%s</span>', $date);
|
||||||
$output .= '</div></div>';
|
$output .= "</div></div>";
|
||||||
$output .= sprintf('<div class="comment__body">%s</div>', $content);
|
$output .= sprintf('<div class="comment__body">%s</div>', $content);
|
||||||
$output .= '<div class="comment__foot">';
|
$output .= '<div class="comment__foot">';
|
||||||
$output .= sprintf('<a class="comment__reply" data-comment-author="%s" ' .
|
$output .= sprintf(
|
||||||
|
'<a class="comment__reply" data-comment-author="%s" ' .
|
||||||
'data-comment-id="%s" href="%s">',
|
'data-comment-id="%s" href="%s">',
|
||||||
$author,
|
$author,
|
||||||
$id,
|
$id,
|
||||||
$reply_link
|
$reply_link
|
||||||
);
|
);
|
||||||
$output .= '<span class="comment__reply-icon">';
|
$output .= '<span class="comment__reply-icon">';
|
||||||
$output .= '<svg class="icon icon--extra-small"><use xlink:href="#icon-reply"></use></svg></span>';
|
$output .=
|
||||||
|
'<svg class="icon icon--extra-small"><use href="#icon-reply"></use></svg></span>';
|
||||||
$output .= '<span class="comment__reply-text">Reply</span></a>';
|
$output .= '<span class="comment__reply-text">Reply</span></a>';
|
||||||
$output .= '<div class="comment__inline-reply"></div>';
|
$output .= '<div class="comment__inline-reply"></div>';
|
||||||
$output .= '</div></div>';
|
$output .= "</div></div>";
|
||||||
}
|
}
|
||||||
|
|
||||||
function end_el(&$output, $comment, $depth = 0, $args = Array()) {
|
function end_el(&$output, $comment, $depth = 0, $args = [])
|
||||||
$output .= '</li>';
|
{
|
||||||
|
$output .= "</li>";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
36
src/classes/walker.navigation.php
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class Windycoys_Walker_Navigation extends Walker_Nav_Menu
|
||||||
|
{
|
||||||
|
function start_lvl(&$output, $depth = 0, $args = [])
|
||||||
|
{
|
||||||
|
$output .= '<ul class="nav__list" role="menu">';
|
||||||
|
}
|
||||||
|
|
||||||
|
function end_lvl(&$output, $depth = 0, $args = [])
|
||||||
|
{
|
||||||
|
$output .= "</ul>";
|
||||||
|
}
|
||||||
|
|
||||||
|
function start_el(&$output, $item, $depth = 0, $args = [], $id = 0)
|
||||||
|
{
|
||||||
|
$title = $item->title;
|
||||||
|
$url = $item->url;
|
||||||
|
if ($title && $url) {
|
||||||
|
$output .= '<li class="nav__list-item" role="none">';
|
||||||
|
$output .= sprintf(
|
||||||
|
'<a class="nav__link" href="%s" role="menuitem">',
|
||||||
|
$url
|
||||||
|
);
|
||||||
|
$output .= $title;
|
||||||
|
$output .= "</a>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function end_el(&$output, $item, $depth = 0, $args = [])
|
||||||
|
{
|
||||||
|
$output .= "</li>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
83
src/comments.php
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
<?php
|
||||||
|
require get_template_directory() . "/classes/walker.comments.php";
|
||||||
|
|
||||||
|
$form_settings = [
|
||||||
|
"class_form" => "comment-respond__form",
|
||||||
|
"class_submit" => "comment-respond__submit",
|
||||||
|
"comment_field" =>
|
||||||
|
'<div class="comment-respond__item comment-respond__item--large"><label class="comment-respond__label" for="comment">Comment</label><textarea class="comment-respond__input" id="comment" name="comment" cols="25" rows="8" aria-required="true"></textarea></div>',
|
||||||
|
"logged_in_as" =>
|
||||||
|
'<p class="comment-respond__text">' .
|
||||||
|
sprintf(
|
||||||
|
__(
|
||||||
|
'Logged in as <a class="link" href="%1$s">%2$s</a>. <a class="link" href="%3$s" title="Log out of this account">Log out?</a>'
|
||||||
|
),
|
||||||
|
admin_url("profile.php"),
|
||||||
|
$user_identity,
|
||||||
|
wp_logout_url(apply_filters("the_permalink", get_permalink()))
|
||||||
|
) .
|
||||||
|
"</p>",
|
||||||
|
"title_reply_before" =>
|
||||||
|
'<h3 id="reply-title" class="comment-respond__title">',
|
||||||
|
"title_reply" => '<span class="comment-respond__title-text">Reply</span>',
|
||||||
|
"cancel_reply_before" => '<span class="comment-respond__cancel">',
|
||||||
|
"cancel_reply_after" => "</span>",
|
||||||
|
"comment_notes_before" =>
|
||||||
|
'<p class="comment-respond__notes">' .
|
||||||
|
__("Your email address will not be published.") .
|
||||||
|
($req ? $required_text : "") .
|
||||||
|
"</p>",
|
||||||
|
"fields" => [
|
||||||
|
"author" =>
|
||||||
|
'<div class="comment-respond__item"><label class="comment-respond__label" for="author">' .
|
||||||
|
__("Name", "domainreference") .
|
||||||
|
($req ? '<span class="comment-respond__required">*</span>' : "") .
|
||||||
|
"</label>" .
|
||||||
|
'<input class="comment-respond__input" id="author" name="author" type="text" value="' .
|
||||||
|
esc_attr($commenter["comment_author"]) .
|
||||||
|
'" size="60" aria-required="true" /></div>',
|
||||||
|
"email" =>
|
||||||
|
'<div class="comment-respond__item"><label class="comment-respond__label" for="email">' .
|
||||||
|
__("Email", "domainreference") .
|
||||||
|
($req ? '<span class="comment-respond__required">*</span>' : "") .
|
||||||
|
"</label>" .
|
||||||
|
'<input class="comment-respond__input" id="email" name="email" type="text" value="' .
|
||||||
|
esc_attr($commenter["comment_author_email"]) .
|
||||||
|
'" size="60" aria-required="true" /></div>',
|
||||||
|
"url" =>
|
||||||
|
'<div class="comment-respond__item"><label class="comment-respond__label" for="url">' .
|
||||||
|
__("Website", "domainreference") .
|
||||||
|
"</label>" .
|
||||||
|
'<input class="comment-respond__input" id="url" name="url" type="text" value="' .
|
||||||
|
esc_attr($commenter["comment_author_url"]) .
|
||||||
|
'" size="30" /></div>',
|
||||||
|
"cookies" =>
|
||||||
|
'<div class="comment-respond__item comment-respond__item--large"><input class="comment-respond__checkbox" id="wp-comment-cookies-consent" name="wp-comment-cookies-consent" type="checkbox" value="yes"/><label class="comment-respond__checkbox-label" for="wp-comment-cookies-consent">Save my name, email, and website in this browser for the next time I comment.</label></div>',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
?>
|
||||||
|
<div class="comments" id="comments">
|
||||||
|
<div class="comments__head">
|
||||||
|
<h2 class="comments__title">
|
||||||
|
<?php if (comments_open()):
|
||||||
|
if (have_comments()):
|
||||||
|
echo "Join the conversation";
|
||||||
|
else:
|
||||||
|
echo "Leave a comment";
|
||||||
|
endif;
|
||||||
|
else:
|
||||||
|
echo "Comments are closed";
|
||||||
|
endif; ?>
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div class="comments__body">
|
||||||
|
<ol class="comments__list">
|
||||||
|
<?php wp_list_comments([
|
||||||
|
"walker" => new Windycoys_Walker_Comments(),
|
||||||
|
]); ?>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
<div class="comments__foot">
|
||||||
|
<?php comment_form($form_settings); ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.comments__list--sub-list .comments__item {
|
.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);
|
padding-left: var(--space-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,35 +39,49 @@
|
|||||||
|
|
||||||
.content ul,
|
.content ul,
|
||||||
.content ol {
|
.content ol {
|
||||||
|
counter-reset: li;
|
||||||
margin-bottom: var(--space-sm);
|
margin-bottom: var(--space-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
.content ul li,
|
.content ul li,
|
||||||
.content ol li {
|
.content ol li {
|
||||||
|
counter-increment: li;
|
||||||
position: relative;
|
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 ul li:before {
|
||||||
content: '';
|
content: "";
|
||||||
position: absolute;
|
|
||||||
left: calc(var(--space-xxs) - (var(--bullet-size) / 2));
|
|
||||||
top: calc(var(--base-line-height) / 2 * 1rem);
|
top: calc(var(--base-line-height) / 2 * 1rem);
|
||||||
background-color: var(--color-dark-blue);
|
background-color: var(--color-light-blue);
|
||||||
width: var(--bullet-size);
|
width: var(--bullet-size);
|
||||||
height: var(--bullet-size);
|
height: var(--bullet-size);
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
transform: translateY(-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 {
|
.content a {
|
||||||
color: var(--color-light-blue);
|
color: var(--color-light-blue);
|
||||||
transition: box-shadow var(--transition-md) ease-out;
|
transition: box-shadow var(--transition-md) ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content a img {
|
.content a img {
|
||||||
transition:
|
transition: box-shadow var(--transition-md) ease-out,
|
||||||
box-shadow var(--transition-md) ease-out,
|
|
||||||
filter var(--transition-md) ease-out;
|
filter var(--transition-md) ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,6 +104,7 @@
|
|||||||
|
|
||||||
.content .twitter-tweet {
|
.content .twitter-tweet {
|
||||||
width: auto !important;
|
width: auto !important;
|
||||||
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content .wp-caption {
|
.content .wp-caption {
|
||||||
@@ -113,8 +128,7 @@
|
|||||||
@media (--viewport-md) {
|
@media (--viewport-md) {
|
||||||
.content .twitter-tweet,
|
.content .twitter-tweet,
|
||||||
.content .wp-caption {
|
.content .wp-caption {
|
||||||
margin: 0 0 var(--space-md);
|
padding: var(--space-sm) var(--space-md);
|
||||||
padding: var(--space-md);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,6 +137,29 @@
|
|||||||
margin: var(--space-sm) 0 0;
|
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 {
|
.content table {
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
table-layout: fixed;
|
table-layout: fixed;
|
||||||
@@ -141,12 +178,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.content th,
|
|
||||||
.content tr,
|
|
||||||
.content td {
|
.content td {
|
||||||
border-width: 0 0 1px;
|
border: 1px solid var(--color-dark-blue);
|
||||||
border-style: solid;
|
|
||||||
border-color: var(--color-dark-blue);
|
|
||||||
padding: var(--space-xs);
|
padding: var(--space-xs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,38 +1,27 @@
|
|||||||
@import "normalize";
|
|
||||||
@import "base";
|
@import "base";
|
||||||
@import "page";
|
|
||||||
@import "nav";
|
|
||||||
@import "menu";
|
|
||||||
@import "logo";
|
|
||||||
@import "social";
|
|
||||||
@import "icon";
|
|
||||||
@import "metadata";
|
|
||||||
@import "search";
|
|
||||||
@import "article";
|
|
||||||
@import "content";
|
|
||||||
@import "footer";
|
|
||||||
@import "comments";
|
|
||||||
@import "comment";
|
|
||||||
@import "comment-respond";
|
|
||||||
@import "avatar";
|
|
||||||
@import "link";
|
|
||||||
@import "pagination";
|
|
||||||
@import "title";
|
|
||||||
@import "archive";
|
@import "archive";
|
||||||
|
@import "article";
|
||||||
|
@import "avatar";
|
||||||
|
@import "comment-respond";
|
||||||
|
@import "comment";
|
||||||
|
@import "comments";
|
||||||
|
@import "content";
|
||||||
@import "excerpt";
|
@import "excerpt";
|
||||||
|
@import "footer";
|
||||||
|
@import "icon";
|
||||||
|
@import "link";
|
||||||
|
@import "logo";
|
||||||
|
@import "menu";
|
||||||
|
@import "metadata";
|
||||||
|
@import "nav";
|
||||||
|
@import "page";
|
||||||
|
@import "pagination";
|
||||||
|
@import "search";
|
||||||
@import "silhouette";
|
@import "silhouette";
|
||||||
|
@import "social";
|
||||||
|
@import "title";
|
||||||
|
|
||||||
:root {
|
body {
|
||||||
--color-black: #000;
|
|
||||||
--color-near-black: #333;
|
|
||||||
--color-dark-blue: #201f42;
|
|
||||||
--color-light-blue: #1574d2;
|
|
||||||
--color-pale-blue: #e9f4ff;
|
|
||||||
--color-grey: #757575;
|
|
||||||
--color-light-grey: #e0e0e0;
|
|
||||||
--color-facebook: #3e5a98;
|
|
||||||
--color-twitter: #1da1f2;
|
|
||||||
--color-rss: #ee802f;
|
|
||||||
--space-xxs: 0.25rem;
|
--space-xxs: 0.25rem;
|
||||||
--space-xs: 0.5rem;
|
--space-xs: 0.5rem;
|
||||||
--space-sm: 1rem;
|
--space-sm: 1rem;
|
||||||
@@ -64,15 +53,28 @@
|
|||||||
--transition-fs: 0.15s;
|
--transition-fs: 0.15s;
|
||||||
--transition-md: 0.3s;
|
--transition-md: 0.3s;
|
||||||
--transition-sl: 0.6s;
|
--transition-sl: 0.6s;
|
||||||
--heading-font-family: 'Raleway', sans-serif;
|
--heading-font-family: "Raleway", sans-serif;
|
||||||
--body-font-family: 'Open Sans', 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@custom-media --viewport-sm (min-width: 30rem);
|
.theme-light {
|
||||||
@custom-media --viewport-md (min-width: 40rem);
|
--color-black: #000;
|
||||||
@custom-media --viewport-lg (min-width: 60rem);
|
--color-near-black: #333;
|
||||||
@custom-media --viewport-xl (min-width: 75rem);
|
--color-dark-blue: #201f42;
|
||||||
|
--color-light-blue: #1574d2;
|
||||||
|
--color-pale-blue: #e9f4ff;
|
||||||
|
--color-grey: #757575;
|
||||||
|
--color-light-grey: #e0e0e0;
|
||||||
|
--color-facebook: #3e5a98;
|
||||||
|
--color-twitter: #1da1f2;
|
||||||
|
--color-rss: #ee802f;
|
||||||
|
--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.45rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@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);
|
@custom-media --viewport-wordpress-admin-bar (min-width: 783px);
|
||||||
@@ -9,8 +9,7 @@
|
|||||||
position: fixed;
|
position: fixed;
|
||||||
top: var(--space-xxs);
|
top: var(--space-xxs);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition:
|
transition: filter var(--transition-md) ease-out,
|
||||||
filter var(--transition-md) ease-out,
|
|
||||||
opacity var(--transition-md) ease-out,
|
opacity var(--transition-md) ease-out,
|
||||||
transform var(--transition-md) ease-out;
|
transform var(--transition-md) ease-out;
|
||||||
width: var(--menu-size);
|
width: var(--menu-size);
|
||||||
@@ -79,8 +79,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.metadata__tag-link {
|
.metadata__tag-link {
|
||||||
transition:
|
transition: box-shadow var(--transition-md) ease-out,
|
||||||
box-shadow var(--transition-md) ease-out,
|
|
||||||
color var(--transition-md) ease-out;
|
color var(--transition-md) ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5,13 +5,12 @@
|
|||||||
.nav:after {
|
.nav:after {
|
||||||
background-color: rgba(0, 0, 0, 0.5);
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
content: '';
|
content: "";
|
||||||
left: 0;
|
left: 0;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
right: 0;
|
right: 0;
|
||||||
transition:
|
transition: opacity var(--nav-transition-duration) ease-out,
|
||||||
opacity var(--nav-transition-duration) ease-out,
|
|
||||||
visibility var(--nav-transition-duration) ease-out;
|
visibility var(--nav-transition-duration) ease-out;
|
||||||
top: 0;
|
top: 0;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
@@ -90,8 +89,7 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
right: var(--space-sm);
|
right: var(--space-sm);
|
||||||
top: var(--space-sm);
|
top: var(--space-sm);
|
||||||
transition:
|
transition: box-shadow var(--nav-transition-duration) ease-out,
|
||||||
box-shadow var(--nav-transition-duration) ease-out,
|
|
||||||
filter var(--nav-transition-duration) ease-out;
|
filter var(--nav-transition-duration) ease-out;
|
||||||
width: var(--icon-size-sm);
|
width: var(--icon-size-sm);
|
||||||
}
|
}
|
||||||
@@ -111,7 +109,8 @@
|
|||||||
height: 8%;
|
height: 8%;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
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%);
|
transform: translateY(-50%);
|
||||||
top: 50%;
|
top: 50%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -143,8 +142,7 @@
|
|||||||
color: #fff;
|
color: #fff;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-bottom: var(--space-xs);
|
margin-bottom: var(--space-xs);
|
||||||
transition:
|
transition: box-shadow var(--nav-transition-duration) ease-out,
|
||||||
box-shadow var(--nav-transition-duration) ease-out,
|
|
||||||
text-shadow var(--nav-transition-duration) ease-out;
|
text-shadow var(--nav-transition-duration) ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,8 +169,7 @@
|
|||||||
.nav__social-link:visited {
|
.nav__social-link:visited {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
transition:
|
transition: box-shadow var(--nav-transition-duration) ease-out,
|
||||||
box-shadow var(--nav-transition-duration) ease-out,
|
|
||||||
filter var(--nav-transition-duration) ease-out;
|
filter var(--nav-transition-duration) ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,7 +72,9 @@
|
|||||||
@media (--viewport-lg) {
|
@media (--viewport-lg) {
|
||||||
.page {
|
.page {
|
||||||
grid-template-columns: var(--logo-size-lg) 1fr;
|
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 {
|
.page__logo {
|
||||||
@@ -93,7 +95,11 @@
|
|||||||
@media (--viewport-xl) {
|
@media (--viewport-xl) {
|
||||||
.page {
|
.page {
|
||||||
grid-template-columns: var(--logo-size-xl) 1fr;
|
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;
|
margin: var(--space-md) auto 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
39
src/css/social.css
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
.social {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social__item {
|
||||||
|
margin-left: var(--space-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.social__link {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social__link:focus,
|
||||||
|
.social__link:hover {
|
||||||
|
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);
|
||||||
|
}
|
||||||
0
src/css/theme.css
Normal file
@@ -1,12 +1,12 @@
|
|||||||
<?php
|
<?php
|
||||||
$js_url = get_template_directory_uri() . '/bundle.js';
|
$js_url = get_template_directory_uri() . "/bundle.js";
|
||||||
$deploy_timestamp = windycoys_get_deploy_timestamp();
|
$deploy_timestamp = windycoys_get_deploy_timestamp();
|
||||||
if ($deploy_timestamp) {
|
if ($deploy_timestamp) {
|
||||||
$js_url .= '?t=' . $deploy_timestamp;
|
$js_url .= "?t=" . $deploy_timestamp;
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
<div id="fb-root"></div>
|
<div id="fb-root"></div>
|
||||||
<script type="text/javascript" src="<?php echo $js_url ?>"></script>
|
<script type="text/javascript" src="<?php echo $js_url; ?>"></script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
(function(d, s, id) {
|
(function(d, s, id) {
|
||||||
var js, fjs = d.getElementsByTagName(s)[0];
|
var js, fjs = d.getElementsByTagName(s)[0];
|
||||||
@@ -31,3 +31,5 @@ window.twttr = (function(d, s, id) {
|
|||||||
};
|
};
|
||||||
return t;
|
return t;
|
||||||
}(document, "script", "twitter-wjs"));</script>
|
}(document, "script", "twitter-wjs"));</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
62
src/front-page.php
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
<?php get_header(); ?>
|
||||||
|
<main class="page">
|
||||||
|
<section class="page__logo is-fixed">
|
||||||
|
<?php get_template_part("partials/logo"); ?>
|
||||||
|
</section>
|
||||||
|
<section class="page__content page__content--centered-md">
|
||||||
|
<h1 class="title">Latest articles</h1>
|
||||||
|
<div class="page__columns">
|
||||||
|
<?php
|
||||||
|
$posts = get_posts([
|
||||||
|
"numberposts" => 4,
|
||||||
|
]);
|
||||||
|
foreach ($posts as $post) {
|
||||||
|
$date = strtotime($post->post_date);
|
||||||
|
$year = date("Y", $date);
|
||||||
|
$month = date("F", $date);
|
||||||
|
$day = date("jS", $date);
|
||||||
|
$date =
|
||||||
|
get_the_date(get_option("date_format"), $post->ID) .
|
||||||
|
" at " .
|
||||||
|
get_the_date(get_option("time_format"), $post->ID);
|
||||||
|
$content =
|
||||||
|
strlen($post->post_excerpt) === 0
|
||||||
|
? strip_tags(substr($post->post_content, 0, 200)) . " [...]"
|
||||||
|
: $post->post_excerpt;
|
||||||
|
$words = count(explode(" ", $post->post_content));
|
||||||
|
$excerpt = [
|
||||||
|
"title" => $post->post_title,
|
||||||
|
"url" => get_permalink($post->ID),
|
||||||
|
"content" => $content,
|
||||||
|
"author" => get_the_author_meta("nickname", $post->post_author),
|
||||||
|
"comments" => $post->comment_count,
|
||||||
|
"words" => $words,
|
||||||
|
"date" => $date,
|
||||||
|
];
|
||||||
|
echo '<div class="page__cell">';
|
||||||
|
get_template_part("partials/excerpt");
|
||||||
|
echo "</div>";
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</div>
|
||||||
|
<h2 class="title">Podcasts</h2>
|
||||||
|
<div class="page__columns">
|
||||||
|
<div class="page__cell">
|
||||||
|
<?php $podcast = windycoys_get_podcast_meta("xin"); ?>
|
||||||
|
<?php get_template_part("partials/podcast"); ?>
|
||||||
|
</div>
|
||||||
|
<div class="page__cell">
|
||||||
|
<?php $podcast = windycoys_get_podcast_meta("tfc"); ?>
|
||||||
|
<?php get_template_part("partials/podcast"); ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="page__banner">
|
||||||
|
<?php get_template_part("partials/banner"); ?>
|
||||||
|
</section>
|
||||||
|
<section class="page__footer">
|
||||||
|
<?php get_template_part("partials/footer"); ?>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
<?php get_template_part("partials/navigation"); ?>
|
||||||
|
<?php get_footer(); ?>
|
||||||
234
src/functions.php
Normal file
@@ -0,0 +1,234 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
function windycoys_the_tags()
|
||||||
|
{
|
||||||
|
$tags = get_the_tags();
|
||||||
|
if (!$tags) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Suppress NewsNow tag
|
||||||
|
foreach ($tags as $key => $tag) {
|
||||||
|
if ($tag->name === "newsnow") {
|
||||||
|
unset($tags[$key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($tags) {
|
||||||
|
foreach ($tags as $key => $tag) {
|
||||||
|
echo '<a class="metadata__tag-link" href="' .
|
||||||
|
home_url() .
|
||||||
|
"/tag/" .
|
||||||
|
$tag->name .
|
||||||
|
'" rel="tag">' .
|
||||||
|
$tag->name .
|
||||||
|
"</a>";
|
||||||
|
if ($key < count($tags) - 1) {
|
||||||
|
echo ", ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function windycoys_get_twitter_share_url()
|
||||||
|
{
|
||||||
|
$url = "https://twitter.com/intent/tweet?text=";
|
||||||
|
$title = get_the_title();
|
||||||
|
$link = get_the_permalink();
|
||||||
|
return $url . urlencode($title . " : " . $link);
|
||||||
|
}
|
||||||
|
|
||||||
|
function windycoys_search_posts_per_page($query)
|
||||||
|
{
|
||||||
|
if ($query->is_search) {
|
||||||
|
$query->set("posts_per_page", "10");
|
||||||
|
}
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
add_filter("pre_get_posts", "windycoys_search_posts_per_page");
|
||||||
|
|
||||||
|
function windycoys_get_deploy_timestamp()
|
||||||
|
{
|
||||||
|
if (file_exists(__DIR__ . "/.timestamp")) {
|
||||||
|
$timestamp = file_get_contents(__DIR__ . "/.timestamp");
|
||||||
|
return $timestamp;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function windycoys_create_podcasts_table()
|
||||||
|
{
|
||||||
|
global $wpdb;
|
||||||
|
$charset_collate = $wpdb->get_charset_collate();
|
||||||
|
$sql = "CREATE TABLE windycoys_podcasts (
|
||||||
|
id int(2) NOT NULL AUTO_INCREMENT,
|
||||||
|
podcast_id varchar(3) DEFAULT '' NOT NULL,
|
||||||
|
last_update bigint(10) DEFAULT 0 NOT NULL,
|
||||||
|
content longtext DEFAULT '' NOT NULL,
|
||||||
|
PRIMARY KEY (id)
|
||||||
|
) $charset_collate;";
|
||||||
|
require_once ABSPATH . "wp-admin/includes/upgrade.php";
|
||||||
|
dbDelta($sql);
|
||||||
|
$wpdb->insert("windycoys_podcasts", [
|
||||||
|
"podcast_id" => "tfc",
|
||||||
|
"last_update" => 0,
|
||||||
|
"content" => "{}",
|
||||||
|
]);
|
||||||
|
$wpdb->insert("windycoys_podcasts", [
|
||||||
|
"podcast_id" => "xin",
|
||||||
|
"last_update" => 0,
|
||||||
|
"content" => "{}",
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function windycoys_fetch_podcast_meta($id)
|
||||||
|
{
|
||||||
|
global $wpdb;
|
||||||
|
$urls = [
|
||||||
|
"tfc" => "https://rss.acast.com/thefightingcock",
|
||||||
|
"xin" => "https://rss.acast.com/theextrainch",
|
||||||
|
];
|
||||||
|
$feed = file_get_contents($urls[$id]);
|
||||||
|
$feed = str_replace("<media:", "<", $feed);
|
||||||
|
$feed = str_replace("</media:", "</", $feed);
|
||||||
|
$feed = str_replace("<itunes:", "<", $feed);
|
||||||
|
$feed = str_replace("</itunes:", "</", $feed);
|
||||||
|
$now = time();
|
||||||
|
$wpdb->update(
|
||||||
|
"windycoys_podcasts",
|
||||||
|
["last_update" => $now, "content" => $feed],
|
||||||
|
["podcast_id" => $id]
|
||||||
|
);
|
||||||
|
return $feed;
|
||||||
|
}
|
||||||
|
|
||||||
|
function windycoys_refresh_podcast_meta($id)
|
||||||
|
{
|
||||||
|
global $wpdb;
|
||||||
|
$now = time();
|
||||||
|
$res = $wpdb->get_row(
|
||||||
|
'SELECT * FROM windycoys_podcasts WHERE
|
||||||
|
podcast_id = "' .
|
||||||
|
$id .
|
||||||
|
'"'
|
||||||
|
);
|
||||||
|
if (!$res) {
|
||||||
|
windycoys_create_podcasts_table();
|
||||||
|
}
|
||||||
|
if ($res->last_update + 300 < $now) {
|
||||||
|
return [
|
||||||
|
"from_cache" => false,
|
||||||
|
"data" => windycoys_fetch_podcast_meta($id),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
"from_cache" => true,
|
||||||
|
"data" => $res->content,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function windycoys_get_episodes_url($id)
|
||||||
|
{
|
||||||
|
$urls = [
|
||||||
|
"tfc" => "https://play.acast.com/s/thefightingcock",
|
||||||
|
"xin" => "https://play.acast.com/s/theextrainch",
|
||||||
|
];
|
||||||
|
return $urls[$id];
|
||||||
|
}
|
||||||
|
|
||||||
|
function windycoys_get_podcast_meta($id)
|
||||||
|
{
|
||||||
|
$res = windycoys_refresh_podcast_meta($id);
|
||||||
|
$rss = simplexml_load_string($res["data"]);
|
||||||
|
$item = $rss->channel->item;
|
||||||
|
$date = strtotime((string) $item->pubDate);
|
||||||
|
return [
|
||||||
|
"id" => $id,
|
||||||
|
"from_cache" => $res["from_cache"],
|
||||||
|
"podcast_title" => (string) $rss->channel->title,
|
||||||
|
"podcast_description" => (string) $rss->channel->description,
|
||||||
|
"podcast_episodes" => windycoys_get_episodes_url($id),
|
||||||
|
"artwork_url" => (string) $rss->channel->image->url,
|
||||||
|
"title" => (string) $item->title,
|
||||||
|
"description" => (string) $item->description,
|
||||||
|
"image" => (string) $item->image["href"],
|
||||||
|
"date" =>
|
||||||
|
date(get_option("date_format"), $date) .
|
||||||
|
" at " .
|
||||||
|
date(get_option("time_format"), $date),
|
||||||
|
"length" => (string) $item->duration,
|
||||||
|
"url" => (string) $item->link,
|
||||||
|
"audio" => (string) $item->enclosure["url"],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function windycoys_get_read_time($words)
|
||||||
|
{
|
||||||
|
$wpm = 250;
|
||||||
|
$mins = ceil($words / $wpm);
|
||||||
|
return $mins;
|
||||||
|
}
|
||||||
|
|
||||||
|
function windycoys_is_ie()
|
||||||
|
{
|
||||||
|
$ua = htmlentities($_SERVER["HTTP_USER_AGENT"], ENT_QUOTES, "UTF-8");
|
||||||
|
if (
|
||||||
|
preg_match("~MSIE|Internet Explorer~i", $ua) ||
|
||||||
|
(strpos($ua, "Trident/7.0") !== false && strpos($ua, "rv:11.0") !== false)
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
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 [
|
||||||
|
[
|
||||||
|
"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");
|
||||||
34
src/header.php
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
$css_url = get_template_directory_uri() . "/style.css";
|
||||||
|
$deploy_timestamp = windycoys_get_deploy_timestamp();
|
||||||
|
if ($deploy_timestamp) {
|
||||||
|
$css_url .= "?t=" . $deploy_timestamp;
|
||||||
|
}
|
||||||
|
$manifest = get_template_directory_uri() . "/manifest.json";
|
||||||
|
?>
|
||||||
|
<!doctype html>
|
||||||
|
<html <?php language_attributes(); ?>>
|
||||||
|
<head>
|
||||||
|
<meta charset="<?php bloginfo("charset"); ?>">
|
||||||
|
<title><?php
|
||||||
|
wp_title("");
|
||||||
|
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">
|
||||||
|
<?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">
|
||||||
|
<link rel="manifest" href="<?php echo $manifest; ?>">
|
||||||
|
<link rel="stylesheet" href="<?php echo $css_url; ?>">
|
||||||
|
<link href="//fonts.googleapis.com/css?family=Open+Sans|Raleway:700" rel="stylesheet">
|
||||||
|
<?php if (windycoys_is_ie()): ?>
|
||||||
|
<style type="text/css">.is-fixed .logo,.is-fixed .metadata{position: static;}</style>
|
||||||
|
<?php endif; ?>
|
||||||
|
</head>
|
||||||
|
<body class="theme-light">
|
||||||
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 1.6 MiB After Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 130 KiB After Width: | Height: | Size: 130 KiB |
|
Before Width: | Height: | Size: 162 KiB After Width: | Height: | Size: 162 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 1.0 MiB After Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 236 KiB After Width: | Height: | Size: 236 KiB |
|
Before Width: | Height: | Size: 444 B After Width: | Height: | Size: 444 B |
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 971 KiB After Width: | Height: | Size: 971 KiB |
1
src/index.css
Normal file
@@ -0,0 +1 @@
|
|||||||
|
@import "./css/index";
|
||||||
57
src/index.php
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
<?php get_header(); ?>
|
||||||
|
<main class="page">
|
||||||
|
<section class="page__logo">
|
||||||
|
<?php get_template_part("partials/logo"); ?>
|
||||||
|
</section>
|
||||||
|
<?php
|
||||||
|
$index = 0;
|
||||||
|
while (have_posts()):
|
||||||
|
|
||||||
|
the_post();
|
||||||
|
$index++;
|
||||||
|
?>
|
||||||
|
<section class="page__article<?php echo $index === 1 ? " is-first" : ""; ?>">
|
||||||
|
<article class="article">
|
||||||
|
<div class="article__date">
|
||||||
|
<svg class="icon icon--extra-small">
|
||||||
|
<use href="#icon-calendar"></use>
|
||||||
|
</svg>
|
||||||
|
<span class="article__date-text">
|
||||||
|
<?php the_time(get_option("date_format")); ?>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="article__content">
|
||||||
|
<h1 class="title">
|
||||||
|
<a href="<?php the_permalink(); ?>">
|
||||||
|
<?php the_title(); ?>
|
||||||
|
</a>
|
||||||
|
</h1>
|
||||||
|
<div class="content">
|
||||||
|
<?php the_content(
|
||||||
|
__(
|
||||||
|
'Continue reading <span class="meta-nav">»</span>',
|
||||||
|
"windycoys"
|
||||||
|
)
|
||||||
|
); ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</section>
|
||||||
|
<section class="page__metadata">
|
||||||
|
<?php get_template_part("partials/metadata"); ?>
|
||||||
|
</section>
|
||||||
|
<?php
|
||||||
|
endwhile;
|
||||||
|
?>
|
||||||
|
<section class="page__pagination">
|
||||||
|
<?php get_template_part("partials/pagination"); ?>
|
||||||
|
</section>
|
||||||
|
<section class="page__banner">
|
||||||
|
<?php get_template_part("partials/banner"); ?>
|
||||||
|
</section>
|
||||||
|
<section class="page__footer">
|
||||||
|
<?php get_template_part("partials/footer"); ?>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
<?php get_template_part("partials/navigation"); ?>
|
||||||
|
<?php get_footer(); ?>
|
||||||
22
src/index.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { App } from "./ts";
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", () => new App());
|
||||||
|
/*
|
||||||
|
import { Image, Menu, Navigation, ReplyToComment } from "./ts";
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
Array.from(document.querySelectorAll("img")).forEach((image) => {
|
||||||
|
new Image(image).start();
|
||||||
|
});
|
||||||
|
const menu = document.querySelector("#menu") as HTMLButtonElement;
|
||||||
|
const navigation = document.querySelector(".nav") as HTMLElement;
|
||||||
|
const form = document.querySelector("#respond") as HTMLDivElement;
|
||||||
|
const links = Array.from(
|
||||||
|
document.querySelectorAll(".comment__reply")
|
||||||
|
) as HTMLLinkElement[];
|
||||||
|
const btn = new Menu(document.querySelector("#menu"), window);
|
||||||
|
btn.init();
|
||||||
|
new Navigation(navigation, btn).init();
|
||||||
|
new ReplyToComment(form, links).init();
|
||||||
|
});
|
||||||
|
*/
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
<?php get_header(); the_post(); ?>
|
<?php get_header();
|
||||||
|
the_post();
|
||||||
|
?>
|
||||||
<main class="page">
|
<main class="page">
|
||||||
<section class="page__logo is-fixed">
|
<section class="page__logo is-fixed">
|
||||||
<?php get_template_part('partials/logo') ?>
|
<?php get_template_part("partials/logo"); ?>
|
||||||
</section>
|
</section>
|
||||||
<section class="page__content">
|
<section class="page__content">
|
||||||
<h1 class="title">
|
<h1 class="title">
|
||||||
@@ -12,14 +14,14 @@
|
|||||||
<div class="content">
|
<div class="content">
|
||||||
<?php the_content(); ?>
|
<?php the_content(); ?>
|
||||||
</div>
|
</div>
|
||||||
<?php get_template_part('partials/archive') ?>
|
<?php get_template_part("partials/archive"); ?>
|
||||||
</section>
|
</section>
|
||||||
<section class="page__banner">
|
<section class="page__banner">
|
||||||
<?php get_template_part('partials/banner') ?>
|
<?php get_template_part("partials/banner"); ?>
|
||||||
</section>
|
</section>
|
||||||
<section class="page__footer">
|
<section class="page__footer">
|
||||||
<?php get_template_part('partials/footer') ?>
|
<?php get_template_part("partials/footer"); ?>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
<?php get_template_part('partials/navigation') ?>
|
<?php get_template_part("partials/navigation"); ?>
|
||||||
<?php get_footer() ?>
|
<?php get_footer(); ?>
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
<?php get_header(); ?>
|
<?php get_header(); ?>
|
||||||
<main class="page">
|
<main class="page">
|
||||||
<section class="page__logo">
|
<section class="page__logo">
|
||||||
<?php get_template_part('partials/logo') ?>
|
<?php get_template_part("partials/logo"); ?>
|
||||||
</section>
|
</section>
|
||||||
<?php if (have_posts()): while (have_posts()) : the_post(); ?>
|
<?php if (have_posts()):
|
||||||
|
while (have_posts()):
|
||||||
|
the_post(); ?>
|
||||||
<section class="page__content">
|
<section class="page__content">
|
||||||
<h1 class="title">
|
<h1 class="title">
|
||||||
<?php the_title(); ?>
|
<?php the_title(); ?>
|
||||||
@@ -12,19 +14,23 @@
|
|||||||
<?php the_content(); ?>
|
<?php the_content(); ?>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<?php endwhile; ?>
|
<?php
|
||||||
<?php else: ?>
|
endwhile; ?>
|
||||||
|
<?php
|
||||||
|
else:
|
||||||
|
?>
|
||||||
<section class="page__content">
|
<section class="page__content">
|
||||||
<h1 class="title">Sorry, there is no article to display</h1>
|
<h1 class="title">Sorry, there is no article to display</h1>
|
||||||
<a class="link" href="<?php echo get_site_url() ?>/">Go back home</a>
|
<a class="link" href="<?php echo get_site_url(); ?>/">Go back home</a>
|
||||||
</section>
|
</section>
|
||||||
<?php endif; ?>
|
<?php
|
||||||
|
endif; ?>
|
||||||
<section class="page__banner">
|
<section class="page__banner">
|
||||||
<?php get_template_part('partials/banner') ?>
|
<?php get_template_part("partials/banner"); ?>
|
||||||
</section>
|
</section>
|
||||||
<section class="page__footer">
|
<section class="page__footer">
|
||||||
<?php get_template_part('partials/footer') ?>
|
<?php get_template_part("partials/footer"); ?>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
<?php get_template_part('partials/navigation') ?>
|
<?php get_template_part("partials/navigation"); ?>
|
||||||
<?php get_footer() ?>
|
<?php get_footer(); ?>
|
||||||
64
src/partials/archive.php
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
<?php
|
||||||
|
$posts = get_posts([
|
||||||
|
"numberposts" => -1,
|
||||||
|
"order" => "ASC",
|
||||||
|
]);
|
||||||
|
$archive = [];
|
||||||
|
|
||||||
|
foreach ($posts as $post) {
|
||||||
|
$date = strtotime($post->post_date);
|
||||||
|
$year = date("Y", $date);
|
||||||
|
$month = date("F", $date);
|
||||||
|
$day = date("jS", $date);
|
||||||
|
$url = get_permalink($post->ID);
|
||||||
|
$comments =
|
||||||
|
$post->comment_count === "1"
|
||||||
|
? "1 comment"
|
||||||
|
: $post->comment_count . " comments";
|
||||||
|
if ($post->post_status === "publish") {
|
||||||
|
$archive[$year][$month][] = [
|
||||||
|
"title" => $post->post_title,
|
||||||
|
"slug" => $post->post_name,
|
||||||
|
"author" => $post->post_author,
|
||||||
|
"day" => $day,
|
||||||
|
"url" => $url,
|
||||||
|
"comments" => $comments,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<div class="archive">
|
||||||
|
<ol class="archive__list">
|
||||||
|
<?php foreach ($archive as $year => $yearData): ?>
|
||||||
|
<li class="archive__item">
|
||||||
|
<h2 class="archive__heading">
|
||||||
|
<?php echo $year; ?>
|
||||||
|
</h2>
|
||||||
|
<ol class="archive__list archive__list--sub-list">
|
||||||
|
<?php foreach ($yearData as $month => $monthData): ?>
|
||||||
|
<li class="archive__item">
|
||||||
|
<h3 class="archive__subheading">
|
||||||
|
<?php echo $month; ?>
|
||||||
|
</h3>
|
||||||
|
<ol class="archive__list archive__list--sub-list">
|
||||||
|
<?php foreach ($monthData as $post): ?>
|
||||||
|
<li class="archive__item">
|
||||||
|
<span class="archive__date">
|
||||||
|
<?php echo $post["day"]; ?>
|
||||||
|
</span>
|
||||||
|
<a class="link" href="<?php echo $post["url"]; ?>">
|
||||||
|
<?php echo $post["title"]; ?>
|
||||||
|
</a>
|
||||||
|
<span class="archive__comments">
|
||||||
|
(<?php echo $post["comments"]; ?>)
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</ol>
|
||||||
|
</li>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</ol>
|
||||||
|
</li>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
26
src/partials/banner.php
Normal file
@@ -0,0 +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 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 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 href="#icon-rss"></use>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="social__item">
|
||||||
|
<?php echo do_shortcode("[paypal-donation]"); ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -1,61 +1,64 @@
|
|||||||
<?php
|
<?php
|
||||||
global $excerpt;
|
global $excerpt; ?>
|
||||||
?>
|
|
||||||
<div class="excerpt">
|
<div class="excerpt">
|
||||||
<div class="excerpt__head">
|
<div class="excerpt__head">
|
||||||
<div class="excerpt__title">
|
<div class="excerpt__title">
|
||||||
<a class="link" href="<?php echo $excerpt['url'] ?>">
|
<a class="link" href="<?php echo $excerpt["url"]; ?>">
|
||||||
<?php echo $excerpt['title'] ?>
|
<?php echo $excerpt["title"]; ?>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="excerpt__body">
|
<div class="excerpt__body">
|
||||||
<?php echo str_replace(
|
<?php echo str_replace(
|
||||||
'[...]',
|
"[...]",
|
||||||
'<a class="link" href="' . $excerpt['url'] . '" aria-label="Read full article">'
|
'<a class="link" href="' .
|
||||||
. '<svg class="icon icon--extra-extra-small"><use xlink:href="#icon-arrow-double-right"></use></a>',
|
$excerpt["url"] .
|
||||||
$excerpt['content']);
|
'" aria-label="Read full article">' .
|
||||||
?>
|
'<svg class="icon icon--extra-extra-small"><use href="#icon-arrow-double-right"></use></a>',
|
||||||
|
$excerpt["content"]
|
||||||
|
); ?>
|
||||||
</div>
|
</div>
|
||||||
<div class="excerpt__foot">
|
<div class="excerpt__foot">
|
||||||
<ul class="excerpt__list">
|
<ul class="excerpt__list">
|
||||||
<li class="excerpt__list-item">
|
<li class="excerpt__list-item">
|
||||||
<span class="excerpt__icon">
|
<span class="excerpt__icon">
|
||||||
<svg class="icon icon--extra-small" aria-hidden="true">
|
<svg class="icon icon--extra-small" aria-hidden="true">
|
||||||
<use xlink:href="#icon-profile"></use>
|
<use href="#icon-profile"></use>
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
<span class="excerpt__text"><?php echo $excerpt['author'] ?></span>
|
<span class="excerpt__text"><?php echo $excerpt["author"]; ?></span>
|
||||||
</li>
|
</li>
|
||||||
<?php if ($excerpt['words']) : ?>
|
<?php if ($excerpt["words"]): ?>
|
||||||
<li class="excerpt__list-item">
|
<li class="excerpt__list-item">
|
||||||
<span class="excerpt__icon">
|
<span class="excerpt__icon">
|
||||||
<svg class="icon icon--extra-small" aria-hidden="true">
|
<svg class="icon icon--extra-small" aria-hidden="true">
|
||||||
<use xlink:href="#icon-clock"></use>
|
<use href="#icon-clock"></use>
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
<span class="excerpt__text"><?php echo windycoys_get_read_time($excerpt['words']) ?> minute read</span>
|
<span class="excerpt__text"><?php echo windycoys_get_read_time(
|
||||||
|
$excerpt["words"]
|
||||||
|
); ?> minute read</span>
|
||||||
</li>
|
</li>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<li class="excerpt__list-item">
|
<li class="excerpt__list-item">
|
||||||
<span class="excerpt__icon">
|
<span class="excerpt__icon">
|
||||||
<svg class="icon icon--extra-small" aria-hidden="true">
|
<svg class="icon icon--extra-small" aria-hidden="true">
|
||||||
<use xlink:href="#icon-calendar"></use>
|
<use href="#icon-calendar"></use>
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
<span class="excerpt__text"><?php echo $excerpt['date'] ?></span>
|
<span class="excerpt__text"><?php echo $excerpt["date"]; ?></span>
|
||||||
</li>
|
</li>
|
||||||
<li class="excerpt__list-item">
|
<li class="excerpt__list-item">
|
||||||
<a class="excerpt__list-link" href="<?php the_permalink() ?>#comments">
|
<a class="excerpt__list-link" href="<?php the_permalink(); ?>#comments">
|
||||||
<span class="excerpt__icon">
|
<span class="excerpt__icon">
|
||||||
<svg class="icon icon--extra-small" aria-hidden="true">
|
<svg class="icon icon--extra-small" aria-hidden="true">
|
||||||
<use xlink:href="#icon-comments"></use>
|
<use href="#icon-comments"></use>
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
<span class="excerpt__text">
|
<span class="excerpt__text">
|
||||||
<?php
|
<?php
|
||||||
$number = $excerpt['comments'];
|
$number = $excerpt["comments"];
|
||||||
echo $number === '1' ? '1 comment' : $number . ' comments';
|
echo $number === "1" ? "1 comment" : $number . " comments";
|
||||||
?>
|
?>
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
14
src/partials/footer.php
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<footer class="footer">
|
||||||
|
<div class="footer__head">
|
||||||
|
<p class="footer__text">© <?php echo date("Y"); ?> <?php bloginfo(
|
||||||
|
"name"
|
||||||
|
); ?></p>
|
||||||
|
</div>
|
||||||
|
<div class="footer__body">
|
||||||
|
<a href="https://www.newsnow.co.uk/h/Sport/Football/Premier+League/Tottenham+Hotspur" title="Click here for more Tottenham Hotspur news from NewsNow">
|
||||||
|
<img src="<?php echo get_template_directory_uri(); ?>/images/newsnow_sw.gif" alt="As featured on NewsNow: Tottenham Hotspur news">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<?php get_template_part("icons"); ?>
|
||||||
|
</footer>
|
||||||
|
<?php wp_footer(); ?>
|
||||||
@@ -3,19 +3,19 @@
|
|||||||
<li class="metadata__list-item metadata__list-item--light">
|
<li class="metadata__list-item metadata__list-item--light">
|
||||||
<span class="metadata__icon">
|
<span class="metadata__icon">
|
||||||
<svg class="icon icon--small" aria-hidden="true">
|
<svg class="icon icon--small" aria-hidden="true">
|
||||||
<use xlink:href="#icon-calendar"></use>
|
<use href="#icon-calendar"></use>
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
<span class="metadata__text">
|
<span class="metadata__text">
|
||||||
<?php the_time(get_option('date_format')) ?>
|
<?php the_time(get_option("date_format")); ?>
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
<?php if (is_user_logged_in()): ?>
|
<?php if (is_user_logged_in()): ?>
|
||||||
<li class="metadata__list-item">
|
<li class="metadata__list-item">
|
||||||
<a class="metadata__link" href="<?php echo get_edit_post_link() ?>">
|
<a class="metadata__link" href="<?php echo get_edit_post_link(); ?>">
|
||||||
<span class="metadata__icon">
|
<span class="metadata__icon">
|
||||||
<svg class="icon icon--small" aria-hidden="true">
|
<svg class="icon icon--small" aria-hidden="true">
|
||||||
<use xlink:href="#icon-edit"></use>
|
<use href="#icon-edit"></use>
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
<span class="metadata__text">
|
<span class="metadata__text">
|
||||||
@@ -25,10 +25,10 @@
|
|||||||
</li>
|
</li>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<li class="metadata__list-item">
|
<li class="metadata__list-item">
|
||||||
<a class="metadata__link" href="<?php the_permalink() ?>#comments">
|
<a class="metadata__link" href="<?php the_permalink(); ?>#comments">
|
||||||
<span class="metadata__icon">
|
<span class="metadata__icon">
|
||||||
<svg class="icon icon--small" aria-hidden="true">
|
<svg class="icon icon--small" aria-hidden="true">
|
||||||
<use xlink:href="#icon-comments"></use>
|
<use href="#icon-comments"></use>
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
<span class="metadata__text">
|
<span class="metadata__text">
|
||||||
@@ -39,17 +39,17 @@
|
|||||||
<li class="metadata__list-item">
|
<li class="metadata__list-item">
|
||||||
<span class="metadata__icon">
|
<span class="metadata__icon">
|
||||||
<svg class="icon icon--small" aria-hidden="true">
|
<svg class="icon icon--small" aria-hidden="true">
|
||||||
<use xlink:href="#icon-tag"></use>
|
<use href="#icon-tag"></use>
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
<span class="metadata__text">
|
<span class="metadata__text">
|
||||||
<?php windycoys_the_tags() ?>
|
<?php windycoys_the_tags(); ?>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<li class="metadata__list-item">
|
<li class="metadata__list-item">
|
||||||
<div
|
<div
|
||||||
class="fb-share-button"
|
class="fb-share-button"
|
||||||
data-href="<?php echo get_the_permalink() ?>"
|
data-href="<?php echo get_the_permalink(); ?>"
|
||||||
data-layout="box_count"
|
data-layout="box_count"
|
||||||
data-size="small"
|
data-size="small"
|
||||||
data-mobile-iframe="false"
|
data-mobile-iframe="false"
|
||||||
@@ -66,8 +66,8 @@
|
|||||||
<li class="metadata__list-item">
|
<li class="metadata__list-item">
|
||||||
<a
|
<a
|
||||||
class="twitter-share-button"
|
class="twitter-share-button"
|
||||||
href="<?php echo windycoys_get_twitter_share_url() ?>"
|
href="<?php echo windycoys_get_twitter_share_url(); ?>"
|
||||||
data-url="<?php echo get_the_permalink() ?>"
|
data-url="<?php echo get_the_permalink(); ?>"
|
||||||
>
|
>
|
||||||
Tweet
|
Tweet
|
||||||
</a>
|
</a>
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
<?php
|
<?php
|
||||||
require get_template_directory() . '/classes/walker.navigation.php';
|
require get_template_directory() . "/classes/walker.navigation.php";
|
||||||
|
|
||||||
$menu = wp_nav_menu(array(
|
$menu = wp_nav_menu([
|
||||||
'echo' => false,
|
"echo" => false,
|
||||||
'menu' => 'navigation',
|
"menu" => "navigation",
|
||||||
'container_class' => 'nav__menu',
|
"container_class" => "nav__menu",
|
||||||
'menu_class' => 'nav__list',
|
"menu_class" => "nav__list",
|
||||||
'walker' => new Windycoys_Walker_Navigation(),
|
"walker" => new Windycoys_Walker_Navigation(),
|
||||||
));
|
]);
|
||||||
|
|
||||||
$menu = str_replace(
|
$menu = str_replace(
|
||||||
'ul id="menu-navigation" class="nav__list"',
|
'ul id="menu-navigation" class="nav__list"',
|
||||||
@@ -15,20 +15,22 @@ $menu = str_replace(
|
|||||||
$menu
|
$menu
|
||||||
);
|
);
|
||||||
?>
|
?>
|
||||||
<nav class="nav<?php echo is_user_logged_in() ? ' logged-in' : '' ?>" aria-label="Main Navigation">
|
<button class="menu" id="menu">
|
||||||
<button class="menu">
|
|
||||||
<span class="menu__line"></span>
|
<span class="menu__line"></span>
|
||||||
<span class="menu__line"></span>
|
<span class="menu__line"></span>
|
||||||
<span class="menu__line"></span>
|
<span class="menu__line"></span>
|
||||||
<span class="menu__text">Open menu</span>
|
<span class="menu__text">Open menu</span>
|
||||||
</button>
|
</button>
|
||||||
|
<nav class="nav<?php echo is_user_logged_in()
|
||||||
|
? " logged-in"
|
||||||
|
: ""; ?>" aria-label="Main Navigation">
|
||||||
<div class="nav__content">
|
<div class="nav__content">
|
||||||
<div class="nav__head">
|
<div class="nav__head">
|
||||||
<h2 class="nav__title">
|
<h2 class="nav__title">
|
||||||
<a class="nav__title-link" href="<?php get_site_url() ?>/" aria-label="Back Home" tabindex="-1">
|
<a class="nav__title-link" href="<?php get_site_url(); ?>/" aria-label="Back Home" tabindex="-1">
|
||||||
<span class="nav__title-link-icon">
|
<span class="nav__title-link-icon">
|
||||||
<svg class="icon icon--extra-large">
|
<svg class="icon icon--extra-large">
|
||||||
<use xlink:href="#icon-windy"></use>
|
<use href="#icon-windy"></use>
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
<img src="<?php echo get_template_directory_uri(); ?>/images/icon.png" alt="WindyCOYS logo" class="nav__title-link-image" data-load-transition="false">
|
<img src="<?php echo get_template_directory_uri(); ?>/images/icon.png" alt="WindyCOYS logo" class="nav__title-link-image" data-load-transition="false">
|
||||||
@@ -36,23 +38,23 @@ $menu = str_replace(
|
|||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="nav__body">
|
<div class="nav__body">
|
||||||
<?php echo $menu ?>
|
<?php echo $menu; ?>
|
||||||
</div>
|
</div>
|
||||||
<div class="nav__foot">
|
<div class="nav__foot">
|
||||||
<div class="nav__social">
|
<div class="nav__social">
|
||||||
<a class="nav__social-link" href="https://www.facebook.com/WindyCOYS/" aria-label="WindyCOYS on Facebook">
|
<a class="nav__social-link" href="https://www.facebook.com/WindyCOYS/" aria-label="WindyCOYS on Facebook">
|
||||||
<svg class="icon icon--large">
|
<svg class="icon icon--large">
|
||||||
<use xlink:href="#icon-facebook"></use>
|
<use href="#icon-facebook"></use>
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
<a class="nav__social-link" href="https://twitter.com/windycoys/" aria-label="WindyCOYS on Twitter">
|
<a class="nav__social-link" href="https://twitter.com/windycoys/" aria-label="WindyCOYS on Twitter">
|
||||||
<svg class="icon icon--large">
|
<svg class="icon icon--large">
|
||||||
<use xlink:href="#icon-twitter"></use>
|
<use href="#icon-twitter"></use>
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="nav__search">
|
<div class="nav__search">
|
||||||
<?php get_search_form() ?>
|
<?php get_search_form(); ?>
|
||||||
</div>
|
</div>
|
||||||
<button class="nav__close">
|
<button class="nav__close">
|
||||||
<span class="nav__close-bar"></span>
|
<span class="nav__close-bar"></span>
|
||||||
84
src/partials/pagination.php
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
<?php
|
||||||
|
global $wp;
|
||||||
|
$url = home_url($wp->request);
|
||||||
|
$base_url = substr($url, 0, strpos($url, "page/"));
|
||||||
|
$current = get_query_var("paged");
|
||||||
|
$total = $wp_query->max_num_pages;
|
||||||
|
$links = paginate_links([
|
||||||
|
"type" => "array",
|
||||||
|
"prev_next" => false,
|
||||||
|
"current" => $current,
|
||||||
|
"end_size" => 2,
|
||||||
|
]);
|
||||||
|
|
||||||
|
function get_previous_url($base_url, $page)
|
||||||
|
{
|
||||||
|
$qs = $_SERVER["QUERY_STRING"];
|
||||||
|
$url = $base_url . "page/" . ($page - 1);
|
||||||
|
if ($qs) {
|
||||||
|
$url .= "/?" . $qs;
|
||||||
|
}
|
||||||
|
return $url;
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_next_url($base_url, $page)
|
||||||
|
{
|
||||||
|
$qs = $_SERVER["QUERY_STRING"];
|
||||||
|
$url = $base_url . "page/";
|
||||||
|
if ($page === 0) {
|
||||||
|
$url .= "2";
|
||||||
|
} else {
|
||||||
|
$url .= $page + 1;
|
||||||
|
}
|
||||||
|
if ($qs) {
|
||||||
|
$url .= "/?" . $qs;
|
||||||
|
}
|
||||||
|
return $url;
|
||||||
|
}
|
||||||
|
function format_link($link)
|
||||||
|
{
|
||||||
|
if (strpos($link, "page-numbers dots")) {
|
||||||
|
return '<span class="pagination__dots"><svg class="icon icon--extra-small"><use href="#icon-ellipsis"></use></svg></span>';
|
||||||
|
}
|
||||||
|
$link = str_replace("page-numbers current", "pagination__current", $link);
|
||||||
|
$link = str_replace("page-numbers", "link", $link);
|
||||||
|
return $link;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($links) > 1): ?>
|
||||||
|
<div class="pagination">
|
||||||
|
<?php if ($current > 1): ?>
|
||||||
|
<div class="pagination__previous">
|
||||||
|
<a class="pagination__link" href="<?php echo get_previous_url(
|
||||||
|
$base_url,
|
||||||
|
$current
|
||||||
|
); ?>" aria-label="Previous page">
|
||||||
|
<svg class="icon icon--small">
|
||||||
|
<use href="#icon-chevron-left"></use>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
<nav class="pagination__content" role="navigation" aria-label="Pagination navigation">
|
||||||
|
<ul class="pagination__list">
|
||||||
|
<?php foreach ($links as $link) { ?>
|
||||||
|
<li class="pagination__item">
|
||||||
|
<?php echo format_link($link); ?>
|
||||||
|
</li>
|
||||||
|
<?php } ?>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
<?php if ($current < $total): ?>
|
||||||
|
<div class="pagination__next">
|
||||||
|
<a class="pagination__link" href="<?php echo get_next_url(
|
||||||
|
$base_url,
|
||||||
|
$current
|
||||||
|
); ?>" aria-label="Next page">
|
||||||
|
<svg class="icon icon--small">
|
||||||
|
<use href="#icon-chevron-right"></use>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
@@ -1,37 +1,39 @@
|
|||||||
<?php
|
<?php
|
||||||
global $podcast;
|
global $podcast;
|
||||||
$artwork = get_template_directory_uri().'/images/'.$podcast['id'].'.png';
|
$artwork = get_template_directory_uri() . "/images/" . $podcast["id"] . ".png";
|
||||||
?>
|
?>
|
||||||
<div class="excerpt">
|
<div class="excerpt">
|
||||||
<div class="excerpt__head">
|
<div class="excerpt__head">
|
||||||
<img
|
<img
|
||||||
class="excerpt__image"
|
class="excerpt__image"
|
||||||
title="<?php echo $podcast['podcast_title'] ?>"
|
title="<?php echo $podcast["podcast_title"]; ?>"
|
||||||
src="<?php echo $artwork ?>">
|
src="<?php echo $artwork; ?>">
|
||||||
<h3 class="excerpt__title">
|
<h3 class="excerpt__title">
|
||||||
<a class="link" href="<?php echo $podcast['url'] ?>">
|
<a class="link" href="<?php echo $podcast["url"]; ?>">
|
||||||
<?php echo $podcast['title'] ?>
|
<?php echo $podcast["title"]; ?>
|
||||||
</a>
|
</a>
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="excerpt__body">
|
<div class="excerpt__body">
|
||||||
<?php echo $podcast['description'] ?>
|
<?php echo $podcast["description"]; ?>
|
||||||
</div>
|
</div>
|
||||||
<div class="excerpt__foot">
|
<div class="excerpt__foot">
|
||||||
<audio class="excerpt__audio" src="<?php echo $podcast['audio'] ?>" controls="true">
|
<audio class="excerpt__audio" src="<?php echo $podcast[
|
||||||
|
"audio"
|
||||||
|
]; ?>" controls="true">
|
||||||
Your browser does not support the <code>audio</code> element.
|
Your browser does not support the <code>audio</code> element.
|
||||||
</audio>
|
</audio>
|
||||||
<ul class="excerpt__links">
|
<ul class="excerpt__links">
|
||||||
<li class="excerpt__link">
|
<li class="excerpt__link">
|
||||||
<a
|
<a
|
||||||
class="link"
|
class="link"
|
||||||
href="<?php echo $podcast['audio'] ?>"
|
href="<?php echo $podcast["audio"]; ?>"
|
||||||
title="Download the MP3">
|
title="Download the MP3">
|
||||||
Download MP3
|
Download MP3
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="excerpt__link">
|
<li class="excerpt__link">
|
||||||
<a class="link" href="<?php echo $podcast['podcast_episodes'] ?>">
|
<a class="link" href="<?php echo $podcast["podcast_episodes"]; ?>">
|
||||||
More episodes
|
More episodes
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
@@ -40,10 +42,10 @@ $artwork = get_template_directory_uri().'/images/'.$podcast['id'].'.png';
|
|||||||
<li class="excerpt__list-item">
|
<li class="excerpt__list-item">
|
||||||
<span class="excerpt__icon">
|
<span class="excerpt__icon">
|
||||||
<svg class="icon icon--extra-small" aria-hidden="true">
|
<svg class="icon icon--extra-small" aria-hidden="true">
|
||||||
<use xlink:href="#icon-calendar"></use>
|
<use href="#icon-calendar"></use>
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
<span class="excerpt__text"><?php echo $podcast['date'] ?></span>
|
<span class="excerpt__text"><?php echo $podcast["date"]; ?></span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
62
src/search.php
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
<?php
|
||||||
|
global $wp_query;
|
||||||
|
get_header();
|
||||||
|
$query = $_GET["s"];
|
||||||
|
$results = $wp_query->found_posts;
|
||||||
|
?>
|
||||||
|
<main class="page">
|
||||||
|
<section class="page__logo is-fixed">
|
||||||
|
<?php get_template_part("partials/logo"); ?>
|
||||||
|
</section>
|
||||||
|
<section class="page__content">
|
||||||
|
<?php if ($results === 0): ?>
|
||||||
|
<h1 class="title">Sorry, no results found.</h1>
|
||||||
|
<a class="link" href="<?php echo get_site_url(); ?>/">Go back home</a>
|
||||||
|
<?php else: ?>
|
||||||
|
<h1 class="title">
|
||||||
|
<?php echo $results .
|
||||||
|
" result" .
|
||||||
|
($results !== "1" ? "s" : "") .
|
||||||
|
' matching "' .
|
||||||
|
$query .
|
||||||
|
'" found.'; ?>
|
||||||
|
</h1>
|
||||||
|
<?php endif; ?>
|
||||||
|
<ol>
|
||||||
|
<?php if (have_posts()):
|
||||||
|
while (have_posts()):
|
||||||
|
the_post(); ?>
|
||||||
|
<li>
|
||||||
|
<?php
|
||||||
|
$excerpt = [
|
||||||
|
"title" => get_the_title(),
|
||||||
|
"url" => get_the_permalink(),
|
||||||
|
"content" => get_the_excerpt(),
|
||||||
|
"author" => get_the_author_meta("nickname"),
|
||||||
|
"comments" => get_comments_number(),
|
||||||
|
"date" =>
|
||||||
|
get_the_date(get_option("date_format"), $id) .
|
||||||
|
" at " .
|
||||||
|
get_the_date(get_option("time_format"), $id),
|
||||||
|
"words" => count(explode(" ", $post->post_content)),
|
||||||
|
];
|
||||||
|
get_template_part("partials/excerpt");
|
||||||
|
?>
|
||||||
|
</li>
|
||||||
|
<?php
|
||||||
|
endwhile;
|
||||||
|
endif; ?>
|
||||||
|
</ol>
|
||||||
|
</section>
|
||||||
|
<section class="page__pagination">
|
||||||
|
<?php get_template_part("partials/pagination"); ?>
|
||||||
|
</section>
|
||||||
|
<section class="page__banner">
|
||||||
|
<?php get_template_part("partials/banner"); ?>
|
||||||
|
</section>
|
||||||
|
<section class="page__footer">
|
||||||
|
<?php get_template_part("partials/footer"); ?>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
<?php get_template_part("partials/navigation"); ?>
|
||||||
|
<?php get_footer(); ?>
|
||||||