Merge branch 'develop'

This commit is contained in:
Stefan Kalscheuer 2020-04-10 17:25:54 +02:00
commit cf65d6d7c3
26 changed files with 640 additions and 365 deletions

View File

@ -1,24 +1,17 @@
kind: pipeline
name: default
clone:
disable: true
type: docker
steps:
- name: clone
image: plugins/git
settings:
depth: 10
skip_verify: true
- name: pre-build
image: composer
commands:
- composer install
- name: test
image: composer
commands:
- ./vendor/bin/robo test
- name: test-style
image: composer
commands:
- ./vendor/bin/robo test:cs
- name: pre-build
image: composer
commands:
- composer install
- name: test
image: composer
commands:
- composer test
- name: lint
image: composer
commands:
- composer lint-php

View File

@ -1,3 +1,25 @@
{
"extends": "./vendor/npm-asset/eslint-config-wordpress/index.js"
"env": {
"es6": false,
"browser": true
},
"globals": {
"sclivetickerAjax": "readonly",
"wp": "readonly"
},
"extends": [
"plugin:@wordpress/eslint-plugin/recommended",
"plugin:@wordpress/eslint-plugin/es5"
],
"overrides": [
{
"files": [
"*"
],
"rules": {
"no-var": "off",
"object-shorthand": "off"
}
}
]
}

2
.gitignore vendored
View File

@ -1,7 +1,9 @@
composer.lock
package-lock.json
/vendor/
/node_modules/
/dist/
.idea
.phpunit.result.cache
**/*.min.css
**/*.min.js

View File

@ -1,16 +1,14 @@
language: php
dist: trusty
php:
- '5.5'
- '5.6'
- '7.0'
- '7.1'
- '7.2'
- '7.3'
- '7.4'
before_script:
- composer install
- npm install
script:
- composer test-all
- composer test
- composer lint-all
notifications:
slack:
secure: "R40BhRCETuDule7lz4oGN+qyLvd7dBmuEu6hVELNhWg3DgCgYOXyrWR2dgxsWsAZ3sldpWGfTJKzSShdDanGCpygpYzuvXxjt23YYJ2ihrohYJwiGIhkR9c24LF2yvWBQDBNZaeLBQ3o6FSnbkTBsmRy5ShgKehfKCOQTKmI1yWHi3fvkMElTorrJc710O41yy/bRKBnoIYd4ZfpLMSSVGCPzR5lZPZy3EiGWXPgYdY7jGMI7ADsy+T5VWHyFqgSSJz/U2bcryKzF08FAry8pyu9lN3r61kXHfVCCJX+kcsFxW9yCfuPLnLu14O776y3U6zrX9is+8mEfkMuTXFaL5o8+iq32AmFjTIDQn6o9BKHsknfmppjwZiLgFTp1T7Z/XR6I4nyK9Z5HXDU2HS0eCUknbgXlMLhxWpKhkyx4rQELuvVlgD+u7yRYraawc3v1ycqaPj0S0G5QBFljSuxsZgNnX1hs8VmgafIvOq5qm4ZVVBhhbz+LgvW1m9COr8DDPVhWWdpcWzF8jtkqC3m4Q/1Ssc6T/MbJMgcXRq/C4DlfEs4aYGYfSl7gLtF2PwlEQCppKJwx0fEPkcbZZ1PjpzF+JMwwRmWS88R0oRyThOyCwlG50c+ktB94pJC+sP1aQZrLAd4WDKUPD9vJTas86V3XBjTUJPs8HQaBDFqFdg="

View File

@ -1,5 +1,6 @@
[![Build Status](https://travis-ci.org/stklcode/wp-liveticker.svg?branch=master)](https://travis-ci.org/stklcode/wp-liveticker)
[![Quality Gate](https://sonarcloud.io/api/project_badges/measure?project=de.stklcode.web.wordpress.plugins%3Awp-liveticker&metric=alert_status)](https://sonarcloud.io/dashboard?id=de.stklcode.web.wordpress.plugins%3Awp-liveticker)
[![WP Plugin Version](https://img.shields.io/wordpress/plugin/v/stklcode-liveticker.svg)](https://wordpress.org/plugins/stklcode-liveticker/)
[![Packagist Version](https://img.shields.io/packagist/v/stklcode/stklcode-liveticker.svg)](https://packagist.org/packages/stklcode/stklcode-liveticker)
[![License](https://img.shields.io/badge/license-GPL%20v2-blue.svg)](https://github.com/stklcode/wp-liveticker/blob/master/LICENSE.md)
@ -8,8 +9,8 @@
* Contributors: Stefan Kalscheuer
* Tags: liveticker, feed, rss
* Requires at least: 4.0
* Tested up to: 5.3
* Requires PHP: 5.2
* Tested up to: 5.4
* Requires PHP: 5.6
* Stable tag: 1.0.0
* License: GPLv2 or later
* License URI: http://www.gnu.org/licenses/gpl-2.0.html
@ -26,9 +27,9 @@ Easily add multiple livetickers, add them to posts with shortcode or use them as
* Handle multiple Tickers
* Automatic update via AJAX
* RSS feed capability
* Shortcode to display liveticker
* Gutenberg block and shortcode to display liveticker
* Add ticker to sidebar widgets
* Ability to customise through CSS
* Ability to customize through CSS
* Localization support
@ -47,7 +48,9 @@ Easily add multiple livetickers, add them to posts with shortcode or use them as
### How do I display a liveticker on my post/page?
Use the shortcode `[liveticker ticker="my-ticker"]`.
On WordPress 5 sites there is a Gutenberg Block available to embed a liveticker in your post.
You can also use the shortcode `[liveticker ticker="my-ticker"]` on WordPress 4 or classic-mode sites.
If you want to define a custom tick limit, you might also add a limit with `[liveticker ticker="my-ticker" limit="10"]`.
### Can I use my own styles?
@ -57,9 +60,11 @@ You can deactivate the default stylesheet on the settings page and include your
### Does the liveticker work with caching?
It strongly depends on the use case.
If you update your ticker every 5 minutes, a caching time of 12 hours obviously makes no sense.
However the AJAX update will fetch the latest ticks and update cached tickers depending on the configured interval.
If you activate AJAX updates (enabled by default), the JavaScript will automatically update the content, even when the
page is loaded from cached.
If AJAX is disabled, it depends on your update and caching intervals. If you update your ticker every 5 minutes, a
caching time of 12 hours obviously makes no sense.
## Screenshots
@ -68,11 +73,18 @@ However the AJAX update will fetch the latest ticks and update cached tickers d
2. Tick management
3. Ticker configuration.
4. Settings page
5. Example shortcode
6. Example widget
5. Gutenberg block
6. Example shortcode
7. Example widget
## Changelog
### 1.1.0 - unreleased
* Requires PHP 5.6 or above
* Use GMT for automatic updates
* Gutenberg Block available
### 1.0.0 - 2018-11-02
* Initial release

View File

@ -26,6 +26,7 @@ class RoboFile extends Tasks {
const OPT_SKIPTEST = 'skipTests';
const OPT_SKIPSTYLE = 'skipStyle';
const OPT_MINIFY = 'minify';
const OPT_NODE = 'node';
/**
* Version tag (read from composer.json).
@ -94,11 +95,31 @@ class RoboFile extends Tasks {
/**
* Run code style tests
*
* @param array $opts Options.
*
* @return void
*/
public function testCS() {
$this->say( 'Executing PHPCS tests...' );
public function testCS(
$opts = array(
self::OPT_TARGET => 'dist',
self::OPT_SKIPTEST => false,
self::OPT_SKIPSTYLE => false,
self::OPT_MINIFY => true,
self::OPT_NODE => false,
)
) {
$this->say( 'Executing PHPCS...' );
$this->_exec( __DIR__ . '/vendor/bin/phpcs --standard=phpcs.xml -s' );
if ( $opts[ self::OPT_NODE ] ) {
$this->say( 'Executing ESLint...' );
$this->_exec( __DIR__ . '/node_modules/eslint/bin/eslint.js ' . __DIR__ . '/scripts/block.js' );
$this->_exec( __DIR__ . '/node_modules/eslint/bin/eslint.js ' . __DIR__ . '/scripts/liveticker.js' );
$this->say( 'Executing StyleLint...' );
$this->_exec( __DIR__ . '/node_modules/stylelint/bin/stylelint.js ' . __DIR__ . '/styles/block.css' );
$this->_exec( __DIR__ . '/node_modules/stylelint/bin/stylelint.js ' . __DIR__ . '/styles/liveticker.css' );
}
}
/**
@ -114,6 +135,7 @@ class RoboFile extends Tasks {
self::OPT_SKIPTEST => false,
self::OPT_SKIPSTYLE => false,
self::OPT_MINIFY => true,
self::OPT_NODE => false,
)
) {
$this->clean( $opts );
@ -125,7 +147,7 @@ class RoboFile extends Tasks {
if ( isset( $opts[ self::OPT_SKIPSTYLE ] ) && true === $opts[ self::OPT_SKIPSTYLE ] ) {
$this->say( 'Style checks skipped' );
} else {
$this->testCS();
$this->testCS( $opts );
}
$this->bundle();
}
@ -137,21 +159,23 @@ class RoboFile extends Tasks {
*/
private function bundle() {
$this->say( 'Bundling resources...' );
$this->taskCopyDir( array(
'includes' => $this->target_dir . '/' . $this->final_name . '/includes',
'scripts' => $this->target_dir . '/' . $this->final_name . '/scripts',
'styles' => $this->target_dir . '/' . $this->final_name . '/styles',
'views' => $this->target_dir . '/' . $this->final_name . '/views',
) )->run();
$this->taskCopyDir(
array(
'includes' => $this->target_dir . '/' . $this->final_name . '/includes',
'scripts' => $this->target_dir . '/' . $this->final_name . '/scripts',
'styles' => $this->target_dir . '/' . $this->final_name . '/styles',
'views' => $this->target_dir . '/' . $this->final_name . '/views',
)
)->run();
$this->_copy( 'stklcode-liveticker.php', $this->target_dir . '/' . $this->final_name . '/stklcode-liveticker.php' );
$this->_copy( 'README.md', $this->target_dir . '/' . $this->final_name . '/README.md' );
$this->_copy( 'LICENSE.md', $this->target_dir . '/' . $this->final_name . '/LICENSE.md' );
// Remove content before title (e.g. badges) from README file.
$this->taskReplaceInFile( $this->target_dir . '/' . $this->final_name . '/README.md' )
->regex( '/^[^\\#]*/' )
->to( '' )
->run();
->regex( '/^[^\\#]*/' )
->to( '' )
->run();
}
/**
@ -190,6 +214,7 @@ class RoboFile extends Tasks {
self::OPT_SKIPTEST => false,
self::OPT_SKIPSTYLE => false,
self::OPT_MINIFY => true,
self::OPT_NODE => false,
)
) {
if ( ! isset( $opts[ self::OPT_MINIFY ] ) ) {
@ -224,6 +249,7 @@ class RoboFile extends Tasks {
self::OPT_SKIPTEST => false,
self::OPT_SKIPSTYLE => false,
self::OPT_MINIFY => true,
self::OPT_NODE => false,
)
) {
if ( ! isset( $opts[ self::OPT_MINIFY ] ) ) {
@ -259,6 +285,7 @@ class RoboFile extends Tasks {
self::OPT_SKIPTEST => false,
self::OPT_SKIPSTYLE => false,
self::OPT_MINIFY => true,
self::OPT_NODE => false,
)
) {
$this->build( $opts );
@ -282,6 +309,7 @@ class RoboFile extends Tasks {
self::OPT_SKIPTEST => false,
self::OPT_SKIPSTYLE => false,
self::OPT_MINIFY => true,
self::OPT_NODE => false,
)
) {
// First execute build job.
@ -315,6 +343,7 @@ class RoboFile extends Tasks {
self::OPT_SKIPTEST => false,
self::OPT_SKIPSTYLE => false,
self::OPT_MINIFY => true,
self::OPT_NODE => false,
)
) {
// First execute build job.
@ -348,6 +377,7 @@ class RoboFile extends Tasks {
self::OPT_SKIPTEST => false,
self::OPT_SKIPSTYLE => false,
self::OPT_MINIFY => true,
self::OPT_NODE => false,
)
) {
// First execute build job.
@ -380,6 +410,7 @@ class RoboFile extends Tasks {
self::OPT_SKIPTEST => false,
self::OPT_SKIPSTYLE => false,
self::OPT_MINIFY => true,
self::OPT_NODE => false,
)
) {
// First execute build job.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 12 KiB

BIN
assets/screenshot-7.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -1,6 +1,6 @@
{
"name": "stklcode/stklcode-liveticker",
"version": "1.0.0",
"version": "1.1.0-alpha",
"description": "A simple Liveticker for Wordpress.",
"keywords": [
"wordpress",
@ -17,23 +17,22 @@
],
"type": "wordpress-plugin",
"require": {
"php": ">=5.2",
"php": ">=5.6",
"composer/installers": "~1.7"
},
"require-dev": {
"php": ">=5.2",
"consolidation/robo": "^1.4",
"phpunit/phpunit": "*",
"phpunit/php-code-coverage": "*",
"dealerdirect/phpcodesniffer-composer-installer": "^0.5",
"php": ">=7",
"consolidation/robo": "^2",
"phpunit/phpunit": "^8",
"phpunit/php-code-coverage": "^7",
"dealerdirect/phpcodesniffer-composer-installer": "^0.6",
"slowprog/composer-copy-file": "~0.3",
"squizlabs/php_codesniffer": "^3.4",
"phpcompatibility/php-compatibility": "^9.2",
"wp-coding-standards/wpcs": "^2.1",
"squizlabs/php_codesniffer": "^3.5",
"phpcompatibility/php-compatibility": "^9.3",
"wp-coding-standards/wpcs": "^2.2",
"patchwork/jsqueeze": "^2.0",
"natxet/cssmin": "^3.0",
"matthiasmullie/minify": "^1.3",
"npm-asset/eslint-config-wordpress": "^2.0"
"matthiasmullie/minify": "^1.3"
},
"scripts": {
"post-install-cmd": [
@ -54,28 +53,30 @@
"@minify",
"robo deploy:all"
],
"test-all": [
"@test",
"@test-cs"
],
"test": [
"phpunit"
],
"test-cs": [
"lint-all": [
"@lint-php",
"@lint-css",
"@lint-js"
],
"lint-php": [
"phpcs --standard=phpcs.xml -s"
],
"fix-cs": [
"phpcbf --standard=phpcs.xml"
"lint-css": [
"./node_modules/stylelint/bin/stylelint.js styles/block.css",
"./node_modules/stylelint/bin/stylelint.js styles/liveticker.css"
],
"lint-js": [
"./node_modules/eslint/bin/eslint.js scripts/block.js",
"./node_modules/eslint/bin/eslint.js scripts/liveticker.js"
],
"minify": [
"minifycss styles/block.css > styles/block.min.css",
"minifycss styles/liveticker.css > styles/liveticker.min.css",
"minifyjs scripts/block.js > scripts/block.min.js",
"minifyjs scripts/liveticker.js > scripts/liveticker.min.js"
]
},
"repositories": [
{
"type": "composer",
"url": "https://asset-packagist.org"
}
]
}
}

View File

@ -4,9 +4,11 @@
*
* This file contains the derived class for the plugin's administration features.
*
* @package Liveticker
* @package SCLiveticker
*/
namespace SCLiveticker;
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
@ -15,7 +17,7 @@ if ( ! defined( 'ABSPATH' ) ) {
/**
* Liveticker admin configuration.
*/
class SCLiveticker_Admin extends SCLiveticker {
class Admin extends SCLiveticker {
/**
* Add to Right Now Widget
*
@ -202,4 +204,35 @@ class SCLiveticker_Admin extends SCLiveticker {
return $result;
}
/**
* Register custom Gutenberg block type.
*
* @return void
* @since 1.1
*/
public static function register_block() {
wp_register_script(
'scliveticker-editor',
SCLIVETICKER_BASE . 'scripts/block.min.js',
array( 'wp-blocks', 'wp-element' ),
self::VERSION,
true
);
wp_register_style(
'scliveticker-editor',
SCLIVETICKER_BASE . 'styles/block.min.css',
array(),
self::VERSION
);
register_block_type(
'scliveticker-block/liveticker',
array(
'editor_script' => 'scliveticker-editor',
'editor_style' => 'scliveticker-editor',
)
);
}
}

View File

@ -4,14 +4,19 @@
*
* This file contains the plugin's base class.
*
* @package Liveticker
* @package SCLiveticker
*/
namespace SCLiveticker;
use WP_Query;
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Liveticker.
*/
@ -21,7 +26,7 @@ class SCLiveticker {
*
* @var string OPTIONS
*/
const VERSION = '1.0.0';
const VERSION = '1.1.0-alpha';
/**
* Options tag.
@ -80,11 +85,8 @@ class SCLiveticker {
// Add shortcode.
add_shortcode( 'liveticker', array( __CLASS__, 'shortcode_ticker_show' ) );
// Enqueue styles.
add_action( 'wp_footer', array( __CLASS__, 'enqueue_styles' ) );
// Enqueue JavaScript.
add_action( 'wp_footer', array( __CLASS__, 'enqueue_scripts' ) );
// Enqueue styles and JavaScript.
add_action( 'wp_footer', array( __CLASS__, 'enqueue_resources' ) );
// Add AJAX hook if configured.
if ( 1 === self::$options['enable_ajax'] ) {
@ -95,11 +97,11 @@ class SCLiveticker {
// Admin only actions.
if ( is_admin() ) {
// Add dashboard "right now" functionality.
add_action( 'right_now_content_table_end', array( 'SCLiveticker_Admin', 'dashboard_right_now' ) );
add_action( 'right_now_content_table_end', array( 'SCLiveticker\\Admin', 'dashboard_right_now' ) );
// Settings.
add_action( 'admin_init', array( 'SCLiveticker_Admin', 'register_settings' ) );
add_action( 'admin_menu', array( 'SCLiveticker_Admin', 'register_settings_page' ) );
add_action( 'admin_init', array( 'SCLiveticker\\Admin', 'register_settings' ) );
add_action( 'admin_menu', array( 'SCLiveticker\\Admin', 'register_settings_page' ) );
}
}
@ -133,6 +135,7 @@ class SCLiveticker {
'show_ui' => true,
'show_admin_column' => true,
'query_var' => true,
'show_in_rest' => true,
)
);
@ -198,14 +201,14 @@ class SCLiveticker {
$show_feed = 1 === self::$options['show_feed'];
}
$output = '<ul class="sclt-ticker';
$output = '<div class="wp-block-scliveticker-ticker';
if ( 1 === self::$options['enable_ajax'] ) {
$output .= ' sclt-ticker-ajax" '
$output .= ' sclt-ajax" '
. 'data-sclt-ticker="' . $ticker . '" '
. 'data-sclt-limit="' . $limit . '" '
. 'data-sclt-last="' . current_time( 'timestamp' );
. 'data-sclt-last="' . current_datetime()->getTimestamp();
}
$output .= '">';
$output .= '"><ul>';
$args = array(
'post_type' => 'scliveticker_tick',
@ -226,7 +229,7 @@ class SCLiveticker {
$output .= self::tick_html( get_the_time( 'd.m.Y H.i' ), get_the_title(), get_the_content() );
}
$output .= '</ul>';
$output .= '</ul></div>';
// Show RSS feed link, if configured.
if ( $show_feed ) {
@ -243,32 +246,15 @@ class SCLiveticker {
return $output;
}
/**
* Register frontend CSS.
*
* @return void
*/
public static function enqueue_styles() {
// Only add if shortcode is present.
if ( self::$shortcode_present || self::$widget_present ) {
wp_enqueue_style(
'wplt-css',
SCLIVETICKER_BASE . 'styles/liveticker.min.css',
'',
self::VERSION,
'all'
);
}
}
/**
* Register frontend JS.
*
* @return void
* @since 1.1 Combined former methods "enqueue_styles" and "enqueue_scripts".
*/
public static function enqueue_scripts() {
public static function enqueue_resources() {
// Only add if shortcode is present.
if ( self::$shortcode_present || self::$widget_present ) {
if ( self::$shortcode_present || self::$widget_present || self::block_present() ) {
wp_enqueue_script(
'scliveticker-js',
SCLIVETICKER_BASE . 'scripts/liveticker.min.js',
@ -287,6 +273,17 @@ class SCLiveticker {
'poll_interval' => self::$options['poll_interval'] * 1000,
)
);
// Enqueue CSS if enabled.
if ( 1 === self::$options['enable_css'] ) {
wp_enqueue_style(
'sclt-css',
SCLIVETICKER_BASE . 'styles/liveticker.min.css',
'',
self::VERSION,
'all'
);
}
}
}
@ -317,7 +314,13 @@ class SCLiveticker {
}
$limit = ( isset( $update_req['l'] ) ) ? intval( $update_req['l'] ) : - 1;
$last_poll = ( isset( $update_req['t'] ) ) ? intval( $update_req['t'] ) : 0;
$last_poll = explode(
',',
gmdate(
'Y,m,d,H,i,s',
( isset( $update_req['t'] ) ) ? intval( $update_req['t'] ) : 0
)
);
// Query new ticks from DB.
$query_args = array(
@ -331,7 +334,15 @@ class SCLiveticker {
),
),
'date_query' => array(
'after' => date( 'c', $last_poll ),
'column' => 'post_date_gmt',
'after' => array(
'year' => intval( $last_poll[0] ),
'month' => intval( $last_poll[1] ),
'day' => intval( $last_poll[2] ),
'hour' => intval( $last_poll[3] ),
'minute' => intval( $last_poll[4] ),
'second' => intval( $last_poll[5] ),
),
),
);
@ -351,13 +362,13 @@ class SCLiveticker {
$res[] = array(
'w' => $slug,
'h' => $out,
't' => current_time( 'timestamp' ),
't' => current_datetime()->getTimestamp(),
);
} else {
$res[] = array(
's' => $slug,
'h' => $out,
't' => current_time( 'timestamp' ),
't' => current_datetime()->getTimestamp(),
);
}
}
@ -443,4 +454,15 @@ class SCLiveticker {
. '<span class="sclt-widget-title">' . $title . '</span>'
. '</li>';
}
/**
* Check if the Gutenberg block is present in current post.
*
* @return boolean True, if Gutenberg block is present.
* @since 1.1
*/
private static function block_present() {
return function_exists( 'has_block' ) && // We are in WP 5.x environment.
has_block( 'scliveticker/ticker' ); // Specific block is present.
}
}

View File

@ -4,9 +4,13 @@
*
* This file contains the derived class for the plugin's system operations.
*
* @package Liveticker
* @package SCLiveticker
*/
namespace SCLiveticker;
use WP_Query;
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
@ -15,7 +19,7 @@ if ( ! defined( 'ABSPATH' ) ) {
/**
* Liveticker system configuration.
*/
class SCLiveticker_System extends SCLiveticker {
class System extends SCLiveticker {
/**
* Activation hook.

View File

@ -4,17 +4,22 @@
*
* This file contains the liveticker widget.
*
* @package Liveticker
* @package SCLiveticker
*/
namespace SCLiveticker;
use WP_Query;
use WP_Widget;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class SCLiveticker_Widget.
* Class Widget.
*/
class SCLiveticker_Widget extends WP_Widget {
class Widget extends WP_Widget {
/**
* SCLiveticker_Widget constructor.
@ -68,12 +73,12 @@ class SCLiveticker_Widget extends WP_Widget {
echo $before_title . esc_html( $title ) . $after_title;
}
echo '<ul class="sclt-widget';
echo '<div class="wp-widget-scliveticker-ticker';
if ( '1' === $ajax ) {
echo ' sclt-widget-ajax" '
echo ' sclt-ajax" '
. 'data-sclt-ticker="' . esc_attr( $category ) . '" '
. 'data-sclt-limit="' . esc_attr( $count ) . '" '
. 'data-sclt-last="' . esc_attr( current_time( 'timestamp' ) );
. 'data-sclt-last="' . esc_attr( current_datetime()->getTimestamp() );
}
echo '">';

88
package-lock.json generated
View File

@ -1,88 +0,0 @@
{
"name": "wp-liveticker2",
"version": "1.0.0-beta",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"cssesc": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-1.0.1.tgz",
"integrity": "sha512-S2hzrpWvE6G/rW7i7IxJfWBYn27QWfOIncUW++8Rbo1VB5zsJDSVPcnI+Q8z7rhxT6/yZeLOCja4cZnghJrNGA=="
},
"indexes-of": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz",
"integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc="
},
"lodash": {
"version": "4.17.11",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg=="
},
"postcss-media-query-parser": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz",
"integrity": "sha1-J7Ocb02U+Bsac7j3Y1HGCeXO8kQ="
},
"postcss-resolve-nested-selector": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.1.tgz",
"integrity": "sha1-Kcy8fDfe36wwTp//C/FZaz9qDk4="
},
"postcss-selector-parser": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-4.0.0.tgz",
"integrity": "sha512-5h+MvEjnzu1qy6MabjuoPatsGAjjDV9B24e7Cktjl+ClNtjVjmvAXjOFQr1u7RlWULKNGYaYVE4s+DIIQ4bOGA==",
"requires": {
"cssesc": "^1.0.1",
"indexes-of": "^1.0.1",
"uniq": "^1.0.1"
}
},
"postcss-value-parser": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz",
"integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ=="
},
"stylelint-config-recommended": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-2.1.0.tgz",
"integrity": "sha512-ajMbivOD7JxdsnlS5945KYhvt7L/HwN6YeYF2BH6kE4UCLJR0YvXMf+2j7nQpJyYLZx9uZzU5G1ZOSBiWAc6yA=="
},
"stylelint-config-recommended-scss": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/stylelint-config-recommended-scss/-/stylelint-config-recommended-scss-3.2.0.tgz",
"integrity": "sha512-M8BFHMRf8KNz5EQPKJd8nMCGmBd2o5coDEObfHVbEkyLDgjIf1V+U5dHjaGgvhm0zToUxshxN+Gc5wpbOOew4g==",
"requires": {
"stylelint-config-recommended": "^2.0.0"
}
},
"stylelint-config-wordpress": {
"version": "13.1.0",
"resolved": "https://registry.npmjs.org/stylelint-config-wordpress/-/stylelint-config-wordpress-13.1.0.tgz",
"integrity": "sha512-dpKj2/d3/XjDVoOvQzd54GoM8Rj5zldluOZKkVhBCc4JYMc6r1VYL5hpcgIjqy/i2Hyqg4Rh7zTafE/2AWq//w==",
"requires": {
"stylelint-config-recommended": "^2.1.0",
"stylelint-config-recommended-scss": "^3.2.0",
"stylelint-scss": "^3.3.0"
}
},
"stylelint-scss": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-3.3.2.tgz",
"integrity": "sha512-0x+nD1heoMJYOfi3FfGcz3Hrwhcm+Qyq+BuvoBv5v9xrZZ1aziRXQauuhjwb87gWAa9MBzxhfUqBnvTUrHlLjA==",
"requires": {
"lodash": "^4.17.10",
"postcss-media-query-parser": "^0.2.3",
"postcss-resolve-nested-selector": "^0.1.1",
"postcss-selector-parser": "^4.0.0",
"postcss-value-parser": "^3.3.0"
}
},
"uniq": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz",
"integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8="
}
}
}

View File

@ -1,10 +1,13 @@
{
"name": "stklcode-liveticker",
"version": "1.0.0",
"version": "1.1.0-alpha",
"description": "A simple Liveticker for Wordpress.",
"author": "Stefan Kalscheuer",
"license": "GPL-2.0+",
"dependencies": {
"stylelint-config-wordpress": "^13.1.0"
"devDependencies": {
"@wordpress/eslint-plugin": "^3.4.1",
"eslint": "^6",
"stylelint": "^13",
"stylelint-config-wordpress": "^16"
}
}

View File

@ -18,6 +18,6 @@
</rule>
<!-- PHP compatibility level -->
<config name="testVersion" value="5.2-"/>
<config name="testVersion" value="5.6-"/>
<rule ref="PHPCompatibility"/>
</ruleset>

190
scripts/block.js Normal file
View File

@ -0,0 +1,190 @@
/**
* stklcode-liveticker Gutenberg Block
*
* Gutenberg Block to integrate the liveticker widget without shortcode.
*/
( function() {
var __ = wp.i18n.__;
var registerBlockType = wp.blocks.registerBlockType;
var registerStore = wp.data.registerStore;
var withSelect = wp.data.withSelect;
var el = wp.element.createElement;
/**
* Datastore actions.
*/
var actions = {
setTickers: function( tickers ) {
return {
type: 'SET_TICKERS',
tickers: tickers,
};
},
getTickers: function( path ) {
return {
type: 'RECEIVE_TICKERS',
path: path,
};
},
};
registerStore( 'scliveticker/ticker', {
reducer: function( state, action ) {
if ( undefined === state ) {
state = { tickers: null };
}
switch ( action.type ) {
case 'SET_TICKERS':
state.tickers = action.tickers;
return state;
case 'RECEIVE_TICKERS':
return action.tickers;
}
return state;
},
actions: actions,
selectors: {
receiveTickers: function( state ) {
return state.tickers;
},
},
resolvers: {
receiveTickers: function() {
return wp.apiFetch( { path: '/wp/v2/scliveticker_ticker' } ).then( function( tickers ) {
return actions.setTickers( tickers.map( function( t ) {
return {
name: t.name,
slug: t.slug,
};
} ) );
} );
},
},
} );
registerBlockType( 'scliveticker/ticker', {
title: __( 'Liveticker', 'stklcode-liveticker' ),
icon: 'rss',
category: 'widgets',
keywords: [
__( 'Liveticker', 'stklcode-liveticker' ),
],
attributes: {
ticker: {
type: 'string',
default: '',
},
limit: {
type: 'number',
default: 5,
},
unlimited: {
type: 'boolean',
default: false,
},
},
edit: withSelect( function( select ) {
return {
tickers: select( 'scliveticker/ticker' ).receiveTickers(),
};
} )( function( props ) {
var label = [
el(
wp.components.Dashicon,
{ icon: 'rss' }
),
__( 'Liveticker', 'stklcode-liveticker' ),
];
var content;
if ( null === props.tickers ) {
// Tickers not yet loaded.
content = [
el(
'span',
{ className: 'components-base-control label' },
label
),
el( wp.components.Spinner ),
];
} else if ( 0 === props.tickers.length ) {
// No tickers available.
content = [
el(
'span',
{ className: 'components-base-control label' },
label
),
el( 'span', null, __( 'No tickers available', 'stklcode-liveticker' ) ),
];
} else {
// Tickers loaded and available.
if ( 0 === props.attributes.ticker.length && props.tickers.length > 0 ) {
props.attributes.ticker = props.tickers[ 0 ].slug;
}
content = [
el(
wp.components.SelectControl,
{
label: label,
value: props.attributes.ticker,
options: props.tickers.map( function( t ) {
return {
value: t.slug,
label: t.name,
};
} ),
onChange: function( val ) {
props.setAttributes( { ticker: val } );
},
}
),
el(
wp.components.TextControl,
{
label: __( 'Number of Ticks', 'stklcode-liveticker' ),
type: 'number',
min: 1,
step: 1,
disabled: props.attributes.unlimited,
value: props.attributes.limit,
onChange: function( val ) {
props.setAttributes( { limit: val } );
},
}
),
el(
wp.components.CheckboxControl,
{
label: __( 'unlimited', 'stklcode-liveticker' ),
checked: props.attributes.unlimited,
onChange: function( val ) {
props.setAttributes( { unlimited: val } );
},
}
),
];
}
return el(
'div',
{ className: props.className + ' components-placeholder' },
content
);
} ),
save: function( props ) {
return el(
'div',
{
className: 'sclt-ajax',
'data-sclt-ticker': props.attributes.ticker,
'data-sclt-limit': props.attributes.unlimited ? 0 : props.attributes.limit,
'data-sclt-last': 0,
}
);
},
} );
}() );

View File

@ -1,154 +1,187 @@
/**
* Contructor of the scLiveticker object.
*
* @constructor
* @class
*/
function scLiveticker() {
}
( function() {
var ajaxURL = sclivetickerAjax.ajax_url;
var nonce = sclivetickerAjax.nonce;
var pollInterval = sclivetickerAjax.poll_interval;
var ticker;
var widgets;
/**
* Initialize iveticker JS component.
*
* @return {void}
*/
scLiveticker.init = function() {
/**
* Initialize iveticker JS component.
*
* @return {void}
*/
var init = function() {
var updateNow = false;
// Opt out if AJAX pobject not present.
if ( 'undefined' === typeof sclivetickerAjax ) {
return;
}
// Extract AJAX settings.
scLiveticker.ajaxURL = sclivetickerAjax.ajax_url;
scLiveticker.nonce = sclivetickerAjax.nonce;
scLiveticker.pollInterval = sclivetickerAjax.poll_interval;
// Get ticker elements.
scLiveticker.ticker = [].map.call(
document.querySelectorAll( 'ul.sclt-ticker-ajax' ),
function( elem ) {
return {
s: elem.getAttribute( 'data-sclt-ticker' ),
l: elem.getAttribute( 'data-sclt-limit' ),
t: elem.getAttribute( 'data-sclt-last' ),
e: elem
};
// Opt out if AJAX pobject not present.
if ( 'undefined' === typeof sclivetickerAjax ) {
return;
}
);
// Get widget elements.
scLiveticker.widgets = [].map.call(
document.querySelectorAll( 'ul.sclt-widget-ajax' ),
function( elem ) {
return {
w: elem.getAttribute( 'data-sclt-ticker' ),
l: elem.getAttribute( 'data-sclt-limit' ),
t: elem.getAttribute( 'data-sclt-last' ),
e: elem
};
}
);
// Extract AJAX settings.
ajaxURL = sclivetickerAjax.ajax_url;
nonce = sclivetickerAjax.nonce;
pollInterval = sclivetickerAjax.poll_interval;
// Trigger update, if necessary.
if ( ( 0 < scLiveticker.ticker.length || scLiveticker.widgets.length ) && 0 < scLiveticker.pollInterval ) {
setTimeout( scLiveticker.update, scLiveticker.pollInterval );
}
};
// Get ticker elements.
ticker = [].map.call(
document.querySelectorAll( 'div.wp-block-scliveticker-ticker.sclt-ajax' ),
function( elem ) {
var list = elem.querySelector( 'ul' );
var last = Number( elem.getAttribute( 'data-sclt-last' ) );
/**
* Update liveticker on current page via AJAX call.
*
* @return {void}
*/
scLiveticker.update = function() {
// Extract ticker-slug, limit and timestamp of last poll.
var updateReq = 'action=sclt_update-ticks&_ajax_nonce=' + scLiveticker.nonce;
var i, j;
var xhr = new XMLHttpRequest();
for ( i = 0; i < scLiveticker.ticker.length; i++ ) {
updateReq = updateReq +
'&update[' + i + '][s]=' + scLiveticker.ticker[ i ].s +
'&update[' + i + '][l]=' + scLiveticker.ticker[ i ].l +
'&update[' + i + '][t]=' + scLiveticker.ticker[ i ].t;
}
for ( j = 0; j < scLiveticker.widgets.length; j++ ) {
updateReq = updateReq +
'&update[' + ( i + j ) + '][w]=' + scLiveticker.widgets[ j ].w +
'&update[' + ( i + j ) + '][l]=' + scLiveticker.widgets[ j ].l +
'&update[' + ( i + j ) + '][t]=' + scLiveticker.widgets[ j ].t;
}
// Issue AJAX request.
xhr.open( 'POST', scLiveticker.ajaxURL, true );
xhr.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded;' );
xhr.onreadystatechange = function() {
var update;
if ( XMLHttpRequest.DONE === this.readyState && 200 === this.status ) {
try {
update = JSON.parse( this.responseText );
if ( update ) {
update.forEach(
function( u ) {
scLiveticker.ticker.forEach(
function( t ) {
if ( t.s === u.s ) {
t.t = u.t; // Update last poll timestamp.
scLiveticker.updateHTML( t, u ); // Update HTML markup.
}
}
);
scLiveticker.widgets.forEach(
function( t ) {
if ( t.w === u.w ) {
t.t = u.t;
scLiveticker.updateHTML( t, u );
}
}
);
}
);
if ( ! list ) {
list = document.createElement( 'ul' );
elem.appendChild( list );
}
setTimeout( scLiveticker.update, scLiveticker.pollInterval ); // Re-trigger update.
} catch ( e ) {
console.warn( 'Liveticker AJAX update failed, stopping automatic updates.' );
if ( 0 === last ) {
updateNow = true;
}
return {
s: elem.getAttribute( 'data-sclt-ticker' ),
l: elem.getAttribute( 'data-sclt-limit' ),
t: last,
e: list,
};
}
);
// Get widget elements.
widgets = [].map.call(
document.querySelectorAll( 'div.wp-widget-scliveticker-ticker.sclt-ajax' ),
function( elem ) {
var list = elem.querySelector( 'ul' );
var last = Number( elem.getAttribute( 'data-sclt-last' ) );
if ( ! list ) {
list = document.createElement( 'ul' );
elem.appendChild( list );
}
if ( 0 === last ) {
updateNow = true;
}
return {
w: elem.getAttribute( 'data-sclt-ticker' ),
l: elem.getAttribute( 'data-sclt-limit' ),
t: last,
e: list,
};
}
);
// Trigger update, if necessary.
if ( ( 0 < ticker.length || widgets.length ) && 0 < pollInterval ) {
if ( updateNow ) {
update();
} else {
setTimeout( update, pollInterval );
}
}
};
xhr.send( updateReq );
};
/**
* Do actual update of HTML code.
*
* @param {Object} t Ticker or Widget reference.
* @param {number} t.l Limit of entries to display.
* @param {HTMLElement} t.e HTML element of the ticker/widget.
* @param {Object} u Update entity.
* @param {string} u.h HTML code to append.
* @param {number} u.t Timetsamp of last update.
* @return {void}
*/
scLiveticker.updateHTML = function( t, u ) {
/**
* Update liveticker on current page via AJAX call.
*
* @return {void}
*/
var update = function() {
// Extract ticker-slug, limit and timestamp of last poll.
var updateReq = 'action=sclt_update-ticks&_ajax_nonce=' + nonce;
var i, j;
var xhr = new XMLHttpRequest();
// Prepend HTML of new ticks.
t.e.innerHTML = u.h + t.e.innerHTML;
t.e.setAttribute( 'data-sclt-last', u.t );
for ( i = 0; i < ticker.length; i++ ) {
updateReq = updateReq +
'&update[' + i + '][s]=' + ticker[ i ].s +
'&update[' + i + '][l]=' + ticker[ i ].l +
'&update[' + i + '][t]=' + ticker[ i ].t;
}
for ( j = 0; j < widgets.length; j++ ) {
updateReq = updateReq +
'&update[' + ( i + j ) + '][w]=' + widgets[ j ].w +
'&update[' + ( i + j ) + '][l]=' + widgets[ j ].l +
'&update[' + ( i + j ) + '][t]=' + widgets[ j ].t;
}
// Remove tail, if limit is set.
if ( 0 < t.l ) {
[].slice.call( t.e.getElementsByTagName( 'li' ), t.l ).forEach(
function( li ) {
li.remove();
// Issue AJAX request.
xhr.open( 'POST', ajaxURL, true );
xhr.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded;' );
xhr.onreadystatechange = function() {
var updateResp;
if ( XMLHttpRequest.DONE === this.readyState && 200 === this.status ) {
try {
updateResp = JSON.parse( this.responseText );
if ( updateResp ) {
updateResp.forEach(
function( u ) {
ticker.forEach(
function( t ) {
if ( t.s === u.s ) {
t.t = u.t; // Update last poll timestamp.
updateHTML( t, u ); // Update HTML markup.
}
}
);
widgets.forEach(
function( t ) {
if ( t.w === u.w ) {
t.t = u.t;
updateHTML( t, u );
}
}
);
}
);
}
setTimeout( update, pollInterval ); // Re-trigger update.
} catch ( e ) {
// eslint-disable-next-line no-console
console.warn( 'Liveticker AJAX update failed, stopping automatic updates.' );
}
}
);
}
};
};
xhr.send( updateReq );
};
document.addEventListener(
'DOMContentLoaded',
function() {
scLiveticker.init(); // Trigger periodic update of livetickers.
}
);
/**
* Do actual update of HTML code.
*
* @param {Object} t Ticker or Widget reference.
* @param {number} t.l Limit of entries to display.
* @param {HTMLElement} t.e HTML element of the ticker/widget.
* @param {Object} u Update entity.
* @param {string} u.h HTML code to append.
* @param {number} u.t Timetsamp of last update.
* @return {void}
*/
var updateHTML = function( t, u ) {
// Prepend HTML of new ticks.
t.e.innerHTML = u.h + t.e.innerHTML;
t.e.parentNode.setAttribute( 'data-sclt-last', u.t );
// Remove tail, if limit is set.
if ( 0 < t.l ) {
[].slice.call( t.e.getElementsByTagName( 'li' ), t.l ).forEach(
function( li ) {
li.remove();
}
);
}
};
document.addEventListener(
'DOMContentLoaded',
function() {
init(); // Trigger periodic update of livetickers.
}
);
}() );

View File

@ -41,19 +41,22 @@ define( 'SCLIVETICKER_BASE', plugin_dir_url( __FILE__ ) );
define( 'SCLIVETICKER_BASENAME', plugin_basename( __FILE__ ) );
// System Hooks.
add_action( 'init', array( 'SCLiveticker', 'register_types' ) );
add_action( 'plugins_loaded', array( 'SCLiveticker', 'init' ) );
register_activation_hook( SCLIVETICKER_FILE, array( 'SCLiveticker_System', 'activate' ) );
register_uninstall_hook( SCLIVETICKER_FILE, array( 'SCLiveticker_System', 'uninstall' ) );
add_action( 'init', array( 'SCLiveticker\\SCLiveticker', 'register_types' ) );
add_action( 'plugins_loaded', array( 'SCLiveticker\\SCLiveticker', 'init' ) );
register_activation_hook( SCLIVETICKER_FILE, array( 'SCLiveticker\\System', 'activate' ) );
register_uninstall_hook( SCLIVETICKER_FILE, array( 'SCLiveticker\\System', 'uninstall' ) );
// Allow shortcodes in widgets.
add_filter( 'widget_text', 'do_shortcode' );
// Add shortcode.
add_shortcode( 'liveticker', array( 'SCLiveticker', 'shortcode_ticker_show' ) );
add_shortcode( 'liveticker', array( 'SCLiveticker\\SCLiveticker', 'shortcode_ticker_show' ) );
// Add Widget.
add_action( 'widgets_init', array( 'SCLiveticker_Widget', 'register' ) );
add_action( 'widgets_init', array( 'SCLiveticker\\Widget', 'register' ) );
// Add Gutenberg block.
add_action( 'enqueue_block_editor_assets', array( 'SCLiveticker\\Admin', 'register_block' ) );
// Autoload.
spl_autoload_register( 'scliveticker_autoload' );
@ -67,16 +70,16 @@ spl_autoload_register( 'scliveticker_autoload' );
*/
function scliveticker_autoload( $class ) {
$plugin_classes = array(
'SCLiveticker',
'SCLiveticker_Admin',
'SCLiveticker_System',
'SCLiveticker_Widget',
'SCLiveticker\\SCLiveticker',
'SCLiveticker\\Admin',
'SCLiveticker\\System',
'SCLiveticker\\Widget',
);
if ( in_array( $class, $plugin_classes, true ) ) {
require_once sprintf(
'%s/includes/class-%s.php',
SCLIVETICKER_DIR,
strtolower( str_replace( '_', '-', $class ) )
strtolower( str_replace( '_', '-', substr( $class, 13 ) ) )
);
}
}

9
styles/block.css Normal file
View File

@ -0,0 +1,9 @@
div.wp-block-scliveticker-ticker div.components-base-control:first-child label,
div.wp-block-scliveticker-ticker .label {
font-weight: 600;
}
div.wp-block-scliveticker-ticker .label > .dashicon,
div.wp-block-scliveticker-ticker div.components-base-control:first-child label > .dashicon {
margin-right: 8px;
}

View File

@ -1,8 +1,10 @@
ul.sclt-ticker {
div.wp-block-scliveticker-ticker ul,
div.wp-block-scliveticker-ajax-ticker ul {
list-style-type: none;
}
ul.sclt-ticker > li.sclt-tick {
div.wp-block-scliveticker-ticker ul > li.sclt-tick,
div.wp-block-scliveticker-ajax-ticker ul > li.sclt-tick {
background-color: #f5f5f5;
list-style-type: none;
margin: 0.1em;

View File

@ -40,7 +40,7 @@ if ( ! defined( 'ABSPATH' ) ) {
</tr>
<tr>
<td>
<label for="<?php echo esc_attr( $this->get_field_id( 'count' ) ); ?>"><?php esc_html_e( 'Number of Ticks:', 'stklcode-liveticker' ); ?></label>
<label for="<?php echo esc_attr( $this->get_field_id( 'count' ) ); ?>"><?php esc_html_e( 'Number of Ticks', 'stklcode-liveticker' ); ?>:</label>
</td>
<td>
<select id="<?php echo esc_attr( $this->get_field_id( 'count' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'count' ) ); ?>">