PluginProbe
Code Block Pro – Beautiful Syntax Highlighting / 1.27.2
Code Block Pro – Beautiful Syntax Highlighting v1.27.2
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.27.2, at src/front/front.js

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