PluginProbe
Code Block Pro – Beautiful Syntax Highlighting / 1.13.0
Code Block Pro – Beautiful Syntax Highlighting v1.13.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 +51 -117 1.27.51.13.0 View file →
@@ -1,19 +1,8 @@
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 6 const buttons = Array.from(
18 7 document.querySelectorAll(
19 8 '.code-block-pro-copy-button:not(.cbp-cb-loaded)',
@@ -22,89 +11,54 @@
22 11 buttons.forEach((button) => {
23 12 button.classList.add('cbp-cb-loaded');
24 13 // Setting it to block here lets users deactivate the plugin safely
25 14 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(
37 - window.cbpCopyOverride?.(code, button) ?? code,
38 - );
39 - copy(content ?? '', {
15 + button.addEventListener('click', (event) => {
16 + const b = event.target?.closest('span');
17 + copy(b?.dataset?.code ?? '', {
40 18 format: 'text/plain',
41 - onCopy: (code) => {
42 - window.cbpCopyCallback?.(code, button);
43 - b.classList.add('cbp-copying');
44 - // Check if there is a data-text-copied attribute
45 - const hasTextCopied = b.dataset.copiedText;
46 - const innerSpan = b.querySelector('span');
47 - if (hasTextCopied) innerSpan.innerText = hasTextCopied;
48 - setTimeout(() => {
49 - b.classList.remove('cbp-copying');
50 - if (hasTextCopied) {
51 - innerSpan.innerText = b.getAttribute('aria-label');
52 - }
53 - }, 2_000);
54 - },
55 19 });
56 - };
57 - ['click', 'keydown'].forEach((evt) =>
58 - button.addEventListener(evt, handler),
59 - );
20 + b.classList.add('copying');
21 + setTimeout(() => {
22 + b.classList.remove('copying');
23 + }, 2000);
24 + });
60 25 });
61 26 };
62 27
63 28 const handleHighlighter = () => {
64 29 const codeBlocks = Array.from(
65 - document.querySelectorAll(`${containerClass}:not(.cbp-hl-loaded)`),
30 + document.querySelectorAll(`${containerClass} pre:not(.cbp-hl-loaded)`),
66 31 );
67 32
68 33 codeBlocks.forEach((codeBlock) => {
69 34 codeBlock.classList.add('cbp-hl-loaded');
70 35 // Search for highlights
71 - const highlighters = new Set(
72 - codeBlock.querySelectorAll('.cbp-line-highlight'),
73 - );
74 - // If the codeblock has .cbp-highlight-hover, then get all lines
75 - if (codeBlock.classList.contains('cbp-highlight-hover')) {
76 - codeBlock
77 - .querySelectorAll('span.line')
78 - .forEach((line) => highlighters.add(line));
79 - }
36 + const highlighters = codeBlock.querySelectorAll('.cbp-line-highlight');
37 + if (!highlighters.length) return;
80 38
81 - if (!highlighters.size) return;
39 + // We need to track the block width so we can adjust the
40 + // highlighter width in case of overflow
41 + const resizeObserver = new ResizeObserver((entries) => {
42 + entries.forEach((entry) => {
43 + // get width of inner code block
44 + codeBlock.style.setProperty('--cbp-block-width', '0');
45 + const codeWidth =
46 + entry.target.querySelector('code').offsetWidth;
47 + const computed =
48 + codeWidth + 16 === entry.target.scrollWidth
49 + ? codeWidth + 16
50 + : entry.target.scrollWidth + 16;
51 + codeBlock.style.setProperty(
52 + '--cbp-block-width',
53 + `${computed}px`,
54 + );
55 + });
56 + });
57 + resizeObserver.observe(codeBlock);
82 58
83 - // If the code block expands, we need to recalculate the width
84 - new ResizeObserver(() => {
85 - // find the longest line
86 - const lines = codeBlock.querySelectorAll('span.line');
87 - codeBlock.style.setProperty('--cbp-block-width', 'unset');
88 - const longestLine = Array.from(lines).reduce((a, b) =>
89 - a.offsetWidth > b.offsetWidth ? a : b,
90 - );
91 - const highestLineHeight = Array.from(lines).reduce((a, b) =>
92 - a.offsetHeight > b.offsetHeight ? a : b,
93 - );
94 - codeBlock.style.setProperty(
95 - '--cbp-block-height',
96 - highestLineHeight.offsetHeight + 'px',
97 - );
98 - codeBlock.style.setProperty(
99 - '--cbp-block-width',
100 - longestLine.offsetWidth + 'px',
101 - );
102 - }).observe(codeBlock);
103 -
104 - // Add the highlighter if not already there
59 + // add the highlighter
105 60 highlighters.forEach((highlighter) => {
106 - if (highlighter.querySelector('.cbp-line-highlighter')) return;
107 61 highlighter.insertAdjacentHTML(
108 62 'beforeend',
109 63 '<span aria-hidden="true" class="cbp-line-highlighter"></span>',
110 64 );
@@ -123,14 +77,13 @@
123 77 const fontsToLoad = new Set(
124 78 elements.map((f) => f.dataset.codeBlockProFontFamily).filter(Boolean),
125 79 );
126 80 [...fontsToLoad].forEach(async (fontName) => {
127 - const [name, ext] = fontName.split('.');
128 - const url = `url(${window.codeBlockPro.pluginUrl}/build/fonts/${name}.${
129 - ext || 'woff2'
130 - })`;
131 - const font = new FontFace(name, url);
132 - await font.load().catch((e) => console.error(e));
81 + const font = new FontFace(
82 + fontName,
83 + `url(${window.codeBlockPro.pluginUrl}/build/fonts/${fontName}.woff2)`,
84 + );
85 + await font.load();
133 86 document.fonts.add(font);
134 87 });
135 88 };
136 89
@@ -145,11 +98,10 @@
145 98 currentContainer.classList.add('cbp-see-more-loaded');
146 99 const pre = line.closest('pre');
147 100 const initialHeight = pre.offsetHeight;
148 101 let animationSpeed = 0;
149 - const transition = line.classList.contains('cbp-see-more-transition');
150 102
151 - if (transition) {
103 + if (line.classList.contains('cbp-see-more-transition')) {
152 104 const lineCount = pre.querySelectorAll('code > *').length;
153 105 const linesBeforeCurrent = Array.from(
154 106 line.closest('code').children,
155 107 ).filter((l) => l.offsetTop < line.offsetTop)?.length;
@@ -173,48 +125,30 @@
173 125 const button = buttonContainer.querySelector(
174 126 '.cbp-see-more-simple-btn',
175 127 );
176 128 if (!button) return;
177 - // Starts off collapsed
178 - button.setAttribute('aria-expanded', 'false');
179 - pre.id = `cbp-see-more-${Math.random().toString(36).slice(2)}`;
180 - button.setAttribute('aria-controls', pre.id);
181 129 if (currentContainer.classList.contains('padding-disabled')) {
182 130 button.classList.remove('cbp-see-more-simple-btn-hover');
183 131 }
132 + button.style.transition = `all ${
133 + Math.max(animationSpeed, 1) / 1.5
134 + }s linear`;
184 135
185 136 const handle = (event) => {
186 137 event.preventDefault();
187 - // disable scrolling
188 - pre.style.setProperty('overflow', 'hidden', 'important');
138 + button.classList.remove('cbp-see-more-simple-btn-hover');
139 + pre.style.maxHeight = initialHeight + 'px';
189 140 setTimeout(() => {
190 - pre.style.overflow = 'auto';
141 + button.style.opacity = 0;
142 + button.style.transform = 'translateY(-100%)';
143 + setTimeout(
144 + () => button.remove(),
145 + Math.max(animationSpeed, 1) * 1000,
146 + );
191 147 }, animationSpeed * 1000);
192 -
193 - pre.style.maxHeight = `${initialHeight}px`;
194 - // If there is data-see-more-collapse-string then we toggle
195 - if (!buttonContainer.dataset?.seeMoreCollapseString) {
196 - buttonContainer.remove();
197 - return;
198 - }
199 - // We're letting them collapse it
200 - if (button.getAttribute('aria-expanded') === 'true') {
201 - button.setAttribute('aria-expanded', 'false');
202 - button.innerText = buttonContainer.dataset.seeMoreString;
203 - pre.style.maxHeight = `${line.offsetTop + lineHeight - headerHeight}px`;
204 -
205 - // Move the scroll so the button is in center view
206 - line.scrollIntoView({
207 - behavior: transition ? 'smooth' : 'auto',
208 - block: 'center',
209 - });
210 - return;
211 - }
212 - button.setAttribute('aria-expanded', 'true');
213 - button.innerText = buttonContainer.dataset.seeMoreCollapseString;
214 148 };
215 149 button.addEventListener('click', handle);
216 - button.addEventListener('keydown', (event) => {
150 + button.addEventListener('keypress', (event) => {
217 151 if (event.key === 'Enter') handle(event);
218 152 });
219 153 });
220 154 };
@@ -219,15 +153,15 @@
219 153 });
220 154 };
221 155
222 156 const init = () => {
223 - handleFontLoading();
224 157 handleSeeMore();
225 158 handleCopyButton();
226 159 handleHighlighter();
160 + handleFontLoading();
227 161 };
228 162
229 -// Functions are idempotent, so we can run them on load, DOMContentLoaded, et al.
163 +// Functions are idempotent, so we can run them on load and DOMContentLoaded
230 164 init();
231 165 // Useful for when the DOM is modified or loaded in late
232 166 window.codeBlockProInit = init;
233 167 window.addEventListener('DOMContentLoaded', init);