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
← All changes | src/front/front.js +182 -9 1.3.01.25.0 View file →
@@ -1,21 +1,194 @@
1 1 import copy from 'copy-to-clipboard';
2 2
3 -addEventListener('DOMContentLoaded', () => {
3 +const containerClass = '.wp-block-kevinbatdorf-code-block-pro';
4 +
5 +const handleCopyButton = () => {
4 6 const buttons = Array.from(
5 - document.querySelectorAll('.code-block-pro-copy-button'),
7 + document.querySelectorAll(
8 + '.code-block-pro-copy-button:not(.cbp-cb-loaded)',
9 + ),
6 10 );
7 11 buttons.forEach((button) => {
12 + button.classList.add('cbp-cb-loaded');
8 13 // Setting it to block here lets users deactivate the plugin safely
9 14 button.style.display = 'block';
10 - button.addEventListener('click', (event) => {
11 - const b = event.target?.closest('span');
12 - copy(b?.dataset?.code ?? '', {
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 ?? '', {
13 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 + },
14 34 });
15 - b.classList.add('copying');
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';
16 166 setTimeout(() => {
17 - b.classList.remove('copying');
18 - }, 1000);
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);
19 178 });
20 179 });
21 -});
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);