PluginProbe
Code Block Pro – Beautiful Syntax Highlighting / 1.28.0
Code Block Pro – Beautiful Syntax Highlighting v1.28.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.28.0, at src/editor/Edit.tsx

258 lines 6.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import {
2 useCallback,
3 useEffect,
4 useLayoutEffect,
5 useRef,
6 useState,
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 type { 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 ? 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 ]);
144
145 useLayoutEffect(() => {
146 if (!codeAreaRef.current) return;
147 if (!lineNumbers) {
148 setAttributes({ lineNumbersWidth: undefined });
149 return;
150 }
151
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 });
158
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 ]);
175
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 );
257 };
258