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

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

264 lines 8.6 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, 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';
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 useEscapeShortCodes,
48 tabSize,
49 useTabs,
50 } = attributes;
51
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 });
65
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]);
84
85 useEffect(() => {
86 if (!highlighter) return;
87 setAttributes({
88 bgColor: highlighter.getBackgroundColor(),
89 textColor: highlighter.getForegroundColor(),
90 });
91 }, [theme, highlighter, setAttributes, canEdit]);
92
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
123 ? escapeShortcodes(codeHTML)
124 : codeHTML,
125 lineHighlightColor,
126 });
127 }, [
128 highlighter,
129 seeMoreAfterLine,
130 seeMoreTransition,
131 canEdit,
132 color,
133 code,
134 enableMaxHeight,
135 language,
136 setAttributes,
137 previousLanguage,
138 attributes,
139 lineHighlights,
140 lineBlurs,
141 getBlurs,
142 getHighlights,
143 useDecodeURI,
144 useEscapeShortCodes,
145 ]);
146
147 useLayoutEffect(() => {
148 if (!codeAreaRef.current) return;
149 if (!lineNumbers) {
150 setAttributes({ lineNumbersWidth: undefined });
151 return;
152 }
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));
164 }, [
165 lineNumbers,
166 startingLineNumber,
167 code,
168 loading,
169 canEdit,
170 error,
171 textAreaRef,
172 codeAreaRef,
173 setAttributes,
174 fontSize,
175 loading,
176 ]);
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
194 if ((loading && code) || error) {
195 return (
196 <div
197 className="px-4 text-left flex items-center"
198 style={{ backgroundColor, color, minHeight: 36 }}>
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)
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 )}
220 <Editor
221 value={decode(code, { useDecodeURI })}
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}
227 padding={{
228 top: disablePadding ? 0 : 16,
229 bottom: disablePadding || hasFooter ? 0 : 16,
230 left: (() => {
231 if (!lineNumbers && disablePadding) return 0;
232 if (!lineNumbers) return 16;
233 if (disablePadding)
234 return (editorLeftPadding ?? 0) + 16;
235 return (editorLeftPadding ?? 0) + 32;
236 })(),
237 right: 0,
238 }}
239 style={{
240 backgroundColor,
241 color,
242 minHeight: canEdit ? undefined : 200,
243 }}
244 // eslint-disable-next-line
245 onKeyDown={(e: any) =>
246 e.key === 'Tab' &&
247 // Tab lock here. Pressing Escape will unlock.
248 codeAreaRef.current?.querySelector('textarea')?.focus()
249 }
250 highlight={(code: string) =>
251 highlighter
252 ?.codeToHtml(decode(code, { useDecodeURI }), {
253 lang: getEditorLanguage(
254 language ?? previousLanguage,
255 ),
256 lineOptions: [...getHighlights(), ...getBlurs()],
257 })
258 ?.replace(/<\/?[pre|code][^>]*>/g, '')
259 }
260 />
261 </div>
262 );
263 };
264