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