Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add custom bylines #3667

Merged
merged 10 commits into from
Jan 17, 2025
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions includes/bylines/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Custom Bylines

This feature allows authors to add custom bylines to posts.

## How it works

When you enable this feature, a new settings panel will be added to the post editor sidebar. You can use this panel to add a custom byline. The byline will be displayed before the post content.

## Usage

This feature can be enabled by adding the following constant to your `wp-config.php`:

```php
define( 'NEWSPACK_BYLINES_ENABLED', true );
```

## Data

Bylines are stored as post meta and consists of the following fields:

| Name | Type | Stored As | Description |
| ----------------------------- | --------- | ----------- | --------------------------------------------------------------------------------------------------------------------- |
| `_newspack_byline_active` | `boolean` | `post_meta` | Whether custom byline is active for the post |
| `_newspack_byline` | `string` | `post_meta` | The custom byline. Author links can be included by wrapping text in the Author tag (`<Author id=5>Jane Doe</Author>`) |
143 changes: 143 additions & 0 deletions includes/bylines/class-bylines.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
<?php
/**
* Newspack Custom Bylines.
*
* @package Newspack
*/

namespace Newspack;

/**
* Class to handle custom bylines.
*/
class Bylines {
/**
* Meta key for the active flag.
*
* @var string
*/
const META_KEY_ACTIVE = '_newspack_byline_active';

/**
* Meta key for the byline.
*
* @var string
*/
const META_KEY_BYLINE = '_newspack_byline';

/**
* Initializes the class.
*/
public static function init() {
if ( ! self::is_enabled() ) {
return;
}
add_action( 'init', [ __CLASS__, 'register_post_meta' ] );
add_action( 'enqueue_block_editor_assets', [ __CLASS__, 'enqueue_block_editor_assets' ] );
add_filter( 'the_content', [ __CLASS__, 'output_byline_on_post' ] );
}

/**
* Checks if the feature is enabled.
*
* True when:
* - NEWSPACK_BYLINES_ENABLED is defined and true.
*
* @return bool True if the feature is enabled, false otherwise.
*/
public static function is_enabled() {
return defined( 'NEWSPACK_BYLINES_ENABLED' ) && NEWSPACK_BYLINES_ENABLED;
}


/**
* Enqueue block editor scripts and styles.
*/
public static function enqueue_block_editor_assets() {
if ( ! is_admin() || \get_current_screen()->id !== 'post' ) {
return;
}
\wp_enqueue_script(
'newspack-bylines',
Newspack::plugin_url() . '/dist/bylines.js',
[],
NEWSPACK_PLUGIN_VERSION,
true
);
\wp_localize_script(
'newspack-bylines',
'newspackBylines',
[
'metaKeyActive' => self::META_KEY_ACTIVE,
'metaKeyByline' => self::META_KEY_BYLINE,
'siteUrl' => \get_site_url(),
]
);
\wp_enqueue_style(
'newspack-bylines',
Newspack::plugin_url() . '/dist/bylines.css',
[],
NEWSPACK_PLUGIN_VERSION
);
}

/**
* Registers custom byline post meta.
*/
public static function register_post_meta() {
\register_post_meta(
'post',
self::META_KEY_ACTIVE,
[
'default' => false,
'description' => 'Whether custom bylines is enabled for the post.',
'show_in_rest' => true,
'single' => true,
'type' => 'boolean',
'auth_callback' => [ __CLASS__, 'auth_callback' ],
]
);
\register_post_meta(
'post',
self::META_KEY_BYLINE,
[
'default' => '',
'description' => 'A custom byline for the post',
'show_in_rest' => true,
'single' => true,
'type' => 'string',
'auth_callback' => [ __CLASS__, 'auth_callback' ],
]
);
}

/**
* Auth callback for custom post meta.
*
* @return bool True if current user can access, false otherwise.
*/
public static function auth_callback() {
return \current_user_can( 'edit_posts' );
}

/**
* Outputs the byline on the post.
*
* @param string $content The post content.
*
* @return string The post content with the byline prepended.
*/
public static function output_byline_on_post( $content ) {
if ( ! \is_single() || ! \get_post_meta( \get_the_ID(), self::META_KEY_ACTIVE, true ) ) {
return $content;
}
$byline = \get_post_meta( \get_the_ID(), self::META_KEY_BYLINE, true );
if ( ! $byline ) {
return $content;
}
$byline = preg_replace( '/<Author id=(\d*)>(\D*)<\/Author>/', '<a href="' . \get_site_url() . '/?author=$1">$2</a>', $byline );
$byline_html = '<div class="newspack-byline">' . \wp_kses_post( $byline ) . '</div>';
return $byline_html . $content;
}
}
Bylines::init();
1 change: 1 addition & 0 deletions includes/class-newspack.php
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ private function includes() {
include_once NEWSPACK_ABSPATH . 'includes/revisions-control/class-revisions-control.php';
include_once NEWSPACK_ABSPATH . 'includes/authors/class-authors-custom-fields.php';
include_once NEWSPACK_ABSPATH . 'includes/corrections/class-corrections.php';
include_once NEWSPACK_ABSPATH . 'includes/bylines/class-bylines.php';

include_once NEWSPACK_ABSPATH . 'includes/starter_content/class-starter-content-provider.php';
include_once NEWSPACK_ABSPATH . 'includes/starter_content/class-starter-content-generated.php';
Expand Down
92 changes: 92 additions & 0 deletions src/bylines/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/* globals newspackBylines */

/**
* WordPress dependencies
*/
import { ToggleControl, TextareaControl } from '@wordpress/components';
import { useSelect, useDispatch } from '@wordpress/data';
import { PluginDocumentSettingPanel } from '@wordpress/edit-post';
import { __ } from '@wordpress/i18n';
import { registerPlugin } from '@wordpress/plugins';
/**
* External dependencies
*/
import { useEffect, useState } from 'react';
/**
* Internal dependencies
*/
import './style.scss';

const BYLINE_ID = 'newspack-byline';

const BylinesSettingsPanel = () => {
const { editPost } = useDispatch( 'core/editor' );
const { getEditedPostAttribute } = useSelect( select => select( 'core/editor' ) );
const [ byline, setByline ] = useState( getEditedPostAttribute( 'meta' )[ newspackBylines.metaKeyByline ] || '' );
const [ isEnabled, setIsEnabled ] = useState( !! getEditedPostAttribute( 'meta' )[ newspackBylines.metaKeyActive ] );
// Update byline text in editor.
useEffect( () => {
prependBylineToContent( isEnabled ? byline : '' );
}, [ byline, isEnabled ] );
// Enabled toggle handler.
const handleEnableToggle = value => {
editPost( { meta: { [ newspackBylines.metaKeyActive ]: value } } );
setIsEnabled( value );
}
// Byline change handler.
const handleBylineChange = value => {
const tags = value.match( /<[^>]+>/g );
if ( tags && tags.some( tag => ! tag.startsWith( '<Author' ) && ! tag.startsWith( '</Author' ) ) ) {
alert( __( 'Only the <Author> tag is allowed.', 'newspack-plugin' ) ); // eslint-disable-line no-alert
return;
}
editPost( { meta: { [ newspackBylines.metaKeyByline ]: value } } );
setByline( value );
}
const prependBylineToContent = text => {
const contentEl = document.querySelector( '.wp-block-post-content' );
if ( contentEl ) {
let bylineEl = document.getElementById( BYLINE_ID );
if ( ! bylineEl ) {
bylineEl = document.createElement( 'div' );
bylineEl.id = BYLINE_ID;
contentEl.insertBefore( bylineEl, contentEl.firstChild );
}
// If there are author tags
if ( /<Author id=(\d+)>/.test( text ) ) {
text = text.replace( /<Author id=(\d+)>([^<]+)<\/Author>/g, ( match, authorId, authorName ) => {
return `<a href="${ newspackBylines.siteUrl }/?author=${ authorId }">${ authorName }</a>`;
} );
}
bylineEl.innerHTML = text;
}
};
return (
<PluginDocumentSettingPanel
className="newspack-byline"
name="Newspack Byline Settings Panel"
title={ __( 'Newspack Custom Byline', 'newspack-plugin' ) }
>
<ToggleControl
className="newspack-byline-toggle"
checked={ isEnabled }
label={ __( 'Enable custom byline', 'newspack-plugin' ) }
onChange={ () => handleEnableToggle( ! isEnabled ) }
/>
{ isEnabled && (
<TextareaControl
className="newspack-byline-textarea"
value={ byline }
onChange={ value => handleBylineChange( value ) }
placeholder={ __( 'Enter custom byline…', 'newspack-plugin' ) }
rows="4"
/>
) }
</PluginDocumentSettingPanel>
);
}

registerPlugin( 'newspack-bylines-sidebar', {
render: BylinesSettingsPanel,
icon: false,
} );
15 changes: 15 additions & 0 deletions src/bylines/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
.newspack-byline {
.newspack-byline-toggle {
margin-top: 1em;
}
.newspack-byline-textarea {
margin-top: 1em;
}
}

#newspack-byline {
font-style: italic;
font-weight: 500;
margin: 1em auto;
max-width: 1200px;
}
1 change: 1 addition & 0 deletions webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ const entry = {
'block-patterns.js'
),
'newspack-ui': path.join( __dirname, 'src', 'newspack-ui', 'index.js' ),
'bylines': path.join( __dirname, 'src', 'bylines', 'index.js' ),
};

// Get files for other scripts.
Expand Down