PageSourceSearch

https://bigmouthblog.tw/wp-content/plugins/achang-pencil-box/publi…?ver=1.9.4.1787561299.1.3.2

js bigmouthblog.tw collected 2026-10-05 10:47:49 UTC 14,600 bytes, 466 lines download raw bytes

1(function( $ ) {
2	'use strict';
3
4	$(document).ready(function() {
5
6		function initCodeHighlighting() {
7			if (typeof Prism !== 'undefined') {
8				setTimeout(function() {
9					Prism.highlightAll();
10				}, 100);
11			} else {
12				// 基本語法高亮備用方案
13				applyBasicHighlighting();
14			}
15		}
16
17		function applyBasicHighlighting() {
18			$('.wp-block-achang-pencil-box-code-box code').each(function() {
19				var $code = $(this);
20				var text = $code.text();
21				var className = $code.attr('class') || '';
22				var languageMatch = className.match(/language-(\w+)/);
23				var language = languageMatch ? languageMatch[1] : 'plaintext';
24				
25				if (language !== 'plaintext') {
26					var highlightedText = basicSyntaxHighlight(text, language);
27					$code.html(highlightedText);
28				}
29			});
30		}
31
32		function basicSyntaxHighlight(text, language) {
33			// 基本的語法高亮函數
34			var highlighted = text;
35			
36			// JavaScript/TypeScript 高亮
37			if (language === 'javascript' || language === 'typescript' || language === 'js' || language === 'ts') {
38				highlighted = highlighted
39					.replace(/\b(function|var|let|const|if|else|for|while|return|class|extends|import|export|from|default|async|await|try|catch|finally|throw|new|this|super|static|public|private|protected)\b/g, '<span class="apb-keyword">$1</span>')
40					.replace(/(["'`])((?:\\.|(?!\1)[^\\])*?)\1/g, '<span class="apb-string">$1$2$1</span>')
41					.replace(/\/\/.*$/gm, '<span class="apb-comment">$&</span>')
42					.replace(/\/\*[\s\S]*?\*\//g, '<span class="apb-comment">$&</span>')
43					.replace(/\b(\d+\.?\d*)\b/g, '<span class="apb-number">$1</span>');
44			}
45			// PHP 高亮
46			else if (language === 'php') {
47				highlighted = highlighted
48					.replace(/\b(function|class|extends|implements|public|private|protected|static|final|abstract|interface|trait|namespace|use|as|if|else|elseif|endif|for|foreach|endfor|while|endwhile|do|switch|case|default|break|continue|return|try|catch|finally|throw|new|clone|instanceof|echo|print|var|const|global|static|unset|isset|empty|array|object|string|int|float|bool|null|true|false)\b/g, '<span class="apb-keyword">$1</span>')
49					.replace(/(["'`])((?:\\.|(?!\1)[^\\])*?)\1/g, '<span class="apb-string">$1$2$1</span>')
50					.replace(/\/\/.*$/gm, '<span class="apb-comment">$&</span>')
51					.replace(/\/\*[\s\S]*?\*\//g, '<span class="apb-comment">$&</span>')
52					.replace(/#.*$/gm, '<span class="apb-comment">$&</span>')
53					.replace(/\$\w+/g, '<span class="apb-variable">$&</span>')
54					.replace(/\b(\d+\.?\d*)\b/g, '<span class="apb-number">$1</span>');
55			}
56			// CSS 高亮
57			else if (language === 'css' || language === 'scss') {
58				highlighted = highlighted
59					.replace(/([.#]?[\w-]+)\s*\{/g, '<span class="apb-selector">$1</span>{')
60					.replace(/([\w-]+)\s*:/g, '<span class="apb-property">$1</span>:')
61					.replace(/:\s*([^;}\n]+)/g, ': <span class="apb-value">$1</span>')
62					.replace(/\/\*[\s\S]*?\*\//g, '<span class="apb-comment">$&</span>');
63			}
64			// HTML 高亮
65			else if (language === 'html' || language === 'xml') {
66				highlighted = highlighted
67					.replace(/(&lt;\/?)(\w+)([^&]*?)(&gt;)/g, '$1<span class="apb-tag">$2</span><span class="apb-attribute">$3</span>$4')
68					.replace(/(&lt;!--[\s\S]*?--&gt;)/g, '<span class="apb-comment">$1</span>');
69			}
70			
71			return highlighted;
72		}
73
74		function initCodeBlockCopyFeature() {
75			$(document).on('click', '.apb-copy-button', function(e) {
76				e.preventDefault();
77				var button = $(this);
78				var codeBlock = button.closest('.wp-block-achang-pencil-box-code-box');
79				var codeElement = codeBlock.find('code');
80				var codeText = '';
81				
82				if (codeElement.length > 0) {
83					// 獲取原始文字內容,忽略HTML標籤
84					var tempDiv = $('<div>').html(codeElement.html());
85					tempDiv.find('span').each(function() {
86						$(this).replaceWith($(this).text());
87					});
88					codeText = tempDiv.text();
89				}
90				
91				if (!codeText) {
92					// 備用方案:直接從 pre 元素獲取
93					var preElement = codeBlock.find('pre');
94					codeText = preElement.length > 0 ? preElement.text() : '';
95				}
96				
97				copyToClipboard(codeText, button);
98			});
99		}
100
101		function copyToClipboard(text, button) {
102			// 優先使用現代 Clipboard API,但需要安全條件
103			if (navigator.clipboard && navigator.clipboard.writeText && 
104				(window.isSecureContext || location.protocol === 'https:' || location.hostname === 'localhost')) {
105				navigator.clipboard.writeText(text).then(function() {
106					showCopySuccess(button);
107				}).catch(function(err) {
108					console.log('Clipboard API failed, using fallback:', err);
109					fallbackCopyToClipboard(text, button);
110				});
111			} else {
112				// 使用更強健的回退方案
113				fallbackCopyToClipboard(text, button);
114			}
115		}
116
117		function fallbackCopyToClipboard(text, button) {
118			// 檢測是否為手機或平板設備
119			var isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ||
120						  'ontouchstart' in window || navigator.maxTouchPoints > 0;
121			
122			if (isMobile) {
123				// 手機設備使用不同的策略
124				mobileCopyToClipboard(text, button);
125			} else {
126				// 桌面設備使用傳統方法
127				desktopCopyToClipboard(text, button);
128			}
129		}
130
131		function desktopCopyToClipboard(text, button) {
132			var textArea = document.createElement('textarea');
133			textArea.value = text;
134			textArea.style.position = 'fixed';
135			textArea.style.left = '-999999px';
136			textArea.style.top = '-999999px';
137			textArea.style.opacity = '0';
138			textArea.style.pointerEvents = 'none';
139			document.body.appendChild(textArea);
140			try {
141				textArea.focus();
142				textArea.select();
143				textArea.setSelectionRange(0, text.length);
144				var successful = document.execCommand('copy');
145				if (successful) {
146					showCopySuccess(button);
147				} else {
148					showCopyError(button);
149				}
150			} catch (err) {
151				console.log('Desktop copy failed:', err);
152				showCopyError(button);
153			} finally {
154				document.body.removeChild(textArea);
155			}
156		}
157
158		function mobileCopyToClipboard(text, button) {
159			// 更好的移动端复制策略:使用现代API并提供回退方案
160			if (navigator.clipboard && navigator.clipboard.writeText) {
161				// 现代浏览器支持 Clipboard API
162				navigator.clipboard.writeText(text).then(function() {
163					showCopySuccess(button);
164				}).catch(function(err) {
165					console.log('Modern clipboard API failed on mobile:', err);
166					// 如果现代API失败,使用传统方法
167					legacyMobileCopy(text, button);
168				});
169			} else {
170				// 使用传统方法
171				legacyMobileCopy(text, button);
172			}
173		}
174
175		function legacyMobileCopy(text, button) {
176			// 为移动端优化的传统复制方法
177			var textArea = document.createElement("textarea");
178			textArea.value = text;
179			
180			// 设置样式以确保在移动端正确工作
181			textArea.style.position = "fixed";
182			textArea.style.top = "50%";
183			textArea.style.left = "50%";
184			textArea.style.transform = "translate(-50%, -50%)";
185			textArea.style.width = "1px";
186			textArea.style.height = "1px";
187			textArea.style.padding = "0";
188			textArea.style.border = "none";
189			textArea.style.outline = "none";
190			textArea.style.boxShadow = "none";
191			textArea.style.background = "transparent";
192			textArea.style.fontSize = "12px";
193			textArea.style.opacity = "0";
194			textArea.style.zIndex = "9999";
195			
196			document.body.appendChild(textArea);
197			
198			// 选择并复制文本
199			textArea.focus();
200			textArea.select();
201			
202			try {
203				var successful = document.execCommand('copy');
204				if (successful) {
205					showCopySuccess(button);
206				} else {
207					showCopyError(button);
208				}
209			} catch (err) {
210				console.log('Legacy copy failed on mobile:', err);
211				// 最后的回退方案:显示提示
212				fallbackMobileCopy(text, button);
213			}
214			
215			// 清理
216			document.body.removeChild(textArea);
217		}
218
219		function fallbackMobileCopy(text, button) {
220			// 完全的回退方案:显示文本让用户手动复制
221			var modal = document.createElement('div');
222			modal.style.cssText = `
223				position: fixed;
224				top: 0;
225				left: 0;
226				width: 100%;
227				height: 100%;
228				background: rgba(0,0,0,0.7);
229				display: flex;
230				align-items: center;
231				justify-content: center;
232				z-index: 10000;
233				font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
234			`;
235			
236			var content = document.createElement('div');
237			content.style.cssText = `
238				background: #fff;
239				border-radius: 8px;
240				padding: 20px;
241				margin: 20px;
242				max-width: 90%;
243				max-height: 90%;
244				overflow: auto;
245				box-shadow: 0 4px 20px rgba(0,0,0,0.3);
246			`;
247			
248			var title = document.createElement('h3');
249			title.textContent = '複製程式碼';
250			title.style.cssText = `
251				margin: 0 0 15px 0;
252				color: #333;
253				font-size: 18px;
254			`;
255			
256			var instructions = document.createElement('p');
257			instructions.innerHTML = '請選取以下文字並複製:<br>(長按文字選擇「複製」)';
258			instructions.style.cssText = `
259				margin: 0 0 15px 0;
260				color: #666;
261				font-size: 14px;
262				line-height: 1.4;
263			`;
264			
265			var textArea = document.createElement('textarea');
266			textArea.value = text;
267			textArea.style.cssText = `
268				width: 100%;
269				min-height: 150px;
270				padding: 12px;
271				border: 1px solid #ddd;
272				border-radius: 4px;
273				font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
274				font-size: 13px;
275				line-height: 1.4;
276				resize: vertical;
277				margin-bottom: 15px;
278				background: #f8f9fa;
279			`;
280			
281			var buttonContainer = document.createElement('div');
282			buttonContainer.style.cssText = `
283				display: flex;
284				gap: 10px;
285				justify-content: flex-end;
286			`;
287			
288			var copyBtn = document.createElement('button');
289			copyBtn.textContent = '複製';
290			copyBtn.style.cssText = `
291				padding: 8px 16px;
292				background: #007cba;
293				color: white;
294				border: none;
295				border-radius: 4px;
296				cursor: pointer;
297				font-size: 14px;
298			`;
299			
300			var closeBtn = document.createElement('button');
301			closeBtn.textContent = '關閉';
302			closeBtn.style.cssText = `
303				padding: 8px 16px;
304				background: #f0f0f0;
305				color: #333;
306				border: 1px solid #ddd;
307				border-radius: 4px;
308				cursor: pointer;
309				font-size: 14px;
310			`;
311			
312			copyBtn.addEventListener('click', function() {
313				textArea.select();
314				try {
315					var successful = document.execCommand('copy');
316					if (successful) {
317						copyBtn.textContent = '已複製!';
318						copyBtn.style.background = '#28a745';
319						setTimeout(function() {
320							copyBtn.textContent = '複製';
321							copyBtn.style.background = '#007cba';
322						}, 2000);
323					} else {
324						copyBtn.textContent = '失敗';
325						copyBtn.style.background = '#dc3545';
326						setTimeout(function() {
327							copyBtn.textContent = '複製';
328							copyBtn.style.background = '#007cba';
329						}, 1500);
330					}
331				} catch (err) {
332					console.log('Copy button failed:', err);
333				}
334			});
335			
336			closeBtn.addEventListener('click', function() {
337				document.body.removeChild(modal);
338				showCopySuccess(button);
339			});
340			
341			modal.addEventListener('click', function(e) {
342				if (e.target === modal) {
343					document.body.removeChild(modal);
344					showCopySuccess(button);
345				}
346			});
347			
348			buttonContainer.appendChild(copyBtn);
349			buttonContainer.appendChild(closeBtn);
350			
351			content.appendChild(title);
352			content.appendChild(instructions);
353			content.appendChild(textArea);
354			content.appendChild(buttonContainer);
355			
356			modal.appendChild(content);
357			document.body.appendChild(modal);
358			
359			// 自动选中文本
360			setTimeout(function() {
361				textArea.focus();
362				textArea.select();
363			}, 100);
364		}
365
366		function showCopySuccess(button) {
367			var originalHtml = button.html();
368			button.addClass('apb-copy-success').text('已複製!').prop('disabled', true);
369			setTimeout(function() {
370				button.removeClass('apb-copy-success').html(originalHtml).prop('disabled', false);
371			}, 2000);
372		}
373
374		function showCopyError(button) {
375			var originalHtml = button.html();
376			button.addClass('apb-copy-error').text('失敗').prop('disabled', true);
377			setTimeout(function() {
378				button.removeClass('apb-copy-error').html(originalHtml).prop('disabled', false);
379			}, 1500);
380		}
381
382		function enhanceLineNumbers() {
383			$(".wp-block-achang-pencil-box-code-box").each(function() {
384				var codeBlock = $(this);
385				var lineNumbers = codeBlock.find('.apb-line-numbers');
386				var codeContent = codeBlock.find('.apb-code-box-content');
387				var codeElement = codeContent.find('code');
388
389				if (lineNumbers.length > 0 && codeElement.length > 0) {
390					var codeText = codeElement.text();
391					
392					if (!codeText) {
393						lineNumbers.html('<div class="apb-line-number">1</div>');
394						return;
395					}
396					
397					// 正確計算行數,考慮所有換行情況
398					var lines = codeText.split('\n');
399					var lineCount = lines.length;
400					
401					// 只有當最後一行完全為空且不是唯一行時才減少計數
402					if (lineCount > 1 && lines[lineCount - 1] === '') {
403						lineCount--;
404					}
405
406					var lineNumbersHtml = '';
407					for (var i = 1; i <= Math.max(1, lineCount); i++) {
408						lineNumbersHtml += '<div class="apb-line-number">' + i + '</div>';
409					}
410					lineNumbers.html(lineNumbersHtml);
411
412					// 改善滾動同步
413					var isSyncing = false;
414					var syncTimeout;
415					
416					codeContent.off('scroll.apbSync').on('scroll.apbSync', function() {
417						if (isSyncing) return;
418						isSyncing = true;
419						
420						var scrollTop = $(this).scrollTop();
421						lineNumbers.scrollTop(scrollTop);
422						
423						// 使用 requestAnimationFrame 確保平滑同步
424						if (syncTimeout) clearTimeout(syncTimeout);
425						syncTimeout = setTimeout(function() {
426							isSyncing = false;
427						}, 16); // 約 60fps
428					});
429					
430					// 確保行號區域不會獨立滾動
431					lineNumbers.off('scroll.apbPrevent').on('scroll.apbPrevent', function(e) {
432						if (!isSyncing) {
433							$(this).scrollTop(codeContent.scrollTop());
434						}
435					});
436				}
437			});
438		}
439
440		function initAll() {
441			initCodeHighlighting();
442			initCodeBlockCopyFeature();
443			enhanceLineNumbers();
444		}
445
446		initAll();
447
448		var observer = new MutationObserver(function(mutations) {
449			mutations.forEach(function(mutation) {
450				if (mutation.addedNodes && mutation.addedNodes.length > 0) {
451					$(mutation.addedNodes).each(function() {
452						var node = $(this);
453						if (node.is('.wp-block-achang-pencil-box-code-box') || node.find('.wp-block-achang-pencil-box-code-box').length > 0) {
454							initAll();
455						}
456					});
457				}
458			});
459		});
460
461		observer.observe(document.body, {
462			childList: true,
463			subtree: true
464		});
465	});
466})( jQuery );

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.