Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/rdoc/generator/template/aliki/_header.rhtml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<form action="#" method="get" accept-charset="utf-8">
<input id="search-field" role="combobox" aria-label="Search"
aria-autocomplete="list" aria-controls="search-results-desktop"
type="text" name="search" placeholder="Search (/) for a class, method..."
type="text" name="search" placeholder="Search globally [/] or inside the class [(Shift) s]"
spellcheck="false" autocomplete="off"
title="Type to search, Up and Down to navigate, Enter to load">
<ul id="search-results-desktop" aria-label="Search Results"
Expand Down
93 changes: 81 additions & 12 deletions lib/rdoc/generator/template/aliki/js/aliki.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,28 @@ function createSearchInstance(input, result) {
return search;
}

// Auto-complete namespace separator '::' when user types ':'
// For both desktop and mobile search inputs
function autoCompleteColons(input, e) {
if (e.key !== ':' || e.ctrlKey || e.metaKey || e.altKey) {
return;
}

const start = input.selectionStart;
const end = input.selectionEnd;
if (start === null || end === null) {
return;
}

e.preventDefault();

const value = input.value;
input.value = value.slice(0, start) + '::' + value.slice(end);

const caret = start + 2;
input.setSelectionRange(caret, caret);
}

function hookSearch() {
const input = document.querySelector('#search-field');
const result = document.querySelector('#search-results-desktop');
Expand All @@ -96,13 +118,52 @@ function hookSearch() {
}
});

// Hide search results on Escape key on desktop too
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && input.matches(":focus")) {
search.hide();
input.blur();
}
});

// Show search results when focusing on input (if there's a query)
input.addEventListener('focus', () => {
if (input.value.trim()) {
search.show();
}
});

// Auto-complete '::' when user types ':'
input.addEventListener('keydown', (e) => autoCompleteColons(input, e));

// Keyboard shortcuts for search
// "/" for global search
// "s" for instance methods (#)
// "Shift+s" for class methods (::)
document.addEventListener('keydown', (e) => {
if (document.activeElement.tagName === 'INPUT') {
return;
}

if (e.key === "/") {
e.preventDefault();
input.value = '';
input.focus();
} else if (e.key === "s" || e.key === "S") {
e.preventDefault();
// Fill search field with current module name and scope by method type
const moduleName = getCurrentModuleName();
if (moduleName) {
const suffix = e.shiftKey ? '::' : '#';
input.value = moduleName + suffix;
input.focus();
} else {
// Fallback to regular focus if not on a module page
input.focus();
}
}
});

// Check for ?q= URL parameter and trigger search automatically
if (typeof URLSearchParams !== 'undefined') {
const urlParams = new URLSearchParams(window.location.search);
Expand All @@ -114,18 +175,23 @@ function hookSearch() {
}
}

/* ===== Keyboard Shortcuts ===== */
/**
* Extract module/class name from URL path
*
* Examples:
* /_site/String.html -> String
* /_site/String/Example.html -> String::Example
*/
function getCurrentModuleName() {

function hookFocus() {
document.addEventListener("keydown", (event) => {
if (document.activeElement.tagName === 'INPUT') {
return;
}
if (event.key === "/") {
event.preventDefault();
document.querySelector('#search-field').focus();
}
});
const path = window.location.pathname; // Ignore the fragment

// In some cases, there may be no file extensions
const match = path.match(/\/([A-Z]\w*(?:\/[A-Z]\w*)*)(\.(html|md|rdoc))?$/);

if (!match) return null;

return match[1].replace(/\//g, '::');
}

/* ===== Mobile Navigation ===== */
Expand Down Expand Up @@ -378,6 +444,10 @@ function hookSearchModal() {
}
});

// Auto-complete '::' when user types ':'
searchInput.addEventListener('keydown', (e) => autoCompleteColons(searchInput, e));


// Check for ?q= URL parameter on mobile and open modal
if (typeof URLSearchParams !== 'undefined') {
const urlParams = new URLSearchParams(window.location.search);
Expand Down Expand Up @@ -495,7 +565,6 @@ function showCopySuccess(button) {
document.addEventListener('DOMContentLoaded', () => {
hookSourceViews();
hookSearch();
hookFocus();
hookSidebar();
generateToc();
setHeadingSelfLinkScrollHandlers();
Expand Down
1 change: 0 additions & 1 deletion lib/rdoc/generator/template/aliki/js/search_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,4 +126,3 @@ SearchController.prototype = Object.assign({}, SearchNavigation, new function()
this.setNavigationActive(true);
}
});