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 +220 -236 1.28.01.13.0 View file →
@@ -1,257 +1,241 @@
1 1 import {
2 - useCallback,
3 - useEffect,
4 - useLayoutEffect,
5 - useRef,
6 - useState,
2 + useCallback,
3 + useEffect,
4 + useLayoutEffect,
5 + useRef,
7 6 } from '@wordpress/element';
7 +import { escapeHTML } from '@wordpress/escape-html';
8 8 import { applyFilters } from '@wordpress/hooks';
9 -import { __, sprintf } from '@wordpress/i18n';
9 +import { decodeEntities } from '@wordpress/html-entities';
10 +import { sprintf, __ } from '@wordpress/i18n';
11 +import { colord } from 'colord';
10 12 import Editor from 'react-simple-code-editor';
11 -import { useCanEditHTML } from '../hooks/useCanEditHTML';
12 13 import { useDefaults } from '../hooks/useDefaults';
13 14 import { useTheme } from '../hooks/useTheme';
14 15 import { useLanguageStore } from '../state/language';
15 -import type { AttributesPropsAndSetter, Lang } from '../types';
16 +import { AttributesPropsAndSetter, Lang } from '../types';
16 17 import { parseJSONArrayWithRanges } from '../util/arrayHelpers';
17 -import { decode, encode, escapeShortcodes } from '../util/code';
18 -import { computeLineHighlightColor } from '../util/colors';
19 -import { getTextWidth } from '../util/fonts';
20 18 import { getEditorLanguage } from '../util/languages';
21 -import { MissingPermissionsTip } from './components/misc/MissingPermissions';
22 19
23 20 export const Edit = ({
24 - attributes,
25 - setAttributes,
21 + attributes,
22 + setAttributes,
26 23 }: AttributesPropsAndSetter) => {
27 - const {
28 - language,
29 - theme,
30 - code = '',
31 - bgColor: backgroundColor,
32 - textColor: color,
33 - disablePadding,
34 - lineNumbers,
35 - startingLineNumber,
36 - footerType,
37 - fontSize,
38 - lineBlurs,
39 - lineHighlights,
40 - enableBlurring,
41 - enableHighlighting,
42 - seeMoreAfterLine,
43 - seeMoreTransition,
44 - enableMaxHeight,
45 - editorHeight,
46 - useDecodeURI,
47 - useEscapeShortCodes,
48 - tabSize,
49 - useTabs,
50 - } = attributes;
24 + const {
25 + language,
26 + theme,
27 + code = '',
28 + bgColor: backgroundColor,
29 + textColor: color,
30 + disablePadding,
31 + lineNumbersWidth,
32 + lineNumbers,
33 + startingLineNumber,
34 + footerType,
35 + fontSize,
36 + fontFamily,
37 + lineHeight,
38 + lineBlurs,
39 + lineHighlights,
40 + enableBlurring,
41 + enableHighlighting,
42 + seeMoreAfterLine,
43 + seeMoreTransition,
44 + enableMaxHeight,
45 + editorHeight,
46 + } = attributes;
51 47
52 - const textAreaRef = useRef<HTMLDivElement>(null);
53 - const canEdit = useCanEditHTML();
54 - const [editorLeftPadding, setEditorLeftPadding] = useState(0);
55 - const codeAreaRef = useRef<HTMLDivElement>(null);
56 - const handleChange = (code: string) =>
57 - setAttributes({ code: encode(code, attributes) });
58 - const { previousLanguage } = useLanguageStore();
59 - const { highlighter, error, loading } = useTheme({
60 - theme,
61 - lang: language ?? previousLanguage,
62 - });
63 - const hasFooter = footerType && footerType !== 'none';
64 - useDefaults({ attributes, setAttributes });
48 + const textAreaRef = useRef<HTMLDivElement>(null);
49 + const handleChange = (code: string) =>
50 + setAttributes({ code: escapeHTML(code) });
51 + const { previousLanguage } = useLanguageStore();
52 + const { highlighter, error, loading } = useTheme({
53 + theme,
54 + lang: language ?? previousLanguage,
55 + });
56 + const hasFooter = footerType && footerType !== 'none';
57 + useDefaults({ attributes, setAttributes });
65 58
66 - const getHighlights = useCallback(() => {
67 - if (!enableHighlighting) return [];
68 - return parseJSONArrayWithRanges(lineHighlights, startingLineNumber).map(
69 - (line: number) => ({
70 - line,
71 - classes: ['cbp-line-highlight'],
72 - }),
73 - );
74 - }, [enableHighlighting, lineHighlights, startingLineNumber]);
75 - const getBlurs = useCallback(() => {
76 - if (!enableBlurring) return [];
77 - return parseJSONArrayWithRanges(lineBlurs, startingLineNumber).map(
78 - (line: number) => ({
79 - line,
80 - classes: ['cbp-no-blur'],
81 - }),
82 - );
83 - }, [enableBlurring, lineBlurs, startingLineNumber]);
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]);
84 77
85 - useEffect(() => {
86 - if (!highlighter) return;
87 - setAttributes({
88 - bgColor: highlighter.getBackgroundColor(),
89 - textColor: highlighter.getForegroundColor(),
90 - });
91 - }, [theme, highlighter, setAttributes, canEdit]);
78 + useEffect(() => {
79 + if (!highlighter) return;
80 + setAttributes({
81 + bgColor: highlighter.getBackgroundColor(),
82 + textColor: highlighter.getForegroundColor(),
83 + });
84 + }, [theme, highlighter, setAttributes]);
92 85
93 - useEffect(() => {
94 - if (!highlighter) return;
95 - const l = (language ?? previousLanguage) as Lang | 'ansi';
96 - const lang = getEditorLanguage(l);
97 - const c = decode(code, { useDecodeURI });
98 - const lineOptions = [
99 - ...getHighlights(),
100 - ...getBlurs(),
101 - enableMaxHeight && !Number.isNaN(seeMoreAfterLine)
102 - ? {
103 - line: Number(seeMoreAfterLine),
104 - classes: [
105 - 'cbp-see-more-line',
106 - seeMoreTransition ? 'cbp-see-more-transition' : '',
107 - ],
108 - }
109 - : {},
110 - ];
111 - const rendered =
112 - l === 'ansi'
113 - ? highlighter.ansiToHtml(c, { lineOptions })
114 - : highlighter.codeToHtml(c, { lang, lineOptions });
115 - const codeHTML = applyFilters(
116 - 'blocks.codeBlockPro.codeHTML',
117 - rendered,
118 - attributes,
119 - ) as string;
120 - const lineHighlightColor = computeLineHighlightColor(color, attributes);
121 - setAttributes({
122 - codeHTML: useEscapeShortCodes ? escapeShortcodes(codeHTML) : codeHTML,
123 - lineHighlightColor,
124 - });
125 - }, [
126 - highlighter,
127 - seeMoreAfterLine,
128 - seeMoreTransition,
129 - canEdit,
130 - color,
131 - code,
132 - enableMaxHeight,
133 - language,
134 - setAttributes,
135 - previousLanguage,
136 - attributes,
137 - lineHighlights,
138 - lineBlurs,
139 - getBlurs,
140 - getHighlights,
141 - useDecodeURI,
142 - useEscapeShortCodes,
143 - ]);
86 + useEffect(() => {
87 + if (!highlighter) return;
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 });
118 + }, [
119 + highlighter,
120 + seeMoreAfterLine,
121 + seeMoreTransition,
122 + color,
123 + code,
124 + enableMaxHeight,
125 + language,
126 + setAttributes,
127 + previousLanguage,
128 + attributes,
129 + lineHighlights,
130 + lineBlurs,
131 + getBlurs,
132 + getHighlights,
133 + ]);
144 134
145 - useLayoutEffect(() => {
146 - if (!codeAreaRef.current) return;
147 - if (!lineNumbers) {
148 - setAttributes({ lineNumbersWidth: undefined });
149 - return;
150 - }
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 + ]);
151 171
152 - // Calulate the line numbers width
153 - const codeLines = codeAreaRef?.current?.querySelectorAll('.line');
154 - const highestLineNumber =
155 - Number(startingLineNumber ?? 0) + (codeLines?.length ?? 0);
156 - if (!codeLines?.[0]) return;
157 - setAttributes({ highestLineNumber });
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 + }
158 187
159 - // Used for the editor, which requires px values
160 - const { font } = getComputedStyle(codeLines?.[0]);
161 - setEditorLeftPadding(getTextWidth(String(highestLineNumber), font));
162 - }, [
163 - lineNumbers,
164 - startingLineNumber,
165 - code,
166 - loading,
167 - canEdit,
168 - error,
169 - textAreaRef,
170 - codeAreaRef,
171 - setAttributes,
172 - fontSize,
173 - loading,
174 - ]);
188 + if ((loading && code) || error) {
189 + return (
190 + <div
191 + className="p-6 px-4 text-left"
192 + style={{ backgroundColor, color }}>
193 + {error?.message ?? ''}
194 + </div>
195 + );
196 + }
175 197
176 - if (!loading && !highlighter) {
177 - return (
178 - <div
179 - className="px-4 text-left flex items-center"
180 - style={{ backgroundColor, color, minHeight: 36 }}
181 - >
182 - {sprintf(
183 - __(
184 - 'Theme %s not found. Please select a different theme.',
185 - 'code-block-pro',
186 - ),
187 - theme,
188 - )}
189 - </div>
190 - );
191 - }
192 -
193 - if ((loading && code) || error) {
194 - return (
195 - <div
196 - className="px-4 text-left flex items-center"
197 - style={{ backgroundColor, color, minHeight: 36 }}
198 - >
199 - {error?.message ?? ''}
200 - </div>
201 - );
202 - }
203 -
204 - if (canEdit === undefined) return null;
205 -
206 - return (
207 - <div
208 - ref={codeAreaRef}
209 - style={{
210 - maxHeight: Number(editorHeight) ? Number(editorHeight) : undefined,
211 - overflow: Number(editorHeight) ? 'auto' : undefined,
212 - }}
213 - >
214 - {canEdit ? null : (
215 - <div className="absolute inset-0 z-10">
216 - <MissingPermissionsTip />
217 - </div>
218 - )}
219 - <Editor
220 - value={decode(code, { useDecodeURI })}
221 - onValueChange={handleChange}
222 - autoFocus={!code}
223 - tabSize={useTabs ? 1 : tabSize || 2}
224 - insertSpaces={!useTabs}
225 - padding={{
226 - top: disablePadding ? 0 : 16,
227 - bottom: disablePadding || hasFooter ? 0 : 16,
228 - left: (() => {
229 - if (!lineNumbers && disablePadding) return 0;
230 - if (!lineNumbers) return 16;
231 - if (disablePadding) return (editorLeftPadding ?? 0) + 16;
232 - return (editorLeftPadding ?? 0) + 32;
233 - })(),
234 - right: 0,
235 - }}
236 - style={{
237 - backgroundColor,
238 - color,
239 - minHeight: canEdit ? undefined : 200,
240 - }}
241 - onKeyDown={(e: any) =>
242 - e.key === 'Tab' &&
243 - // Tab lock here. Pressing Escape will unlock.
244 - codeAreaRef.current?.querySelector('textarea')?.focus()
245 - }
246 - highlight={(code: string) =>
247 - highlighter
248 - ?.codeToHtml(decode(code, { useDecodeURI }), {
249 - lang: getEditorLanguage(language ?? previousLanguage),
250 - lineOptions: [...getHighlights(), ...getBlurs()],
251 - })
252 - ?.replace(/<\/?[pre|code][^>]*>/g, '')
253 - }
254 - />
255 - </div>
256 - );
198 + return (
199 + <div
200 + ref={textAreaRef}
201 + style={{
202 + maxHeight: Number(editorHeight)
203 + ? Number(editorHeight)
204 + : undefined,
205 + overflow: Number(editorHeight) ? 'auto' : undefined,
206 + }}>
207 + <Editor
208 + value={decodeEntities(code)}
209 + onValueChange={handleChange}
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 + }}
221 + style={{ backgroundColor, color }}
222 + // eslint-disable-next-line
223 + onKeyDown={(e: any) =>
224 + e.key === 'Tab' &&
225 + // Tab lock here. Pressing Escape will unlock.
226 + textAreaRef.current?.querySelector('textarea')?.focus()
227 + }
228 + highlight={(code: string) =>
229 + highlighter
230 + ?.codeToHtml(decodeEntities(code), {
231 + lang: getEditorLanguage(
232 + language ?? previousLanguage,
233 + ),
234 + lineOptions: [...getHighlights(), ...getBlurs()],
235 + })
236 + ?.replace(/<\/?[pre|code][^>]*>/g, '')
237 + }
238 + />
239 + </div>
240 + );
257 241 };