PluginProbe
Code Block Pro – Beautiful Syntax Highlighting / 1.25.0
Code Block Pro – Beautiful Syntax Highlighting v1.25.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 / front / front.js

front.js in Code Block Pro – Beautiful Syntax Highlighting 1.25.0, at src/front/front.js

195 lines 7.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import copy from 'copy-to-clipboard';
2
3 const containerClass = '.wp-block-kevinbatdorf-code-block-pro';
4
5 const handleCopyButton = () => {
6 const buttons = Array.from(
7 document.querySelectorAll(
8 '.code-block-pro-copy-button:not(.cbp-cb-loaded)',
9 ),
10 );
11 buttons.forEach((button) => {
12 button.classList.add('cbp-cb-loaded');
13 // Setting it to block here lets users deactivate the plugin safely
14 button.style.display = 'block';
15 const handler = (event) => {
16 const { type, key, target } = event;
17 // if keydown event, make sure it's enter or space
18 if (type === 'keydown' && !['Enter', ' '].includes(key)) return;
19 event.preventDefault();
20 const b = target?.closest('span');
21 const code = b?.dataset?.encoded
22 ? decodeURIComponent(decodeURIComponent(b?.dataset?.code))
23 : b?.dataset?.code;
24 const content = window.cbpCopyOverride?.(code, button) ?? code;
25 copy(content ?? '', {
26 format: 'text/plain',
27 onCopy: (code) => {
28 window.cbpCopyCallback?.(code, button);
29 b.classList.add('cbp-copying');
30 setTimeout(() => {
31 b.classList.remove('cbp-copying');
32 }, 2_000);
33 },
34 });
35 };
36 ['click', 'keydown'].forEach((evt) =>
37 button.addEventListener(evt, handler),
38 );
39 });
40 };
41
42 const handleHighlighter = () => {
43 const codeBlocks = Array.from(
44 document.querySelectorAll(`${containerClass}:not(.cbp-hl-loaded)`),
45 );
46
47 codeBlocks.forEach((codeBlock) => {
48 codeBlock.classList.add('cbp-hl-loaded');
49 // Search for highlights
50 const highlighters = new Set(
51 codeBlock.querySelectorAll('.cbp-line-highlight'),
52 );
53 // If the codeblock has .cbp-highlight-hover, then get all lines
54 if (codeBlock.classList.contains('cbp-highlight-hover')) {
55 codeBlock
56 .querySelectorAll('span.line')
57 .forEach((line) => highlighters.add(line));
58 }
59
60 if (!highlighters.size) return;
61
62 // If the code block expands, we need to recalculate the width
63 new ResizeObserver(() => {
64 // find the longest line
65 const lines = codeBlock.querySelectorAll('span.line');
66 codeBlock.style.setProperty('--cbp-block-width', 'unset');
67 const longestLine = Array.from(lines).reduce((a, b) =>
68 a.offsetWidth > b.offsetWidth ? a : b,
69 );
70 const highestLineHeight = Array.from(lines).reduce((a, b) =>
71 a.offsetHeight > b.offsetHeight ? a : b,
72 );
73 codeBlock.style.setProperty(
74 '--cbp-block-height',
75 highestLineHeight.offsetHeight + 'px',
76 );
77 codeBlock.style.setProperty(
78 '--cbp-block-width',
79 longestLine.offsetWidth + 'px',
80 );
81 }).observe(codeBlock);
82
83 // Add the highlighter if not already there
84 highlighters.forEach((highlighter) => {
85 if (highlighter.querySelector('.cbp-line-highlighter')) return;
86 highlighter.insertAdjacentHTML(
87 'beforeend',
88 '<span aria-hidden="true" class="cbp-line-highlighter"></span>',
89 );
90 });
91 });
92 };
93
94 const handleFontLoading = () => {
95 if (!window.codeBlockPro?.pluginUrl) return;
96 const elements = Array.from(
97 document.querySelectorAll(
98 '[data-code-block-pro-font-family]:not(.cbp-ff-loaded)',
99 ) || [],
100 );
101 elements.forEach((e) => e.classList.add('cbp-ff-loaded'));
102 const fontsToLoad = new Set(
103 elements.map((f) => f.dataset.codeBlockProFontFamily).filter(Boolean),
104 );
105 [...fontsToLoad].forEach(async (fontName) => {
106 const [name, ext] = fontName.split('.');
107 const url = `url(${window.codeBlockPro.pluginUrl}/build/fonts/${name}.${
108 ext || 'woff2'
109 })`;
110 const font = new FontFace(name, url);
111 await font.load().catch((e) => console.error(e));
112 document.fonts.add(font);
113 });
114 };
115
116 const handleSeeMore = () => {
117 const seeMoreLines = Array.from(
118 document.querySelectorAll(
119 `${containerClass}:not(.cbp-see-more-loaded) .cbp-see-more-line`,
120 ),
121 );
122 seeMoreLines.forEach((line) => {
123 const currentContainer = line.closest(containerClass);
124 currentContainer.classList.add('cbp-see-more-loaded');
125 const pre = line.closest('pre');
126 const initialHeight = pre.offsetHeight;
127 let animationSpeed = 0;
128
129 if (line.classList.contains('cbp-see-more-transition')) {
130 const lineCount = pre.querySelectorAll('code > *').length;
131 const linesBeforeCurrent = Array.from(
132 line.closest('code').children,
133 ).filter((l) => l.offsetTop < line.offsetTop)?.length;
134 animationSpeed = 0.5 + (lineCount - linesBeforeCurrent) * 0.01;
135 pre.style.transition = `max-height ${animationSpeed}s ease-out`;
136 }
137
138 // if the first child it a span then get the height of that span
139 const headerHeight =
140 currentContainer.children[0].tagName === 'SPAN'
141 ? currentContainer.children[0].offsetHeight
142 : 0;
143 const lineHeight = parseFloat(window.getComputedStyle(line).lineHeight);
144 pre.style.maxHeight = `${line.offsetTop + lineHeight - headerHeight}px`;
145
146 const buttonContainer = line
147 .closest(containerClass)
148 .querySelector('.cbp-see-more-container');
149 if (!buttonContainer) return;
150 buttonContainer.style.display = 'flex';
151 const button = buttonContainer.querySelector(
152 '.cbp-see-more-simple-btn',
153 );
154 if (!button) return;
155 if (currentContainer.classList.contains('padding-disabled')) {
156 button.classList.remove('cbp-see-more-simple-btn-hover');
157 }
158 button.style.transition = `all ${
159 Math.max(animationSpeed, 1) / 1.5
160 }s linear`;
161
162 const handle = (event) => {
163 event.preventDefault();
164 button.classList.remove('cbp-see-more-simple-btn-hover');
165 pre.style.maxHeight = initialHeight + 'px';
166 setTimeout(() => {
167 button.style.opacity = 0;
168 button.style.transform = 'translateY(-100%)';
169 setTimeout(
170 () => button.remove(),
171 Math.max(animationSpeed, 1) * 1000,
172 );
173 }, animationSpeed * 1000);
174 };
175 button.addEventListener('click', handle);
176 button.addEventListener('keydown', (event) => {
177 if (event.key === 'Enter') handle(event);
178 });
179 });
180 };
181
182 const init = () => {
183 handleFontLoading();
184 handleSeeMore();
185 handleCopyButton();
186 handleHighlighter();
187 };
188
189 // Functions are idempotent, so we can run them on load, DOMContentLoaded, et al.
190 init();
191 // Useful for when the DOM is modified or loaded in late
192 window.codeBlockProInit = init;
193 window.addEventListener('DOMContentLoaded', init);
194 window.addEventListener('load', init);
195