Skip to content

Commit fb70b56

Browse files
committed
Add server-side syntax highlighting via scrivo/highlight.php
Introduces a "Highlighting Mode" setting (client/server). In server mode, highlight.php pre-renders syntax highlighting in the PHP render filter — no Prism JS is loaded on the frontend. Changes: - composer.json: add scrivo/highlight.php ^9.18 runtime dependency - Plugin entry: conditionally load Composer autoloader for vendor deps - build-hljs.js + npm build:hljs script: copy 15 hljs theme CSS files from highlight.js npm package into includes/assets/ with .min and -rtl variants generated by build-assets.js - class-settings.php: new $hljs_color_schemes registry (15 themes), highlighting-mode radio setting, hljs-color-scheme select setting, get_hljs_color_scheme_css() helper, mode-aware get_color_scheme_css() - class-styles-handler.php: split enqueue_assets() into client/server branches; server mode loads hljs theme CSS + hljs-server-mode.css + hljs-clipboard.js (no Prism JS or CSS) - class-blocks.php: render_code_block() dispatches to new render_code_block_server() in server mode; implements language slug mapping (Prism → hljs), proper HTML-aware line wrapping with span- balancing for line numbers and highlighting, PHP toolbar injection (copy button, language label, file name); editor canvas style extraction updated to parse .hljs CSS selector in server mode - class-admin.php: admin notice when server mode is active but vendor is missing - includes/assets/hljs-server-mode.css: structural styles (line numbers, toolbar, copy button, highlighted lines) - includes/assets/hljs-clipboard.js: standalone copy-to-clipboard handler (Clipboard API + execCommand fallback) - build-zip.sh: install production Composer deps before packaging and include vendor/scrivo in the zip while excluding dev-only packages https://claude.ai/code/session_01DG7h2psTB1ThgNtaQtMRMa
1 parent fa6e7ef commit fb70b56

59 files changed

Lines changed: 4414 additions & 51 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

build-hljs.js

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/**
2+
* Build script: copies highlight.js theme CSS files from node_modules into includes/assets/.
3+
*
4+
* Run via: npm run build:hljs
5+
*/
6+
7+
const fs = require( 'fs' );
8+
const path = require( 'path' );
9+
10+
const root = __dirname;
11+
12+
const themes = [
13+
{
14+
src: 'node_modules/highlight.js/styles/a11y-dark.css',
15+
dest: 'includes/assets/hljs-a11y-dark.css',
16+
},
17+
{
18+
src: 'node_modules/highlight.js/styles/a11y-light.css',
19+
dest: 'includes/assets/hljs-a11y-light.css',
20+
},
21+
{
22+
src: 'node_modules/highlight.js/styles/atom-one-dark.css',
23+
dest: 'includes/assets/hljs-atom-one-dark.css',
24+
},
25+
{
26+
src: 'node_modules/highlight.js/styles/atom-one-light.css',
27+
dest: 'includes/assets/hljs-atom-one-light.css',
28+
},
29+
{
30+
src: 'node_modules/highlight.js/styles/shades-of-purple.css',
31+
dest: 'includes/assets/hljs-shades-of-purple.css',
32+
},
33+
{
34+
src: 'node_modules/highlight.js/styles/github.css',
35+
dest: 'includes/assets/hljs-github.css',
36+
},
37+
{
38+
src: 'node_modules/highlight.js/styles/github-dark.css',
39+
dest: 'includes/assets/hljs-github-dark.css',
40+
},
41+
{
42+
src: 'node_modules/highlight.js/styles/monokai.css',
43+
dest: 'includes/assets/hljs-monokai.css',
44+
},
45+
{
46+
src: 'node_modules/highlight.js/styles/night-owl.css',
47+
dest: 'includes/assets/hljs-night-owl.css',
48+
},
49+
{
50+
src: 'node_modules/highlight.js/styles/nord.css',
51+
dest: 'includes/assets/hljs-nord.css',
52+
},
53+
{
54+
src: 'node_modules/highlight.js/styles/stackoverflow-dark.css',
55+
dest: 'includes/assets/hljs-stackoverflow-dark.css',
56+
},
57+
{
58+
src: 'node_modules/highlight.js/styles/stackoverflow-light.css',
59+
dest: 'includes/assets/hljs-stackoverflow-light.css',
60+
},
61+
{
62+
src: 'node_modules/highlight.js/styles/vs.css',
63+
dest: 'includes/assets/hljs-vs.css',
64+
},
65+
{
66+
src: 'node_modules/highlight.js/styles/vs2015.css',
67+
dest: 'includes/assets/hljs-vs2015.css',
68+
},
69+
{
70+
src: 'node_modules/highlight.js/styles/xcode.css',
71+
dest: 'includes/assets/hljs-xcode.css',
72+
},
73+
];
74+
75+
let success = true;
76+
77+
themes.forEach( ( { src, dest } ) => {
78+
const srcPath = path.resolve( root, src );
79+
const destPath = path.resolve( root, dest );
80+
81+
if ( ! fs.existsSync( srcPath ) ) {
82+
console.error( `✖ Source not found: ${ src }` );
83+
success = false;
84+
return;
85+
}
86+
87+
fs.mkdirSync( path.dirname( destPath ), { recursive: true } );
88+
fs.copyFileSync( srcPath, destPath );
89+
console.log( `✔ ${ src }${ dest }` );
90+
} );
91+
92+
if ( ! success ) {
93+
process.exit( 1 );
94+
}

build-zip.sh

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,15 @@ TEMP_DIR="$BUILD_DIR/$PLUGIN_SLUG"
1010

1111
echo "Creating distribution zip for $PLUGIN_SLUG..."
1212

13+
# Install production-only Composer dependencies before packaging.
14+
echo "Installing production Composer dependencies..."
15+
COMPOSER_ALLOW_SUPERUSER=1 composer install --no-dev --optimize-autoloader --classmap-authoritative
16+
1317
# Clean build directory
1418
rm -rf "$BUILD_DIR"
1519
mkdir -p "$TEMP_DIR"
1620

17-
# Copy plugin files (excluding dev/build artifacts and all of vendor)
21+
# Copy plugin files (excluding dev/build artifacts; vendor IS included for runtime deps).
1822
echo "Copying plugin files..."
1923
rsync -av --exclude-from=- . "$TEMP_DIR/" <<EOF
2024
.*
@@ -24,13 +28,30 @@ node_modules/
2428
phpcompat-tools/
2529
phpunit/
2630
/build/
27-
vendor/
31+
vendor/bin/
32+
vendor/squizlabs/
33+
vendor/phpstan/
34+
vendor/phpunit/
35+
vendor/sebastian/
36+
vendor/nikic/
37+
vendor/phar-io/
38+
vendor/theseer/
39+
vendor/myclabs/
40+
vendor/szepeviktor/
41+
vendor/php-stubs/
42+
vendor/phpcsstandards/
43+
vendor/wp-coding-standards/
44+
vendor/dealerdirect/
45+
vendor/yoast/
46+
vendor/staabm/
47+
vendor/symfony/
2848
dev-helpers/
2949
dev-tools/
3050
wporg-assets/
3151
test-tools/
3252
docs/
3353
build-assets.js
54+
build-hljs.js
3455
*.zip
3556
*.dist
3657
*.yml

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
}
2020
],
2121
"require": {
22-
"php": ">=7.4"
22+
"php": ">=7.4",
23+
"scrivo/highlight.php": "^9.18"
2324
},
2425
"require-dev": {
2526
"szepeviktor/phpstan-wordpress": "^1",

includes/admin/class-admin.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,32 @@ public function __construct() {
6969
$this->admin_banner = new Admin_Banner( $this->get_admin_banner_config() );
7070

7171
Hook_Registry::add_filter( 'plugin_action_links_' . plugin_basename( WZCBH_PLUGIN_FILE ), array( $this, 'plugin_action_links' ) );
72+
Hook_Registry::add_action( 'admin_notices', array( $this, 'maybe_show_server_mode_notice' ) );
73+
}
74+
75+
/**
76+
* Show an admin notice when server-side highlighting mode is active but the
77+
* highlight.php library is not available (vendor/ not installed).
78+
*
79+
* @since 1.2.0
80+
*/
81+
public function maybe_show_server_mode_notice(): void {
82+
if ( 'server' !== wzcbh_get_option( 'highlighting-mode', 'client' ) ) {
83+
return;
84+
}
85+
86+
if ( class_exists( '\Highlight\Highlighter' ) ) {
87+
return;
88+
}
89+
90+
$settings_url = admin_url( 'options-general.php?page=wzcbh_settings' );
91+
printf(
92+
'<div class="notice notice-error"><p><strong>%s</strong> %s <a href="%s">%s</a></p></div>',
93+
esc_html__( 'Code Block Highlighting:', 'webberzone-code-block-highlighting' ),
94+
esc_html__( 'Server-side highlighting mode is active, but the highlight.php library is missing. Run composer install in the plugin directory, or switch back to client-side mode in', 'webberzone-code-block-highlighting' ),
95+
esc_url( $settings_url ),
96+
esc_html__( 'Settings.', 'webberzone-code-block-highlighting' )
97+
);
7298
}
7399

74100
/**

includes/admin/class-settings.php

Lines changed: 86 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,31 @@
2121
*/
2222
class Settings {
2323

24+
/**
25+
* Available hljs color schemes for server-side mode: slug => label.
26+
*
27+
* @since 1.2.0
28+
*
29+
* @var array<string, string>
30+
*/
31+
public static array $hljs_color_schemes = array(
32+
'hljs-a11y-dark' => 'A11y Dark',
33+
'hljs-a11y-light' => 'A11y Light',
34+
'hljs-atom-one-dark' => 'Atom One Dark',
35+
'hljs-atom-one-light' => 'Atom One Light',
36+
'hljs-github' => 'GitHub',
37+
'hljs-github-dark' => 'GitHub Dark',
38+
'hljs-monokai' => 'Monokai',
39+
'hljs-night-owl' => 'Night Owl',
40+
'hljs-nord' => 'Nord',
41+
'hljs-shades-of-purple' => 'Shades of Purple',
42+
'hljs-stackoverflow-dark' => 'Stack Overflow Dark',
43+
'hljs-stackoverflow-light' => 'Stack Overflow Light',
44+
'hljs-vs' => 'Visual Studio',
45+
'hljs-vs2015' => 'VS 2015 (Dark)',
46+
'hljs-xcode' => 'Xcode',
47+
);
48+
2449
/**
2550
* Available color schemes: slug => label.
2651
*
@@ -178,14 +203,33 @@ public static function get_settings_sections(): array {
178203
public static function get_registered_settings(): array {
179204
return array(
180205
'general' => array(
206+
array(
207+
'id' => 'highlighting-mode',
208+
'name' => __( 'Highlighting Mode', 'webberzone-code-block-highlighting' ),
209+
'desc' => __( 'Client-side: Prism.js runs in the browser (default, supports all block features). Server-side: highlight.php pre-renders syntax on the server — no JavaScript required.', 'webberzone-code-block-highlighting' ),
210+
'type' => 'radio',
211+
'default' => 'client',
212+
'options' => array(
213+
'client' => __( 'Client-side (Prism.js)', 'webberzone-code-block-highlighting' ),
214+
'server' => __( 'Server-side (highlight.php)', 'webberzone-code-block-highlighting' ),
215+
),
216+
),
181217
array(
182218
'id' => 'color-scheme',
183-
'name' => __( 'Color Scheme', 'webberzone-code-block-highlighting' ),
184-
'desc' => __( 'Choose the syntax highlighting theme for all code blocks. This styling is applied only on the frontend and not in the block editor.', 'webberzone-code-block-highlighting' ),
219+
'name' => __( 'Color Scheme (Prism)', 'webberzone-code-block-highlighting' ),
220+
'desc' => __( 'Prism.js theme applied in client-side mode.', 'webberzone-code-block-highlighting' ),
185221
'type' => 'select',
186222
'default' => 'prism-onedark',
187223
'options' => self::$color_schemes,
188224
),
225+
array(
226+
'id' => 'hljs-color-scheme',
227+
'name' => __( 'Color Scheme (Server)', 'webberzone-code-block-highlighting' ),
228+
'desc' => __( 'highlight.php theme applied in server-side mode.', 'webberzone-code-block-highlighting' ),
229+
'type' => 'select',
230+
'default' => 'hljs-atom-one-dark',
231+
'options' => self::$hljs_color_schemes,
232+
),
189233
array(
190234
'id' => 'copy-to-clipboard',
191235
'name' => __( 'Copy to Clipboard', 'webberzone-code-block-highlighting' ),
@@ -310,17 +354,25 @@ public function enqueue_language_data( string $hook ): void {
310354
/**
311355
* Get the URL (or filesystem path) to the active color scheme CSS file.
312356
*
313-
* Returns the plain unminified LTR path. Frontend enqueuing (with SCRIPT_DEBUG
314-
* and RTL awareness) is handled by Styles_Handler.
315-
*
316-
* Falls back to the default One Dark theme if the chosen file does not exist.
357+
* Delegates to the hljs helper when server-side mode is active.
358+
* Returns the plain unminified LTR path; enqueuing with SCRIPT_DEBUG and RTL
359+
* awareness is handled by Styles_Handler.
317360
*
318361
* @since 1.0.0
319362
*
320-
* @param bool $return_path When true, returns the filesystem path instead of the URL.
363+
* @param bool $return_path When true, returns the filesystem path instead of the URL.
364+
* @param string $mode Explicit mode override; reads the setting when empty.
321365
* @return string
322366
*/
323-
public static function get_color_scheme_css( bool $return_path = false ): string {
367+
public static function get_color_scheme_css( bool $return_path = false, string $mode = '' ): string {
368+
if ( '' === $mode ) {
369+
$mode = wzcbh_get_option( 'highlighting-mode', 'client' );
370+
}
371+
372+
if ( 'server' === $mode ) {
373+
return self::get_hljs_color_scheme_css( $return_path );
374+
}
375+
324376
$option = wzcbh_get_option( 'color-scheme', 'prism-onedark' );
325377
if ( ! array_key_exists( $option, self::$color_schemes ) ) {
326378
$option = 'prism-onedark';
@@ -344,4 +396,30 @@ public static function get_color_scheme_css( bool $return_path = false ): string
344396
*/
345397
return apply_filters( 'wzcbh_color_scheme_css_url', WZCBH_PLUGIN_URL . $rel_path ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
346398
}
399+
400+
/**
401+
* Get the URL (or filesystem path) to the active hljs color scheme CSS file.
402+
*
403+
* @since 1.2.0
404+
*
405+
* @param bool $return_path When true, returns the filesystem path instead of the URL.
406+
* @return string
407+
*/
408+
private static function get_hljs_color_scheme_css( bool $return_path = false ): string {
409+
$option = wzcbh_get_option( 'hljs-color-scheme', 'hljs-atom-one-dark' );
410+
if ( ! array_key_exists( $option, self::$hljs_color_schemes ) ) {
411+
$option = 'hljs-atom-one-dark';
412+
}
413+
$rel_path = "includes/assets/{$option}.css";
414+
415+
if ( ! file_exists( WZCBH_PLUGIN_DIR . $rel_path ) ) {
416+
$rel_path = 'includes/assets/hljs-atom-one-dark.css';
417+
}
418+
419+
if ( $return_path ) {
420+
return WZCBH_PLUGIN_DIR . $rel_path;
421+
}
422+
423+
return WZCBH_PLUGIN_URL . $rel_path;
424+
}
347425
}

0 commit comments

Comments
 (0)