diff --git a/assets/css/debug.css b/assets/css/debug.css
deleted file mode 100644
index e69de29..0000000
diff --git a/assets/css/shortcodes.css b/assets/css/shortcodes.css
deleted file mode 100644
index e69de29..0000000
diff --git a/assets/js/contribution-calendar.js b/assets/js/contribution-calendar.js
deleted file mode 100644
index 4b2265b..0000000
--- a/assets/js/contribution-calendar.js
+++ /dev/null
@@ -1,140 +0,0 @@
-// ✅ Constants organization
-const CALENDAR_CONFIG = {
- TOOLTIP_OFFSET: 10,
- SCROLL_DEBOUNCE_MS: 16, // 60fps for smooth scrolling
- CELL_SELECTOR: '.calendar-cell',
- CONTAINER_SELECTOR: '.calendar-container',
- MONTHS_SELECTOR: '.calendar-months'
-};
-
-const TOOLTIP_STYLES = {
- display: 'none',
- position: 'absolute',
- zIndex: '1000',
- pointerEvents: 'none'
-};
-
-// ✅ Utility functions with error handling
-const createTooltip = () => {
- const tooltip = document.createElement('div');
- tooltip.className = 'cell-tooltip';
- Object.assign(tooltip.style, TOOLTIP_STYLES);
- document.body.appendChild(tooltip);
- return tooltip;
-};
-
-const formatContributionDate = date => {
- try {
- return new Date(date).toLocaleDateString(undefined, {
- weekday: 'long',
- year: 'numeric',
- month: 'long',
- day: 'numeric'
- });
- } catch (error) {
- console.error('Date formatting failed:', error);
- return 'Invalid date';
- }
-};
-
-const createTooltipContent = (date, count) => {
- const formattedDate = formatContributionDate(date);
- const plural = count !== '1' ? 's' : '';
- return `${formattedDate}
${count} contribution${plural}`;
-};
-
-// ✅ Performance helper - debounced scroll
-const debounce = (fn, ms) => {
- let timer;
- return (...args) => {
- clearTimeout(timer);
- timer = setTimeout(() => fn.apply(this, args), ms);
- };
-};
-
-// ✅ Tooltip position with boundary checking
-const positionTooltip = (tooltip, x, y) => {
- const { innerWidth, innerHeight } = window;
- const rect = tooltip.getBoundingClientRect();
-
- const left = Math.min(x + CALENDAR_CONFIG.TOOLTIP_OFFSET, innerWidth - rect.width - 10);
- const top = Math.min(y + CALENDAR_CONFIG.TOOLTIP_OFFSET, innerHeight - rect.height - 10);
-
- Object.assign(tooltip.style, {
- left: `${Math.max(10, left)}px`,
- top: `${Math.max(10, top)}px`
- });
-};
-
-// ✅ Main calendar functionality with error boundaries
-const initializeCalendar = () => {
- try {
- const calendarCells = document.querySelectorAll(CALENDAR_CONFIG.CELL_SELECTOR);
- if (!calendarCells.length) {
- console.warn('No calendar cells found');
- return;
- }
-
- const tooltip = createTooltip();
-
- // ✅ Event delegation for better performance
- document.addEventListener('mouseover', e => {
- const cell = e.target.closest(CALENDAR_CONFIG.CELL_SELECTOR);
- if (!cell) return;
-
- const { date, count } = cell.dataset;
- if (!date || count === undefined) return;
-
- tooltip.innerHTML = createTooltipContent(date, count);
- tooltip.style.display = 'block';
- positionTooltip(tooltip, e.pageX, e.pageY);
- });
-
- document.addEventListener('mousemove', e => {
- const cell = e.target.closest(CALENDAR_CONFIG.CELL_SELECTOR);
- if (!cell || tooltip.style.display === 'none') return;
-
- positionTooltip(tooltip, e.pageX, e.pageY);
- });
-
- document.addEventListener('mouseout', e => {
- const cell = e.target.closest(CALENDAR_CONFIG.CELL_SELECTOR);
- if (!cell) return;
-
- tooltip.style.display = 'none';
- });
-
- initializeScrollSync();
-
- } catch (error) {
- console.error('Calendar initialization failed:', error);
- }
-};
-
-// ✅ Scroll synchronization with performance optimization
-const initializeScrollSync = () => {
- const calendarContainer = document.querySelector(CALENDAR_CONFIG.CONTAINER_SELECTOR);
- if (!calendarContainer) return;
-
- const monthsContainer = calendarContainer.querySelector(CALENDAR_CONFIG.MONTHS_SELECTOR);
- if (!monthsContainer) return;
-
- const handleScroll = debounce(() => {
- const { scrollLeft } = calendarContainer;
- monthsContainer.style.transform = `translateX(${scrollLeft}px)`;
- }, CALENDAR_CONFIG.SCROLL_DEBOUNCE_MS);
-
- calendarContainer.addEventListener('scroll', handleScroll, { passive: true });
-};
-
-// ✅ DOM ready with modern approach
-if (document.readyState === 'loading') {
- document.addEventListener('DOMContentLoaded', initializeCalendar);
-} else {
- initializeCalendar();
-}
-
-// ✅ Export for potential module usage
-if (typeof module !== 'undefined' && module.exports) {
- module.exports = { initializeCalendar, formatContributionDate };
-}
diff --git a/assets/js/fuzzy-search.js b/assets/js/fuzzy-search.js
index 37a5bba..1ae79de 100644
--- a/assets/js/fuzzy-search.js
+++ b/assets/js/fuzzy-search.js
@@ -1,4 +1,4 @@
-(function() {
+(function () {
'use strict';
function FuzzySearch(options) {
@@ -6,111 +6,90 @@
this.maxResults = (options && options.maxResults) || 10;
this.searchData = [];
this.searchIndex = null;
- this.init();
+ this.loadPromise = null;
+ this.setupSearchHandlers();
}
- FuzzySearch.prototype.init = function() {
- var self = this;
- this.loadSearchData().then(function() {
- self.setupSearchHandlers();
- });
- };
-
- FuzzySearch.prototype.loadSearchData = function() {
- var self = this;
- // Use relative URL - this will work with any baseURL including subpaths
- var indexUrl = '/index.json';
-
- // For sites with subpath (like GitHub Pages), get the full path from current location
- var currentPath = window.location.pathname;
- var basePath = '';
-
- // If we're in a subpath (not root), extract the base path
+ // Compute the index URL once, supporting subpath deployments (e.g. GitHub Pages).
+ FuzzySearch.prototype.getIndexUrl = function () {
+ const currentPath = window.location.pathname;
+ let basePath = '';
if (currentPath !== '/' && currentPath.indexOf('/') === 0) {
- var pathParts = currentPath.split('/').filter(function(part) { return part.length > 0; });
- // For GitHub Pages format like /PKB-theme/, the first part is usually the repo name
+ const pathParts = currentPath.split('/').filter((part) => part.length > 0);
if (pathParts.length > 0 && currentPath.startsWith('/' + pathParts[0] + '/')) {
basePath = '/' + pathParts[0];
}
}
-
- indexUrl = basePath + '/index.json';
-
- return fetch(indexUrl)
- .then(function(response) {
- if (response.ok) {
- return response.json();
- } else {
- throw new Error('Search index not found (HTTP ' + response.status + ') at ' + window.location.origin + indexUrl);
+ return basePath + '/index.json';
+ };
+
+ // Lazy-load: returns a cached promise so multiple inputs share one fetch.
+ FuzzySearch.prototype.ensureLoaded = function () {
+ if (this.loadPromise) return this.loadPromise;
+ const self = this;
+ const indexUrl = this.getIndexUrl();
+
+ this.loadPromise = fetch(indexUrl)
+ .then((response) => {
+ if (!response.ok) {
+ throw new Error('Search index not found (HTTP ' + response.status + ') at ' + indexUrl);
}
+ return response.json();
})
- .then(function(data) {
+ .then((data) => {
if (Array.isArray(data) && data.length > 0) {
self.searchData = data;
- console.log('Search index loaded with ' + data.length + ' pages');
} else {
- console.warn('Search index is empty or invalid');
self.searchData = [];
}
self.buildSearchIndex();
})
- .catch(function(error) {
+ .catch((error) => {
console.warn('Could not load search data:', error.message);
- console.info('GitHub Pages Debug: Ensure these Hugo config settings:');
- console.info('1. In hugo.toml: outputs.home = ["HTML", "RSS", "JSON"]');
- console.info('2. In params.toml: taxonomies.mainSections = ["posts", "docs", etc.]');
- console.info('3. File exists: layouts/index.json');
- console.info('4. Content exists in mainSections directories');
- console.info('5. For GitHub Pages: baseURL should match your site URL exactly');
- console.info('Current pathname:', currentPath);
- console.info('Constructed base path:', basePath);
- console.info('Final fetch URL:', window.location.origin + indexUrl);
self.searchData = [];
self.buildSearchIndex();
});
+
+ return this.loadPromise;
};
- FuzzySearch.prototype.buildSearchIndex = function() {
- var self = this;
- this.searchIndex = this.searchData.map(function(item) {
- return {
- id: item.id,
- title: item.title,
- content: item.content,
- summary: item.summary,
- url: item.url,
- date: item.date,
- tags: item.tags,
- categories: item.categories,
- section: item.section,
- type: item.type,
- searchText: (item.title + ' ' + item.content + ' ' + (item.tags || []).join(' ') + ' ' + (item.categories || []).join(' ')).toLowerCase(),
- titleWords: self.tokenize(item.title.toLowerCase()),
- contentWords: self.tokenize((item.content || '').toLowerCase())
- };
- });
+ FuzzySearch.prototype.buildSearchIndex = function () {
+ const self = this;
+ this.searchIndex = this.searchData.map((item) => ({
+ id: item.id,
+ title: item.title,
+ content: item.content,
+ summary: item.summary,
+ url: item.url,
+ date: item.date,
+ tags: item.tags,
+ categories: item.categories,
+ section: item.section,
+ type: item.type,
+ searchText: (item.title + ' ' + item.content + ' ' + (item.tags || []).join(' ') + ' ' + (item.categories || []).join(' ')).toLowerCase(),
+ titleWords: self.tokenize(item.title.toLowerCase()),
+ contentWords: self.tokenize((item.content || '').toLowerCase())
+ }));
};
- FuzzySearch.prototype.tokenize = function(text) {
- return text.split(/\s+/).filter(function(word) {
- return word.length > 2;
- });
+ FuzzySearch.prototype.tokenize = function (text) {
+ return text.split(/\s+/).filter((word) => word.length > 2);
};
- FuzzySearch.prototype.fuzzyMatch = function(pattern, text) {
- var patternLength = pattern.length;
- var textLength = text.length;
-
+ FuzzySearch.prototype.fuzzyMatch = function (pattern, text) {
+ const patternLength = pattern.length;
+ const textLength = text.length;
+
if (patternLength === 0) return { score: 1, matches: [] };
if (textLength === 0) return { score: 0, matches: [] };
-
- var matches = [];
- var patternIndex = 0;
- var score = 0;
- var consecutiveMatches = 0;
- var maxConsecutive = 0;
-
- for (var textIndex = 0; textIndex < textLength && patternIndex < patternLength; textIndex++) {
+
+ const matches = [];
+ let patternIndex = 0;
+ let score = 0;
+ let consecutiveMatches = 0;
+ let maxConsecutive = 0;
+
+ for (let textIndex = 0; textIndex < textLength && patternIndex < patternLength; textIndex++) {
if (pattern[patternIndex] === text[textIndex]) {
matches.push(textIndex);
patternIndex++;
@@ -121,40 +100,40 @@
consecutiveMatches = 0;
}
}
-
+
if (patternIndex !== patternLength) {
return { score: 0, matches: [] };
}
-
- var matchRatio = patternLength / textLength;
- var consecutiveBonus = maxConsecutive / patternLength;
- var finalScore = (score / (textLength * patternLength)) + matchRatio + consecutiveBonus;
-
+
+ const matchRatio = patternLength / textLength;
+ const consecutiveBonus = maxConsecutive / patternLength;
+ const finalScore = (score / (textLength * patternLength)) + matchRatio + consecutiveBonus;
+
return { score: finalScore, matches: matches };
};
- FuzzySearch.prototype.calculateWordScore = function(queryWords, itemWords) {
- var self = this;
- var score = 0;
- for (var i = 0; i < queryWords.length; i++) {
- for (var j = 0; j < itemWords.length; j++) {
- var match = self.fuzzyMatch(queryWords[i], itemWords[j]);
+ FuzzySearch.prototype.calculateWordScore = function (queryWords, itemWords) {
+ const self = this;
+ let score = 0;
+ for (let i = 0; i < queryWords.length; i++) {
+ for (let j = 0; j < itemWords.length; j++) {
+ const match = self.fuzzyMatch(queryWords[i], itemWords[j]);
score += match.score;
}
}
return score;
};
- FuzzySearch.prototype.highlightMatches = function(text, query) {
- var escapedQuery = query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
- var regex = new RegExp('(' + escapedQuery + ')', 'gi');
+ FuzzySearch.prototype.highlightMatches = function (text, query) {
+ const escapedQuery = query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+ const regex = new RegExp('(' + escapedQuery + ')', 'gi');
return text.replace(regex, '$1');
};
- FuzzySearch.prototype.search = function(query) {
+ FuzzySearch.prototype.search = function (query) {
if (!query || query.length < 2) return [];
-
- if (this.searchIndex.length === 0) {
+
+ if (!this.searchIndex || this.searchIndex.length === 0) {
return [{
id: 'no-index',
title: 'Search index not available',
@@ -166,38 +145,38 @@
highlight: 'Search index not available'
}];
}
-
- var normalizedQuery = query.toLowerCase().trim();
- var queryWords = this.tokenize(normalizedQuery);
- var results = [];
- var self = this;
-
- for (var i = 0; i < this.searchIndex.length; i++) {
- var item = this.searchIndex[i];
+
+ const normalizedQuery = query.toLowerCase().trim();
+ const queryWords = this.tokenize(normalizedQuery);
+ const results = [];
+ const self = this;
+
+ for (let i = 0; i < this.searchIndex.length; i++) {
+ const item = this.searchIndex[i];
if (item.url === window.location.pathname) continue;
-
- var totalScore = 0;
-
- var directMatch = self.fuzzyMatch(normalizedQuery, item.searchText);
+
+ let totalScore = 0;
+
+ const directMatch = self.fuzzyMatch(normalizedQuery, item.searchText);
if (directMatch.score > 0) {
totalScore += directMatch.score * 2;
}
-
- var titleScore = self.calculateWordScore(queryWords, item.titleWords) * 3;
- var contentScore = self.calculateWordScore(queryWords, item.contentWords);
-
+
+ const titleScore = self.calculateWordScore(queryWords, item.titleWords) * 3;
+ const contentScore = self.calculateWordScore(queryWords, item.contentWords);
+
totalScore += titleScore + contentScore;
-
+
if (item.title.toLowerCase().indexOf(normalizedQuery) !== -1) {
totalScore += 5;
}
-
- var escapedQuery = normalizedQuery.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
- var wordBoundaryRegex = new RegExp('\\b' + escapedQuery, 'i');
+
+ const escapedQuery = normalizedQuery.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+ const wordBoundaryRegex = new RegExp('\\b' + escapedQuery, 'i');
if (wordBoundaryRegex.test(item.title) || wordBoundaryRegex.test(item.content)) {
totalScore += 2;
}
-
+
if (totalScore > self.threshold) {
results.push({
id: item.id,
@@ -213,59 +192,62 @@
}
}
- results.sort(function(a, b) {
- return b.score - a.score;
- });
-
+ results.sort((a, b) => b.score - a.score);
+
return results.slice(0, this.maxResults);
};
- FuzzySearch.prototype.setupSearchHandlers = function() {
- var desktopInput = document.getElementById('search-input');
- var mobileInput = document.getElementById('mobile-search-input');
-
+ FuzzySearch.prototype.setupSearchHandlers = function () {
+ const desktopInput = document.getElementById('search-input');
+ const mobileInput = document.getElementById('mobile-search-input');
+
if (desktopInput) {
this.setupSearchInput(desktopInput, 'desktop');
}
-
+
if (mobileInput) {
this.setupSearchInput(mobileInput, 'mobile');
}
};
- FuzzySearch.prototype.setupSearchInput = function(input, type) {
- var self = this;
- var timeoutId;
- var resultsContainer = this.createResultsContainer(input, type);
+ FuzzySearch.prototype.setupSearchInput = function (input, type) {
+ const self = this;
+ let timeoutId;
+ const resultsContainer = this.createResultsContainer(input, type);
- input.addEventListener('input', function(e) {
+ // Lazy load: kick off fetch on first focus rather than on page load.
+ input.addEventListener('focus', () => { self.ensureLoaded(); }, { once: true });
+
+ input.addEventListener('input', (e) => {
clearTimeout(timeoutId);
- timeoutId = setTimeout(function() {
- var query = e.target.value.trim();
+ timeoutId = setTimeout(() => {
+ const query = e.target.value.trim();
if (query.length >= 2) {
- var results = self.search(query);
- self.displayResults(results, resultsContainer, query);
+ self.ensureLoaded().then(() => {
+ const results = self.search(query);
+ self.displayResults(results, resultsContainer, query);
+ });
} else {
self.hideResults(resultsContainer);
}
}, 200);
});
- input.addEventListener('keydown', function(e) {
+ input.addEventListener('keydown', (e) => {
self.handleKeyboardNavigation(e, resultsContainer);
});
- document.addEventListener('click', function(e) {
+ document.addEventListener('click', (e) => {
if (!input.contains(e.target) && !resultsContainer.contains(e.target)) {
self.hideResults(resultsContainer);
}
});
- var form = input.closest('form');
+ const form = input.closest('form');
if (form) {
- form.addEventListener('submit', function(e) {
+ form.addEventListener('submit', (e) => {
e.preventDefault();
- var query = input.value.trim();
+ const query = input.value.trim();
if (query) {
self.performSearch(query);
}
@@ -273,12 +255,12 @@
}
};
- FuzzySearch.prototype.createResultsContainer = function(input, type) {
- var container = document.createElement('div');
+ FuzzySearch.prototype.createResultsContainer = function (input, type) {
+ const container = document.createElement('div');
container.className = 'search-results search-results-' + type;
container.style.cssText = 'position: absolute; top: 100%; left: 0; right: 0; background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--border-radius); box-shadow: var(--shadow-lg); max-height: 400px; overflow-y: auto; z-index: var(--z-index-dropdown); display: none;';
- var wrapper = input.closest('.header-search, .mobile-search-form');
+ const wrapper = input.closest('.header-search, .mobile-search-form');
if (wrapper) {
wrapper.style.position = 'relative';
wrapper.appendChild(container);
@@ -287,32 +269,31 @@
return container;
};
- FuzzySearch.prototype.displayResults = function(results, container, query) {
- var self = this;
+ FuzzySearch.prototype.displayResults = function (results, container, query) {
if (results.length === 0) {
container.innerHTML = '