223 | : 224 | composer install && npm install && npm run build' 230 | ) 231 | ); 232 | ?> 233 |
234 |` start tag.
414 | * @param string $code_start_tag The `` start tag.
415 | * @param array{
416 | * language: string,
417 | * highlightedLines: string,
418 | * showLineNumbers: bool,
419 | * wrapLines: bool
420 | * } $attributes Attributes.
421 | * @param string $content Content.
422 | * @return string Injected markup.
423 | */
424 | function inject_markup( string $pre_start_tag, string $code_start_tag, array $attributes, string $content ): string {
425 | $added_classes = 'hljs';
426 |
427 | if ( $attributes['language'] ) {
428 | $added_classes .= " language-{$attributes['language']}";
429 | }
430 |
431 | if ( $attributes['showLineNumbers'] || $attributes['highlightedLines'] ) {
432 | $added_classes .= ' shcb-code-table';
433 | }
434 |
435 | if ( $attributes['showLineNumbers'] ) {
436 | $added_classes .= ' shcb-line-numbers';
437 | }
438 |
439 | if ( $attributes['wrapLines'] ) {
440 | $added_classes .= ' shcb-wrap-lines';
441 | }
442 |
443 | // @todo Update this to use WP_HTML_Tag_Processor.
444 | $code_start_tag = (string) preg_replace(
445 | '/(]*\sclass=")/',
446 | '$1' . esc_attr( $added_classes ) . ' ',
447 | $code_start_tag,
448 | 1,
449 | $count
450 | );
451 | if ( 0 === $count ) {
452 | $code_start_tag = (string) preg_replace(
453 | '/(?<=';
461 |
462 | // Add language label if one was detected and if we're not in a feed.
463 | if ( ! is_feed() && ! empty( $attributes['language'] ) ) {
464 | $language_names = get_language_names();
465 | $language_name = $language_names[ $attributes['language'] ] ?? $attributes['language'];
466 |
467 | $element_id = wp_unique_id( 'shcb-language-' );
468 |
469 | // Add the language info to markup with semantic label.
470 | $end_tags .= sprintf(
471 | '%s %s (%s)',
472 | esc_attr( $element_id ),
473 | esc_html__( 'Code language:', 'syntax-highlighting-code-block' ),
474 | esc_html( $language_name ),
475 | esc_html( $attributes['language'] )
476 | );
477 |
478 | // Also include the language in data attributes on the root element for maximum styling flexibility.
479 | $pre_start_tag = str_replace(
480 | '>',
481 | sprintf(
482 | ' aria-describedby="%s" data-shcb-language-name="%s" data-shcb-language-slug="%s">',
483 | esc_attr( $element_id ),
484 | esc_attr( $language_name ),
485 | esc_attr( $attributes['language'] )
486 | ),
487 | $pre_start_tag
488 | );
489 | }
490 | $end_tags .= '
';
491 |
492 | return $pre_start_tag . '' . $code_start_tag . escape( $content ) . $end_tags;
493 | }
494 |
495 | /**
496 | * Escape content.
497 | *
498 | * In order to prevent WordPress the_content filters from rendering embeds/shortcodes, it's important
499 | * to re-escape the content in the same way as the editor is doing with the Code block's save function.
500 | * Note this does not need to escape ampersands because they will already be escaped by highlight.php.
501 | * Also, escaping of ampersands was removed in
502 | * once HTML editing of Code blocks was implemented.
503 | *
504 | * @link
505 | * @link
506 | * @link
507 | *
508 | * @param string $content Highlighted content.
509 | * @return string Escaped content.
510 | */
511 | function escape( string $content ): string {
512 | // See escapeOpeningSquareBrackets: .
513 | $content = str_replace( '[', '[', $content );
514 |
515 | // See escapeProtocolInIsolatedUrls: .
516 | return (string) preg_replace( '/^(\s*https?:)\/\/([^\s<>"]+\s*)$/m', '$1//$2', $content );
517 | }
518 |
519 | /**
520 | * Get transient key.
521 | *
522 | * Returns null if key cannot be computed.
523 | *
524 | * @param string $content Content.
525 | * @param array{
526 | * language: string,
527 | * highlightedLines: string,
528 | * showLineNumbers: bool,
529 | * wrapLines: bool
530 | * } $attributes Attributes.
531 | * @param bool $is_feed Is feed.
532 | * @param string[] $auto_detect_languages Auto-detect languages.
533 | *
534 | * @return string|null Transient key.
535 | */
536 | function get_transient_key( string $content, array $attributes, bool $is_feed, array $auto_detect_languages ): ?string {
537 | $hash_input = wp_json_encode(
538 | [
539 | 'content' => $content,
540 | 'attributes' => $attributes,
541 | 'is_feed' => $is_feed, // TODO: This is obsolete.
542 | 'auto_detect_languages' => $auto_detect_languages,
543 | 'version' => PLUGIN_VERSION,
544 | ]
545 | );
546 | if ( ! is_string( $hash_input ) ) {
547 | return null;
548 | }
549 | return 'shcb-' . md5( $hash_input );
550 | }
551 |
552 | /**
553 | * Render code block.
554 | *
555 | * @param array{
556 | * language: string,
557 | * highlightedLines: string,
558 | * showLineNumbers: bool,
559 | * wrapLines: bool,
560 | * selectedLines?: string,
561 | * showLines?: bool
562 | * } $attributes Attributes.
563 | * @param string $content Content.
564 | * @return string Highlighted content.
565 | */
566 | function render_block( array $attributes, string $content ): string {
567 | $pattern = '(?P]*?>)(?P]*?>)';
568 | $pattern .= '(?P.*)';
569 | $pattern .= '
';
570 |
571 | if ( ! preg_match( '#^\s*' . $pattern . '\s*$#s', $content, $matches ) ) {
572 | return $content;
573 | }
574 |
575 | // Migrate legacy attribute names.
576 | if ( isset( $attributes['selectedLines'] ) ) {
577 | $attributes['highlightedLines'] = $attributes['selectedLines'];
578 | unset( $attributes['selectedLines'] );
579 | }
580 | if ( isset( $attributes['showLines'] ) ) {
581 | $attributes['showLineNumbers'] = $attributes['showLines'];
582 | unset( $attributes['showLines'] );
583 | }
584 |
585 | /**
586 | * Filters the list of languages that are used for auto-detection.
587 | *
588 | * @param string[] $auto_detect_language Auto-detect languages.
589 | */
590 | $auto_detect_languages = apply_filters( 'syntax_highlighting_code_block_auto_detect_languages', [] );
591 | if ( ! is_array( $auto_detect_languages ) ) {
592 | $auto_detect_languages = [];
593 | }
594 | $auto_detect_languages = array_filter( $auto_detect_languages, 'is_string' );
595 |
596 | // Use the previously-highlighted content if cached.
597 | $transient_key = ! DEVELOPMENT_MODE ? get_transient_key( $matches['content'], $attributes, is_feed(), $auto_detect_languages ) : null;
598 | $highlighted = $transient_key ? get_transient( $transient_key ) : null;
599 | if (
600 | is_array( $highlighted )
601 | &&
602 | isset( $highlighted['content'] ) && is_string( $highlighted['content'] )
603 | &&
604 | is_array( $highlighted['attributes'] )
605 | &&
606 | isset( $highlighted['attributes']['language'] ) && is_string( $highlighted['attributes']['language'] )
607 | &&
608 | isset( $highlighted['attributes']['highlightedLines'] ) && is_string( $highlighted['attributes']['highlightedLines'] )
609 | &&
610 | isset( $highlighted['attributes']['showLineNumbers'] ) && is_bool( $highlighted['attributes']['showLineNumbers'] )
611 | &&
612 | isset( $highlighted['attributes']['wrapLines'] ) && is_bool( $highlighted['attributes']['wrapLines'] )
613 | ) {
614 | return inject_markup( $matches['pre_start_tag'], $matches['code_start_tag'], $highlighted['attributes'], $highlighted['content'] );
615 | }
616 |
617 | try {
618 | if ( ! class_exists( '\Highlight\Autoloader' ) ) {
619 | require_once PLUGIN_DIR . '/' . get_highlight_php_vendor_path() . '/Highlight/Autoloader.php';
620 | spl_autoload_register( 'Highlight\Autoloader::load' );
621 | }
622 |
623 | $highlighter = new Highlighter();
624 | if ( ! empty( $auto_detect_languages ) ) {
625 | $highlighter->setAutodetectLanguages( $auto_detect_languages );
626 | }
627 |
628 | $language = $attributes['language'];
629 |
630 | // As of Gutenberg 17.1, line breaks in Code blocks are serialized as
tags whereas previously they were newlines.
631 | $content = str_replace( '
', "\n", $matches['content'] );
632 |
633 | // Note that the decoding here is reversed later in the escape() function.
634 | // @todo Now that Code blocks may have markup (e.g. bolding, italics, and hyperlinks), these need to be removed and then restored after highlighting is completed.
635 | $content = html_entity_decode( $content, ENT_QUOTES );
636 |
637 | // Convert from Prism.js languages names.
638 | if ( 'clike' === $language ) {
639 | $language = 'cpp';
640 | } elseif ( 'git' === $language ) {
641 | $language = 'diff'; // Best match.
642 | } elseif ( 'markup' === $language ) {
643 | $language = 'xml';
644 | }
645 |
646 | if ( $language ) {
647 | $r = $highlighter->highlight( $language, $content );
648 | } else {
649 | $r = $highlighter->highlightAuto( $content );
650 | }
651 | $attributes['language'] = $r->language;
652 |
653 | $content = $r->value;
654 | if ( $attributes['showLineNumbers'] || $attributes['highlightedLines'] ) {
655 | require_highlight_php_functions();
656 |
657 | $highlighted_lines = parse_highlighted_lines( $attributes['highlightedLines'] );
658 | $lines = split_code_into_array( $content );
659 | $content = '';
660 |
661 | // We need to wrap the line of code twice in order to let out `white-space: pre` CSS setting to be respected
662 | // by our `table-row`.
663 | foreach ( $lines as $i => $line ) {
664 | $tag_name = in_array( $i, $highlighted_lines, true ) ? 'mark' : 'span';
665 | $content .= "<$tag_name class='shcb-loc'>$line\n$tag_name>";
666 | }
667 | }
668 |
669 | if ( $transient_key ) {
670 | set_transient( $transient_key, compact( 'content', 'attributes' ), MONTH_IN_SECONDS );
671 | }
672 |
673 | return inject_markup( $matches['pre_start_tag'], $matches['code_start_tag'], $attributes, $content );
674 | } catch ( Exception $e ) {
675 | return sprintf(
676 | '%s',
677 | get_class( $e ),
678 | $e->getCode(),
679 | str_replace( '--', '', $e->getMessage() ),
680 | $content
681 | );
682 | }
683 | }
684 |
685 | /**
686 | * Split code into an array.
687 | *
688 | * @param string $code Code to split.
689 | * @return string[] Lines.
690 | * @throws Exception If an error occurred in splitting up by lines.
691 | */
692 | function split_code_into_array( string $code ): array {
693 | $lines = splitCodeIntoArray( $code );
694 | if ( ! is_array( $lines ) ) {
695 | throw new Exception( 'Unable to split code into array.' );
696 | }
697 | return $lines;
698 | }
699 |
700 | /**
701 | * Parse the highlighted line syntax from the front-end and return an array of highlighted line numbers.
702 | *
703 | * @param string $highlighted_lines The highlighted line syntax.
704 | * @return int[]
705 | */
706 | function parse_highlighted_lines( string $highlighted_lines ): array {
707 | $highlighted_line_numbers = [];
708 |
709 | if ( ! $highlighted_lines || empty( trim( $highlighted_lines ) ) ) {
710 | return $highlighted_line_numbers;
711 | }
712 |
713 | $ranges = explode( ',', (string) preg_replace( '/\s/', '', $highlighted_lines ) );
714 |
715 | foreach ( $ranges as $chunk ) {
716 | if ( strpos( $chunk, '-' ) !== false ) {
717 | $range = explode( '-', $chunk );
718 |
719 | if ( count( $range ) === 2 ) {
720 | for ( $i = (int) $range[0]; $i <= (int) $range[1]; $i++ ) {
721 | $highlighted_line_numbers[] = $i - 1;
722 | }
723 | }
724 | } else {
725 | $highlighted_line_numbers[] = (int) $chunk - 1;
726 | }
727 | }
728 |
729 | return $highlighted_line_numbers;
730 | }
731 |
732 | /**
733 | * Validate the given stylesheet name against available stylesheets.
734 | *
735 | * @param WP_Error $validity Validator object.
736 | * @param string $input Incoming theme name.
737 | * @return WP_Error Amended errors.
738 | */
739 | function validate_theme_name( WP_Error $validity, string $input ): WP_Error {
740 | require_highlight_php_functions();
741 |
742 | $themes = getAvailableStyleSheets();
743 |
744 | if ( ! in_array( $input, $themes, true ) ) {
745 | $validity->add( 'invalid_theme', __( 'Unrecognized theme', 'syntax-highlighting-code-block' ) );
746 | }
747 |
748 | return $validity;
749 | }
750 |
751 | /**
752 | * Add plugin settings to Customizer.
753 | *
754 | * @param WP_Customize_Manager $wp_customize The Customizer object.
755 | */
756 | function customize_register( WP_Customize_Manager $wp_customize ): void {
757 | if ( has_filter( BLOCK_STYLE_FILTER ) && has_filter( HIGHLIGHTED_LINE_BACKGROUND_COLOR_FILTER ) ) {
758 | return;
759 | }
760 |
761 | if ( ! is_styling_enabled() ) {
762 | return;
763 | }
764 |
765 | require_highlight_php_functions();
766 |
767 | $theme_name = get_theme_name();
768 |
769 | if ( ! has_filter( BLOCK_STYLE_FILTER ) ) {
770 | $themes = getAvailableStyleSheets();
771 | sort( $themes );
772 | $choices = array_combine( $themes, $themes );
773 |
774 | $setting = $wp_customize->add_setting(
775 | 'syntax_highlighting[theme_name]',
776 | [
777 | 'type' => 'option',
778 | 'default' => DEFAULT_THEME,
779 | 'validate_callback' => __NAMESPACE__ . '\validate_theme_name',
780 | ]
781 | );
782 |
783 | // Obtain the working theme name in the changeset.
784 | /**
785 | * Theme name sanitized by Customizer setting callback & default
786 | *
787 | * @var string $theme_name
788 | */
789 | $theme_name = $setting->post_value( $theme_name );
790 |
791 | $wp_customize->add_control(
792 | 'syntax_highlighting[theme_name]',
793 | [
794 | 'type' => 'select',
795 | 'section' => 'colors',
796 | 'label' => __( 'Syntax Highlighting Theme', 'syntax-highlighting-code-block' ),
797 | 'description' => __( 'Preview the theme by navigating to a page with a Code block to see the different themes in action.', 'syntax-highlighting-code-block' ),
798 | 'choices' => $choices,
799 | ]
800 | );
801 | }
802 |
803 | if ( ! has_filter( HIGHLIGHTED_LINE_BACKGROUND_COLOR_FILTER ) && $theme_name ) {
804 | $default_color = strtolower( get_default_line_background_color( $theme_name ) );
805 | $wp_customize->add_setting(
806 | 'syntax_highlighting[highlighted_line_background_color]',
807 | [
808 | 'type' => 'option',
809 | 'default' => $default_color,
810 | 'sanitize_callback' => 'sanitize_hex_color',
811 | ]
812 | );
813 | $wp_customize->add_control(
814 | new WP_Customize_Color_Control(
815 | $wp_customize,
816 | 'syntax_highlighting[highlighted_line_background_color]',
817 | [
818 | 'section' => 'colors',
819 | 'setting' => 'syntax_highlighting[highlighted_line_background_color]',
820 | 'label' => __( 'Highlighted Line Color', 'syntax-highlighting-code-block' ),
821 | 'description' => __( 'The background color of a highlighted line in a Code block.', 'syntax-highlighting-code-block' ),
822 | ]
823 | )
824 | );
825 |
826 | // Add the script to synchronize the default highlighting line color with the selected theme.
827 | if ( ! has_filter( BLOCK_STYLE_FILTER ) ) {
828 | add_action( 'customize_controls_enqueue_scripts', __NAMESPACE__ . '\enqueue_customize_scripts' );
829 | }
830 | }
831 | }
832 |
833 | /**
834 | * Enqueue scripts for Customizer.
835 | *
836 | * @noinspection PhpUnused -- See https://youtrack.jetbrains.com/issue/WI-22217/Extend-possible-linking-between-function-and-callback-using-different-constants-NAMESPACE-CLASS-and-class
837 | */
838 | function enqueue_customize_scripts(): void {
839 | $script_handle = 'syntax-highlighting-code-block-customize-controls';
840 | $script_path = '/build/customize-controls.js';
841 | $script_asset = require PLUGIN_DIR . '/build/customize-controls.asset.php';
842 |
843 | wp_enqueue_script(
844 | $script_handle,
845 | plugins_url( $script_path, PLUGIN_MAIN_FILE ),
846 | array_merge( [ 'customize-controls' ], $script_asset['dependencies'] ),
847 | $script_asset['version'],
848 | true
849 | );
850 | }
851 |
852 | /**
853 | * Register REST endpoint.
854 | *
855 | * @noinspection PhpUnused -- See https://youtrack.jetbrains.com/issue/WI-22217/Extend-possible-linking-between-function-and-callback-using-different-constants-NAMESPACE-CLASS-and-class
856 | */
857 | function register_rest_endpoint(): void {
858 | register_rest_route(
859 | REST_API_NAMESPACE,
860 | '/highlighted-line-background-color/(?P[^/]+)',
861 | [
862 | 'methods' => WP_REST_Server::READABLE,
863 | 'permission_callback' => static function () {
864 | return current_user_can( 'customize' );
865 | },
866 | 'callback' => static function ( WP_REST_Request $request ) {
867 | $theme_name = $request['theme_name'];
868 | $validity = validate_theme_name( new WP_Error(), $theme_name );
869 | if ( $validity->errors ) {
870 | return $validity;
871 | }
872 | return new WP_REST_Response( get_default_line_background_color( $theme_name ) );
873 | },
874 | ]
875 | );
876 | }
877 |
878 | /**
879 | * Gets relative path to highlight.php library in vendor directory.
880 | *
881 | * @return string Relative path.
882 | */
883 | function get_highlight_php_vendor_path(): string {
884 | if ( DEVELOPMENT_MODE && file_exists( PLUGIN_DIR . '/vendor/scrivo/highlight.php' ) ) {
885 | return 'vendor/scrivo/highlight.php';
886 | } else {
887 | return 'vendor/scrivo/highlight-php';
888 | }
889 | }
890 |
--------------------------------------------------------------------------------
/language-names.php:
--------------------------------------------------------------------------------
1 | __( '1C:Enterprise (v7, v8)', 'syntax-highlighting-code-block' ),
6 | 'abnf' => __( 'Augmented Backus-Naur Form', 'syntax-highlighting-code-block' ),
7 | 'accesslog' => __( 'Access log', 'syntax-highlighting-code-block' ),
8 | 'actionscript' => __( 'ActionScript', 'syntax-highlighting-code-block' ),
9 | 'ada' => __( 'Ada', 'syntax-highlighting-code-block' ),
10 | 'angelscript' => __( 'AngelScript', 'syntax-highlighting-code-block' ),
11 | 'apache' => __( 'Apache', 'syntax-highlighting-code-block' ),
12 | 'applescript' => __( 'AppleScript', 'syntax-highlighting-code-block' ),
13 | 'arcade' => __( 'ArcGIS Arcade', 'syntax-highlighting-code-block' ),
14 | 'arduino' => __( 'Arduino', 'syntax-highlighting-code-block' ),
15 | 'armasm' => __( 'ARM Assembly', 'syntax-highlighting-code-block' ),
16 | 'asciidoc' => __( 'AsciiDoc', 'syntax-highlighting-code-block' ),
17 | 'aspectj' => __( 'AspectJ', 'syntax-highlighting-code-block' ),
18 | 'autohotkey' => __( 'AutoHotkey', 'syntax-highlighting-code-block' ),
19 | 'autoit' => __( 'AutoIt', 'syntax-highlighting-code-block' ),
20 | 'avrasm' => __( 'AVR Assembler', 'syntax-highlighting-code-block' ),
21 | 'awk' => __( 'Awk', 'syntax-highlighting-code-block' ),
22 | 'axapta' => __( 'Microsoft Axapta (now Dynamics 365)', 'syntax-highlighting-code-block' ),
23 | 'bash' => __( 'Bash', 'syntax-highlighting-code-block' ),
24 | 'basic' => __( 'Basic', 'syntax-highlighting-code-block' ),
25 | 'bnf' => __( 'Backus–Naur Form', 'syntax-highlighting-code-block' ),
26 | 'brainfuck' => __( 'Brainfuck', 'syntax-highlighting-code-block' ),
27 | 'cal' => __( 'C/AL', 'syntax-highlighting-code-block' ),
28 | 'capnproto' => __( 'Cap’n Proto', 'syntax-highlighting-code-block' ),
29 | 'ceylon' => __( 'Ceylon', 'syntax-highlighting-code-block' ),
30 | 'clean' => __( 'Clean', 'syntax-highlighting-code-block' ),
31 | 'clojure-repl' => __( 'Clojure REPL', 'syntax-highlighting-code-block' ),
32 | 'clojure' => __( 'Clojure', 'syntax-highlighting-code-block' ),
33 | 'cmake' => __( 'CMake', 'syntax-highlighting-code-block' ),
34 | 'coffeescript' => __( 'CoffeeScript', 'syntax-highlighting-code-block' ),
35 | 'coq' => __( 'Coq', 'syntax-highlighting-code-block' ),
36 | 'cos' => __( 'Caché Object Script', 'syntax-highlighting-code-block' ),
37 | 'cpp' => __( 'C++', 'syntax-highlighting-code-block' ),
38 | 'crmsh' => __( 'crmsh', 'syntax-highlighting-code-block' ),
39 | 'crystal' => __( 'Crystal', 'syntax-highlighting-code-block' ),
40 | 'cs' => __( 'C#', 'syntax-highlighting-code-block' ),
41 | 'csp' => __( 'CSP', 'syntax-highlighting-code-block' ),
42 | 'css' => __( 'CSS', 'syntax-highlighting-code-block' ),
43 | 'd' => __( 'D', 'syntax-highlighting-code-block' ),
44 | 'dart' => __( 'Dart', 'syntax-highlighting-code-block' ),
45 | 'delphi' => __( 'Delphi', 'syntax-highlighting-code-block' ),
46 | 'diff' => __( 'Diff', 'syntax-highlighting-code-block' ),
47 | 'django' => __( 'Django', 'syntax-highlighting-code-block' ),
48 | 'dns' => __( 'DNS Zone file', 'syntax-highlighting-code-block' ),
49 | 'dockerfile' => __( 'Dockerfile', 'syntax-highlighting-code-block' ),
50 | 'dos' => __( 'DOS .bat', 'syntax-highlighting-code-block' ),
51 | 'dsconfig' => __( 'dsconfig', 'syntax-highlighting-code-block' ),
52 | 'dts' => __( 'Device Tree', 'syntax-highlighting-code-block' ),
53 | 'dust' => __( 'Dust', 'syntax-highlighting-code-block' ),
54 | 'ebnf' => __( 'Extended Backus-Naur Form', 'syntax-highlighting-code-block' ),
55 | 'elixir' => __( 'Elixir', 'syntax-highlighting-code-block' ),
56 | 'elm' => __( 'Elm', 'syntax-highlighting-code-block' ),
57 | 'erb' => __( 'ERB (Embedded Ruby)', 'syntax-highlighting-code-block' ),
58 | 'erlang-repl' => __( 'Erlang REPL', 'syntax-highlighting-code-block' ),
59 | 'erlang' => __( 'Erlang', 'syntax-highlighting-code-block' ),
60 | 'excel' => __( 'Excel', 'syntax-highlighting-code-block' ),
61 | 'fix' => __( 'FIX', 'syntax-highlighting-code-block' ),
62 | 'flix' => __( 'Flix', 'syntax-highlighting-code-block' ),
63 | 'fortran' => __( 'Fortran', 'syntax-highlighting-code-block' ),
64 | 'fsharp' => __( 'F#', 'syntax-highlighting-code-block' ),
65 | 'gams' => __( 'GAMS', 'syntax-highlighting-code-block' ),
66 | 'gauss' => __( 'GAUSS', 'syntax-highlighting-code-block' ),
67 | 'gcode' => __( 'G-code (ISO 6983)', 'syntax-highlighting-code-block' ),
68 | 'gherkin' => __( 'Gherkin', 'syntax-highlighting-code-block' ),
69 | 'glsl' => __( 'GLSL', 'syntax-highlighting-code-block' ),
70 | 'gml' => __( 'GML', 'syntax-highlighting-code-block' ),
71 | 'go' => __( 'Go', 'syntax-highlighting-code-block' ),
72 | 'golo' => __( 'Golo', 'syntax-highlighting-code-block' ),
73 | 'gradle' => __( 'Gradle', 'syntax-highlighting-code-block' ),
74 | 'groovy' => __( 'Groovy', 'syntax-highlighting-code-block' ),
75 | 'haml' => __( 'Haml', 'syntax-highlighting-code-block' ),
76 | 'handlebars' => __( 'Handlebars', 'syntax-highlighting-code-block' ),
77 | 'haskell' => __( 'Haskell', 'syntax-highlighting-code-block' ),
78 | 'haxe' => __( 'Haxe', 'syntax-highlighting-code-block' ),
79 | 'hsp' => __( 'HSP', 'syntax-highlighting-code-block' ),
80 | 'htmlbars' => __( 'HTMLBars', 'syntax-highlighting-code-block' ),
81 | 'http' => __( 'HTTP', 'syntax-highlighting-code-block' ),
82 | 'hy' => __( 'Hy', 'syntax-highlighting-code-block' ),
83 | 'inform7' => __( 'Inform 7', 'syntax-highlighting-code-block' ),
84 | 'ini' => __( 'TOML, also INI', 'syntax-highlighting-code-block' ),
85 | 'irpf90' => __( 'IRPF90', 'syntax-highlighting-code-block' ),
86 | 'isbl' => __( 'ISBL', 'syntax-highlighting-code-block' ),
87 | 'java' => __( 'Java', 'syntax-highlighting-code-block' ),
88 | 'javascript' => __( 'JavaScript', 'syntax-highlighting-code-block' ),
89 | 'jboss-cli' => __( 'jboss-cli', 'syntax-highlighting-code-block' ),
90 | 'json' => __( 'JSON / JSON with Comments', 'syntax-highlighting-code-block' ),
91 | 'julia-repl' => __( 'Julia REPL', 'syntax-highlighting-code-block' ),
92 | 'julia' => __( 'Julia', 'syntax-highlighting-code-block' ),
93 | 'kotlin' => __( 'Kotlin', 'syntax-highlighting-code-block' ),
94 | 'lasso' => __( 'Lasso', 'syntax-highlighting-code-block' ),
95 | 'ldif' => __( 'LDIF', 'syntax-highlighting-code-block' ),
96 | 'leaf' => __( 'Leaf', 'syntax-highlighting-code-block' ),
97 | 'less' => __( 'Less', 'syntax-highlighting-code-block' ),
98 | 'lisp' => __( 'Lisp', 'syntax-highlighting-code-block' ),
99 | 'livecodeserver' => __( 'LiveCode', 'syntax-highlighting-code-block' ),
100 | 'livescript' => __( 'LiveScript', 'syntax-highlighting-code-block' ),
101 | 'llvm' => __( 'LLVM IR', 'syntax-highlighting-code-block' ),
102 | 'lsl' => __( 'LSL (Linden Scripting Language)', 'syntax-highlighting-code-block' ),
103 | 'lua' => __( 'Lua', 'syntax-highlighting-code-block' ),
104 | 'makefile' => __( 'Makefile', 'syntax-highlighting-code-block' ),
105 | 'markdown' => __( 'Markdown', 'syntax-highlighting-code-block' ),
106 | 'mathematica' => __( 'Mathematica', 'syntax-highlighting-code-block' ),
107 | 'matlab' => __( 'Matlab', 'syntax-highlighting-code-block' ),
108 | 'maxima' => __( 'Maxima', 'syntax-highlighting-code-block' ),
109 | 'mel' => __( 'MEL', 'syntax-highlighting-code-block' ),
110 | 'mercury' => __( 'Mercury', 'syntax-highlighting-code-block' ),
111 | 'mipsasm' => __( 'MIPS Assembly', 'syntax-highlighting-code-block' ),
112 | 'mizar' => __( 'Mizar', 'syntax-highlighting-code-block' ),
113 | 'mojolicious' => __( 'Mojolicious', 'syntax-highlighting-code-block' ),
114 | 'monkey' => __( 'Monkey', 'syntax-highlighting-code-block' ),
115 | 'moonscript' => __( 'MoonScript', 'syntax-highlighting-code-block' ),
116 | 'n1ql' => __( 'N1QL', 'syntax-highlighting-code-block' ),
117 | 'nginx' => __( 'Nginx', 'syntax-highlighting-code-block' ),
118 | 'nimrod' => __( 'Nim (formerly Nimrod)', 'syntax-highlighting-code-block' ),
119 | 'nix' => __( 'Nix', 'syntax-highlighting-code-block' ),
120 | 'nsis' => __( 'NSIS', 'syntax-highlighting-code-block' ),
121 | 'objectivec' => __( 'Objective-C', 'syntax-highlighting-code-block' ),
122 | 'ocaml' => __( 'OCaml', 'syntax-highlighting-code-block' ),
123 | 'openscad' => __( 'OpenSCAD', 'syntax-highlighting-code-block' ),
124 | 'oxygene' => __( 'Oxygene', 'syntax-highlighting-code-block' ),
125 | 'parser3' => __( 'Parser3', 'syntax-highlighting-code-block' ),
126 | 'perl' => __( 'Perl', 'syntax-highlighting-code-block' ),
127 | 'pf' => __( 'pf.conf', 'syntax-highlighting-code-block' ),
128 | 'pgsql' => __( 'PostgreSQL SQL dialect and PL/pgSQL', 'syntax-highlighting-code-block' ),
129 | 'php' => __( 'PHP', 'syntax-highlighting-code-block' ),
130 | 'plaintext' => __( 'plaintext', 'syntax-highlighting-code-block' ),
131 | 'pony' => __( 'Pony', 'syntax-highlighting-code-block' ),
132 | 'powershell' => __( 'PowerShell', 'syntax-highlighting-code-block' ),
133 | 'processing' => __( 'Processing', 'syntax-highlighting-code-block' ),
134 | 'profile' => __( 'Python profile', 'syntax-highlighting-code-block' ),
135 | 'prolog' => __( 'Prolog', 'syntax-highlighting-code-block' ),
136 | 'properties' => __( 'Properties', 'syntax-highlighting-code-block' ),
137 | 'protobuf' => __( 'Protocol Buffers', 'syntax-highlighting-code-block' ),
138 | 'puppet' => __( 'Puppet', 'syntax-highlighting-code-block' ),
139 | 'purebasic' => __( 'PureBASIC', 'syntax-highlighting-code-block' ),
140 | 'python' => __( 'Python', 'syntax-highlighting-code-block' ),
141 | 'q' => __( 'Q', 'syntax-highlighting-code-block' ),
142 | 'qml' => __( 'QML', 'syntax-highlighting-code-block' ),
143 | 'r' => __( 'R', 'syntax-highlighting-code-block' ),
144 | 'reasonml' => __( 'ReasonML', 'syntax-highlighting-code-block' ),
145 | 'rib' => __( 'RenderMan RIB', 'syntax-highlighting-code-block' ),
146 | 'roboconf' => __( 'Roboconf', 'syntax-highlighting-code-block' ),
147 | 'routeros' => __( 'Microtik RouterOS script', 'syntax-highlighting-code-block' ),
148 | 'rsl' => __( 'RenderMan RSL', 'syntax-highlighting-code-block' ),
149 | 'ruby' => __( 'Ruby', 'syntax-highlighting-code-block' ),
150 | 'ruleslanguage' => __( 'Oracle Rules Language', 'syntax-highlighting-code-block' ),
151 | 'rust' => __( 'Rust', 'syntax-highlighting-code-block' ),
152 | 'sas' => __( 'SAS', 'syntax-highlighting-code-block' ),
153 | 'scala' => __( 'Scala', 'syntax-highlighting-code-block' ),
154 | 'scheme' => __( 'Scheme', 'syntax-highlighting-code-block' ),
155 | 'scilab' => __( 'Scilab', 'syntax-highlighting-code-block' ),
156 | 'scss' => __( 'SCSS', 'syntax-highlighting-code-block' ),
157 | 'shell' => __( 'Shell Session', 'syntax-highlighting-code-block' ),
158 | 'smali' => __( 'Smali', 'syntax-highlighting-code-block' ),
159 | 'smalltalk' => __( 'Smalltalk', 'syntax-highlighting-code-block' ),
160 | 'sml' => __( 'SML (Standard ML)', 'syntax-highlighting-code-block' ),
161 | 'sqf' => __( 'SQF', 'syntax-highlighting-code-block' ),
162 | 'sql' => __( 'SQL (Structured Query Language)', 'syntax-highlighting-code-block' ),
163 | 'stan' => __( 'Stan', 'syntax-highlighting-code-block' ),
164 | 'stata' => __( 'Stata', 'syntax-highlighting-code-block' ),
165 | 'step21' => __( 'STEP Part 21', 'syntax-highlighting-code-block' ),
166 | 'stylus' => __( 'Stylus', 'syntax-highlighting-code-block' ),
167 | 'subunit' => __( 'SubUnit', 'syntax-highlighting-code-block' ),
168 | 'swift' => __( 'Swift', 'syntax-highlighting-code-block' ),
169 | 'taggerscript' => __( 'Tagger Script', 'syntax-highlighting-code-block' ),
170 | 'tap' => __( 'Test Anything Protocol', 'syntax-highlighting-code-block' ),
171 | 'tcl' => __( 'Tcl', 'syntax-highlighting-code-block' ),
172 | 'tex' => __( 'TeX', 'syntax-highlighting-code-block' ),
173 | 'thrift' => __( 'Thrift', 'syntax-highlighting-code-block' ),
174 | 'tp' => __( 'TP', 'syntax-highlighting-code-block' ),
175 | 'twig' => __( 'Twig', 'syntax-highlighting-code-block' ),
176 | 'typescript' => __( 'TypeScript', 'syntax-highlighting-code-block' ),
177 | 'vala' => __( 'Vala', 'syntax-highlighting-code-block' ),
178 | 'vbnet' => __( 'VB.NET', 'syntax-highlighting-code-block' ),
179 | 'vbscript-html' => __( 'VBScript in HTML', 'syntax-highlighting-code-block' ),
180 | 'vbscript' => __( 'VBScript', 'syntax-highlighting-code-block' ),
181 | 'verilog' => __( 'Verilog', 'syntax-highlighting-code-block' ),
182 | 'vhdl' => __( 'VHDL', 'syntax-highlighting-code-block' ),
183 | 'vim' => __( 'Vim Script', 'syntax-highlighting-code-block' ),
184 | 'x86asm' => __( 'Intel x86 Assembly', 'syntax-highlighting-code-block' ),
185 | 'xl' => __( 'XL', 'syntax-highlighting-code-block' ),
186 | 'xml' => __( 'HTML, XML', 'syntax-highlighting-code-block' ),
187 | 'xquery' => __( 'XQuery', 'syntax-highlighting-code-block' ),
188 | 'yaml' => __( 'YAML', 'syntax-highlighting-code-block' ),
189 | 'zephir' => __( 'Zephir', 'syntax-highlighting-code-block' ),
190 | ];
191 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "syntax-highlighting-code-block",
3 | "private": true,
4 | "description": "Extending the Code block with syntax highlighting rendered on the server, thus being AMP-compatible and having faster frontend performance.",
5 | "author": "Weston Ruter",
6 | "license": "GPL-2.0-or-later",
7 | "keywords": [
8 | "wordpress",
9 | "wordpress-plugin"
10 | ],
11 | "homepage": "https://github.com/westonruter/syntax-highlighting-code-block",
12 | "repository": {
13 | "type": "git",
14 | "url": "https://github.com/westonruter/syntax-highlighting-code-block.git"
15 | },
16 | "bugs": {
17 | "url": "https://github.com/westonruter/syntax-highlighting-code-block/issues"
18 | },
19 | "devDependencies": {
20 | "@wordpress/api-fetch": "7.24.0",
21 | "@wordpress/block-editor": "14.19.0",
22 | "@wordpress/block-library": "9.24.0",
23 | "@wordpress/blocks": "14.13.0",
24 | "@wordpress/components": "29.10.0",
25 | "@wordpress/editor": "14.24.0",
26 | "@wordpress/element": "6.24.0",
27 | "@wordpress/env": "10.24.0",
28 | "@wordpress/eslint-plugin": "22.10.0",
29 | "@wordpress/hooks": "4.24.0",
30 | "@wordpress/i18n": "5.24.0",
31 | "@wordpress/scripts": "30.17.0",
32 | "eslint": "8.57.1",
33 | "grunt": "1.6.1",
34 | "grunt-wp-deploy": "2.1.2",
35 | "highlight.js": "git+https://github.com/highlightjs/highlight.js.git#9.18.1",
36 | "husky": "9.1.7",
37 | "lint-staged": "16.0.0",
38 | "lodash": "4.17.21",
39 | "npm-run-all": "4.1.5",
40 | "prettier": "3.5.3"
41 | },
42 | "scripts": {
43 | "update": "bin/update-highlight-libs.sh",
44 | "build": "npm-run-all build:*",
45 | "build:transform-readme": "php ./bin/transform-readme.php",
46 | "build:clean": "if [ -e dist ]; then rm -r dist; fi; if [ -e build ]; then rm -r build; fi; if [ -e syntax-highlighting-code-block ]; then rm -r syntax-highlighting-code-block; fi",
47 | "build:js": "wp-scripts build src/index.js src/customize-controls.js --output-path=build",
48 | "build:dist": "bash ./bin/build-dist.sh",
49 | "build:zip": "if [ -e syntax-highlighting-code-block.zip ]; then rm syntax-highlighting-code-block.zip; fi; vendor/bin/wp dist-archive --plugin-dirname=syntax-highlighting-code-block dist syntax-highlighting-code-block.zip && echo \"ZIP of build: $(pwd)/syntax-highlighting-code-block.zip\"",
50 | "verify-matching-versions": "php ./bin/verify-version-consistency.php",
51 | "deploy": "npm-run-all verify-matching-versions build && unzip syntax-highlighting-code-block.zip && grunt wp_deploy && rm -r syntax-highlighting-code-block",
52 | "generate-language-names": "php ./bin/generate-language-names.php",
53 | "check-engines": "wp-scripts check-engines",
54 | "check-licenses": "wp-scripts check-licenses",
55 | "lint": "npm-run-all --parallel lint:*",
56 | "lint:composer": "composer normalize --dry-run",
57 | "lint:composer:fix": "composer normalize",
58 | "lint:css": "wp-scripts lint-style",
59 | "lint:css:fix": "npm run lint:css -- --fix",
60 | "lint:js": "wp-scripts lint-js",
61 | "lint:js:fix": "wp-scripts lint-js --fix",
62 | "lint:js:report": "npm run lint:js -- --output-file lint-js-report.json --format json .",
63 | "lint:php": "composer phpcs",
64 | "lint:php:fix": "composer phpcbf",
65 | "lint:phpstan": "composer analyze",
66 | "lint:pkg-json": "wp-scripts lint-pkg-json . --ignorePath .gitignore",
67 | "md5sum:check": "md5sum -c block-library.md5",
68 | "md5sum:update": "md5sum node_modules/@wordpress/block-library/src/code/edit.js > block-library.md5",
69 | "prepare": "husky",
70 | "start": "wp-scripts start src/index.js src/customize-controls.js --output-path=build",
71 | "symlink-wp-env-install-paths": "bin/symlink-wp-env-install-paths.sh",
72 | "wp-env": "wp-env"
73 | },
74 | "npmpackagejsonlint": {
75 | "extends": "@wordpress/npm-package-json-lint-config",
76 | "rules": {
77 | "require-version": "off"
78 | }
79 | },
80 | "title": "Syntax-highlighting Code Block (with Server-side Rendering)"
81 | }
82 |
--------------------------------------------------------------------------------
/phpstan.neon.dist:
--------------------------------------------------------------------------------
1 | includes:
2 | # @see https://github.com/phpstan/phpstan-src/blob/b9f62d63f2deaa0a5e97f51073e41a422c48aa01/conf/bleedingEdge.neon
3 | - phar://phpstan.phar/conf/bleedingEdge.neon
4 | - vendor/szepeviktor/phpstan-wordpress/extension.neon
5 | parameters:
6 | level: 9
7 | paths:
8 | - syntax-highlighting-code-block.php
9 | - language-names.php
10 | - uninstall.php
11 | - inc/
12 | stubFiles:
13 | # https://github.com/scrivo/highlight.php/pull/107
14 | - tests/phpstan/HighlightAutoloader.stub
15 | dynamicConstantNames:
16 | - Syntax_Highlighting_Code_Block\DEVELOPMENT_MODE
17 | treatPhpDocTypesAsCertain: false
18 |
--------------------------------------------------------------------------------
/src/customize-controls.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies
3 | */
4 | import apiFetch from '@wordpress/api-fetch';
5 |
6 | /**
7 | * External dependencies
8 | */
9 | import { memoize } from 'lodash';
10 |
11 | const { customize } = global.wp;
12 |
13 | const themeNameCustomizeId = 'syntax_highlighting[theme_name]';
14 | const lineColorCustomizeId =
15 | 'syntax_highlighting[highlighted_line_background_color]';
16 |
17 | /**
18 | * Init.
19 | *
20 | * @param {Object} args
21 | * @param {wp.customize.Control} args.themeNameControl
22 | * @param {wp.customize.Control} args.lineColorControl
23 | */
24 | function init({ themeNameControl, lineColorControl }) {
25 | const colorPickerElement =
26 | lineColorControl.container.find('.color-picker-hex');
27 |
28 | themeNameControl.setting.bind(async (newThemeName) => {
29 | const isColorCustomized =
30 | lineColorControl.setting().toLowerCase() !==
31 | lineColorControl.params.defaultValue.toLowerCase();
32 |
33 | lineColorControl.params.defaultValue =
34 | await getDefaultThemeLineColor(newThemeName);
35 |
36 | // Make sure the default value gets propagated into the wpColorPicker.
37 | colorPickerElement.wpColorPicker(
38 | 'defaultColor',
39 | lineColorControl.params.defaultValue
40 | );
41 |
42 | // Update the color to be the default if it was not customized.
43 | if (!isColorCustomized) {
44 | lineColorControl.setting.set(lineColorControl.params.defaultValue);
45 | }
46 | });
47 | }
48 |
49 | /**
50 | * Get default theme line color.
51 | *
52 | * @param {string} themeName
53 | * @return {Promise} Promise.
54 | */
55 | const getDefaultThemeLineColor = memoize((themeName) => {
56 | return apiFetch({
57 | path: `/syntax-highlighting-code-block/v1/highlighted-line-background-color/${themeName}`,
58 | });
59 | });
60 |
61 | // Initialize once the controls are available.
62 | customize.control(
63 | themeNameCustomizeId,
64 | lineColorCustomizeId,
65 | (themeNameControl, lineColorControl) => {
66 | init({
67 | themeNameControl,
68 | lineColorControl,
69 | });
70 | }
71 | );
72 |
--------------------------------------------------------------------------------
/src/edit.js:
--------------------------------------------------------------------------------
1 | /* global syntaxHighlightingCodeBlockLanguageNames */
2 |
3 | /**
4 | * WordPress dependencies
5 | */
6 |
7 | import { Fragment, useRef, useEffect, useState } from '@wordpress/element';
8 | import { __ } from '@wordpress/i18n';
9 | import {
10 | RichText,
11 | useBlockProps,
12 | InspectorControls,
13 | } from '@wordpress/block-editor';
14 | import {
15 | SelectControl,
16 | TextControl,
17 | CheckboxControl,
18 | PanelBody,
19 | PanelRow,
20 | } from '@wordpress/components';
21 | import { createBlock, getDefaultBlockName } from '@wordpress/blocks';
22 |
23 | /**
24 | * External dependencies
25 | */
26 | import { sortBy } from 'lodash';
27 |
28 | const languageNames = syntaxHighlightingCodeBlockLanguageNames;
29 |
30 | const HighlightableTextArea = (props_) => {
31 | const { highlightedLines, ...props } = props_;
32 | const textAreaRef = useRef();
33 | const [styles, setStyles] = useState({});
34 |
35 | useEffect(() => {
36 | if (textAreaRef.current !== null) {
37 | const element = textAreaRef.current;
38 | const computedStyles = window.getComputedStyle(element);
39 |
40 | setStyles({
41 | fontFamily: computedStyles.getPropertyValue('font-family'),
42 | fontSize: computedStyles.getPropertyValue('font-size'),
43 | overflow: 'hidden', // Prevent doubled-scrollbars from appearing.
44 | overflowWrap: computedStyles.getPropertyValue('overflow-wrap'),
45 | resize: computedStyles.getPropertyValue('resize'),
46 | });
47 | }
48 | }, [props.style]);
49 |
50 | return (
51 |
52 |
53 |
58 | {(props.value || '').split(/\n|
/i).map((v, i) => {
59 | let cName = 'loc';
60 |
61 | if (highlightedLines.has(i)) {
62 | cName += ' highlighted';
63 | }
64 |
65 | return (
66 |
73 | );
74 | })}
75 |
76 |
77 | );
78 | };
79 |
80 | /**
81 | * Parse a string representation of highlighted lines into a set of each highlighted line number.
82 | *
83 | * @param {string} highlightedLines Highlighted lines.
84 | * @return {Set} Highlighted lines.
85 | */
86 | const parseHighlightedLines = (highlightedLines) => {
87 | const highlightedLinesSet = new Set();
88 |
89 | if (!highlightedLines || highlightedLines.trim().length === 0) {
90 | return highlightedLinesSet;
91 | }
92 |
93 | let chunk;
94 | const ranges = highlightedLines.replace(/\s/, '').split(',');
95 |
96 | for (chunk of ranges) {
97 | if (chunk.indexOf('-') >= 0) {
98 | let i;
99 | const range = chunk.split('-');
100 |
101 | if (range.length === 2) {
102 | for (i = +range[0]; i <= +range[1]; ++i) {
103 | highlightedLinesSet.add(i - 1);
104 | }
105 | }
106 | } else {
107 | highlightedLinesSet.add(+chunk - 1);
108 | }
109 | }
110 |
111 | return highlightedLinesSet;
112 | };
113 |
114 | export default function CodeEdit({
115 | attributes,
116 | setAttributes,
117 | onRemove,
118 | insertBlocksAfter,
119 | mergeBlocks,
120 | }) {
121 | const blockProps = useBlockProps();
122 |
123 | const updateLanguage = (language) => {
124 | setAttributes({ language });
125 | };
126 |
127 | const updateHighlightedLines = (highlightedLines) => {
128 | setAttributes({ highlightedLines });
129 | };
130 |
131 | const updateShowLineNumbers = (showLineNumbers) => {
132 | setAttributes({ showLineNumbers });
133 | };
134 |
135 | const updateWrapLines = (wrapLines) => {
136 | setAttributes({ wrapLines });
137 | };
138 |
139 | const sortedLanguageNames = sortBy(
140 | Object.entries(languageNames).map(([value, label]) => ({
141 | label,
142 | value,
143 | })),
144 | (languageOption) => languageOption.label.toLowerCase()
145 | );
146 |
147 | const richTextProps = {
148 | // These RichText props must mirror core .
149 | ...{
150 | tagName: 'code',
151 | identifier: 'content',
152 | value: attributes.content,
153 | onChange: (content) => setAttributes({ content }),
154 | onRemove,
155 | onMerge: mergeBlocks,
156 | placeholder: __('Write code…'),
157 | 'aria-label': __('Code'),
158 | preserveWhiteSpace: true,
159 | __unstablePastePlainText: true, // See .
160 | __unstableOnSplitAtDoubleLineEnd: () => {
161 | insertBlocksAfter(createBlock(getDefaultBlockName()));
162 | },
163 | },
164 |
165 | // Additional props unique to HighlightableTextArea.
166 | ...{
167 | highlightedLines: parseHighlightedLines(
168 | attributes.highlightedLines
169 | ),
170 | className: [
171 | 'shcb-textedit',
172 | attributes.wrapLines ? 'shcb-textedit-wrap-lines' : '',
173 | ].join(' '),
174 |
175 | // Copy the styles to ensure that the code-block-overlay is updated when the font size is changed.
176 | style: blockProps.style,
177 | },
178 | };
179 |
180 | return (
181 |
182 |
183 |
190 |
191 |
209 |
210 |
211 |
223 |
224 |
225 |
233 |
234 |
235 |
243 |
244 |
245 |
246 | {/* Keep in sync with https://github.com/WordPress/gutenberg/blob/e95bb8c9530bbdef1db623eca11b80bd73493197/packages/block-library/src/code/edit.js#L17 */}
247 |
248 |
249 |
250 |
251 | );
252 | }
253 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | /* global syntaxHighlightingCodeBlockType */
2 |
3 | /**
4 | * WordPress dependencies
5 | */
6 | import { addFilter } from '@wordpress/hooks';
7 |
8 | /**
9 | * Internal dependencies
10 | */
11 | import edit from './edit';
12 |
13 | /**
14 | * Extend code block with syntax highlighting.
15 | *
16 | * @param {Object} settings Settings.
17 | * @return {Object} Modified settings.
18 | */
19 | const extendCodeBlockWithSyntaxHighlighting = (settings) => {
20 | if (syntaxHighlightingCodeBlockType.name !== settings.name) {
21 | return settings;
22 | }
23 |
24 | return {
25 | ...settings,
26 |
27 | /*
28 | * @todo Why do the attributes need to be augmented here when they have already been declared for the block type in PHP?
29 | * There seems to be a race condition, as wp.blocks.getBlockType('core/code') returns the PHP-augmented data after the
30 | * page loads, but at the moment this filter calls it is still undefined.
31 | */
32 | attributes: {
33 | ...settings.attributes,
34 | ...syntaxHighlightingCodeBlockType.attributes, // @todo Why can't this be supplied via a blocks.getBlockAttributes filter?
35 | },
36 |
37 | edit,
38 |
39 | deprecated: [
40 | ...(settings.deprecated || []),
41 | {
42 | attributes: {
43 | ...settings.attributes,
44 | ...syntaxHighlightingCodeBlockType.deprecated,
45 | },
46 | isEligible(attributes) {
47 | return Object.keys(attributes).some((attribute) => {
48 | return syntaxHighlightingCodeBlockType.deprecated.hasOwnProperty(
49 | attribute
50 | );
51 | });
52 | },
53 | migrate(attributes, innerBlocks) {
54 | return [
55 | {
56 | ...attributes,
57 | highlightedLines: attributes.selectedLines,
58 | showLineNumbers: attributes.showLines,
59 | },
60 | innerBlocks,
61 | ];
62 | },
63 | },
64 | ],
65 | };
66 | };
67 |
68 | addFilter(
69 | 'blocks.registerBlockType',
70 | 'westonruter/syntax-highlighting-code-block-type',
71 | extendCodeBlockWithSyntaxHighlighting
72 | );
73 |
--------------------------------------------------------------------------------
/style.css:
--------------------------------------------------------------------------------
1 | .wp-block-code {
2 | border: 0;
3 | padding: 0;
4 | -webkit-text-size-adjust: 100%;
5 | text-size-adjust: 100%;
6 | }
7 |
8 | .wp-block-code > span {
9 | display: block;
10 | overflow: auto;
11 | }
12 |
13 | .shcb-language {
14 | border: 0;
15 | clip: rect(1px, 1px, 1px, 1px);
16 | -webkit-clip-path: inset(50%);
17 | clip-path: inset(50%);
18 | height: 1px;
19 | margin: -1px;
20 | overflow: hidden;
21 | padding: 0;
22 | position: absolute;
23 | width: 1px;
24 | word-wrap: normal;
25 | word-break: normal;
26 | }
27 |
28 | .hljs {
29 | box-sizing: border-box;
30 | }
31 |
32 | .hljs.shcb-code-table {
33 | display: table;
34 | width: 100%;
35 | }
36 |
37 | .hljs.shcb-code-table > .shcb-loc {
38 | color: inherit;
39 | display: table-row;
40 | width: 100%;
41 | }
42 |
43 | .hljs.shcb-code-table .shcb-loc > span {
44 | display: table-cell;
45 | }
46 |
47 | .wp-block-code code.hljs:not(.shcb-wrap-lines) {
48 | white-space: pre;
49 | }
50 |
51 | .wp-block-code code.hljs.shcb-wrap-lines {
52 | white-space: pre-wrap;
53 | }
54 |
55 | .hljs.shcb-line-numbers {
56 | border-spacing: 0;
57 | counter-reset: line;
58 | }
59 |
60 | .hljs.shcb-line-numbers > .shcb-loc {
61 | counter-increment: line;
62 | }
63 |
64 | .hljs.shcb-line-numbers .shcb-loc > span {
65 | padding-left: 0.75em;
66 | }
67 |
68 | .hljs.shcb-line-numbers .shcb-loc::before {
69 | border-right: 1px solid #ddd;
70 | content: counter(line);
71 | display: table-cell;
72 | padding: 0 0.75em;
73 | text-align: right;
74 | -webkit-user-select: none;
75 | -moz-user-select: none;
76 | -ms-user-select: none;
77 | user-select: none;
78 | white-space: nowrap;
79 | width: 1%;
80 | }
81 |
--------------------------------------------------------------------------------
/syntax-highlighting-code-block.php:
--------------------------------------------------------------------------------
1 | [
54 | 'type' => 'string',
55 | 'default' => '',
56 | ],
57 | 'highlightedLines' => [
58 | 'type' => 'string',
59 | 'default' => '',
60 | ],
61 | 'showLineNumbers' => [
62 | 'type' => 'boolean',
63 | 'default' => false,
64 | ],
65 | 'wrapLines' => [
66 | 'type' => 'boolean',
67 | 'default' => false,
68 | ],
69 | ];
70 |
71 | require_once __DIR__ . '/inc/functions.php';
72 |
73 | add_action( 'plugins_loaded', __NAMESPACE__ . '\boot' );
74 |
--------------------------------------------------------------------------------
/tests/phpstan/HighlightAutoloader.stub:
--------------------------------------------------------------------------------
1 |