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

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