PluginProbe
Code Block Pro – Beautiful Syntax Highlighting / 1.26.0
Code Block Pro – Beautiful Syntax Highlighting v1.26.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
code-block-pro / src / editor / Edit.tsx

Edit.tsx in Code Block Pro – Beautiful Syntax Highlighting 1.26.0, at src/editor/Edit.tsx

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