PluginProbe
Code Block Pro – Beautiful Syntax Highlighting / 1.26.7
Code Block Pro – Beautiful Syntax Highlighting v1.26.7
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 +170 -53 1.9.31.26.7 View file →
@@ -1,60 +1,96 @@
1 1 import copy from 'copy-to-clipboard';
2 2
3 -// Handle copy button
4 -addEventListener('DOMContentLoaded', () => {
3 +const containerClass = '.wp-block-kevinbatdorf-code-block-pro';
4 +
5 +const handleCopyButton = () => {
5 6 const buttons = Array.from(
6 - document.querySelectorAll('.code-block-pro-copy-button'),
7 + document.querySelectorAll(
8 + '.code-block-pro-copy-button:not(.cbp-cb-loaded)',
9 + ),
7 10 );
8 11 buttons.forEach((button) => {
12 + button.classList.add('cbp-cb-loaded');
9 13 // Setting it to block here lets users deactivate the plugin safely
10 14 button.style.display = 'block';
11 - button.addEventListener('click', (event) => {
12 - const b = event.target?.closest('span');
13 - 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[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 ?? '', {
14 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 + },
15 41 });
16 - b.classList.add('copying');
17 - setTimeout(() => {
18 - b.classList.remove('copying');
19 - }, 2000);
20 - });
42 + };
43 + ['click', 'keydown'].forEach((evt) =>
44 + button.addEventListener(evt, handler),
45 + );
21 46 });
22 -});
47 +};
23 48
24 -// Handle highlighter
25 -addEventListener('DOMContentLoaded', () => {
49 +const handleHighlighter = () => {
26 50 const codeBlocks = Array.from(
27 - document.querySelectorAll('.wp-block-kevinbatdorf-code-block-pro pre'),
51 + document.querySelectorAll(`${containerClass}:not(.cbp-hl-loaded)`),
28 52 );
29 53
30 54 codeBlocks.forEach((codeBlock) => {
31 - // search for highlights
32 - const highlighters = codeBlock.querySelectorAll('.cbp-line-highlight');
33 - if (!highlighters.length) return;
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 + }
34 66
35 - // We need to track the block width so we can adjust the
36 - // highlighter width in case of overflow
37 - const resizeObserver = new ResizeObserver((entries) => {
38 - entries.forEach((entry) => {
39 - // get width of inner code block
40 - codeBlock.style.setProperty('--cbp-block-width', '0');
41 - const codeWidth =
42 - entry.target.querySelector('code').offsetWidth;
43 - const computed =
44 - codeWidth + 16 === entry.target.scrollWidth
45 - ? codeWidth + 16
46 - : entry.target.scrollWidth + 16;
47 - codeBlock.style.setProperty(
48 - '--cbp-block-width',
49 - `${computed}px`,
50 - );
51 - });
52 - });
53 - resizeObserver.observe(codeBlock);
67 + if (!highlighters.size) return;
54 68
55 - // add the highlighter
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
56 91 highlighters.forEach((highlighter) => {
92 + if (highlighter.querySelector('.cbp-line-highlighter')) return;
57 93 highlighter.insertAdjacentHTML(
58 94 'beforeend',
59 95 '<span aria-hidden="true" class="cbp-line-highlighter"></span>',
60 96 );
@@ -59,26 +95,107 @@
59 95 '<span aria-hidden="true" class="cbp-line-highlighter"></span>',
60 96 );
61 97 });
62 98 });
63 -});
99 +};
64 100
65 -// Handle font loading
66 -addEventListener('DOMContentLoaded', () => {
101 +const handleFontLoading = () => {
67 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'));
68 109 const fontsToLoad = new Set(
69 - Array.from(
70 - document.querySelectorAll('[data-code-block-pro-font-family]') ??
71 - [],
72 - )
73 - .map((f) => f.dataset.codeBlockProFontFamily)
74 - .filter(Boolean),
110 + elements.map((f) => f.dataset.codeBlockProFontFamily).filter(Boolean),
75 111 );
76 112 [...fontsToLoad].forEach(async (fontName) => {
77 - const font = new FontFace(
78 - fontName,
79 - `url(${window.codeBlockPro.pluginUrl}/build/fonts/${fontName}.woff2)`,
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',
80 160 );
81 - await font.load();
82 - document.fonts.add(font);
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 + });
83 186 });
84 -});
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);