\d+)/sections', array( 'methods' => 'GET', 'callback' => static function ( $request ) { $post = get_post( (int) $request['id'] ); if ( ! $post || PostTypes::ARTICLE !== $post->post_type ) { return new \WP_Error( 'kdocs_not_found', __( 'Article not found.', 'karma-docs' ), array( 'status' => 404 ) ); } $list = array(); foreach ( array_slice( Index::sections( $post ), 1 ) as $section ) { if ( $section['anchor'] ) { $list[] = array( 'id' => $section['anchor'], 'text' => $section['heading'], ); } } return rest_ensure_response( $list ); }, 'permission_callback' => array( Routes::class, 'can_edit' ), ) ); } /** * Adds the dark-mode variant next to the image. * * @param string $html Block HTML. * @param array $block Block. * @return string */ public function image( $html, $block ) { $dark = esc_url( (string) ( $block['attrs']['darkUrl'] ?? '' ) ); if ( '' === $dark || ! preg_match( '#]*>#i', $html, $img ) ) { return $html; } $alt = preg_match( '/\salt=(["\'])(.*?)\1/i', $img[0], $m ) ? $m[2] : ''; $variant = '' . $alt . ''; $html = str_replace( $img[0], $img[0] . $variant, $html ); return preg_replace( '/class="wp-block-image/', 'class="wp-block-image has-dark-variant', $html, 1 ); } /** * One section of an article: from its heading to the next heading of the * same or a higher level. * * @param int $article_id Article. * @param string $anchor Heading id ('' = the intro before the first heading). * @return array{title:string,html:string}|null */ public static function section( $article_id, $anchor ) { $post = get_post( (int) $article_id ); if ( ! $post || PostTypes::ARTICLE !== $post->post_type || 'publish' !== $post->post_status || ! apply_filters( 'kdocs_can_view_article', true, $post ) ) { return null; } $html = Content::process( do_blocks( Blocks::strip_private( (string) $post->post_content ) ), 5 )['html']; $html = preg_replace( '#]*>.*?#s', '', $html ); $parts = preg_split( '#(]*>.*?)#is', $html, -1, PREG_SPLIT_DELIM_CAPTURE ); if ( '' === $anchor ) { return array( 'title' => '', 'html' => trim( (string) $parts[0] ), ); } $out = ''; $level = 0; $title = ''; $count = count( $parts ); for ( $i = 1; $i < $count; $i += 3 ) { $heading = $parts[ $i ]; $depth = (int) $parts[ $i + 1 ]; $body = $parts[ $i + 2 ] ?? ''; if ( $level ) { if ( $depth <= $level ) { break; } $out .= $heading . $body; continue; } if ( preg_match( '/\sid=(["\'])' . preg_quote( $anchor, '/' ) . '\1/', $heading ) ) { $level = $depth; $title = trim( wp_strip_all_tags( $heading ) ); $out = $body; } } return $level ? array( 'title' => html_entity_decode( $title, ENT_QUOTES, 'UTF-8' ), 'html' => trim( $out ), ) : null; } /** * Section embed block. * * @param array $attrs article, anchor. * @return string */ public static function section_embed( $attrs ) { $id = (int) ( $attrs['article'] ?? 0 ); if ( ! $id || self::$depth > 1 ) { return ''; } ++self::$depth; $section = self::section( $id, (string) ( $attrs['anchor'] ?? '' ) ); --self::$depth; if ( ! $section || '' === $section['html'] ) { return ''; } Assets::request(); $link = get_permalink( $id ) . ( ! empty( $attrs['anchor'] ) ? '#' . rawurlencode( $attrs['anchor'] ) : '' ); $label = html_entity_decode( get_the_title( $id ), ENT_QUOTES, 'UTF-8' ) . ( $section['title'] ? ' › ' . $section['title'] : '' ); // Headings inside the embed must not take part in this page's anchors and TOC. $body = preg_replace( '#]*)>(.*?)#is', '

$3

', $section['html'] ); return '

' . esc_html__( 'From', 'karma-docs' ) . ' ' . esc_html( $label ) . '

' . '
' . $body . '
'; } }