1
0
mirror of https://github.com/dwaxweiler/connector-mobilizon synced 2025-06-05 21:59:25 +02:00

101 Commits

Author SHA1 Message Date
4fd516bcf8 release version 0.11.3 2023-07-25 22:25:55 +02:00
238fdcf261 release version 0.11.2 2023-07-25 22:14:24 +02:00
eaf1ffa0c8 confirm compatibility with WordPress 6.3 2023-07-25 20:17:28 +02:00
48477a158e add missing changelog entry 2023-07-25 20:16:49 +02:00
9b4778b494 npm audit fix 2023-07-25 20:04:04 +02:00
60363d8a5c update dependencies 2023-07-25 20:03:31 +02:00
51ae9b9616 update dependencies 2023-05-07 18:00:02 +02:00
637142d00d release version 0.11.1 2023-05-07 17:46:27 +02:00
163d0d27a6 revert minimum PHP version to 7.4 to allow some more time for upgrading PHP 2023-05-07 17:41:36 +02:00
d0ada74642 release version 0.11.0 2023-03-29 23:00:36 +02:00
b242c33733 remove bootstrap from qodana.yaml 2023-03-29 22:47:59 +02:00
cac233ec45 set minimum PHP version to 8.0 2023-03-29 22:32:29 +02:00
9e394ac837 confirm compatibility with WP 6.2 2023-03-29 22:30:45 +02:00
b2811ad169 update dependencies 2023-03-29 22:26:44 +02:00
40f351efbf prefer online 2023-02-16 08:10:10 +01:00
ad0a9f77f8 ignore scripts too 2023-02-16 08:04:32 +01:00
76a2217582 use npm ci in qodana instead 2023-02-16 08:00:01 +01:00
742b16808c add qodana.yaml 2023-02-16 07:57:10 +01:00
41419e6550 Merge pull request #14 from wordpress-connector-for-mobilizon/update-deps
Update dependencies
2023-02-12 18:13:49 +01:00
ab7e7274d0 fix versions 2023-02-12 18:11:24 +01:00
b7742d3803 npm audit fix 2023-02-12 18:05:30 +01:00
e5dc313f6c update luxon 2023-02-12 18:02:33 +01:00
3b4c53da81 update dev deps 2023-02-12 17:56:48 +01:00
87434fc1c1 add qodana 2023-02-12 17:20:20 +01:00
81c4759a9e release version 0.10.1 2022-10-26 23:41:32 +02:00
ad5f1753cb change node version of test pipeline 2022-10-26 23:34:03 +02:00
6cde75b65c npm audit fix 2022-10-26 23:32:23 +02:00
5a3550568c update dependencies 2022-10-26 23:31:05 +02:00
23390db0a9 update dev dependencies 2022-10-26 23:18:39 +02:00
4d5acc3714 confirm compatibility with WordPress 6.1 2022-10-26 22:57:39 +02:00
72df79b092 add note to release procedure about adding new files in SVN 2022-06-19 09:52:14 +02:00
d1be3b169d release version 0.10.0 2022-06-19 09:48:06 +02:00
5af00fc85e reuse constant 2022-06-19 09:30:36 +02:00
f76653f05c remove unused Gutenberg block editor dependency 2022-06-19 09:24:42 +02:00
995f6681e6 split up release procedure step to be clearer 2022-06-19 09:19:58 +02:00
a6f0bd9584 update dev dependencies 2022-06-19 09:18:45 +02:00
59970db3b8 add comma 2022-06-10 12:37:17 +02:00
d2e3c55e1a add missing changelog entry 2022-06-10 12:33:45 +02:00
99d97c5a5e update dev dependencies 2022-06-09 20:24:32 +02:00
64a61426e0 fix typo 2022-06-09 20:24:04 +02:00
3f09fbb563 create installation section in readme.txt 2022-06-09 20:16:07 +02:00
3360a9e7a7 add Gutenberg events list block 2022-06-09 20:09:08 +02:00
c042851184 Merge branch 'main' into block 2022-06-03 23:32:46 +02:00
09a47029f3 add missing changelog entries to readme.txt 2022-05-19 18:30:10 +02:00
977d7e57c6 release version 0.9.1 2022-05-19 18:27:06 +02:00
e84a4cfc73 real fix 2022-05-19 18:25:38 +02:00
eb10341337 fix WordPress compatibility version number 2022-05-19 18:22:04 +02:00
f769d5d8c1 release version 0.9.0 2022-05-19 18:16:17 +02:00
f54126babc confirm compatibility with WordPress 6.0 2022-05-19 18:09:37 +02:00
7dab9d71c6 update dependencies 2022-05-19 18:02:49 +02:00
0479deca56 improve explanation of group name filter 2022-05-19 08:39:20 +02:00
c51610b054 fix displaying error message for the case the group is not found 2022-05-19 08:08:21 +02:00
367a1c97b2 update changelog 2022-03-20 10:27:30 +01:00
01c5f19e39 switch to exact version number dependencies 2022-03-20 10:25:05 +01:00
42c2c945ba npm audit fix 2022-03-20 10:23:31 +01:00
9311fb42bb update dependencies 2022-03-20 10:21:38 +01:00
ba3b069527 update dev dependencies 2022-03-20 10:15:34 +01:00
87a55e5302 update dev dependencies 2022-02-18 16:30:46 +01:00
dd42ebf712 release version 0.8.0 2022-01-09 13:10:38 +01:00
0c6bf3d3a4 npm audit fix 2022-01-09 12:59:52 +01:00
77b58ccf07 add changelog reference to README 2022-01-09 12:58:27 +01:00
184d5627a3 trim events' location, change variable name 2022-01-09 12:54:58 +01:00
ec12889815 add babel 2022-01-09 12:35:26 +01:00
4270145a55 update changelog 2022-01-09 12:16:38 +01:00
3ed5129cd2 update dependency luxon 2022-01-09 12:14:49 +01:00
6dca75b2ce update dev dependencies c8, lint-staged 2022-01-09 12:13:15 +01:00
647629b5a9 update dev dependencies ava, eslint, eslint-plugin-ava and migrate to ES modules 2022-01-09 12:11:24 +01:00
fa91324f18 confirm compatibility with WordPress 5.9 2022-01-05 17:45:40 +01:00
c47ed3385d release version 0.7.0 2021-12-23 17:51:22 +01:00
740e59ea66 improve a changelog entry 2021-12-23 17:17:59 +01:00
a287d93b9f Fix Invalid DateTime on event end time being null 2021-12-23 17:16:03 +01:00
688868cbe9 update dependencies graphql, luxon 2021-12-23 16:49:43 +01:00
924aa095ff update dev dependencies copy-webpack-plugin, lint-staged 2021-12-23 16:44:50 +01:00
876c3e3840 Merge branch 'main' into block 2021-12-14 17:54:47 +01:00
d5419448e1 remove unneeded folder 2021-12-14 17:51:13 +01:00
53d08e7174 Merge branch 'main' into block 2021-12-14 15:33:07 +01:00
d32320a540 remove bartlett/php-compatinfo 2021-12-09 19:58:45 +01:00
acf86e42f5 add eslint and prettier to pre-commit hook 2021-12-09 19:54:38 +01:00
35e29cf793 add prettier 2021-12-09 19:46:19 +01:00
b0f4ea8d7a add missing test change 2021-12-09 19:05:29 +01:00
34aaaa7db9 add group not found error message #11 2021-12-09 19:03:00 +01:00
80dd0c9c50 update changelog too 2021-12-09 18:35:38 +01:00
f8583423a4 set minimum PHP version to oldest officially supported 7.4 2021-12-09 18:34:46 +01:00
86ed058363 update dependencies graphql, graphql-request 2021-12-09 18:23:57 +01:00
2033cc7328 prevent husky from being installed in CI 2021-12-09 18:20:03 +01:00
fad14485bd add husky and pre-commit hook to run tests 2021-12-09 18:14:16 +01:00
8d4735f138 update dev dependencies copy-webpack-plugin, eslint, jsdom, webpack 2021-12-09 18:09:25 +01:00
2b9cde92ac npm audit fix 2021-11-10 21:36:12 +01:00
51686efe93 update dependencies graphql, luxon 2021-11-09 22:23:48 +01:00
78f7c693e8 update dev dependencies eslint, webpack 2021-11-09 22:19:18 +01:00
632cdfdb0d add missing changelog entry 2021-11-09 22:11:49 +01:00
af46fe974f use webpack directly and npm scripts more in general 2021-11-09 22:10:35 +01:00
4417ae78b6 update jsdom, webpack 2021-11-01 21:19:15 +01:00
b487013cac refactor entry file 2021-11-01 21:17:10 +01:00
2725707296 use const instead of let 2021-11-01 21:06:07 +01:00
0b7021942e add basic block 2021-11-01 20:55:37 +01:00
0ed7a57a01 Update codeql-analysis.yml 2021-10-26 20:39:14 +02:00
4abde347a4 update dependencies graphql, graphql-request, and changelog 2021-10-26 20:33:56 +02:00
036fd1da41 update dev dependencies c8, eslint, eslint-plugin-ava, jsdom, webpack, webpack-cli, webpack-stream 2021-10-26 20:11:16 +02:00
d1617fed00 add composer commands 2021-08-26 22:30:43 +02:00
fada60e0b8 set minimum PHP version to oldest officially supported 7.3 2021-08-26 22:30:18 +02:00
58 changed files with 14214 additions and 13827 deletions

View File

@ -3,29 +3,50 @@
"browser": true, "browser": true,
"es2020": true "es2020": true
}, },
"globals": {
"MOBILIZON_CONNECTOR": "readonly"
},
"parser": "@babel/eslint-parser",
"extends": [ "extends": [
"eslint:recommended", "eslint:recommended",
"plugin:ava/recommended" "plugin:ava/recommended",
"plugin:@wordpress/eslint-plugin/recommended"
], ],
"parserOptions": { "parserOptions": {
"ecmaVersion": 11, "ecmaVersion": 11,
"sourceType": "module" "requireConfigFile": false,
"sourceType": "module",
"babelOptions": {
"presets": ["@babel/preset-react"]
}
}, },
"plugins": [ "plugins": [
"ava" "ava",
], "jsx"
"ignorePatterns": [
"gulpfile.js"
], ],
"rules": { "rules": {
"import/no-unresolved": [
"off"
],
"indent": [ "indent": [
"error", "error",
2 2
], ],
"no-console": [
"error", {
"allow": ["error"]
}
],
"prettier/prettier": [
"off"
],
"quotes": [ "quotes": [
"error", "error",
"single" "single"
], ],
"react/jsx-key": [
"off"
],
"semi": [ "semi": [
"error", "error",
"never" "never"

View File

@ -9,14 +9,14 @@
# the `language` matrix defined below to confirm you have the correct set of # the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages. # supported CodeQL languages.
# #
name: "CodeQL" name: 'CodeQL'
on: on:
push: push:
branches: [ main ] branches: [main]
pull_request: pull_request:
# The branches below must be a subset of the branches above # The branches below must be a subset of the branches above
branches: [ main ] branches: [main]
schedule: schedule:
- cron: '39 19 * * 4' - cron: '39 19 * * 4'
@ -32,40 +32,40 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
language: [ 'javascript' ] language: ['javascript']
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
# Learn more: # Learn more:
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v2 uses: actions/checkout@v2
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@v1 uses: github/codeql-action/init@v1
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file. # If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file. # By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file. # Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main # queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below) # If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild #- name: Autobuild
uses: github/codeql-action/autobuild@v1 # uses: github/codeql-action/autobuild@v1
# Command-line programs to run using the OS shell. # Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl # 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project # and modify them (or add more) to build your code if your project
# uses a compiled language # uses a compiled language
#- run: | - run: |
# make bootstrap npm install
# make release npm run build-prod
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1 uses: github/codeql-action/analyze@v1

View File

@ -0,0 +1,23 @@
name: Qodana
on:
workflow_dispatch:
pull_request:
push:
branches:
- main
- 'releases/*'
jobs:
qodana:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: 'Qodana Scan'
uses: JetBrains/qodana-action@v2022.3.2
env:
QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }}
- uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: ${{ runner.temp }}/qodana/results/qodana.sarif.json

View File

@ -5,25 +5,26 @@ name: Node.js CI
on: on:
push: push:
branches: [ main ] branches: [main]
pull_request: pull_request:
branches: [ main ] branches: [main]
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
node-version: [14.x] node-version: [16.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/ # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }} - name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2 uses: actions/setup-node@v2
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
- run: npm ci - name: Upgrade NPM
- run: npm run build-prod run: npm install -g npm
- run: npm ci --ignore-scripts
- run: npm run build-prod

5
.gulp.json Normal file
View File

@ -0,0 +1,5 @@
{
"flags": {
"gulpfile": "gulpfile.cjs"
}
}

5
.husky/pre-commit Executable file
View File

@ -0,0 +1,5 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npm test
npx lint-staged

8
.prettierignore Normal file
View File

@ -0,0 +1,8 @@
.idea
build
coverage
node_modules
vendor
.eslintrc.json
composer.lock
LICENSE.md

4
.prettierrc.json Normal file
View File

@ -0,0 +1,4 @@
{
"semi": false,
"singleQuote": true
}

View File

@ -4,34 +4,43 @@ Connector for Mobilizon allows you to display the upcoming events of [Mobilizon]
More details can be found in the [WordPress Plugin Directory](https://wordpress.org/plugins/connector-mobilizon/). More details can be found in the [WordPress Plugin Directory](https://wordpress.org/plugins/connector-mobilizon/).
The current changelog can be found under [source/changelog.txt](source/changelog.txt).
## Development ## Development
### Setup ### Setup
1. Make sure `npm` and `composer` are installed. 1. Make sure `npm` and `composer` are installed.
2. Run: `npm install` 2. Run: `npm install`
3. Run: `php composer.phar install` 3. Run: `composer install`
### Development build ### Development build
1. Build: `npm run build-dev` 1. Build: `npm run build-dev`
2. Make sure to keep `changelog.txt` up-to-date. 2. Make sure to keep `changelog.txt` up-to-date.
### Release procedure ### Release procedure
1. Make sure `changelog.txt` is up-to-date. Use a new version number and copy over the new section into `readme.txt`.
2. Update `package.json` with the same version number. 1. Make sure `changelog.txt` is up-to-date.
3. Update the `package-lock.json`: `npm i --package-lock-only` 2. Use a new version number and copy over the new section into `readme.txt`.
4. Build: `npm run build-prod` 3. Update `package.json` with the same version number.
5. Determine minimum PHP version for code and update package.json if needed: `./vendor/bin/phpcompatinfo analyser:run ./source` 4. Update the `package-lock.json`: `npm i --package-lock-only`
5. Build: `npm run build-prod`
6. Make sure screenshots are up-to-date. 6. Make sure screenshots are up-to-date.
7. Copy the built plugin into `/trunk` of SVN. 7. Copy the built plugin into `/trunk` of SVN.
8. Create a new tag of the new version: `svn cp trunk tags/<version>` 8. Create a new tag of the new version: `svn cp trunk tags/<version>`
9. Check the version number occurrences in both folders. 9. Check the version number occurrences in both folders.
10. Commit everything together to the release SVN: `svn ci -m "release version <version>"` 10. Commit everything together to the release SVN: `svn ci -m "release version <version>"` Make sure to add new files beforehand.
11. Commit the new version in git with the same message. 11. Commit the new version in git with the same message.
12. Tag the new version: `git tag v<version>` 12. Tag the new version: `git tag v<version>`
13. Push the new tag to the repository: `git push --tags` 13. Push the new tag to the repository: `git push --tags`
### Other commands ### Other commands
- Run ESLint: `npm run eslint` - Run ESLint: `npm run eslint`
- Run JavaScript code coverage with tests: `npm run coverage` - Run JavaScript code coverage with tests: `npm run coverage`
- Run tests: `npm test` - Run tests: `npm test`
- Delete build folder: `gulp clean` - Delete build folder: `npm run clean`
- Update PHP dependencies: `composer update`
- Check for direct PHP dependency updates: `composer outdated --direct`
- Format code with prettier: `npm run format`

BIN
assets/screenshot-5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

3
babel.config.json Normal file
View File

@ -0,0 +1,3 @@
{
"presets": ["@babel/preset-env", "@babel/preset-react"]
}

View File

@ -1,5 +1 @@
{ {}
"require": {
"bartlett/php-compatinfo": "^5.4"
}
}

2912
composer.lock generated

File diff suppressed because it is too large Load Diff

50
gulpfile.cjs Normal file
View File

@ -0,0 +1,50 @@
/* eslint-disable no-undef */
const { dest, src } = require('gulp')
const replace = require('gulp-replace')
const PACKAGE = require('./package.json')
const FOLDER_BUILD = './build'
function injectMetadata() {
return src(
[
FOLDER_BUILD + '/front/block-events-loader.js',
FOLDER_BUILD + '/front/events-loader.js',
FOLDER_BUILD + '/' + PACKAGE.name + '.php',
FOLDER_BUILD + '/includes/constants.php',
FOLDER_BUILD + '/readme.txt',
],
{ base: './' }
)
.pipe(replace('<wordpress-author-name>', PACKAGE.author.name))
.pipe(replace('<wordpress-author-url>', PACKAGE.author.url))
.pipe(replace('<wordpress-description>', PACKAGE.description))
.pipe(replace('<wordpress-donation-link>', PACKAGE.funding.url))
.pipe(replace('<wordpress-license>', PACKAGE.license))
.pipe(
replace(
'<wordpress-minimum-version>',
PACKAGE.additionalDetails.wordpressMinimumVersion
)
)
.pipe(replace('<wordpress-name>', PACKAGE.name))
.pipe(replace('<wordpress-nice-name>', PACKAGE.additionalDetails.niceName))
.pipe(
replace(
'<wordpress-php-minimum-version>',
PACKAGE.additionalDetails.phpMinimumVersion
)
)
.pipe(
replace(
'<wordpress-tested-up-to-version>',
PACKAGE.additionalDetails.wordpressTestedUpToVersion
)
)
.pipe(replace('<wordpress-version>', PACKAGE.version))
.pipe(dest('.'))
}
exports.inject = injectMetadata

View File

@ -1,80 +0,0 @@
const { dest, series, src } = require('gulp');
const del = require('del');
const replace = require('gulp-replace');
const webpack = require('webpack-stream');
const PACKAGE = require('./package.json');
const FOLDER_SOURCE = './source'
const FOLDER_BUILD = './build';
let mode = 'development';
function clean(cb) {
del(FOLDER_BUILD);
cb();
}
const eventsLoaderOutputPath = PACKAGE.name + '/front/events-loader';
const eventsLoaderInputPath = FOLDER_SOURCE + '/' + PACKAGE.name + '/front/events-loader.js';
function bundleFrontend() {
return src(FOLDER_SOURCE + '/' + PACKAGE.name + '/front/events-loader.js')
.pipe(webpack({
mode,
entry: {
[eventsLoaderOutputPath]: eventsLoaderInputPath,
},
output: {
filename: '[name].js',
},
}))
.pipe(dest(FOLDER_BUILD));
}
function copyBackend() {
return src([
FOLDER_SOURCE + '/**/*.php',
FOLDER_SOURCE + '/**/*.txt'
])
.pipe(dest(FOLDER_BUILD));
}
function injectMetadata() {
return src([
FOLDER_BUILD + '/' + eventsLoaderOutputPath + '.js',
FOLDER_BUILD + '/' + PACKAGE.name + '/' + PACKAGE.name + '.php',
FOLDER_BUILD + '/' + PACKAGE.name + '/includes/constants.php',
FOLDER_BUILD + '/' + PACKAGE.name + '/readme.txt'
], { base: './' })
.pipe(replace('<wordpress-author-name>', PACKAGE.author.name))
.pipe(replace('<wordpress-author-url>', PACKAGE.author.url))
.pipe(replace('<wordpress-description>', PACKAGE.description))
.pipe(replace('<wordpress-donation-link>', PACKAGE.funding.url))
.pipe(replace('<wordpress-license>', PACKAGE.license))
.pipe(replace('<wordpress-minimum-version>', PACKAGE.additionalDetails.wordpressMinimumVersion))
.pipe(replace('<wordpress-name>', PACKAGE.name))
.pipe(replace('<wordpress-nice-name>', PACKAGE.additionalDetails.niceName))
.pipe(replace('<wordpress-php-minimum-version>', PACKAGE.additionalDetails.phpMinimumVersion))
.pipe(replace('<wordpress-tested-up-to-version>', PACKAGE.additionalDetails.wordpressTestedUpToVersion))
.pipe(replace('<wordpress-version>', PACKAGE.version))
.pipe(dest('.'));
}
exports.front = bundleFrontend;
exports.copy = copyBackend;
exports.inject = injectMetadata;
const build = series(clean, bundleFrontend, copyBackend, injectMetadata);
const buildDev = series((cb) => { mode = 'development'; cb(); }, build);
const buildProd = series((cb) => { mode = 'production'; cb(); }, build);
exports.clean = clean;
exports.dev = buildDev;
exports.default = buildDev;
exports.prod = buildProd;

23499
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,13 +1,17 @@
{ {
"name": "connector-mobilizon", "name": "connector-mobilizon",
"version": "0.6.2", "version": "0.11.3",
"description": "Display Mobilizon events in WordPress.", "description": "Display Mobilizon events in WordPress.",
"private": true, "private": true,
"type": "module",
"scripts": { "scripts": {
"build-dev": "gulp dev", "build-dev": "webpack --mode=development && gulp inject",
"build-prod": "ava && gulp prod", "build-prod": "rimraf -- build && ava && webpack --mode=production && gulp inject",
"clean": "rimraf -- build",
"coverage": "c8 --all --reporter=html --reporter=text --include=source/**/*.js ava", "coverage": "c8 --all --reporter=html --reporter=text --include=source/**/*.js ava",
"eslint": "npx eslint source/**/*.js", "eslint": "npx eslint source/**/*.js",
"format": "npx prettier --write .",
"prepare": "husky install",
"test": "ava" "test": "ava"
}, },
"author": { "author": {
@ -20,37 +24,49 @@
}, },
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"graphql": "^15.5.1", "graphql": "16.7.1",
"graphql-request": "^3.5.0", "graphql-request": "6.1.0",
"luxon": "^2.0.2", "luxon": "3.3.0",
"object-hash": "^2.2.0" "object-hash": "3.0.0"
}, },
"devDependencies": { "devDependencies": {
"ava": "^3.15.0", "@babel/core": "7.22.9",
"c8": "^7.8.0", "@babel/eslint-parser": "7.22.9",
"del": "^6.0.0", "@babel/preset-env": "7.22.9",
"eslint": "^7.32.0", "@babel/preset-react": "7.22.5",
"eslint-plugin-ava": "^12.0.0", "@wordpress/eslint-plugin": "14.11.0",
"esm": "^3.2.25", "ava": "5.3.1",
"gulp": "^4.0.2", "babel-loader": "9.1.3",
"gulp-replace": "^1.1.3", "browser-env": "3.3.0",
"jsdom": "^17.0.0", "c8": "8.0.1",
"webpack": "^5.51.1", "copy-webpack-plugin": "11.0.0",
"webpack-cli": "^4.8.0", "eslint": "8.45.0",
"webpack-stream": "^6.1.2" "eslint-plugin-ava": "14.0.0",
"eslint-plugin-jsx": "0.1.0",
"eslint-plugin-react": "7.33.0",
"esm": "3.2.25",
"gulp": "4.0.2",
"gulp-replace": "1.1.4",
"husky": "8.0.3",
"lint-staged": "13.2.3",
"prettier": "3.0.0",
"rimraf": "5.0.1",
"webpack": "5.88.2",
"webpack-cli": "5.1.4"
}, },
"ava": { "ava": {
"files": [ "files": [
"./source/**/*test.js" "./source/**/*test.js"
],
"require": [
"esm"
] ]
}, },
"additionalDetails": { "additionalDetails": {
"niceName": "Connector for Mobilizon", "niceName": "Connector for Mobilizon",
"phpMinimumVersion": 5.4, "phpMinimumVersion": 7.4,
"wordpressMinimumVersion": 5.6, "wordpressMinimumVersion": 5.6,
"wordpressTestedUpToVersion": 5.8 "wordpressTestedUpToVersion": "6.3"
},
"lint-staged": {
"source/**/*.js": "eslint",
"**/*": "prettier --write --ignore-unknown"
} }
} }

2
qodana.yaml Normal file
View File

@ -0,0 +1,2 @@
version: '1.0'
linter: jetbrains/qodana-js:2022.3-eap

View File

@ -6,6 +6,75 @@
#### Fixed #### Fixed
#### Security #### Security
### [0.11.3]
#### Fixed
- Clean up distributed files
### [0.11.2]
#### Changed
- Update dependencies
- Confirm compatibility with WordPress 6.3
### [0.11.1]
#### Fixed
- Revert minimum PHP version to 7.4 to allow some more time for upgrading PHP
### [0.11.0]
#### Changed
- Update dependencies
- Confirm compatibility with WordPress 6.2
#### Security
- Set minimum PHP version to oldest stable 8.0
### [0.10.1]
#### Changed
- Confirm compatibility with WordPress 6.1
- Update dependencies
### [0.10.0]
#### Added
- Add Gutenberg events list block
- Show loading indicator during request
#### Changed
- Set list style type to none and left padding to zero for all occurences
- Move shortcut usage description into installation section in `readme.txt`
- Update dependencies
### [0.9.1] - 2020-05-19
#### Fixed
- Fix WordPress compatibility version number
### [0.9.0] - 2020-05-19
#### Added
- Improve explanation of group name filter
#### Changed
- Update dependencies
- Confirm compatibility with WordPress 6.0
#### Fixed
- Fix displaying error message for the case the group is not found
### [0.8.0] - 2022-01-09
#### Added
- Add support for older browsers using babel
#### Changed
- Confirm compatibility with WordPress 5.9
- Update dependencies
#### Fixed
- Use ES modules correctly
- Trim events' location
### [0.7.0] - 2021-12-23
#### Added
- Add specific error message for the case the group is not found
- Add code formatter prettier
#### Changed
- Update dependencies
- Simplify build process
#### Fixed
- Fix Invalid DateTime on event end time being null
#### Security
- Set minimum PHP version to oldest stable 7.4
### [0.6.2] - 2021-08-24 ### [0.6.2] - 2021-08-24
#### Changed #### Changed
- Update dependencies - Update dependencies

View File

@ -0,0 +1,86 @@
<?php
/**
* Plugin Name: <wordpress-nice-name>
* Author: <wordpress-author-name>
* Author URI: <wordpress-author-url>
* Description: <wordpress-description>
* Version: <wordpress-version>
* Requires at least: <wordpress-minimum-version>
* Requires PHP: <wordpress-php-minimum-version>
* License: <wordpress-license>
*/
require_once __DIR__ . '/includes/constants.php';
require_once __DIR__ . '/includes/settings.php';
require_once __DIR__ . '/includes/events-list-block.php';
require_once __DIR__ . '/includes/events-list-shortcut.php';
require_once __DIR__ . '/includes/events-list-widget.php';
// Exit if this file is called directly.
if (!defined('ABSPATH')) {
exit;
}
final class Mobilizon_Connector {
private function __construct() {
add_action('init', [$this, 'register_blocks']);
add_action('init', [$this, 'register_settings'], 1); // required for register_blocks
add_action('init', [$this, 'register_shortcut']);
add_action('widgets_init', [$this, 'register_widget']);
add_action('wp_enqueue_scripts', [$this, 'register_scripts']);
register_activation_hook(__FILE__, [$this, 'enable_activation']);
}
public static function init() {
// Create singleton instance.
static $instance = false;
if(!$instance) {
$instance = new self();
}
return $instance;
}
public function enable_activation() {
MobilizonConnector\Settings::setDefaultOptions();
}
private function load_settings_globally_before_script($scriptName) {
$settings = array(
'isShortOffsetNameShown' => MobilizonConnector\Settings::isShortOffsetNameShown(),
'locale' => str_replace('_', '-', get_locale()),
'timeZone' => wp_timezone_string(),
'url' => MobilizonConnector\Settings::getUrl()
);
wp_add_inline_script($scriptName, 'var MOBILIZON_CONNECTOR = ' . json_encode($settings), 'before');
}
public function register_blocks() {
$scriptName = MobilizonConnector\EventsListBlock::initAndReturnScriptName();
$this->load_settings_globally_before_script($scriptName);
}
public function register_settings() {
MobilizonConnector\Settings::init();
}
public function register_scripts() {
$name = MobilizonConnector\NAME . '-js';
wp_enqueue_script($name, plugins_url('front/events-loader.js', __FILE__ ));
$this->load_settings_globally_before_script($name);
}
public function register_shortcut() {
MobilizonConnector\EventsListShortcut::init();
}
public function register_widget() {
register_widget('MobilizonConnector\EventsListWidget');
}
}
function mobilizon_connector_run_plugin() {
return Mobilizon_Connector::init();
}
mobilizon_connector_run_plugin();

View File

@ -1,42 +0,0 @@
<?php
/**
* Plugin Name: <wordpress-nice-name>
* Author: <wordpress-author-name>
* Author URI: <wordpress-author-url>
* Description: <wordpress-description>
* Version: <wordpress-version>
* Requires at least: <wordpress-minimum-version>
* Requires PHP: <wordpress-php-minimum-version>
* License: <wordpress-license>
*/
require_once __DIR__ . '/includes/constants.php';
require_once __DIR__ . '/includes/settings.php';
require_once __DIR__ . '/includes/events-list-shortcut.php';
require_once __DIR__ . '/includes/events-list-widget.php';
// Exit if this file is called directly.
if (!defined('ABSPATH')) {
exit;
}
function mobilizon_connector_activate() {
MobilizonConnector\Settings::setDefaultOptions();
}
register_activation_hook(__FILE__, 'mobilizon_connector_activate');
function mobilizon_connector_initialize() {
MobilizonConnector\Settings::init();
MobilizonConnector\EventsListShortcut::init();
}
add_action('init', 'mobilizon_connector_initialize');
function mobilizon_connector_load_scripts() {
wp_enqueue_script(MobilizonConnector\NAME . '-js', plugins_url('front/events-loader.js', __FILE__ ));
}
add_action('wp_enqueue_scripts', 'mobilizon_connector_load_scripts');
function mobilizon_connector_register_events_list_widget() {
register_widget('MobilizonConnector\EventsListWidget');
}
add_action('widgets_init', 'mobilizon_connector_register_events_list_widget');

View File

@ -1,61 +0,0 @@
import test from 'ava'
import { JSDOM } from 'jsdom'
import { displayEvents, displayErrorMessage } from './events-displayer'
let document
test.before(() => {
document = new JSDOM().window.document
})
test.beforeEach(t => {
t.context.list = document.createElement('ul')
t.context.list.setAttribute('data-locale', 'en-GB')
t.context.list.setAttribute('data-maximum', '2')
t.context.list.setAttribute('data-time-zone', 'utc')
const listElement = document.createElement('li')
listElement.setAttribute('style', 'display: none;')
t.context.list.appendChild(listElement)
})
test('#displayEvents one event', t => {
const list = t.context.list
const data = {
events: {
elements: [
{
title: 'a',
url: 'b',
beginsOn: '2021-04-15T10:30:00Z',
endsOn: '2021-04-15T15:30:00Z',
physicalAddress: {
description: 'c',
locality: 'd'
}
}
]
}
}
displayEvents({ data, document, list })
t.is(list.children.length, 2)
t.is(list.children[1].childNodes[0].tagName, 'A')
t.is(list.children[1].childNodes[0].getAttribute('href'), 'b')
t.is(list.children[1].childNodes[0].childNodes[0].nodeValue, 'a')
t.is(list.children[1].childNodes[1].tagName, 'BR')
t.is(list.children[1].childNodes[2].nodeValue, '15/04/2021 10:30 - 15:30')
t.is(list.children[1].childNodes[3].tagName, 'BR')
t.is(list.children[1].childNodes[4].nodeValue, 'c, d')
})
test('#displayErrorMessage no children added', t => {
const list = t.context.list
displayErrorMessage({ data: '', list })
t.is(list.children.length, 1)
})
test('#displayErrorMessage error message display', t => {
const list = t.context.list
displayErrorMessage({ data: '', list })
t.is(list.children[0].style.display, 'block')
})

View File

@ -1,51 +0,0 @@
import Formatter from './formatter'
import { createAnchorElement } from './html-creator'
export function displayEvents({ data, document, list }) {
const locale = list.getAttribute('data-locale')
const maxEventsCount = list.getAttribute('data-maximum')
const timeZone = list.getAttribute('data-time-zone')
const isShortOffsetNameShown = list.hasAttribute('data-is-short-offset-name-shown')
const events = data.events ? data.events.elements : data.group.organizedEvents.elements
const eventsCount = Math.min(maxEventsCount, events.length)
for (let i = 0; i < eventsCount; i++) {
const li = document.createElement('li')
const a = createAnchorElement({ document, text: events[i].title, url: events[i].url })
li.appendChild(a)
const br = document.createElement('br')
li.appendChild(br)
const date = Formatter.formatDate({
locale,
start: events[i].beginsOn,
end: events[i].endsOn,
timeZone,
isShortOffsetNameShown,
})
const textnode = document.createTextNode(date)
li.appendChild(textnode)
if (events[i].physicalAddress) {
const location = Formatter.formatLocation({
description: events[i].physicalAddress.description,
locality: events[i].physicalAddress.locality
})
if (location) {
const brBeforeLocation = document.createElement('br')
li.appendChild(brBeforeLocation)
const textnodeLocation = document.createTextNode(location)
li.appendChild(textnodeLocation)
}
}
list.appendChild(li)
}
}
export function displayErrorMessage({ data, list }) {
console.error(data)
list.children[0].style.display = 'block'
}

View File

@ -1,22 +0,0 @@
import { displayEvents, displayErrorMessage } from './events-displayer'
import * as GraphqlWrapper from './graphql-wrapper'
const NAME = '<wordpress-name>'
document.addEventListener('DOMContentLoaded', () => {
const eventLists = document.getElementsByClassName(NAME + '_events-list')
for (let list of eventLists) {
const url = list.getAttribute('data-url') + '/api'
const limit = parseInt(list.getAttribute('data-maximum'))
const groupName = list.getAttribute('data-group-name')
if (groupName) {
GraphqlWrapper.getUpcomingEventsByGroupName({ url, limit, groupName })
.then((data) => displayEvents({ data, document, list }))
.catch((data) => displayErrorMessage({ data, list }))
} else {
GraphqlWrapper.getUpcomingEvents({ url, limit })
.then((data) => displayEvents({ data, document, list }))
.catch((data) => displayErrorMessage({ data, list }))
}
}
})

View File

@ -1,37 +0,0 @@
import test from 'ava'
import Formatter from './formatter'
test('#formatDate one date', t => {
const date = Formatter.formatDate({ start: '2021-04-15T10:30:00Z', end: '2021-04-15T15:30:00Z' })
t.is(date, '15/04/2021 10:30 - 15:30')
})
test('#formatDate one date with short offset name', t => {
const date = Formatter.formatDate({ start: '2021-04-15T10:30:00Z', end: '2021-04-15T15:30:00Z', isShortOffsetNameShown: true })
t.is(date, '15/04/2021 10:30 - 15:30 (UTC)')
})
test('#formatDate two dates', t => {
const date = Formatter.formatDate({ start: '2021-04-15T10:30:00Z', end: '2021-04-16T15:30:00Z' })
t.is(date, '15/04/2021 10:30 - 16/04/2021 15:30')
})
test('#formatDate two dates with short offset name', t => {
const date = Formatter.formatDate({ start: '2021-04-15T10:30:00Z', end: '2021-04-16T15:30:00Z', isShortOffsetNameShown: true })
t.is(date, '15/04/2021 10:30 (UTC) - 16/04/2021 15:30 (UTC)')
})
test('#formatLocation both parameters', t => {
const date = Formatter.formatLocation({ description: 'a', locality: 'b' })
t.is(date, 'a, b')
})
test('#formatLocation description only', t => {
const date = Formatter.formatLocation({ description: 'a' })
t.is(date, 'a')
})
test('#formatLocation locality only', t => {
const date = Formatter.formatLocation({ locality: 'a' })
t.is(date, 'a')
})

View File

@ -1,39 +0,0 @@
import DateTimeWrapper from './date-time-wrapper'
export default class Formatter {
static formatDate({ locale, timeZone, start, end, isShortOffsetNameShown }) {
const startDateTime = new DateTimeWrapper({ locale, text: start, timeZone })
const endDateTime = new DateTimeWrapper({ locale, text: end, timeZone })
let dateText = startDateTime.getShortDate()
dateText += ' ' + startDateTime.get24Time()
if (!startDateTime.equalsDate(endDateTime)) {
if (isShortOffsetNameShown) {
dateText += ' (' + startDateTime.getShortOffsetName() + ')'
}
dateText += ' - '
dateText += endDateTime.getShortDate() + ' '
} else {
dateText += ' - '
}
dateText += endDateTime.get24Time()
if (isShortOffsetNameShown) {
dateText += ' (' + endDateTime.getShortOffsetName() + ')'
}
return dateText
}
static formatLocation({ description, locality }) {
let location = ''
if (description) {
location += description
}
if (location && locality) {
location += ', '
}
if (locality) {
location += locality
}
return location
}
}

View File

@ -1,6 +0,0 @@
import test from 'ava'
import hash from './object-hash-wrapper'
test('#hash object', t => {
t.is(hash({foo: 'bar'}), 'a75c05bdca7d704bdfcd761913e5a4e4636e956b')
})

View File

@ -1,15 +0,0 @@
<?php
// Exit if this file is called directly.
if (!defined('ABSPATH')) {
exit;
}
?>
<ul class="<?php echo esc_attr($classNamePrefix); ?>_events-list"
data-url="<?php echo esc_attr($url); ?>"
data-locale="<?php echo esc_attr($locale); ?>"
data-maximum="<?php echo esc_attr($eventsCount); ?>"
data-group-name="<?php echo esc_attr($groupName); ?>"
data-time-zone="<?php echo esc_attr($timeZone); ?>"
<?php echo $isShortOffsetNameShown ? 'data-is-short-offset-name-shown' : ''; ?>>
<li style="display: none;"><?php esc_html_e('The events could not be loaded!', 'connector-mobilizon'); ?></li>
</ul>

View File

@ -0,0 +1 @@
import './blocks/events-list/index.js'

View File

@ -0,0 +1,87 @@
/* eslint-disable @wordpress/i18n-ellipsis */
import { loadEventList } from '../../events-loader.js'
const { InspectorControls, useBlockProps } = wp.blockEditor
const { PanelBody } = wp.components
const { useEffect } = wp.element
const { __ } = wp.i18n
const NAME = '<wordpress-name>'
let timer
export default ({ attributes, setAttributes }) => {
const blockProps = useBlockProps({
className: NAME + '_events-list',
'data-maximum': attributes.eventsCount,
'data-group-name': attributes.groupName,
})
function reloadEventList() {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
const container = document.getElementById(blockProps.id)
if (container) {
loadEventList(container)
}
}, 500)
}
useEffect(() => {
reloadEventList()
}, [])
function updateEventsCount(event) {
let newValue = Number(event.target.value)
if (newValue < 1) newValue = 1
setAttributes({ eventsCount: newValue })
reloadEventList()
}
function updateGroupName(event) {
setAttributes({ groupName: event.target.value })
reloadEventList()
}
return [
<InspectorControls>
<PanelBody title={__('Events List Settings', '<wordpress-name>')}>
<label
className="components-base-control__label"
htmlFor={NAME + '_events-count'}
>
{__('Number of events to show', '<wordpress-name>')}
</label>
<input
className="components-text-control__input"
type="number"
value={attributes.eventsCount}
onChange={updateEventsCount}
id={NAME + '_events-count'}
/>
<label
className="components-base-control__label"
htmlFor={NAME + '_group-name'}
>
{__('Group name (optional)', '<wordpress-name>')}
</label>
<input
className="components-text-control__input"
type="text"
value={attributes.groupName}
onChange={updateGroupName}
id={NAME + '_group-name'}
/>
</PanelBody>
</InspectorControls>,
<div {...blockProps}>
<div className="general-error" style={{ display: 'none' }}>
{__('The events could not be loaded!', '<wordpress-name>')}
</div>
<div className="group-not-found" style={{ display: 'none' }}>
{__('The group could not be found!', '<wordpress-name>')}
</div>
<div className="loading-indicator" style={{ display: 'none' }}>
{__('Loading...', '<wordpress-name>')}
</div>
<ul style={{ 'list-style-type': 'none', 'padding-left': 0 }}></ul>
</div>,
]
}

View File

@ -0,0 +1,7 @@
import edit from './edit.js'
const { registerBlockType } = wp.blocks
const NAME = '<wordpress-name>'
registerBlockType(NAME + '/events-list', { edit })

View File

@ -1,67 +1,76 @@
import test from 'ava' import test from 'ava'
import DateTimeWrapper from './date-time-wrapper' import DateTimeWrapper from './date-time-wrapper.js'
test('#getShortDate usual date', t => { test('#getShortDate usual date', (t) => {
const d = new DateTimeWrapper({ text: '2020-12-24T16:45:00Z' }) const d = new DateTimeWrapper({ text: '2020-12-24T16:45:00Z' })
t.is(d.getShortDate(), '24/12/2020') t.is(d.getShortDate(), '24/12/2020')
}) })
test('#getShortDate usual date with timezone string', t => { test('#getShortDate usual date with timezone string', (t) => {
const d = new DateTimeWrapper({ text: '2020-12-24T16:45:00Z', timeZone: 'Europe/Rome' }) const d = new DateTimeWrapper({
text: '2020-12-24T16:45:00Z',
timeZone: 'Europe/Rome',
})
t.is(d.getShortDate(), '24/12/2020') t.is(d.getShortDate(), '24/12/2020')
}) })
test('#getShortDate usual date with fixed offset', t => { test('#getShortDate usual date with fixed offset', (t) => {
const d = new DateTimeWrapper({ text: '2020-12-24T16:45:00Z', timeZone: 'UTC+02:00' }) const d = new DateTimeWrapper({
text: '2020-12-24T16:45:00Z',
timeZone: 'UTC+02:00',
})
t.is(d.getShortDate(), '24/12/2020') t.is(d.getShortDate(), '24/12/2020')
}) })
test('#getShortDate usual date with fixed offset without UTC prefix', t => { test('#getShortDate usual date with fixed offset without UTC prefix', (t) => {
const d = new DateTimeWrapper({ text: '2020-12-24T16:45:00Z', timeZone: '+02:00' }) const d = new DateTimeWrapper({
text: '2020-12-24T16:45:00Z',
timeZone: '+02:00',
})
t.is(d.getShortDate(), '24/12/2020') t.is(d.getShortDate(), '24/12/2020')
}) })
test('#getShortDate usual date with empty time zone', t => { test('#getShortDate usual date with empty time zone', (t) => {
const d = new DateTimeWrapper({ text: '2020-12-24T16:45:00Z', timeZone: '' }) const d = new DateTimeWrapper({ text: '2020-12-24T16:45:00Z', timeZone: '' })
t.is(d.getShortDate(), '24/12/2020') t.is(d.getShortDate(), '24/12/2020')
}) })
test('#get24Time usual time', t => { test('#get24Time usual time', (t) => {
const d = new DateTimeWrapper({ text: '2020-12-24T16:45:00Z' }) const d = new DateTimeWrapper({ text: '2020-12-24T16:45:00Z' })
t.is(d.get24Time(), '16:45') t.is(d.get24Time(), '16:45')
}) })
test('#equalsDate same date, different time', t => { test('#equalsDate same date, different time', (t) => {
const d = new DateTimeWrapper({ text: '2020-12-24T16:45:00Z' }) const d = new DateTimeWrapper({ text: '2020-12-24T16:45:00Z' })
const e = new DateTimeWrapper({ text: '2020-12-24T17:46:01Z' }) const e = new DateTimeWrapper({ text: '2020-12-24T17:46:01Z' })
t.true(d.equalsDate(e)) t.true(d.equalsDate(e))
}) })
test('#equalsDate different date, different time', t => { test('#equalsDate different date, different time', (t) => {
const d = new DateTimeWrapper({ text: '2020-12-24T16:45:00Z' }) const d = new DateTimeWrapper({ text: '2020-12-24T16:45:00Z' })
const e = new DateTimeWrapper({ text: '2021-11-25T17:46:01Z' }) const e = new DateTimeWrapper({ text: '2021-11-25T17:46:01Z' })
t.false(d.equalsDate(e)) t.false(d.equalsDate(e))
}) })
test('#equalsDate different day, different time', t => { test('#equalsDate different day, different time', (t) => {
const d = new DateTimeWrapper({ text: '2020-12-24T16:45:00Z' }) const d = new DateTimeWrapper({ text: '2020-12-24T16:45:00Z' })
const e = new DateTimeWrapper({ text: '2020-12-25T17:46:01Z' }) const e = new DateTimeWrapper({ text: '2020-12-25T17:46:01Z' })
t.false(d.equalsDate(e)) t.false(d.equalsDate(e))
}) })
test('#equalsDate different month, different time', t => { test('#equalsDate different month, different time', (t) => {
const d = new DateTimeWrapper({ text: '2020-12-24T16:45:00Z' }) const d = new DateTimeWrapper({ text: '2020-12-24T16:45:00Z' })
const e = new DateTimeWrapper({ text: '2020-11-24T17:46:01Z' }) const e = new DateTimeWrapper({ text: '2020-11-24T17:46:01Z' })
t.false(d.equalsDate(e)) t.false(d.equalsDate(e))
}) })
test('#equalsDate different year, different time', t => { test('#equalsDate different year, different time', (t) => {
const d = new DateTimeWrapper({ text: '2020-12-24T16:45:00Z' }) const d = new DateTimeWrapper({ text: '2020-12-24T16:45:00Z' })
const e = new DateTimeWrapper({ text: '2021-12-24T17:46:01Z' }) const e = new DateTimeWrapper({ text: '2021-12-24T17:46:01Z' })
t.false(d.equalsDate(e)) t.false(d.equalsDate(e))
}) })
test('#getCurrentDatetimeAsString correct format', t => { test('#getCurrentDatetimeAsString correct format', (t) => {
const d = DateTimeWrapper.getCurrentDatetimeAsString() const d = DateTimeWrapper.getCurrentDatetimeAsString()
t.is(d[4], '-') t.is(d[4], '-')
t.is(d[7], '-') t.is(d[7], '-')
@ -69,10 +78,10 @@ test('#getCurrentDatetimeAsString correct format', t => {
t.is(d[13], ':') t.is(d[13], ':')
t.is(d[16], ':') t.is(d[16], ':')
t.is(d[19], '.') t.is(d[19], '.')
t.is(d[d.length-3], ':') t.is(d[d.length - 3], ':')
}) })
test('#getShortOffsetName usual time', t => { test('#getShortOffsetName usual time', (t) => {
const d = new DateTimeWrapper({ text: '2020-12-24T16:45:00Z' }) const d = new DateTimeWrapper({ text: '2020-12-24T16:45:00Z' })
t.is(d.getShortOffsetName(), 'UTC') t.is(d.getShortOffsetName(), 'UTC')
}) })

View File

@ -1,12 +1,14 @@
import { DateTime } from 'luxon' import { DateTime } from 'luxon'
export default class DateTimeWrapper { export default class DateTimeWrapper {
constructor({ locale = 'en-GB', text, timeZone = 'utc' } = {}) { constructor({ locale = 'en-GB', text, timeZone = 'utc' } = {}) {
if (!timeZone) { if (!timeZone) {
timeZone = 'utc' timeZone = 'utc'
} }
if (timeZone.includes(':') && timeZone.substring(0, 3).toUpperCase() !== 'UTC') { if (
timeZone.includes(':') &&
timeZone.substring(0, 3).toUpperCase() !== 'UTC'
) {
timeZone = 'UTC' + timeZone timeZone = 'UTC' + timeZone
} }
this.dateTime = DateTime.fromISO(text, { locale, zone: timeZone }) this.dateTime = DateTime.fromISO(text, { locale, zone: timeZone })
@ -25,11 +27,13 @@ export default class DateTimeWrapper {
} }
equalsDate(other) { equalsDate(other) {
return this.dateTime && return (
this.dateTime &&
other.dateTime && other.dateTime &&
this.dateTime.day === other.dateTime.day && this.dateTime.day === other.dateTime.day &&
this.dateTime.month === other.dateTime.month && this.dateTime.month === other.dateTime.month &&
this.dateTime.year === other.dateTime.year this.dateTime.year === other.dateTime.year
)
} }
static getCurrentDatetimeAsString() { static getCurrentDatetimeAsString() {

View File

@ -0,0 +1,122 @@
import test from 'ava'
import browserEnv from 'browser-env'
import {
displayEvents,
displayErrorMessage,
hideErrorMessages,
showLoadingIndicator,
} from './events-displayer.js'
test.before(() => {
browserEnv()
window.MOBILIZON_CONNECTOR = {
locale: 'en-GB',
timeZone: 'utc',
}
})
test.beforeEach((t) => {
t.context.container = document.createElement('div')
t.context.container.setAttribute('data-maximum', '2')
const errorMessageGeneral = document.createElement('div')
errorMessageGeneral.setAttribute('class', 'general-error')
errorMessageGeneral.setAttribute('style', 'display: none;')
t.context.container.appendChild(errorMessageGeneral)
const errorMessageGroupNotFound = document.createElement('div')
errorMessageGroupNotFound.setAttribute('class', 'group-not-found')
errorMessageGroupNotFound.setAttribute('style', 'display: none;')
t.context.container.appendChild(errorMessageGroupNotFound)
const loadingIndicator = document.createElement('div')
loadingIndicator.setAttribute('class', 'loading-indicator')
loadingIndicator.setAttribute('style', 'display: none;')
t.context.container.appendChild(loadingIndicator)
const list = document.createElement('ul')
t.context.container.appendChild(list)
})
test('#displayEvents one event', (t) => {
const data = {
events: {
elements: [
{
title: 'a',
url: 'b',
beginsOn: '2021-04-15T10:30:00Z',
endsOn: '2021-04-15T15:30:00Z',
physicalAddress: {
description: 'c',
locality: 'd',
},
},
],
},
}
const container = t.context.container
displayEvents({ data, document, container })
const list = container.querySelector('ul')
t.is(list.children[0].childNodes[0].tagName, 'A')
t.is(list.children[0].childNodes[0].getAttribute('href'), 'b')
t.is(list.children[0].childNodes[0].childNodes[0].nodeValue, 'a')
t.is(list.children[0].childNodes[1].tagName, 'BR')
t.is(list.children[0].childNodes[2].nodeValue, '15/04/2021 10:30 - 15:30')
t.is(list.children[0].childNodes[3].tagName, 'BR')
t.is(list.children[0].childNodes[4].nodeValue, 'c, d')
})
test('#displayErrorMessage no list entries shown', (t) => {
const container = t.context.container
displayErrorMessage({ data: '', container })
const list = container.querySelector('ul')
t.is(list.children.length, 0)
})
test('#displayErrorMessage general error message display', (t) => {
const container = t.context.container
displayErrorMessage({ data: '', container })
t.is(container.querySelector('.general-error').style.display, 'block')
t.is(container.querySelector('.group-not-found').style.display, 'none')
t.is(container.querySelector('.loading-indicator').style.display, 'none')
})
test('#displayErrorMessage group not found error message display', (t) => {
const container = t.context.container
const data = {
response: {
errors: [
{
code: 'group_not_found',
},
],
},
}
displayErrorMessage({ data, container })
t.is(container.querySelector('.general-error').style.display, 'none')
t.is(container.querySelector('.group-not-found').style.display, 'block')
t.is(container.querySelector('.loading-indicator').style.display, 'none')
})
test('#showLoadingIndicator remove events', (t) => {
const container = t.context.container
const loadingIndicator = container.querySelector('.loading-indicator')
t.is(loadingIndicator.style.display, 'none')
showLoadingIndicator(container)
t.is(loadingIndicator.style.display, 'block')
})
test('#hideErrorMessages remove events', (t) => {
const container = t.context.container
const generalErrorMessage = container.querySelector('.general-error')
const groupNotFoundErrorMessage = container.querySelector('.group-not-found')
generalErrorMessage.style.display = 'block'
groupNotFoundErrorMessage.style.display = 'block'
t.is(generalErrorMessage.style.display, 'block')
t.is(groupNotFoundErrorMessage.style.display, 'block')
hideErrorMessages(container)
t.is(generalErrorMessage.style.display, 'none')
t.is(groupNotFoundErrorMessage.style.display, 'none')
})

View File

@ -0,0 +1,95 @@
import Formatter from './formatter.js'
import { createAnchorElement } from './html-creator.js'
export function clearEventsList(container) {
const list = container.querySelector('ul')
list.replaceChildren()
}
export function displayEvents({ data, document, container }) {
hideLoadingIndicator(container)
const isShortOffsetNameShown =
window.MOBILIZON_CONNECTOR.isShortOffsetNameShown
const locale = window.MOBILIZON_CONNECTOR.locale
const maxEventsCount = container.getAttribute('data-maximum')
const timeZone = window.MOBILIZON_CONNECTOR.timeZone
const events = data.events
? data.events.elements
: data.group.organizedEvents.elements
const eventsCount = Math.min(maxEventsCount, events.length)
const list = container.querySelector('ul')
for (let i = 0; i < eventsCount; i++) {
const li = document.createElement('li')
const a = createAnchorElement({
document,
text: events[i].title,
url: events[i].url,
})
li.appendChild(a)
const br = document.createElement('br')
li.appendChild(br)
const date = Formatter.formatDate({
locale,
start: events[i].beginsOn,
end: events[i].endsOn,
timeZone,
isShortOffsetNameShown,
})
const textnode = document.createTextNode(date)
li.appendChild(textnode)
if (events[i].physicalAddress) {
const location = Formatter.formatLocation({
description: events[i].physicalAddress.description,
locality: events[i].physicalAddress.locality,
})
if (location) {
const brBeforeLocation = document.createElement('br')
li.appendChild(brBeforeLocation)
const textnodeLocation = document.createTextNode(location)
li.appendChild(textnodeLocation)
}
}
list.appendChild(li)
}
}
export function displayErrorMessage({ data, container }) {
hideLoadingIndicator(container)
if (
Object.prototype.hasOwnProperty.call(data, 'response') &&
Object.prototype.hasOwnProperty.call(data.response, 'errors') &&
data.response.errors.length > 0 &&
Object.prototype.hasOwnProperty.call(data.response.errors[0], 'code') &&
data.response.errors[0].code === 'group_not_found'
) {
const message = container.querySelector('.group-not-found')
message.style.display = 'block'
} else {
const message = container.querySelector('.general-error')
message.style.display = 'block'
console.error(data)
}
}
export function showLoadingIndicator(container) {
const indicator = container.querySelector('.loading-indicator')
indicator.style.display = 'block'
}
function hideLoadingIndicator(container) {
const indicator = container.querySelector('.loading-indicator')
indicator.style.display = 'none'
}
export function hideErrorMessages(container) {
container.querySelector('.group-not-found').style.display = 'none'
container.querySelector('.general-error').style.display = 'none'
}

View File

@ -0,0 +1,38 @@
import {
clearEventsList,
displayEvents,
displayErrorMessage,
hideErrorMessages,
showLoadingIndicator,
} from './events-displayer.js'
import * as GraphqlWrapper from './graphql-wrapper.js'
const NAME = '<wordpress-name>'
const URL_SUFFIX = '/api'
document.addEventListener('DOMContentLoaded', loadEventLists)
function loadEventLists() {
const eventLists = document.getElementsByClassName(NAME + '_events-list')
for (const list of eventLists) {
loadEventList(list)
}
}
export function loadEventList(container) {
const url = MOBILIZON_CONNECTOR.url + URL_SUFFIX
const limit = parseInt(container.getAttribute('data-maximum'))
const groupName = container.getAttribute('data-group-name')
hideErrorMessages(container)
clearEventsList(container)
showLoadingIndicator(container)
if (groupName) {
GraphqlWrapper.getUpcomingEventsByGroupName({ url, limit, groupName })
.then((data) => displayEvents({ data, document, container }))
.catch((data) => displayErrorMessage({ data, container }))
} else {
GraphqlWrapper.getUpcomingEvents({ url, limit })
.then((data) => displayEvents({ data, document, container }))
.catch((data) => displayErrorMessage({ data, container }))
}
}

View File

@ -0,0 +1,73 @@
import test from 'ava'
import Formatter from './formatter.js'
test('#formatDate one date', (t) => {
const date = Formatter.formatDate({
start: '2021-04-15T10:30:00Z',
end: '2021-04-15T15:30:00Z',
})
t.is(date, '15/04/2021 10:30 - 15:30')
})
test('#formatDate one date with short offset name', (t) => {
const date = Formatter.formatDate({
start: '2021-04-15T10:30:00Z',
end: '2021-04-15T15:30:00Z',
isShortOffsetNameShown: true,
})
t.is(date, '15/04/2021 10:30 - 15:30 (UTC)')
})
test('#formatDate two dates', (t) => {
const date = Formatter.formatDate({
start: '2021-04-15T10:30:00Z',
end: '2021-04-16T15:30:00Z',
})
t.is(date, '15/04/2021 10:30 - 16/04/2021 15:30')
})
test('#formatDate two dates with short offset name', (t) => {
const date = Formatter.formatDate({
start: '2021-04-15T10:30:00Z',
end: '2021-04-16T15:30:00Z',
isShortOffsetNameShown: true,
})
t.is(date, '15/04/2021 10:30 - 16/04/2021 15:30 (UTC)')
})
test('#formatDate second date is null', (t) => {
const date = Formatter.formatDate({
start: '2021-04-15T10:30:00Z',
end: null,
})
t.is(date, '15/04/2021 10:30')
})
test('#formatDate second date is null with short offset name', (t) => {
const date = Formatter.formatDate({
start: '2021-04-15T10:30:00Z',
end: null,
isShortOffsetNameShown: true,
})
t.is(date, '15/04/2021 10:30 (UTC)')
})
test('#formatLocation both parameters', (t) => {
const location = Formatter.formatLocation({ description: 'a', locality: 'b' })
t.is(location, 'a, b')
})
test('#formatLocation description only', (t) => {
const location = Formatter.formatLocation({ description: 'a' })
t.is(location, 'a')
})
test('#formatLocation description with space only', (t) => {
const location = Formatter.formatLocation({ description: ' ' })
t.is(location, '')
})
test('#formatLocation locality only', (t) => {
const location = Formatter.formatLocation({ locality: 'a' })
t.is(location, 'a')
})

45
source/front/formatter.js Normal file
View File

@ -0,0 +1,45 @@
import DateTimeWrapper from './date-time-wrapper.js'
export default class Formatter {
static formatDate({ locale, timeZone, start, end, isShortOffsetNameShown }) {
const startDateTime = new DateTimeWrapper({
locale,
text: start,
timeZone,
})
let dateText = startDateTime.getShortDate()
dateText += ' ' + startDateTime.get24Time()
if (!end && isShortOffsetNameShown) {
dateText += ' (' + startDateTime.getShortOffsetName() + ')'
}
if (end) {
const endDateTime = new DateTimeWrapper({ locale, text: end, timeZone })
if (!startDateTime.equalsDate(endDateTime)) {
dateText += ' - '
dateText += endDateTime.getShortDate() + ' '
} else {
dateText += ' - '
}
dateText += endDateTime.get24Time()
if (isShortOffsetNameShown) {
dateText += ' (' + endDateTime.getShortOffsetName() + ')'
}
}
return dateText
}
static formatLocation({ description, locality }) {
let location = ''
if (description && description.trim()) {
location += description.trim()
}
if (location && locality) {
location += ', '
}
if (locality) {
location += locality
}
return location
}
}

View File

@ -1,6 +1,6 @@
import SessionCache from './session-cache' import SessionCache from './session-cache.js'
import { request } from 'graphql-request' import { request } from 'graphql-request'
import DateTimeWrapper from './date-time-wrapper' import DateTimeWrapper from './date-time-wrapper.js'
export function getUpcomingEvents({ url, limit }) { export function getUpcomingEvents({ url, limit }) {
const query = ` const query = `
@ -21,19 +21,23 @@ export function getUpcomingEvents({ url, limit }) {
} }
} }
` `
const dataInCache = SessionCache.get(sessionStorage, { url, query, variables: { limit }}) const dataInCache = SessionCache.get(sessionStorage, {
if (dataInCache !== null) url,
query,
variables: { limit },
})
if (dataInCache !== null) {
return Promise.resolve(dataInCache) return Promise.resolve(dataInCache)
return request(url, query, { limit }) }
.then((data) => { return request(url, query, { limit }).then((data) => {
SessionCache.add(sessionStorage, { url, query, variables: { limit }}, data) SessionCache.add(sessionStorage, { url, query, variables: { limit } }, data)
return Promise.resolve(data) return Promise.resolve(data)
}) })
} }
export function getUpcomingEventsByGroupName({ url, limit, groupName }) { export function getUpcomingEventsByGroupName({ url, limit, groupName }) {
const query = ` const query = `
query ($afterDatetime: DateTime, $groupName: String, $limit: Int) { query ($afterDatetime: DateTime, $groupName: String!, $limit: Int) {
group(preferredUsername: $groupName) { group(preferredUsername: $groupName) {
organizedEvents(afterDatetime: $afterDatetime, limit: $limit) { organizedEvents(afterDatetime: $afterDatetime, limit: $limit) {
elements { elements {
@ -53,12 +57,22 @@ export function getUpcomingEventsByGroupName({ url, limit, groupName }) {
} }
` `
const afterDatetime = DateTimeWrapper.getCurrentDatetimeAsString() const afterDatetime = DateTimeWrapper.getCurrentDatetimeAsString()
const dataInCache = SessionCache.get(sessionStorage, { url, query, variables: { afterDatetime, groupName, limit }}) const dataInCache = SessionCache.get(sessionStorage, {
if (dataInCache !== null) url,
query,
variables: { afterDatetime, groupName, limit },
})
if (dataInCache !== null) {
return Promise.resolve(dataInCache) return Promise.resolve(dataInCache)
return request(url, query, { afterDatetime, groupName, limit }) }
.then((data) => { return request(url, query, { afterDatetime, groupName, limit }).then(
SessionCache.add(sessionStorage, { url, query, variables: { afterDatetime, groupName, limit }}, data) (data) => {
SessionCache.add(
sessionStorage,
{ url, query, variables: { afterDatetime, groupName, limit } },
data
)
return Promise.resolve(data) return Promise.resolve(data)
}) }
)
} }

View File

@ -1,15 +1,13 @@
import test from 'ava' import test from 'ava'
import { JSDOM } from 'jsdom' import browserEnv from 'browser-env'
import { createAnchorElement } from './html-creator' import { createAnchorElement } from './html-creator.js'
let document
test.beforeEach(() => { test.beforeEach(() => {
document = new JSDOM().window.document browserEnv()
}) })
test('#createAnchorElement usual parameters', t => { test('#createAnchorElement usual parameters', (t) => {
const a = createAnchorElement({ document, text: 'a', url: 'b' }) const a = createAnchorElement({ document, text: 'a', url: 'b' })
t.is(a.tagName, 'A') t.is(a.tagName, 'A')
t.is(a.innerHTML, 'a') t.is(a.innerHTML, 'a')

View File

@ -0,0 +1,6 @@
import test from 'ava'
import hash from './object-hash-wrapper.js'
test('#hash object', (t) => {
t.is(hash({ foo: 'bar' }), 'a75c05bdca7d704bdfcd761913e5a4e4636e956b')
})

View File

@ -1,8 +1,7 @@
import test from 'ava' import test from 'ava'
import SessionCache from './session-cache' import SessionCache from './session-cache.js'
const fakeStorage = { const fakeStorage = {
elements: {}, elements: {},
clear() { clear() {
@ -17,18 +16,18 @@ const fakeStorage = {
setItem(key, value) { setItem(key, value) {
this.elements[key] = value this.elements[key] = value
} },
} }
test.afterEach(() => { test.afterEach(() => {
fakeStorage.clear() fakeStorage.clear()
}) })
test('#add & #get', t => { test('#add & #get', (t) => {
SessionCache.add(fakeStorage, { a: 'b' }, { c: 'd' }) SessionCache.add(fakeStorage, { a: 'b' }, { c: 'd' })
t.deepEqual(SessionCache.get(fakeStorage, { a: 'b' }), { c: 'd' }) t.deepEqual(SessionCache.get(fakeStorage, { a: 'b' }), { c: 'd' })
}) })
test('#get no entry', t => { test('#get no entry', (t) => {
t.is(SessionCache.get(fakeStorage, { a: 'bb' }), null) t.is(SessionCache.get(fakeStorage, { a: 'bb' }), null)
}) })

View File

@ -1,9 +1,8 @@
import hash from './object-hash-wrapper' import hash from './object-hash-wrapper.js'
const MAX_AGE_IN_MS = 120000 const MAX_AGE_IN_MS = 120000
export default class SessionCache { export default class SessionCache {
static add(storage, parameters, data) { static add(storage, parameters, data) {
const key = hash(parameters) const key = hash(parameters)
const timestamp = Date.now() const timestamp = Date.now()
@ -17,7 +16,11 @@ export default class SessionCache {
static get(storage, parameters) { static get(storage, parameters) {
const key = hash(parameters) const key = hash(parameters)
const value = JSON.parse(storage.getItem(key)) const value = JSON.parse(storage.getItem(key))
if (value && value.timestamp && value.timestamp > Date.now() - MAX_AGE_IN_MS) if (
value &&
value.timestamp &&
value.timestamp > Date.now() - MAX_AGE_IN_MS
)
return value.data return value.data
return null return null
} }

View File

@ -0,0 +1,53 @@
<?php
namespace MobilizonConnector;
// Exit if this file is called directly.
if (!defined('ABSPATH')) {
exit;
}
class EventsListBlock {
public static function initAndReturnScriptName(): string {
$scriptName = NAME . '-block-starter';
wp_register_script($scriptName, plugins_url(NAME . '/front/block-events-loader.js'), [
'wp-block-editor',
'wp-blocks',
'wp-components',
'wp-i18n'
]);
register_block_type(NAME . '/events-list', [
'api_version' => 2,
'title' => __('Events List', 'connector-mobilizon'),
'description' => __('A list of the upcoming events of the connected Mobilizon instance.', 'connector-mobilizon'),
'category' => 'widgets',
'icon' => 'list-view',
'supports' => [
'html' => false
],
'attributes' => [
'eventsCount' => [
'type' => 'number',
'default' => 3,
],
'groupName' => [
'type' => 'string',
],
],
'editor_script' => $scriptName,
'render_callback' => 'MobilizonConnector\EventsListBlock::render',
]);
return $scriptName;
}
public static function render($block_attributes, $content) {
$classNamePrefix = NAME;
$eventsCount = $block_attributes['eventsCount'];
$groupName = isset($block_attributes['groupName']) ? $block_attributes['groupName'] : '';
ob_start();
require dirname(__DIR__) . '/view/events-list.php';
$output = ob_get_clean();
return $output;
}
}

View File

@ -26,11 +26,7 @@ class EventsListShortcut {
$classNamePrefix = NAME; $classNamePrefix = NAME;
$eventsCount = $atts_with_overriden_defaults['events-count']; $eventsCount = $atts_with_overriden_defaults['events-count'];
$locale = str_replace('_', '-', get_locale());
$groupName = $atts_with_overriden_defaults['group-name']; $groupName = $atts_with_overriden_defaults['group-name'];
$url = Settings::getUrl();
$timeZone = wp_timezone_string();
$isShortOffsetNameShown = Settings::isShortOffsetNameShown();
ob_start(); ob_start();
require dirname(__DIR__) . '/view/events-list.php'; require dirname(__DIR__) . '/view/events-list.php';

View File

@ -27,11 +27,7 @@ class EventsListWidget extends \WP_Widget {
$classNamePrefix = NAME; $classNamePrefix = NAME;
$eventsCount = $options['eventsCount']; $eventsCount = $options['eventsCount'];
$locale = str_replace('_', '-', get_locale());
$groupName = isset($options['groupName']) ? $options['groupName'] : ''; $groupName = isset($options['groupName']) ? $options['groupName'] : '';
$url = Settings::getUrl();
$timeZone = wp_timezone_string();
$isShortOffsetNameShown = Settings::isShortOffsetNameShown();
require dirname(__DIR__) . '/view/events-list.php'; require dirname(__DIR__) . '/view/events-list.php';

View File

@ -15,27 +15,101 @@ License: <wordpress-license>
<wordpress-nice-name> allows you to display the upcoming events of [Mobilizon](https://joinmobilizon.org/), which is a federated event listing platform, on your WordPress website. <wordpress-nice-name> allows you to display the upcoming events of [Mobilizon](https://joinmobilizon.org/), which is a federated event listing platform, on your WordPress website.
Features Features
- Display events as widget and as shortcut - Display events as Gutenberg block, as widget and as shortcut
- Display events' title, date, and location if available - Display events' title, date, and location, if available
- Cache requests' responses for 2 minutes in the browser's `sessionStorage` - Cache requests' responses for 2 minutes in the browser's `sessionStorage`
- Configure number of events to show per widget and per shortcut - Configure number of events to show per block, per widget and per shortcut
- Optionally filter events by a specific group per widget and per shortcut - Optionally filter events by a specific group per block, per widget and per shortcut
- Set the URL of the Mobilizon instance in the settings - Set the URL of the Mobilizon instance in the settings
- Toggle adding named offset in brackets after the time in the settings - Toggle adding named offset in brackets after the time in the settings
Shortcut format with limiting the number of events to show to 3 for example: `[<wordpress-name>-events-list events-count=3]`
Optionally, you can only show the events of a specific group by indicatings its name: `[<wordpress-name>-events-list events-count=3 group-name="mygroup"]`
The source code is available on [Github](https://github.com/dwaxweiler/connector-mobilizon). The source code is available on [Github](https://github.com/dwaxweiler/connector-mobilizon).
## Installation
Shortcut format with limiting the number of events to show to 3 for example: `[<wordpress-name>-events-list events-count=3]`
Optionally, you can only show the events of a specific group by indicatings its name: `[<wordpress-name>-events-list events-count=3 group-name="mygroup"]`
You have to use their username, e.g. `@nosliensvivants`, and append the name of their instance if they use a different one, e.g. `@yaam_berlin@mobilize.berlin`.
## Screenshots ## Screenshots
1. Events list 1. Events list
2. General settings 2. General settings
3. Widget creation 3. Widget creation
4. Shortcut creation 4. Shortcut creation
5. Gutenberg block in editor
## Changelog ## Changelog
### [0.11.3]
#### Fixed
- Clean up distributed files
### [0.11.2]
#### Changed
- Update dependencies
- Confirm compatibility with WordPress 6.3
### [0.11.1]
#### Fixed
- Revert minimum PHP version to 7.4 to allow some more time for upgrading PHP
### [0.11.0]
#### Changed
- Update dependencies
- Confirm compatibility with WordPress 6.2
#### Security
- Set minimum PHP version to oldest stable 8.0
### [0.10.1]
#### Changed
- Confirm compatibility with WordPress 6.1
- Update dependencies
### [0.10.0]
#### Added
- Add Gutenberg events list block
- Show loading indicator during request
#### Changed
- Set list style type to none and left padding to zero for all occurences
- Move shortcut usage description into installation section in `readme.txt`
- Update dependencies
### [0.9.1] - 2020-05-19
#### Fixed
- Fix WordPress compatibility version number
### [0.9.0] - 2020-05-19
#### Added
- Improve explanation of group name filter
#### Changed
- Update dependencies
- Confirm compatibility with WordPress 6.0
#### Fixed
- Fix displaying error message for the case the group is not found
### [0.8.0] - 2022-01-09
#### Added
- Add support for older browsers using babel
#### Changed
- Confirm compatibility with WordPress 5.9
- Update dependencies
#### Fixed
- Use ES modules correctly
- Trim events' location
### [0.7.0] - 2021-12-23
#### Added
- Add specific error message for the case the group is not found
- Add code formatter prettier
#### Changed
- Update dependencies
- Simplify build process
#### Fixed
- Fix Invalid DateTime on event end time being null
#### Security
- Set minimum PHP version to oldest stable 7.4
### [0.6.2] - 2021-08-24 ### [0.6.2] - 2021-08-24
#### Changed #### Changed
- Update dependencies - Update dependencies

View File

@ -0,0 +1,14 @@
<?php
// Exit if this file is called directly.
if (!defined('ABSPATH')) {
exit;
}
?>
<div class="<?php echo esc_attr($classNamePrefix); ?>_events-list"
data-maximum="<?php echo esc_attr($eventsCount); ?>"
data-group-name="<?php echo esc_attr($groupName); ?>">
<div class="general-error" style="display: none;"><?php esc_html_e('The events could not be loaded!', 'connector-mobilizon'); ?></div>
<div class="group-not-found" style="display: none;"><?php esc_html_e('The group could not be found!', 'connector-mobilizon'); ?></div>
<div class="loading-indicator" style="display: none;"><?php esc_html_e('Loading...', 'connector-mobilizon'); ?></div>
<ul style="list-style-type: none; padding-left: 0;"></ul>
</div>

47
webpack.config.cjs Normal file
View File

@ -0,0 +1,47 @@
/* eslint-disable no-undef */
const path = require('path')
const CopyPlugin = require('copy-webpack-plugin')
const FOLDER_SOURCE = './source'
module.exports = {
entry: {
'block-events-loader': FOLDER_SOURCE + '/front/block-events-loader.js',
'events-loader': FOLDER_SOURCE + '/front/events-loader.js',
},
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'build/front'),
},
module: {
rules: [
{
test: /\.m?js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [['@babel/preset-env', { targets: 'defaults' }]],
},
},
},
],
},
plugins: [
new CopyPlugin({
patterns: [
{
context: FOLDER_SOURCE,
from: '**/*.php',
to: '../',
},
{
context: FOLDER_SOURCE,
from: '**/*.txt',
to: '../',
},
],
}),
],
}