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

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