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 / front / front.js

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

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