Skip to content

Commit

Permalink
Add labels to the lesson status icons (#2786)
Browse files Browse the repository at this point in the history
* Add aria attributes to the lesson navigation SVGs

* Add aria attributes to the lesson outline SVGs

* Add screen reader text to the custom "in progress" icon

* Refactor the course outline icon replacement to use new status classes

* Switch to using aria-label on in progress icon to avoid CSS issue

* Add doc comments to the script
  • Loading branch information
ryelle authored Jul 30, 2024
1 parent 2d973e7 commit e357cbf
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 66 deletions.
34 changes: 34 additions & 0 deletions wp-content/themes/pub/wporg-learn-2024/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace WordPressdotorg\Theme\Learn_2024;

use WP_HTML_Tag_Processor;
use function WPOrg_Learn\Sensei\{get_my_courses_page_url, get_lesson_has_published_course};

// Block files
Expand Down Expand Up @@ -42,6 +43,8 @@
add_filter( 'wporg_block_site_breadcrumbs', __NAMESPACE__ . '\set_site_breadcrumbs' );
add_filter( 'taxonomy_template_hierarchy', __NAMESPACE__ . '\modify_taxonomy_template_hierarchy' );

add_filter( 'sensei_learning_mode_lesson_status_icon', __NAMESPACE__ . '\modify_lesson_status_icon_add_aria', 10, 2 );

remove_filter( 'template_include', array( 'Sensei_Templates', 'template_loader' ), 10, 1 );

/**
Expand Down Expand Up @@ -405,3 +408,34 @@ function modify_taxonomy_template_hierarchy( $templates ) {

return $templates;
}

/**
* Filter the lesson status icon.
*
* @param string $icon The icon HTML.
* @param string $status The lesson status.
*
* @return string The updated icon HTML with aria data.
*/
function modify_lesson_status_icon_add_aria( $icon, $status ) {
// These statuses have been copied from Sensei\Blocks\Course_Theme\Course_Navigation\ICONS.
$labels = array(
'not-started' => __( 'Not started', 'wporg-learn' ),
'in-progress' => __( 'In progress', 'wporg-learn' ),
'ungraded' => __( 'Ungraded', 'wporg-learn' ),
'completed' => __( 'Completed', 'wporg-learn' ),
'failed' => __( 'Failed', 'wporg-learn' ),
'locked' => __( 'Locked', 'wporg-learn' ),
'preview' => __( 'Preview', 'wporg-learn' ),
);

if ( ! isset( $labels[ $status ] ) ) {
return $icon;
}

$html = new WP_HTML_Tag_Processor( $icon );
$html->next_tag( 'svg' );
$html->set_attribute( 'aria-label', $labels[ $status ] );
$html->set_attribute( 'role', 'img' );
return $html->get_updated_html();
}
79 changes: 78 additions & 1 deletion wp-content/themes/pub/wporg-learn-2024/inc/block-hooks.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@
* @package wporg-learn-2024
*/

use function DevHub\is_parsed_post_type;
namespace WordPressdotorg\Theme\Learn_2024\Block_Hooks;

use WP_HTML_Tag_Processor, Sensei_Utils, Sensei_Course, Sensei_Lesson;

add_filter( 'render_block_data', __NAMESPACE__ . '\modify_header_template_part' );
add_filter( 'render_block_data', __NAMESPACE__ . '\modify_course_outline_lesson_block_attrs' );
add_filter( 'render_block_sensei-lms/course-outline', __NAMESPACE__ . '\update_course_outline_block_add_aria', 10, 2 );

/**
* Update header template based on current query.
Expand All @@ -27,3 +31,76 @@ function modify_header_template_part( $parsed_block ) {
}
return $parsed_block;
}

/**
* Add the status to the outline lesson block as a class, so that it can be
* read by the `update_course_outline_block_add_aria` function.
*
* @param array $parsed_block The block being rendered.
*
* @return array The updated block.
*/
function modify_course_outline_lesson_block_attrs( $parsed_block ) {
if (
'sensei-lms/course-outline-lesson' !== $parsed_block['blockName'] ||
! isset( $parsed_block['attrs']['id'] )
) {
return $parsed_block;
}

$lesson_id = $parsed_block['attrs']['id'];
$classes = array();
$classes[] = $parsed_block['attrs']['className'] ?? '';

$status = 'not-started';
$lesson_status = Sensei_Utils::user_lesson_status( $lesson_id );
if ( $lesson_status ) {
$status = $lesson_status->comment_approved;
}
$classes[] = 'is-' . $status;

// Add previewable and prerequisite-required lesson title to lesson data
if (
( ! Sensei_Utils::is_preview_lesson( $lesson_id ) && ! Sensei_Course::is_user_enrolled( get_the_ID() ) )
|| ! Sensei_Lesson::is_prerequisite_complete( $lesson_id, get_current_user_id() )
) {
$classes[] = 'is-locked';
}

$parsed_block['attrs']['className'] = implode( ' ', $classes );

return $parsed_block;
}

/**
* Filter the course outline block to add accessible attributes.
*
* Note, this filters the entire `sensei-lms/course-outline` block instead of
* `sensei-lms/course-outline-lesson` due to Sensei's rendering of these
* blocks. The outline module & outline lesson blocks are not rendered
* individually, so they cannot be independently filtered.
*
* @param string $block_content The block content.
* @param array $block The full block, including name and attributes.
*
* @return string The updated icon HTML with aria data.
*/
function update_course_outline_block_add_aria( $block_content, $block ) {
$html = new WP_HTML_Tag_Processor( $block_content );

$label = '';
while ( $html->next_tag( array( 'class_name' => 'wp-block-sensei-lms-course-outline-lesson' ) ) ) {
if ( $html->has_class( 'is-complete' ) ) {
$label = __( 'Completed', 'wporg-learn' );
} else if ( $html->has_class( 'is-in-progress' ) ) {
$label = __( 'In progress', 'wporg-learn' );
} else {
$label = __( 'Not started', 'wporg-learn' );
}

$html->next_tag( 'svg' );
$html->set_attribute( 'aria-label', $label );
$html->set_attribute( 'role', 'img' );
}
return $html->get_updated_html();
}
58 changes: 35 additions & 23 deletions wp-content/themes/pub/wporg-learn-2024/src/course-outline/index.js
Original file line number Diff line number Diff line change
@@ -1,32 +1,44 @@
/* global wporgCourseOutlineData */
/* global wporgCourseOutlineL10n */

import { Icon, drafts, lockOutline } from '@wordpress/icons';
import { renderToString } from '@wordpress/element';

document.addEventListener( 'DOMContentLoaded', () => {
if ( ! wporgCourseOutlineData ) {
return;
}
/**
* Find all in progress lessons, and replace the status icon with the Gutenberg-style `drafts` icon.
*/
document.querySelectorAll( '.wp-block-sensei-lms-course-outline-lesson.is-in-progress' ).forEach( ( link ) => {
const statusIcon = link.querySelector( '.wp-block-sensei-lms-course-outline-lesson__status' );
if ( statusIcon ) {
const iconString = renderToString(
<Icon
icon={ drafts }
style={ { transform: 'scale(1.5)' } }
aria-label={ wporgCourseOutlineL10n.inProgress }
role="img"
/>
);

wporgCourseOutlineData[ 'in-progress' ]?.forEach( ( title ) => {
const lessonLinks = document.querySelectorAll( '.wp-block-sensei-lms-course-outline-lesson' );
lessonLinks.forEach( ( link ) => {
const span = link.querySelector( 'span' );
if ( span && span.textContent.trim() === title ) {
const statusIcon = link.querySelector( '.wp-block-sensei-lms-course-outline-lesson__status' );
if ( statusIcon ) {
statusIcon.outerHTML = renderToString( <Icon icon={ drafts } transform={ 'scale(1.5)' } /> );
}
}
} );
// Remove the `aria-hidden` attribute from the icon, as it has a readable label.
statusIcon.outerHTML = iconString.replace( ' aria-hidden="true"', '' );
}
} );
wporgCourseOutlineData.locked?.forEach( ( title ) => {
const lessonLinks = document.querySelectorAll( '.wp-block-sensei-lms-course-outline-lesson' );
lessonLinks.forEach( ( link ) => {
const span = link.querySelector( 'span' );
if ( span && span.textContent.trim() === title ) {
span.insertAdjacentHTML( 'afterend', renderToString( <Icon icon={ lockOutline } /> ) );
}
} );

/**
* Find all locked lessons, and inject a `lock` icon after the title.
*/
document.querySelectorAll( '.wp-block-sensei-lms-course-outline-lesson.is-locked' ).forEach( ( link ) => {
const span = link.querySelector( 'span' );
if ( span ) {
span.insertAdjacentHTML(
'afterend',
renderToString(
<>
<Icon icon={ lockOutline } />
<span className="screen-reader-text">{ wporgCourseOutlineL10n.locked }</span>
</>
)
);
}
} );
} );
Original file line number Diff line number Diff line change
Expand Up @@ -22,50 +22,13 @@ function enqueue_assets() {
true
);

$lesson_data = get_lesson_data();
wp_localize_script(
'wporg-learn-2024-course-outline',
'wporgCourseOutlineData',
$lesson_data
'wporgCourseOutlineL10n',
array(
'inProgress' => __( 'In progress', 'wporg-learn' ),
'locked' => __( 'Locked', 'wporg-learn' ),
)
);
}
add_action( 'wp_enqueue_scripts', 'enqueue_assets' );

/**
* Get the titles of specific status lessons.
*
* The returned array $lesson_data has the following structure:
* [
* 'in-progress' => [ (string) The title of the lesson, ... ],
* 'locked' => [ (string) The title of the lesson, ... ],
* ]
*
* @return array $lesson_data Array of lesson data.
*/
function get_lesson_data() {
$lesson_data = array();
$lesson_ids = Sensei()->course->course_lessons( get_the_ID(), 'publish', 'ids' );

foreach ( $lesson_ids as $lesson_id ) {
$user_lesson_status = Sensei_Utils::user_lesson_status( $lesson_id, get_current_user_id() );
$lesson_title = get_the_title( $lesson_id );
$is_preview_lesson = Sensei_Utils::is_preview_lesson( $lesson_id );

// Add in-progress lesson title to lesson data
if ( $user_lesson_status ) {
$lesson_status = $user_lesson_status->comment_approved;
if ( 'in-progress' === $lesson_status ) {
$lesson_data['in-progress'][] = $lesson_title;
}
}

// Add previewable and prerequisite-required lesson title to lesson data
if ( ( ! $is_preview_lesson && ! Sensei_Course::is_user_enrolled( get_the_ID() ) )
|| ! Sensei_Lesson::is_prerequisite_complete( $lesson_id, get_current_user_id() )
) {
$lesson_data['locked'][] = $lesson_title;
}
}

return $lesson_data;
}

0 comments on commit e357cbf

Please sign in to comment.