PluginProbe
Code Block Pro – Beautiful Syntax Highlighting / 1.13.0
Code Block Pro – Beautiful Syntax Highlighting v1.13.0
1.27.1 1.27.2 1.27.3 1.27.4 1.27.5 1.27.6 1.27.7 1.28.0 1.3.0 1.4.0 1.5.0 1.5.1 1.5.2 1.6.0 1.7.0 1.8.0 1.9.0 1.9.1 1.9.2 1.9.3 trunk 1.1.0 1.10.0 1.11.0 1.11.1 All 63 releases
← All changes | src/editor/Edit.tsx +168 -45 1.3.01.13.0 View file →
@@ -1,13 +1,22 @@
1 -import { useEffect, useRef } from '@wordpress/element';
1 +import {
2 + useCallback,
3 + useEffect,
4 + useLayoutEffect,
5 + useRef,
6 +} from '@wordpress/element';
7 +import { escapeHTML } from '@wordpress/escape-html';
2 8 import { applyFilters } from '@wordpress/hooks';
9 +import { decodeEntities } from '@wordpress/html-entities';
10 +import { sprintf, __ } from '@wordpress/i18n';
11 +import { colord } from 'colord';
3 12 import Editor from 'react-simple-code-editor';
4 -import { Theme } from 'shiki';
13 +import { useDefaults } from '../hooks/useDefaults';
5 14 import { useTheme } from '../hooks/useTheme';
6 -import { useGlobalStore } from '../state/global';
7 15 import { useLanguageStore } from '../state/language';
8 -import { useThemeStore } from '../state/theme';
9 -import { AttributesPropsAndSetter } from '../types';
16 +import { AttributesPropsAndSetter, Lang } from '../types';
17 +import { parseJSONArrayWithRanges } from '../util/arrayHelpers';
18 +import { getEditorLanguage } from '../util/languages';
10 19
11 20 export const Edit = ({
12 21 attributes,
13 22 setAttributes,
@@ -17,44 +26,57 @@
17 26 theme,
18 27 code = '',
19 28 bgColor: backgroundColor,
20 29 textColor: color,
30 + disablePadding,
31 + lineNumbersWidth,
32 + lineNumbers,
33 + startingLineNumber,
34 + footerType,
21 35 fontSize,
36 + fontFamily,
22 37 lineHeight,
38 + lineBlurs,
39 + lineHighlights,
40 + enableBlurring,
41 + enableHighlighting,
42 + seeMoreAfterLine,
43 + seeMoreTransition,
44 + enableMaxHeight,
45 + editorHeight,
23 46 } = attributes;
47 +
24 48 const textAreaRef = useRef<HTMLDivElement>(null);
25 - const handleChange = (code: string) => setAttributes({ code });
49 + const handleChange = (code: string) =>
50 + setAttributes({ code: escapeHTML(code) });
26 51 const { previousLanguage } = useLanguageStore();
27 - const { previousSettings } = useGlobalStore();
28 - const { previousTheme, previousFontSize, previousLineHeight } =
29 - useThemeStore();
30 52 const { highlighter, error, loading } = useTheme({
31 53 theme,
32 54 lang: language ?? previousLanguage,
33 55 });
56 + const hasFooter = footerType && footerType !== 'none';
57 + useDefaults({ attributes, setAttributes });
34 58
35 - useEffect(() => {
36 - if (!attributes.copyButton) {
37 - setAttributes({ copyButton: previousSettings.copyButton });
38 - }
39 - }, [previousSettings, attributes, setAttributes]);
59 + const getHighlights = useCallback(() => {
60 + if (!enableHighlighting) return [];
61 + return parseJSONArrayWithRanges(lineHighlights, startingLineNumber).map(
62 + (line: number) => ({
63 + line,
64 + classes: ['cbp-line-highlight'],
65 + }),
66 + );
67 + }, [enableHighlighting, lineHighlights, startingLineNumber]);
68 + const getBlurs = useCallback(() => {
69 + if (!enableBlurring) return [];
70 + return parseJSONArrayWithRanges(lineBlurs, startingLineNumber).map(
71 + (line: number) => ({
72 + line,
73 + classes: ['cbp-no-blur'],
74 + }),
75 + );
76 + }, [enableBlurring, lineBlurs, startingLineNumber]);
40 77
41 78 useEffect(() => {
42 - if (theme || !previousTheme) return;
43 - setAttributes({ theme: previousTheme as Theme });
44 - }, [previousTheme, theme, setAttributes]);
45 -
46 - useEffect(() => {
47 - if (fontSize || !previousFontSize) return;
48 - setAttributes({ fontSize: previousFontSize });
49 - }, [previousFontSize, fontSize, setAttributes]);
50 -
51 - useEffect(() => {
52 - if (lineHeight || !previousLineHeight) return;
53 - setAttributes({ lineHeight: previousLineHeight });
54 - }, [previousLineHeight, lineHeight, setAttributes]);
55 -
56 - useEffect(() => {
57 79 if (!highlighter) return;
58 80 setAttributes({
59 81 bgColor: highlighter.getBackgroundColor(),
60 82 textColor: highlighter.getForegroundColor(),
@@ -62,31 +84,112 @@
62 84 }, [theme, highlighter, setAttributes]);
63 85
64 86 useEffect(() => {
65 87 if (!highlighter) return;
66 - // applyFilters()
67 - setAttributes({
68 - codeHTML: applyFilters(
69 - 'blocks.codeBlockPro.codeHTML',
70 - highlighter.codeToHtml(code, {
71 - lang: language ?? previousLanguage,
72 - }),
73 - attributes,
74 - ) as string,
75 - });
88 + const l = (language ?? previousLanguage) as Lang | 'ansi';
89 + const lang = getEditorLanguage(l);
90 + const c = decodeEntities(code);
91 + const lineOptions = [
92 + ...getHighlights(),
93 + ...getBlurs(),
94 + enableMaxHeight && !Number.isNaN(seeMoreAfterLine)
95 + ? {
96 + line: Number(seeMoreAfterLine),
97 + classes: [
98 + 'cbp-see-more-line',
99 + seeMoreTransition ? 'cbp-see-more-transition' : '',
100 + ],
101 + }
102 + : {},
103 + ];
104 + const rendered =
105 + l === 'ansi'
106 + ? highlighter.ansiToHtml(c, { lineOptions })
107 + : highlighter.codeToHtml(c, { lang, lineOptions });
108 + const codeHTML = applyFilters(
109 + 'blocks.codeBlockPro.codeHTML',
110 + rendered,
111 + attributes,
112 + ) as string;
113 + const lineHighlightColor = colord(color)
114 + .saturate(0.5)
115 + .alpha(0.2)
116 + .toRgbString();
117 + setAttributes({ codeHTML, lineHighlightColor });
76 118 }, [
77 119 highlighter,
120 + seeMoreAfterLine,
121 + seeMoreTransition,
122 + color,
78 123 code,
124 + enableMaxHeight,
79 125 language,
80 126 setAttributes,
81 127 previousLanguage,
82 128 attributes,
129 + lineHighlights,
130 + lineBlurs,
131 + getBlurs,
132 + getHighlights,
83 133 ]);
84 134
135 + useLayoutEffect(() => {
136 + if (!textAreaRef.current) return;
137 + if (!lineNumbers) {
138 + setAttributes({ lineNumbersWidth: undefined });
139 + return;
140 + }
141 + // Get the last line (assumingly the widest)
142 + const lastLine = Array.from(
143 + textAreaRef?.current?.querySelectorAll('.line') ?? [],
144 + )?.at(-1) as HTMLElement;
145 + // Make sure there are no width constraints
146 + lastLine?.classList?.add('cbp-line-number-width-forced');
147 + const lastLineWidth = lastLine?.getBoundingClientRect()?.width ?? 0;
148 + // Add .cbp-line-number-disabled to disable the line number
149 + lastLine?.classList.add('cbp-line-number-disabled');
150 + // Re calculate the width of the last line
151 + const newWidth = lastLine?.getBoundingClientRect()?.width ?? 0;
152 + // Remove the classes
153 + lastLine?.classList.remove('cbp-line-number-disabled');
154 + lastLine?.classList?.remove('cbp-line-number-width-forced');
155 + // Calculate the difference
156 + if (lastLineWidth - newWidth > 0) {
157 + setAttributes({ lineNumbersWidth: lastLineWidth - newWidth - 12 });
158 + }
159 + }, [
160 + lineNumbers,
161 + startingLineNumber,
162 + code,
163 + loading,
164 + error,
165 + textAreaRef,
166 + setAttributes,
167 + fontSize,
168 + fontFamily,
169 + lineHeight,
170 + ]);
171 +
172 + if (!loading && !highlighter) {
173 + return (
174 + <div
175 + className="p-8 px-4 text-left"
176 + style={{ backgroundColor, color }}>
177 + {sprintf(
178 + __(
179 + 'Theme %s not found. Please select a different theme.',
180 + 'code-block-pro',
181 + ),
182 + theme,
183 + )}
184 + </div>
185 + );
186 + }
187 +
85 188 if ((loading && code) || error) {
86 189 return (
87 190 <div
88 - className="p-8 px-6 text-left"
191 + className="p-6 px-4 text-left"
89 192 style={{ backgroundColor, color }}>
90 193 {error?.message ?? ''}
91 194 </div>
92 195 );
@@ -92,13 +195,30 @@
92 195 );
93 196 }
94 197
95 198 return (
96 - <div ref={textAreaRef}>
199 + <div
200 + ref={textAreaRef}
201 + style={{
202 + maxHeight: Number(editorHeight)
203 + ? Number(editorHeight)
204 + : undefined,
205 + overflow: Number(editorHeight) ? 'auto' : undefined,
206 + }}>
97 207 <Editor
98 - value={code}
208 + value={decodeEntities(code)}
99 209 onValueChange={handleChange}
100 - padding={24}
210 + padding={{
211 + top: disablePadding ? 0 : 16,
212 + bottom: disablePadding || hasFooter ? 0 : 16,
213 + left: (() => {
214 + if (!lineNumbers && disablePadding) return 0;
215 + if (!lineNumbers) return 16;
216 + if (disablePadding) return (lineNumbersWidth ?? 0) + 16;
217 + return (lineNumbersWidth ?? 0) + 32;
218 + })(),
219 + right: 0,
220 + }}
101 221 style={{ backgroundColor, color }}
102 222 // eslint-disable-next-line
103 223 onKeyDown={(e: any) =>
104 224 e.key === 'Tab' &&
@@ -106,10 +226,13 @@
106 226 textAreaRef.current?.querySelector('textarea')?.focus()
107 227 }
108 228 highlight={(code: string) =>
109 229 highlighter
110 - ?.codeToHtml(code, {
111 - lang: language ?? previousLanguage,
230 + ?.codeToHtml(decodeEntities(code), {
231 + lang: getEditorLanguage(
232 + language ?? previousLanguage,
233 + ),
234 + lineOptions: [...getHighlights(), ...getBlurs()],
112 235 })
113 236 ?.replace(/<\/?[pre|code][^>]*>/g, '')
114 237 }
115 238 />