Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 4 additions & 0 deletions .github/changelog/fix-visibility-meta-sync
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: fixed

Fixed visibility setting not being saved correctly in block editor and classic editor.
2 changes: 1 addition & 1 deletion build/editor-plugin/plugin.asset.php

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion build/editor-plugin/plugin.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions integration/class-classic-editor.php
Original file line number Diff line number Diff line change
Expand Up @@ -153,15 +153,15 @@ public static function render_meta_box( $post ) {
<p>
<strong><?php \esc_html_e( 'Visibility', 'activitypub' ); ?></strong><br />
<label>
<input type="radio" name="activitypub_content_visibility" value="public" <?php \checked( $content_visibility, ACTIVITYPUB_CONTENT_VISIBILITY_PUBLIC ); ?> />
<input type="radio" name="activitypub_content_visibility" value="<?php echo \esc_attr( ACTIVITYPUB_CONTENT_VISIBILITY_PUBLIC ); ?>" <?php \checked( $content_visibility, ACTIVITYPUB_CONTENT_VISIBILITY_PUBLIC ); ?> />
<?php \esc_html_e( 'Public', 'activitypub' ); ?>
</label><br />
<label>
<input type="radio" name="activitypub_content_visibility" value="quiet_public" <?php \checked( $content_visibility, ACTIVITYPUB_CONTENT_VISIBILITY_QUIET_PUBLIC ); ?> />
<input type="radio" name="activitypub_content_visibility" value="<?php echo \esc_attr( ACTIVITYPUB_CONTENT_VISIBILITY_QUIET_PUBLIC ); ?>" <?php \checked( $content_visibility, ACTIVITYPUB_CONTENT_VISIBILITY_QUIET_PUBLIC ); ?> />
<?php \esc_html_e( 'Quiet public', 'activitypub' ); ?>
</label><br />
<label>
<input type="radio" name="activitypub_content_visibility" value="local" <?php \checked( $content_visibility, ACTIVITYPUB_CONTENT_VISIBILITY_LOCAL ); ?> />
<input type="radio" name="activitypub_content_visibility" value="<?php echo \esc_attr( ACTIVITYPUB_CONTENT_VISIBILITY_LOCAL ); ?>" <?php \checked( $content_visibility, ACTIVITYPUB_CONTENT_VISIBILITY_LOCAL ); ?> />
<?php \esc_html_e( 'Do not federate', 'activitypub' ); ?>
</label><br />
<span class="howto"><?php \esc_html_e( 'This adjusts the visibility of a post in the fediverse, but note that it won\'t affect how the post appears on the blog.', 'activitypub' ); ?></span>
Expand Down
104 changes: 104 additions & 0 deletions src/editor-plugin/__tests__/plugin.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,3 +125,107 @@ describe( 'EditorPlugin getDefaultVisibility', () => {
expect( [ 'public', 'local' ] ).toContain( result );
} );
} );

/**
* Tests for the visibility sync logic.
*
* The useEffect in EditorPlugin syncs the computed default visibility to meta
* when there's no stored value and the default isn't 'public'.
* We test this logic by simulating the conditions the useEffect checks.
*/
describe( 'EditorPlugin visibility sync logic', () => {
/**
* Simulates the sync logic from the useEffect hook.
*
* @param {Object} meta The meta object.
* @param {string} postDate The post date.
* @param {Function} setMetaFn The setMeta function.
*/
const simulateSyncLogic = ( meta, postDate, setMetaFn ) => {
const defaultVisibility = getDefaultVisibility( meta, postDate );
const storedVisibility = meta?.activitypub_content_visibility;

// This mirrors the useEffect logic in plugin.js.
// WordPress may return '' (empty string) or undefined for unset meta.
// We skip 'public' since it's the implicit default.
if ( ! storedVisibility && defaultVisibility !== 'public' ) {
setMetaFn( { ...meta, activitypub_content_visibility: defaultVisibility } );
}
};

test( 'syncs local visibility for old posts without stored value', () => {
const setMeta = jest.fn();
const meta = {};
const postDate = new Date( Date.now() - 60 * 24 * 60 * 60 * 1000 ); // 60 days ago.

simulateSyncLogic( meta, postDate, setMeta );

expect( setMeta ).toHaveBeenCalledWith( {
activitypub_content_visibility: 'local',
} );
} );

test( 'does not sync for new posts with public default', () => {
const setMeta = jest.fn();
const meta = {};
const postDate = new Date(); // Now.

simulateSyncLogic( meta, postDate, setMeta );

// Public is the implicit default, no sync needed.
expect( setMeta ).not.toHaveBeenCalled();
} );

test( 'does not sync when visibility is already stored', () => {
const setMeta = jest.fn();
const meta = { activitypub_content_visibility: 'quiet_public' };
const postDate = new Date( Date.now() - 60 * 24 * 60 * 60 * 1000 ); // 60 days ago.

simulateSyncLogic( meta, postDate, setMeta );

expect( setMeta ).not.toHaveBeenCalled();
} );

test( 'syncs when stored value is empty string and default is not public', () => {
const setMeta = jest.fn();
// Empty string means unset - sync if default differs from public.
const meta = { activitypub_content_visibility: '' };
const postDate = new Date( Date.now() - 60 * 24 * 60 * 60 * 1000 ); // 60 days ago.

simulateSyncLogic( meta, postDate, setMeta );

// Old post defaults to local, so we sync.
expect( setMeta ).toHaveBeenCalledWith( {
activitypub_content_visibility: 'local',
} );
} );

test( 'does not sync when stored value is empty string and default is public', () => {
const setMeta = jest.fn();
// Empty string stored, default is public = no sync needed.
const meta = { activitypub_content_visibility: '' };
const postDate = new Date(); // Now - defaults to public.

simulateSyncLogic( meta, postDate, setMeta );

// Default is public, no sync needed.
expect( setMeta ).not.toHaveBeenCalled();
} );

test( 'preserves existing meta fields when syncing', () => {
const setMeta = jest.fn();
const meta = {
activitypub_content_warning: 'some warning',
activitypub_max_image_attachments: 5,
};
const postDate = new Date( Date.now() - 60 * 24 * 60 * 60 * 1000 ); // 60 days ago.

simulateSyncLogic( meta, postDate, setMeta );

expect( setMeta ).toHaveBeenCalledWith( {
activitypub_content_warning: 'some warning',
activitypub_max_image_attachments: 5,
activitypub_content_visibility: 'local',
} );
} );
} );
19 changes: 18 additions & 1 deletion src/editor-plugin/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
import { Icon, globe, people, external } from '@wordpress/icons';
import { useSelect, select } from '@wordpress/data';
import { useEntityProp } from '@wordpress/core-data';
import { useEffect } from '@wordpress/element';
import { addQueryArgs } from '@wordpress/url';
import { __ } from '@wordpress/i18n';
import { SVG, Path } from '@wordpress/primitives';
Expand All @@ -27,6 +28,22 @@ const EditorPlugin = () => {
const [ meta, setMeta ] = useEntityProp( 'postType', postType, 'meta' );
const postDate = useSelect( ( selectFn ) => selectFn( editorStore ).getCurrentPost().date, [] );

// Get the computed default visibility.
const defaultVisibility = getDefaultVisibility( meta, postDate );

// Sync computed default to meta when it differs from stored value.
// This ensures the default is persisted even if user doesn't change it.
useEffect( () => {
const storedVisibility = meta?.activitypub_content_visibility;

// Only sync if visibility was never set and the default isn't 'public'.
// WordPress may return '' (empty string) or undefined for unset meta.
// We skip 'public' since it's the implicit default.
if ( ! storedVisibility && defaultVisibility !== 'public' ) {
setMeta( { ...meta, activitypub_content_visibility: defaultVisibility } );
}
}, [ defaultVisibility, meta, setMeta ] );

// Don't show when editing sync blocks.
if ( 'wp_block' === postType ) {
return null;
Expand Down Expand Up @@ -124,7 +141,7 @@ const EditorPlugin = () => {
"This adjusts the visibility of a post in the fediverse, but note that it won't affect how the post appears on the blog.",
'activitypub'
) }
selected={ getDefaultVisibility( meta, postDate ) }
selected={ defaultVisibility }
options={ [
{
label: enhancedLabel(
Expand Down
Loading