added additional plugins

This commit is contained in:
Rodolfo Martinez 2025-12-12 19:05:48 -05:00
parent c85895d306
commit 00e60ec1b7
132 changed files with 27514 additions and 0 deletions

View file

@ -0,0 +1,647 @@
/**
* GitHub Code Viewer - Main JavaScript
*/
(function($) {
'use strict';
// GitHub Code Viewer Class
class GitHubCodeViewer {
constructor(element) {
this.$element = $(element);
this.repo = this.$element.data('repo');
this.theme = this.$element.data('theme');
this.showLineNumbers = this.$element.data('show-line-numbers');
this.initialFile = this.$element.data('initial-file');
this.files = [];
this.openTabs = [];
this.activeTab = null;
this.fileCache = {};
this.activeRequests = []; // Track active AJAX requests
this.currentPath = ''; // Track current folder path
this.init();
}
init() {
this.bindEvents();
this.loadRepositoryFiles();
}
bindEvents() {
// Search functionality with debouncing
let searchTimeout;
this.$element.on('input', '.mcb-search-input', (e) => {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
this.filterFiles($(e.target).val());
}, 300); // Debounce for 300ms
});
// File/folder selection
this.$element.on('click', '.mcb-file-item', (e) => {
const $item = $(e.currentTarget);
const isFolder = $item.data('is-folder');
const path = $item.data('path');
if (isFolder) {
// Navigate to folder
this.loadRepositoryFiles(path);
} else {
// Load file
this.loadFile(path);
}
});
// Tab management
this.$element.on('click', '.mcb-tab', (e) => {
const $tab = $(e.currentTarget);
const filePath = $tab.data('path');
this.switchToTab(filePath);
});
this.$element.on('click', '.mcb-tab-close', (e) => {
e.stopPropagation();
const $tab = $(e.target).closest('.mcb-tab');
const filePath = $tab.data('path');
this.closeTab(filePath);
});
// Copy button
this.$element.on('click', '.mcb-copy-btn', (e) => {
this.copyCode($(e.target));
});
// Home button - go to root
this.$element.on('click', '.mcb-home-btn', () => {
this.loadRepositoryFiles(''); // Load root
});
// Refresh button
this.$element.on('click', '.mcb-refresh-btn', () => {
this.refreshFiles();
});
// Fullscreen toggle
this.$element.on('click', '.mcb-fullscreen-btn', () => {
this.toggleFullscreen();
});
}
loadRepositoryFiles(path = '') {
console.log('MCB: Loading repository files for:', this.repo, 'path:', path);
const $fileList = this.$element.find('.mcb-file-list');
// Update current path
this.currentPath = path;
// Show loading indicator
$fileList.html('<div class="mcb-loading"><div class="mcb-spinner"></div><span>Loading files...</span></div>');
// Abort any pending requests
this.abortActiveRequests();
console.log('MCB: Making AJAX request to:', mcb_ajax.ajax_url);
const request = $.ajax({
url: mcb_ajax.ajax_url,
type: 'POST',
data: {
action: 'mcb_get_repo_files',
repo: this.repo,
path: path,
nonce: mcb_ajax.nonce
},
success: (response) => {
console.log('MCB: AJAX response:', response);
this.removeRequest(request);
if (response.success) {
console.log('MCB: Files loaded:', response.data);
this.files = response.data;
this.renderFileList();
// Load initial file if specified
if (this.initialFile) {
const file = this.files.find(f => f.path === this.initialFile);
if (file) {
this.loadFile(this.initialFile);
}
}
} else {
console.error('MCB: Error:', response.data);
$fileList.html('<div class="mcb-error">' + response.data + '</div>');
}
},
error: (xhr, status, error) => {
console.error('MCB: AJAX failed:', status, error);
this.removeRequest(request);
$fileList.html('<div class="mcb-error">Failed to load repository files: ' + error + '</div>');
}
});
this.activeRequests.push(request);
}
renderFileList() {
const $fileList = this.$element.find('.mcb-file-list');
$fileList.empty();
// Add current path breadcrumb if not at root
if (this.currentPath) {
const $breadcrumb = $('<div class="mcb-breadcrumb">');
$breadcrumb.append('<span class="mcb-path-label">Path: </span>');
$breadcrumb.append('<span class="mcb-current-path">/' + this.escapeHtml(this.currentPath) + '</span>');
$fileList.append($breadcrumb);
}
// Render files and folders
this.files.forEach(file => {
const $item = $('<div class="mcb-file-item">')
.attr('data-path', file.path)
.attr('data-name', file.name.toLowerCase())
.attr('data-is-folder', file.is_folder || false);
// Add appropriate icon
let icon;
if (file.type === 'parent') {
icon = '⬆'; // Up arrow for parent
$item.addClass('mcb-parent-folder');
} else if (file.is_folder) {
icon = '📁'; // Folder icon
$item.addClass('mcb-folder');
} else {
icon = this.getFileIcon(file.type);
$item.addClass('mcb-file');
}
$item.append('<span class="mcb-file-icon">' + icon + '</span>');
$item.append('<span class="mcb-file-name">' + this.escapeHtml(file.name) + '</span>');
// Add file size for files only
if (!file.is_folder) {
const sizeStr = this.formatFileSize(file.size);
$item.append('<span class="mcb-file-size">' + sizeStr + '</span>');
}
$fileList.append($item);
});
}
organizeFileTree(files) {
const tree = {};
files.forEach(file => {
const parts = file.path.split('/');
let current = tree;
parts.forEach((part, index) => {
if (index === parts.length - 1) {
// It's a file
current[part] = file;
} else {
// It's a directory
if (!current[part]) {
current[part] = {};
}
current = current[part];
}
});
});
return tree;
}
filterFiles(searchTerm) {
const $items = this.$element.find('.mcb-file-item');
const term = searchTerm.toLowerCase();
$items.each((index, item) => {
const $item = $(item);
const fileName = $item.data('name');
if (!term || fileName.includes(term)) {
$item.show();
} else {
$item.hide();
}
});
}
loadFile(filePath) {
// Limit cache size to prevent memory issues
this.limitCacheSize();
// Check if file is already in cache
if (this.fileCache[filePath]) {
this.displayFile(filePath, this.fileCache[filePath]);
return;
}
// Update status
this.updateStatus('Loading file...');
const request = $.ajax({
url: mcb_ajax.ajax_url,
type: 'POST',
data: {
action: 'mcb_load_file',
repo: this.repo,
file_path: filePath,
nonce: mcb_ajax.nonce
},
success: (response) => {
this.removeRequest(request);
if (response.success) {
this.fileCache[filePath] = response.data;
this.displayFile(filePath, response.data);
this.updateStatus('Ready');
} else {
this.showError('Failed to load file: ' + response.data);
this.updateStatus('Error loading file');
}
},
error: () => {
this.removeRequest(request);
this.showError('Network error while loading file');
this.updateStatus('Network error');
}
});
this.activeRequests.push(request);
}
displayFile(filePath, fileData) {
// Store in cache
this.fileCache[filePath] = fileData;
// Limit number of open tabs to prevent memory issues
const maxTabs = 10;
// Add to tabs if not already open
if (!this.openTabs.includes(filePath)) {
// Check tab limit
if (this.openTabs.length >= maxTabs) {
// Close the oldest tab
const oldestTab = this.openTabs[0];
this.closeTab(oldestTab);
}
this.openTabs.push(filePath);
this.addTab(filePath, fileData.filename);
}
// Only switch to tab if not already active
if (this.activeTab !== filePath) {
this.switchToTab(filePath);
} else {
// Just update the content if already active
const $codeArea = this.$element.find('.mcb-code-area');
const safeContent = this.createSafeCodeDisplay(fileData.content, fileData.filename);
$codeArea.html(safeContent);
// Apply syntax highlighting
if (typeof Prism !== 'undefined') {
Prism.highlightAll();
}
// Update status
const fileSize = this.formatFileSize(fileData.content.length);
const lineCount = fileData.content.split('\n').length;
this.$element.find('.mcb-file-info').text(fileData.filename + ' • ' + lineCount + ' lines • ' + fileSize);
}
// Display content
const $codeArea = this.$element.find('.mcb-code-area');
// Create safe HTML content
const safeContent = this.createSafeCodeDisplay(fileData.content, fileData.filename);
$codeArea.html(safeContent);
// Apply syntax highlighting if Prism is loaded
if (typeof Prism !== 'undefined') {
Prism.highlightAll();
}
// Mark file as active in sidebar
this.$element.find('.mcb-file-item').removeClass('active');
this.$element.find('.mcb-file-item[data-path="' + filePath + '"]').addClass('active');
// Update file info
const file = this.files.find(f => f.path === filePath);
if (file) {
this.updateFileInfo(file);
}
}
createSafeCodeDisplay(content, filename) {
// The content is already escaped by PHP, but we'll double-check
const $container = $('<div class="mcb-code-container">');
// Header
const $header = $('<div class="mcb-code-header">');
$header.append($('<span class="mcb-filename">').text(filename));
// Copy button - store content in data, not attribute
const $copyBtn = $('<button class="mcb-copy-btn">Copy</button>');
$copyBtn.data('content', content); // Use data() instead of attr()
$header.append($copyBtn);
$container.append($header);
// Code wrapper
const $wrapper = $('<div class="mcb-code-wrapper">');
const language = this.detectLanguage(filename);
// Create pre/code structure
const $pre = $('<pre>').addClass('line-numbers');
const $code = $('<code>').addClass('language-' + language);
// CRITICAL: Ensure content is text, not HTML
$code.text(content);
$pre.append($code);
$wrapper.append($pre);
$container.append($wrapper);
return $container;
}
addTab(filePath, filename) {
const $tabs = this.$element.find('.mcb-tabs');
const $tab = $('<div class="mcb-tab">')
.attr('data-path', filePath);
$tab.append('<span class="mcb-tab-title">' + this.escapeHtml(filename) + '</span>');
$tab.append('<span class="mcb-tab-close">×</span>');
$tabs.append($tab);
}
switchToTab(filePath) {
// Prevent switching if already active
if (this.activeTab === filePath) {
return;
}
this.activeTab = filePath;
// Update tab states
this.$element.find('.mcb-tab').removeClass('active');
this.$element.find('.mcb-tab[data-path="' + filePath + '"]').addClass('active');
// Load file content if cached, otherwise just display the tab
if (this.fileCache[filePath]) {
// Display cached content without calling displayFile
const $codeArea = this.$element.find('.mcb-code-area');
const fileData = this.fileCache[filePath];
const safeContent = this.createSafeCodeDisplay(fileData.content, fileData.filename);
$codeArea.html(safeContent);
// Apply syntax highlighting if Prism is loaded
if (typeof Prism !== 'undefined') {
Prism.highlightAll();
}
// Update status
const fileSize = this.formatFileSize(fileData.content.length);
const lineCount = fileData.content.split('\n').length;
this.$element.find('.mcb-file-info').text(fileData.filename + ' • ' + lineCount + ' lines • ' + fileSize);
}
}
closeTab(filePath) {
// Remove from open tabs
const index = this.openTabs.indexOf(filePath);
if (index > -1) {
this.openTabs.splice(index, 1);
}
// Remove tab element
this.$element.find('.mcb-tab[data-path="' + filePath + '"]').remove();
// If this was the active tab, switch to another
if (this.activeTab === filePath) {
if (this.openTabs.length > 0) {
this.switchToTab(this.openTabs[this.openTabs.length - 1]);
} else {
// Show welcome screen
this.showWelcome();
}
}
}
copyCode($button) {
const content = $button.data('content'); // Use data() instead of attr()
if (!content) {
return;
}
// Create temporary textarea
const $temp = $('<textarea>');
$temp.val(content);
$('body').append($temp);
$temp.select();
try {
document.execCommand('copy');
$button.text('Copied!').addClass('copied');
setTimeout(() => {
$button.text('Copy').removeClass('copied');
}, 2000);
} catch (err) {
console.error('Failed to copy:', err);
}
$temp.remove();
}
refreshFiles() {
// Abort any pending requests first
this.abortActiveRequests();
// Clear cache
this.fileCache = {};
// Reload files at current path
this.loadRepositoryFiles(this.currentPath);
this.updateStatus('Repository refreshed');
}
toggleFullscreen() {
this.$element.toggleClass('fullscreen');
}
showWelcome() {
const $codeArea = this.$element.find('.mcb-code-area');
const welcome = `
<div class="mcb-welcome">
<svg viewBox="0 0 24 24" width="64" height="64">
<path d="M9.4 16.6L4.8 12l4.6-4.6L8 6l-6 6 6 6 1.4-1.4zm5.2 0l4.6-4.6-4.6-4.6L16 6l6 6-6 6-1.4-1.4z"/>
</svg>
<h4>GitHub Code Viewer</h4>
<p>Select a file from the sidebar to view its content</p>
</div>
`;
$codeArea.html(welcome);
}
showError(message) {
const $codeArea = this.$element.find('.mcb-code-area');
$codeArea.html('<div class="mcb-error">' + this.escapeHtml(message) + '</div>');
}
updateStatus(text) {
this.$element.find('.mcb-status-text').text(text);
}
updateFileInfo(file) {
const info = file.name + ' • ' + this.formatFileSize(file.size) + ' • ' + file.type;
this.$element.find('.mcb-file-info').text(info);
}
formatFileSize(bytes) {
if (bytes === 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
}
detectLanguage(filename) {
const ext = filename.split('.').pop().toLowerCase();
const languageMap = {
'js': 'javascript',
'jsx': 'jsx',
'ts': 'typescript',
'tsx': 'tsx',
'py': 'python',
'rb': 'ruby',
'php': 'php',
'java': 'java',
'c': 'c',
'cpp': 'cpp',
'cs': 'csharp',
'go': 'go',
'rs': 'rust',
'swift': 'swift',
'kt': 'kotlin',
'scala': 'scala',
'r': 'r',
'sql': 'sql',
'sh': 'bash',
'yml': 'yaml',
'json': 'json',
'xml': 'xml',
'html': 'html',
'css': 'css',
'scss': 'scss',
'md': 'markdown'
};
return languageMap[ext] || 'plain';
}
getFileIcon(type) {
const icons = {
'javascript': '📜',
'python': '🐍',
'php': '🐘',
'java': '☕',
'cpp': '⚙️',
'go': '🐹',
'rust': '🦀',
'ruby': '💎',
'html': '🌐',
'css': '🎨',
'json': '📋',
'markdown': '📝',
'default': '📄'
};
return icons[type] || icons.default;
}
escapeHtml(text) {
const map = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#039;'
};
return text.replace(/[&<>"']/g, m => map[m]);
}
limitCacheSize() {
// Limit cache to 20 files to prevent memory issues
const maxCacheSize = 20;
const cacheKeys = Object.keys(this.fileCache);
if (cacheKeys.length >= maxCacheSize) {
// Remove oldest cached files (FIFO)
const toRemove = cacheKeys.slice(0, cacheKeys.length - maxCacheSize + 1);
toRemove.forEach(key => {
delete this.fileCache[key];
});
}
}
cleanupBeforeUnload() {
// Clean up event listeners and large objects
this.$element.off();
this.abortActiveRequests();
this.fileCache = null;
this.files = null;
this.openTabs = null;
}
abortActiveRequests() {
// Abort all pending AJAX requests
if (this.activeRequests && this.activeRequests.length > 0) {
this.activeRequests.forEach(request => {
if (request && request.abort) {
request.abort();
}
});
this.activeRequests = [];
}
}
removeRequest(request) {
const index = this.activeRequests.indexOf(request);
if (index > -1) {
this.activeRequests.splice(index, 1);
}
}
}
// Initialize all viewers on page
$(document).ready(() => {
console.log('MCB: Document ready, looking for .maple-code-blocks elements');
const viewers = [];
$('.maple-code-blocks').each(function() {
console.log('MCB: Initializing viewer for element:', this);
try {
const viewer = new GitHubCodeViewer(this);
viewers.push(viewer);
console.log('MCB: Viewer initialized successfully');
} catch (error) {
console.error('MCB: Error initializing viewer:', error);
}
});
console.log('MCB: Total viewers initialized:', viewers.length);
// Cleanup on page unload to prevent memory leaks
$(window).on('beforeunload', () => {
viewers.forEach(viewer => {
if (viewer && viewer.cleanupBeforeUnload) {
viewer.cleanupBeforeUnload();
}
});
});
});
})(jQuery);