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