dashboard-search script
URLを検索し飛べるダッシュボード用のスクリプト
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="euc-jp">
<title>dashboard</title>
<script src="https://cdn.jsdelivr.net/npm/fuse.js/dist/fuse.min.js"></script>
<meta http-equiv="refresh" content="120"/>
<!--<style> body { margin: 0; overflow: hidden; } </style>-->
<style>
/* ===== 基本スタイル ===== */
body {
font-family: sans-serif;
padding-top: 60px; /* 検索バーのためのスペース */
margin: 0;
}
/* 既存の要素のスタイル例 */
dt { font-weight: bold; margin-top: 1em; margin-left: 1em; }
dd { margin-left: 3em; margin-bottom: 0.5em; }
p, dl, details { margin: 1em; }
details { border: 1px solid #ccc; padding: 0.5em; border-radius: 8px; }
summary { font-weight: bold; cursor: pointer; }
a { color: blue; text-decoration: underline; }
/* ===== 検索機能のスタイル ===== */
.search-overlay {
position: fixed;
top: 0;
left: 50%;
transform: translateX(-50%);
width: 90%;
max-width: 512px;
padding: 1rem;
z-index: 50;
background-color: white;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
border: 1px solid #e5e7eb;
border-top: none;
border-radius: 0 0 0.5rem 0.5rem;
box-sizing: border-box;
}
.search-input {
width: 100%;
padding: 0.5rem;
border: 1px solid #d1d5db;
border-radius: 0.375rem;
box-sizing: border-box;
font-size: 1rem;
}
.search-input:focus {
outline: none;
border-color: #3b82f6;
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.4);
}
.search-results {
margin-top: 0.5rem;
list-style: none;
padding: 0;
margin-left: 0;
margin-right: 0;
margin-bottom: 0;
background-color: white;
border: 1px solid #e5e7eb;
border-radius: 0.375rem;
max-height: calc(100vh - 100px);
overflow-y: auto;
}
.search-result-item {
padding: 0.5rem;
border-bottom: 1px solid #f3f4f6;
cursor: pointer;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.search-result-item:last-child {
border-bottom: none;
}
.search-result-item:hover {
background-color: #f9fafb;
}
/* 選択された項目のスタイル */
.search-result-item.selected {
background-color: #e0e7ff;
}
/* 見つからない場合のメッセージ */
.no-results {
padding: 0.5rem;
color: #6b7280;
text-align: center;
}
/* 非表示用クラス */
.hidden {
display: none;
}
/* ===== 検索を開くボタンのスタイル ===== */
.search-open-button {
padding: 0.5em 1em;
margin-bottom: 1em;
cursor: pointer;
border: 1px solid #ccc;
background-color: #f0f0f0;
border-radius: 4px;
font-size: 0.9em;
display: none;
}
@media (max-width: 767px) {
.search-open-button {
display: inline-block;
}
}
.content-wrapper {
padding: 1em;
}
</style>
</head>
<body>
<div id="searchOverlay" class="search-overlay hidden">
<input type="text" id="searchInput" placeholder="検索..." class="search-input">
<ul id="searchResults" class="search-results">
</ul>
</div>
<div>
<button id="openSearchButton" class="search-open-button">検索を開く</button>
</div>
<a href="hoge">
<img src="./logos/hoge.png">
hoge
</a>
<dt>hoge</dt>
<dd>hoge<dd>
<details>
<summary>hoge</summary>
<a href="fuga">fuga</a>
</details>
</body>
<script>
// --- Settings ---
const searchData = [
// { url: "", searchwords: "", displayText: "" },
{ url: "./hoge", searchwords: "fuga", displayText: "piyo" },
];
const aliases = {
"hg": { url: "./hoge" }
};
const fuseOptions = {
keys: [
"searchwords",
"url"
],
threshold: 0.4,
};
// --- End of Settings ---
const fuse = new Fuse(searchData, fuseOptions);
// --- Element References ---
const searchOverlay = document.getElementById('searchOverlay');
const searchInput = document.getElementById('searchInput');
const searchResults = document.getElementById('searchResults');
const openSearchButton = document.getElementById('openSearchButton');
// --- State Variables ---
let selectedIndex = -1;
let dKeyPressed = false;
// --- Functions ---
function showSearch() {
searchOverlay.classList.remove('hidden');
searchInput.focus();
searchInput.value = '';
clearResults();
}
function hideSearch() {
searchOverlay.classList.add('hidden');
searchInput.blur();
clearResults();
selectedIndex = -1;
dKeyPressed = false;
}
function clearResults() {
searchResults.innerHTML = '';
}
function updateResults() {
const query = searchInput.value.trim();
clearResults();
// Reset selectedIndex *before* populating new results
selectedIndex = -1;
if (!query) {
return;
}
let results = [];
let aliasResult = null;
// Alias check
if (aliases[query]) {
aliasResult = aliases[query];
// Find the corresponding item in searchData to ensure consistency
const aliasInSearchData = searchData.find(item => item.url === aliasResult.url);
// Use the full object from searchData if found, otherwise use the alias object directly
results.push({ item: aliasInSearchData || aliasResult });
}
// Fuse search
const fuseResults = fuse.search(query);
// Merge alias and Fuse results
fuseResults.forEach(fuseResult => {
if (!aliasResult || fuseResult.item.url !== aliasResult.url) {
results.push(fuseResult);
}
});
if (results.length > 0) {
results.forEach((result, index) => {
const li = document.createElement('li');
li.classList.add('search-result-item');
// Display format: displayText (url)
li.textContent = `${result.item.displayText || ''} (${result.item.url})`;
// Set title attribute (optional, keeping it simpler)
li.title = result.item.displayText || result.item.url;
li.dataset.url = result.item.url;
li.dataset.index = index;
// Event listeners for click and mouseover
li.addEventListener('click', () => {
window.location.href = result.item.url;
});
li.addEventListener('mouseover', () => {
deselectAll();
li.classList.add('selected');
selectedIndex = index;
});
searchResults.appendChild(li);
});
// Select the first item by default
selectedIndex = 0;
highlightResult(selectedIndex);
} else {
// No results found message
const li = document.createElement('li');
li.classList.add('no-results');
li.textContent = '結果が見つかりません';
searchResults.appendChild(li);
}
}
// deselectAll, highlightResult, navigateResults, openSelected functions remain the same
function deselectAll() {
const items = searchResults.querySelectorAll('.search-result-item');
items.forEach(item => item.classList.remove('selected'));
}
function highlightResult(index) {
deselectAll();
const items = searchResults.querySelectorAll('.search-result-item');
if (items[index]) {
items[index].classList.add('selected');
// Scroll into view if needed
items[index].scrollIntoView({ block: 'nearest', behavior: 'smooth' });
}
}
function navigateResults(direction) {
const items = searchResults.querySelectorAll('.search-result-item');
const itemCount = items.length;
if (itemCount === 0) return;
let newIndex = selectedIndex;
if (direction === 'down') {
// If nothing is selected, start from the first item
newIndex = selectedIndex === -1 ? 0 : (selectedIndex + 1) % itemCount;
} else if (direction === 'up') {
// If nothing is selected, start from the last item
newIndex = selectedIndex === -1 ? itemCount - 1 : (selectedIndex - 1 + itemCount) % itemCount;
}
selectedIndex = newIndex;
highlightResult(selectedIndex);
}
function openSelected() {
const selectedItem = searchResults.querySelector('.search-result-item.selected');
if (selectedItem && selectedItem.dataset.url) {
window.location.href = selectedItem.dataset.url;
}
}
// --- Event Listeners ---
// *** Add event listener for the open search button ***
if (openSearchButton) { // Check if the button exists
openSearchButton.addEventListener('click', showSearch);
}
// Keyboard event listener (remains the same)
document.addEventListener('keydown', (e) => {
// When search overlay is hidden
if (searchOverlay.classList.contains('hidden')) {
// Exclude modifier keys and special keys
// Start search on character input or spacebar
if (!e.ctrlKey && !e.altKey && !e.metaKey && e.key.length === 1) {
// Further exclude function keys etc. if needed
if (!e.key.startsWith('F') && e.key !== 'Escape' && e.key !== 'Tab' && e.key !== 'Shift' && e.key !== 'Control' && e.key !== 'Alt' && e.key !== 'Meta' && e.key !== 'CapsLock' && e.key !== 'ArrowUp' && e.key !== 'ArrowDown' && e.key !== 'ArrowLeft' && e.key !== 'ArrowRight' && e.key !== 'Enter' && e.key !== 'Backspace' && e.key !== 'Delete' && e.key !== 'Home' && e.key !== 'End' && e.key !== 'PageUp' && e.key !== 'PageDown') {
showSearch();
}
}
}
// When search overlay is visible
else {
if (e.key === 'Escape') {
hideSearch();
} else if (e.key === 'ArrowDown') {
e.preventDefault(); // Prevent page scroll
navigateResults('down');
} else if (e.key === 'ArrowUp') {
e.preventDefault(); // Prevent page scroll
navigateResults('up');
} else if (e.key === 'Enter') {
e.preventDefault(); // Prevent form submission etc.
openSelected();
} else if (e.key.toLowerCase() === 'd') {
dKeyPressed = true; // 'd' key pressed
} else if (e.key.toLowerCase() === 'u' && dKeyPressed) {
e.preventDefault(); // Prevent default browser action (e.g., view source)
hideSearch();
dKeyPressed = false; // Reset
}
}
});
// Reset flag when 'd' key is released
document.addEventListener('keyup', (e) => {
if (e.key.toLowerCase() === 'd') {
dKeyPressed = false;
}
});
// Search input event
searchInput.addEventListener('input', updateResults);
</script>
</html>
ライセンス
MITです。AIが全部書いたし。一応。
Copyright (c) 2025 15km Released under the MIT license https://opensource.org/licenses/mit-license.php