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

221 lines 8.5 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 const transition = line.classList.contains('cbp-see-more-transition');
136
137 if (transition) {
138 const lineCount = pre.querySelectorAll('code > *').length;
139 const linesBeforeCurrent = Array.from(
140 line.closest('code').children,
141 ).filter((l) => l.offsetTop < line.offsetTop)?.length;
142 animationSpeed = 0.5 + (lineCount - linesBeforeCurrent) * 0.01;
143 pre.style.transition = `max-height ${animationSpeed}s ease-out`;
144 }
145
146 // if the first child it a span then get the height of that span
147 const headerHeight =
148 currentContainer.children[0].tagName === 'SPAN'
149 ? currentContainer.children[0].offsetHeight
150 : 0;
151 const lineHeight = parseFloat(window.getComputedStyle(line).lineHeight);
152 pre.style.maxHeight = `${line.offsetTop + lineHeight - headerHeight}px`;
153
154 const buttonContainer = line
155 .closest(containerClass)
156 .querySelector('.cbp-see-more-container');
157 if (!buttonContainer) return;
158 buttonContainer.style.display = 'flex';
159 const button = buttonContainer.querySelector(
160 '.cbp-see-more-simple-btn',
161 );
162 if (!button) return;
163 // Starts off collapsed
164 button.setAttribute('aria-expanded', 'false');
165 pre.id = `cbp-see-more-${Math.random().toString(36).slice(2)}`;
166 button.setAttribute('aria-controls', pre.id);
167 if (currentContainer.classList.contains('padding-disabled')) {
168 button.classList.remove('cbp-see-more-simple-btn-hover');
169 }
170
171 const handle = (event) => {
172 event.preventDefault();
173 // disable scrolling
174 pre.style.setProperty('overflow', 'hidden', 'important');
175 setTimeout(() => {
176 pre.style.overflow = 'auto';
177 }, animationSpeed * 1000);
178
179 pre.style.maxHeight = `${initialHeight}px`;
180 // If there is data-see-more-collapse-string then we toggle
181 if (!buttonContainer.dataset?.seeMoreCollapseString) {
182 buttonContainer.remove();
183 return;
184 }
185 // We're letting them collapse it
186 if (button.getAttribute('aria-expanded') === 'true') {
187 button.setAttribute('aria-expanded', 'false');
188 button.innerText = buttonContainer.dataset.seeMoreString;
189 pre.style.maxHeight = `${line.offsetTop + lineHeight - headerHeight}px`;
190
191 // Move the scroll so the button is in center view
192 line.scrollIntoView({
193 behavior: transition ? 'smooth' : 'auto',
194 block: 'center',
195 });
196 return;
197 }
198 button.setAttribute('aria-expanded', 'true');
199 button.innerText = buttonContainer.dataset.seeMoreCollapseString;
200 };
201 button.addEventListener('click', handle);
202 button.addEventListener('keydown', (event) => {
203 if (event.key === 'Enter') handle(event);
204 });
205 });
206 };
207
208 const init = () => {
209 handleFontLoading();
210 handleSeeMore();
211 handleCopyButton();
212 handleHighlighter();
213 };
214
215 // Functions are idempotent, so we can run them on load, DOMContentLoaded, et al.
216 init();
217 // Useful for when the DOM is modified or loaded in late
218 window.codeBlockProInit = init;
219 window.addEventListener('DOMContentLoaded', init);
220 window.addEventListener('load', init);
221