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

64 Commits

Author SHA1 Message Date
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
d5419448e1 remove unneeded folder 2021-12-14 17:51:13 +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
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
07534e08ea release version 0.6.2 2021-08-24 19:35:10 +02:00
32e87115b3 fix empty WordPress timezone_string option resulting in Invalid DateTime [#10] 2021-08-24 19:23:57 +02:00
cee386acd2 update webpack(-cli) 2021-08-24 18:12:09 +02:00
70e0d96e1f upgrade to Luxon 2.x [#9] 2021-08-24 18:10:23 +02:00
4707fedee0 npm audit fix 2021-08-15 21:51:31 +02:00
0b6350f03e update graphql-request 2021-08-15 21:49:14 +02:00
39b6800e97 update dev dependencies 2021-08-15 21:47:34 +02:00
da9afa64c8 ignore .idea folder 2021-08-15 21:26:37 +02:00
53393ef0df fix typo 2021-08-15 21:26:37 +02:00
20d3d771c8 create LICENSE.md 2021-07-31 16:01:23 +02:00
50 changed files with 7264 additions and 10894 deletions

View File

@ -14,9 +14,6 @@
"plugins": [
"ava"
],
"ignorePatterns": [
"gulpfile.js"
],
"rules": {
"indent": [
"error",

View File

@ -9,14 +9,14 @@
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
name: 'CodeQL'
on:
push:
branches: [ main ]
branches: [main]
pull_request:
# The branches below must be a subset of the branches above
branches: [ main ]
branches: [main]
schedule:
- cron: '39 19 * * 4'
@ -32,40 +32,40 @@ jobs:
strategy:
fail-fast: false
matrix:
language: [ 'javascript' ]
language: ['javascript']
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
# 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
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Checkout repository
uses: actions/checkout@v2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# 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.
# 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
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# 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.
# 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
# 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)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# 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)
#- name: Autobuild
# uses: github/codeql-action/autobuild@v1
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ 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
# uses a compiled language
# ✏️ 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
# uses a compiled language
#- run: |
# make bootstrap
# make release
- run: |
npm install
npm run build-prod
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

View File

@ -5,13 +5,12 @@ name: Node.js CI
on:
push:
branches: [ main ]
branches: [main]
pull_request:
branches: [ main ]
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
strategy:
@ -20,10 +19,10 @@ jobs:
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm run build-prod
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
- run: npm ci --omit=dev --ignore-scripts
- run: npm run build-prod

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
.idea/
build/
coverage/
node_modules/

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
}

201
LICENSE.md Normal file
View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -4,34 +4,42 @@ 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/).
The current changelog can be under [source/changelog.txt](source/changelog.txt).
## Development
### Setup
1. Make sure `npm` and `composer` are installed.
2. Run: `npm install`
3. Run: `php composer.phar install`
3. Run: `composer install`
### Development build
1. Build: `npm run build-dev`
2. Make sure to keep `changelog.txt` up-to-date.
### 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.
3. Update the `package-lock.json`: `npm i --package-lock-only`
4. Build: `npm run build-prod`
5. Determine minimum PHP version for code and update package.json if needed: `./vendor/bin/phpcompatinfo analyser:run ./source`
6. Make sure screenshots are up-to-date.
7. Copy the built plugin into `/trunk` of SVN.
8. Create a new tag of the new version: `svn cp trunk tags/<version>`
9. Check the version number occurences in both folders.
10. Commit everything together to the release SVN: `svn ci -m "release version <version>"`
11. Commit the new version in git with the same message.
12. Tag the new version: `git tag v<version>`
13. Push the new tag to the repository: `git push --tags`
5. Make sure screenshots are up-to-date.
6. Copy the built plugin into `/trunk` of SVN.
7. Create a new tag of the new version: `svn cp trunk tags/<version>`
8. Check the version number occurrences in both folders.
9. Commit everything together to the release SVN: `svn ci -m "release version <version>"`
10. Commit the new version in git with the same message.
11. Tag the new version: `git tag v<version>`
12. Push the new tag to the repository: `git push --tags`
### Other commands
- Run ESLint: `npm run eslint`
- Run JavaScript code coverage with tests: `npm run coverage`
- 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`

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

49
gulpfile.cjs Normal file
View File

@ -0,0 +1,49 @@
/* 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/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;

13876
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,13 +1,17 @@
{
"name": "connector-mobilizon",
"version": "0.6.1",
"version": "0.9.1",
"description": "Display Mobilizon events in WordPress.",
"private": true,
"type": "module",
"scripts": {
"build-dev": "gulp dev",
"build-prod": "ava && gulp prod",
"build-dev": "webpack --mode=development && gulp inject",
"build-prod": "ava && webpack --mode=production && gulp inject",
"clean": "rimraf -- build",
"coverage": "c8 --all --reporter=html --reporter=text --include=source/**/*.js ava",
"eslint": "npx eslint source/**/*.js",
"format": "npx prettier --write .",
"prepare": "husky install",
"test": "ava"
},
"author": {
@ -20,37 +24,44 @@
},
"license": "Apache-2.0",
"dependencies": {
"graphql": "^15.5.1",
"graphql-request": "^3.4.0",
"luxon": "^1.28.0",
"object-hash": "^2.2.0"
"graphql": "16.5.0",
"graphql-request": "4.2.0",
"luxon": "2.4.0",
"object-hash": "3.0.0"
},
"devDependencies": {
"ava": "^3.15.0",
"c8": "^7.7.3",
"del": "^6.0.0",
"eslint": "^7.30.0",
"eslint-plugin-ava": "^12.0.0",
"esm": "^3.2.25",
"gulp": "^4.0.2",
"gulp-replace": "^1.1.3",
"jsdom": "^16.6.0",
"webpack": "^5.44.0",
"webpack-cli": "^4.7.2",
"webpack-stream": "^6.1.2"
"@babel/core": "7.17.12",
"@babel/preset-env": "7.17.12",
"ava": "4.2.0",
"babel-loader": "8.2.5",
"c8": "7.11.3",
"copy-webpack-plugin": "11.0.0",
"eslint": "8.15.0",
"eslint-plugin-ava": "13.2.0",
"esm": "3.2.25",
"gulp": "4.0.2",
"gulp-replace": "1.1.3",
"husky": "8.0.1",
"jsdom": "19.0.0",
"lint-staged": "12.4.1",
"prettier": "2.6.2",
"rimraf": "3.0.2",
"webpack": "5.72.1",
"webpack-cli": "4.9.2"
},
"ava": {
"files": [
"./source/**/*test.js"
],
"require": [
"esm"
]
},
"additionalDetails": {
"niceName": "Connector for Mobilizon",
"phpMinimumVersion": 5.4,
"phpMinimumVersion": 7.4,
"wordpressMinimumVersion": 5.6,
"wordpressTestedUpToVersion": 5.8
"wordpressTestedUpToVersion": "6.0"
},
"lint-staged": {
"source/**/*.js": "eslint",
"**/*": "prettier --write --ignore-unknown"
}
}

View File

@ -6,6 +6,47 @@
#### Fixed
#### Security
### [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
#### Changed
- Update dependencies
#### Fixed
- Fix empty WordPress timezone_string option resulting in Invalid DateTime
### [0.6.1] - 2021-07-13
#### Changed
- Confirm compatibility with WordPress 5.8

View File

@ -0,0 +1,67 @@
<?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;
}
final class Mobilizon_Connector {
private function __construct() {
add_action('init', [$this, 'register_settings']);
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();
}
public function register_settings() {
MobilizonConnector\Settings::init();
}
public function register_scripts() {
wp_enqueue_script(MobilizonConnector\NAME . '-js', plugins_url('front/events-loader.js', __FILE__ ));
}
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,58 +0,0 @@
import test from 'ava'
import DateTimeWrapper from './date-time-wrapper'
test('#getShortDate usual date', t => {
const d = new DateTimeWrapper({ text: '2020-12-24T16:45:00Z' })
t.is(d.getShortDate(), '24/12/2020')
})
test('#get24Time usual time', t => {
const d = new DateTimeWrapper({ text: '2020-12-24T16:45:00Z' })
t.is(d.get24Time(), '16:45')
})
test('#equalsDate same date, different time', t => {
const d = new DateTimeWrapper({ text: '2020-12-24T16:45:00Z' })
const e = new DateTimeWrapper({ text: '2020-12-24T17:46:01Z' })
t.true(d.equalsDate(e))
})
test('#equalsDate different date, different time', t => {
const d = new DateTimeWrapper({ text: '2020-12-24T16:45:00Z' })
const e = new DateTimeWrapper({ text: '2021-11-25T17:46:01Z' })
t.false(d.equalsDate(e))
})
test('#equalsDate different day, different time', t => {
const d = new DateTimeWrapper({ text: '2020-12-24T16:45:00Z' })
const e = new DateTimeWrapper({ text: '2020-12-25T17:46:01Z' })
t.false(d.equalsDate(e))
})
test('#equalsDate different month, different time', t => {
const d = new DateTimeWrapper({ text: '2020-12-24T16:45:00Z' })
const e = new DateTimeWrapper({ text: '2020-11-24T17:46:01Z' })
t.false(d.equalsDate(e))
})
test('#equalsDate different year, different time', t => {
const d = new DateTimeWrapper({ text: '2020-12-24T16:45:00Z' })
const e = new DateTimeWrapper({ text: '2021-12-24T17:46:01Z' })
t.false(d.equalsDate(e))
})
test('#getCurrentDatetimeAsString correct format', t => {
const d = DateTimeWrapper.getCurrentDatetimeAsString()
t.is(d[4], '-')
t.is(d[7], '-')
t.is(d[10], 'T')
t.is(d[13], ':')
t.is(d[16], ':')
t.is(d[19], '.')
t.is(d[d.length-3], ':')
})
test('#getShortOffsetName usual time', t => {
const d = new DateTimeWrapper({ text: '2020-12-24T16:45:00Z' })
t.is(d.getShortOffsetName(), 'UTC')
})

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,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

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

View File

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

View File

@ -0,0 +1,81 @@
import test from 'ava'
import { JSDOM } from 'jsdom'
import { displayEvents, displayErrorMessage } from './events-displayer.js'
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)
const listElement2 = document.createElement('li')
listElement2.setAttribute('style', 'display: none;')
t.context.list.appendChild(listElement2)
})
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, 3)
t.is(list.children[2].childNodes[0].tagName, 'A')
t.is(list.children[2].childNodes[0].getAttribute('href'), 'b')
t.is(list.children[2].childNodes[0].childNodes[0].nodeValue, 'a')
t.is(list.children[2].childNodes[1].tagName, 'BR')
t.is(list.children[2].childNodes[2].nodeValue, '15/04/2021 10:30 - 15:30')
t.is(list.children[2].childNodes[3].tagName, 'BR')
t.is(list.children[2].childNodes[4].nodeValue, 'c, d')
})
test('#displayErrorMessage no children added', (t) => {
const list = t.context.list
displayErrorMessage({ data: '', list })
t.is(list.children.length, 2)
})
test('#displayErrorMessage error message display', (t) => {
const list = t.context.list
displayErrorMessage({ data: '', list })
t.is(list.children[0].style.display, 'block')
t.is(list.children[1].style.display, 'none')
})
test('#displayErrorMessage group not found error message display', (t) => {
const list = t.context.list
const data = {
response: {
errors: [
{
code: 'group_not_found',
},
],
},
}
displayErrorMessage({ data, list })
t.is(list.children[0].style.display, 'none')
t.is(list.children[1].style.display, 'block')
})

View File

@ -1,17 +1,25 @@
import Formatter from './formatter'
import { createAnchorElement } from './html-creator'
import Formatter from './formatter.js'
import { createAnchorElement } from './html-creator.js'
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 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 })
const a = createAnchorElement({
document,
text: events[i].title,
url: events[i].url,
})
li.appendChild(a)
const br = document.createElement('br')
@ -30,7 +38,7 @@ export function displayEvents({ data, document, list }) {
if (events[i].physicalAddress) {
const location = Formatter.formatLocation({
description: events[i].physicalAddress.description,
locality: events[i].physicalAddress.locality
locality: events[i].physicalAddress.locality,
})
if (location) {
const brBeforeLocation = document.createElement('br')
@ -47,5 +55,15 @@ export function displayEvents({ data, document, list }) {
export function displayErrorMessage({ data, list }) {
console.error(data)
list.children[0].style.display = 'block'
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'
) {
list.children[1].style.display = 'block'
} else {
list.children[0].style.display = 'block'
}
}

View File

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

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

View File

@ -1,7 +1,7 @@
import test from 'ava'
import { JSDOM } from 'jsdom'
import { createAnchorElement } from './html-creator'
import { createAnchorElement } from './html-creator.js'
let document
@ -9,7 +9,7 @@ test.beforeEach(() => {
document = new JSDOM().window.document
})
test('#createAnchorElement usual parameters', t => {
test('#createAnchorElement usual parameters', (t) => {
const a = createAnchorElement({ document, text: 'a', url: 'b' })
t.is(a.tagName, '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 SessionCache from './session-cache'
import SessionCache from './session-cache.js'
const fakeStorage = {
elements: {},
clear() {
@ -17,18 +16,18 @@ const fakeStorage = {
setItem(key, value) {
this.elements[key] = value
}
},
}
test.afterEach(() => {
fakeStorage.clear()
})
test('#add & #get', t => {
test('#add & #get', (t) => {
SessionCache.add(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)
})

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
export default class SessionCache {
static add(storage, parameters, data) {
const key = hash(parameters)
const timestamp = Date.now()
@ -17,7 +16,11 @@ export default class SessionCache {
static get(storage, parameters) {
const key = hash(parameters)
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 null
}

View File

@ -29,7 +29,7 @@ class EventsListShortcut {
$locale = str_replace('_', '-', get_locale());
$groupName = $atts_with_overriden_defaults['group-name'];
$url = Settings::getUrl();
$timeZone = get_option('timezone_string');
$timeZone = wp_timezone_string();
$isShortOffsetNameShown = Settings::isShortOffsetNameShown();
ob_start();

View File

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

View File

@ -24,7 +24,9 @@ Features
- 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"]`
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`.
The source code is available on [Github](https://github.com/dwaxweiler/connector-mobilizon).
@ -36,6 +38,34 @@ The source code is available on [Github](https://github.com/dwaxweiler/connector
## Changelog
### [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
#### Changed
- Update dependencies
#### Fixed
- Fix empty WordPress timezone_string option resulting in Invalid DateTime
### [0.6.1] - 2021-07-13
#### Changed
- Confirm compatibility with WordPress 5.8

View File

@ -12,4 +12,5 @@ if (!defined('ABSPATH')) {
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>
<li style="display: none;"><?php esc_html_e('The group could not be found!', 'connector-mobilizon'); ?></li>
</ul>

44
webpack.config.cjs Normal file
View File

@ -0,0 +1,44 @@
/* eslint-disable no-undef */
const path = require('path')
const CopyPlugin = require('copy-webpack-plugin')
const FOLDER_SOURCE = './source'
module.exports = {
entry: FOLDER_SOURCE + '/front/events-loader.js',
output: {
filename: 'events-loader.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: '../',
},
],
}),
],
}