From bb9073df109bfdc2494ef129912fdf31d1da9dc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Andreas=20Nilsen?= Date: Tue, 3 Mar 2026 11:19:01 +0100 Subject: [PATCH] updated innsida-search to show search results --- scripts/innsida-search/innsida-search.user.js | 228 ++++++++++++++++-- 1 file changed, 207 insertions(+), 21 deletions(-) diff --git a/scripts/innsida-search/innsida-search.user.js b/scripts/innsida-search/innsida-search.user.js index 3570450..51f5941 100644 --- a/scripts/innsida-search/innsida-search.user.js +++ b/scripts/innsida-search/innsida-search.user.js @@ -1,8 +1,8 @@ // ==UserScript== // @name Innsida - Quick Search // @namespace https://git.ntnu.no/M365-Drift/MonkeyMagic/ -// @version 1.2.1 -// @description `Ctrl` + `Shift` + `F` to search on Innsida +// @version 2.1.0 +// @description `Ctrl` + `Shift` + `F` to search on Innsida with real-time results // @author Øyvind Nilsen (on@ntnu.no) // @match https://innsida.ntnu.no/* // @grant none @@ -15,26 +15,48 @@ (function() { 'use strict'; - let currentSelection = 0; // 0 = input box, 1+ = menu items + let currentSelection = 0; let menuItems = []; let links = []; + let searchResults = []; + let searchMode = false; + let searchDebounceTimeout; + let abortController; // Language support const translations = { en: { searchPlaceholder: 'Search Innsida...', mineNotFound: 'Mine div not found, using default links', - linksFound: 'Found {count} links in "mine" section' + linksFound: 'Found {count} links in "mine" section', + searching: 'Searching...', + noResults: 'No results found', + quickLinks: 'Quick Links', + searchResults: 'Search Results', + typeToSearch: 'Type to search...', + enterToSearchAll: 'Press Enter to see all results' }, nb: { searchPlaceholder: 'Søk på Innsida...', mineNotFound: 'Mine-div ikke funnet, bruker standardlenker', - linksFound: 'Fant {count} lenker i "mine"-seksjonen' + linksFound: 'Fant {count} lenker i "mine"-seksjonen', + searching: 'Søker...', + noResults: 'Ingen resultater funnet', + quickLinks: 'Hurtiglenker', + searchResults: 'Søkeresultater', + typeToSearch: 'Skriv for å søke...', + enterToSearchAll: 'Trykk Enter for å se alle resultater' }, nn: { searchPlaceholder: 'Søk på Innsida...', mineNotFound: 'Mine-div ikkje funne, brukar standardlenkjer', - linksFound: 'Fann {count} lenkjer i "mine"-seksjonen' + linksFound: 'Fann {count} lenkjer i "mine"-seksjonen', + searching: 'Søkjer...', + noResults: 'Ingen resultat funne', + quickLinks: 'Hurtiglenkjer', + searchResults: 'Søkjeresultat', + typeToSearch: 'Skriv for å søkje...', + enterToSearchAll: 'Trykk Enter for å sjå alle resultat' } }; @@ -68,7 +90,7 @@ container.style.borderRadius = '8px'; container.style.padding = '20px'; container.style.boxShadow = '0 4px 20px rgba(0,0,0,0.3)'; - container.style.minWidth = '300px'; + container.style.minWidth = '400px'; container.style.maxHeight = '80vh'; container.style.overflowY = 'auto'; @@ -76,15 +98,18 @@ const inputBox = document.createElement('input'); inputBox.type = 'text'; inputBox.style.width = '100%'; - inputBox.style.padding = '10px'; + inputBox.style.padding = '12px'; inputBox.style.fontSize = '16px'; inputBox.style.border = '2px solid #007acc'; inputBox.style.borderRadius = '4px'; inputBox.style.marginBottom = '10px'; inputBox.style.outline = 'none'; + inputBox.style.boxSizing = 'border-box'; // Create menu container const menu = document.createElement('div'); + menu.style.maxHeight = '500px'; + menu.style.overflowY = 'auto'; container.appendChild(inputBox); container.appendChild(menu); @@ -126,31 +151,114 @@ ]; } - function createMenu() { + // Fetch search results from Innsida API + async function fetchSearchResults(query) { + if (!query || query.length < 2) { + return []; + } + + // Abort previous request if still in progress + if (abortController) { + abortController.abort(); + } + abortController = new AbortController(); + + try { + // The actual Innsida search API endpoint + const url = `/api/search-api/query?site=innsida&q=${encodeURIComponent(query)}&pageSize=8&pageNr=1`; + + console.log(`Fetching search results from: ${url}`); + + const response = await fetch(url, { signal: abortController.signal }); + + if (!response.ok) { + console.log('Search API error:', response.status); + return []; + } + + const data = await response.json(); + console.log('Search results received:', { + numFound: data.numFound, + docsCount: data.docs?.length || 0, + sampleDoc: data.docs?.[0] + }); + + // Parse results from the 'docs' array + if (data.docs && Array.isArray(data.docs)) { + return data.docs.map(doc => ({ + text: doc.title || doc.name || '', + url: doc.url || '', + description: doc.description || doc.teaser || '', + type: doc.type || 'document' + })).filter(r => r.text && r.url); + } + + return []; + } catch (error) { + if (error.name !== 'AbortError') { + console.log('Search error:', error.message); + } + return []; + } + } + + function createMenuItems(results) { // Clear existing menu menu.innerHTML = ''; menuItems = []; - // Load links from "mine" div only - links = loadLinksFromMine(); + if (results.length === 0 && searchMode) { + const noResultsDiv = document.createElement('div'); + noResultsDiv.textContent = t('noResults'); + noResultsDiv.style.padding = '12px'; + noResultsDiv.style.color = '#999'; + noResultsDiv.style.textAlign = 'center'; + noResultsDiv.style.fontStyle = 'italic'; + menu.appendChild(noResultsDiv); + return; + } - links.forEach((link, index) => { + // Add section headers and items + results.forEach((item) => { const menuItem = document.createElement('div'); - menuItem.textContent = link.text; - menuItem.style.padding = '8px 12px'; + menuItem.style.padding = '10px 12px'; menuItem.style.cursor = 'pointer'; menuItem.style.borderRadius = '4px'; - menuItem.style.marginBottom = '2px'; + menuItem.style.marginBottom = '4px'; menuItem.style.backgroundColor = '#f5f5f5'; menuItem.style.border = '1px solid transparent'; - menuItem.style.whiteSpace = 'nowrap'; - menuItem.style.overflow = 'hidden'; - menuItem.style.textOverflow = 'ellipsis'; - menuItem.dataset.url = link.url; - menuItem.title = link.url; // Show full URL on hover + menuItem.style.transition = 'background-color 0.2s'; + menuItem.dataset.url = item.url; + + const titleDiv = document.createElement('div'); + titleDiv.textContent = item.text; + titleDiv.style.fontWeight = '500'; + titleDiv.style.whiteSpace = 'nowrap'; + titleDiv.style.overflow = 'hidden'; + titleDiv.style.textOverflow = 'ellipsis'; + menuItem.appendChild(titleDiv); + + if (item.description) { + const descDiv = document.createElement('div'); + descDiv.textContent = item.description; + descDiv.style.fontSize = '12px'; + descDiv.style.color = '#666'; + descDiv.style.marginTop = '2px'; + descDiv.style.whiteSpace = 'nowrap'; + descDiv.style.overflow = 'hidden'; + descDiv.style.textOverflow = 'ellipsis'; + menuItem.appendChild(descDiv); + } + + menuItem.title = item.url; menuItem.addEventListener('click', function() { - window.location.href = link.url; + window.location.href = item.url; + }); + + menuItem.addEventListener('mouseenter', function() { + currentSelection = menuItems.indexOf(menuItem) + 1; + updateSelection(); }); menu.appendChild(menuItem); @@ -158,6 +266,65 @@ }); } + function createMenu() { + // Load and display quick links + links = loadLinksFromMine(); + createMenuItems(links); + searchMode = false; + } + + async function updateSearchResults(query) { + if (!query || query.length < 2) { + createMenuItems(links); + searchMode = false; + return; + } + + searchMode = true; + currentSelection = 0; + + // Show searching state + menu.innerHTML = ''; + const searchingDiv = document.createElement('div'); + searchingDiv.textContent = t('searching'); + searchingDiv.style.padding = '12px'; + searchingDiv.style.color = '#999'; + searchingDiv.style.textAlign = 'center'; + menu.appendChild(searchingDiv); + + const results = await fetchSearchResults(query); + + if (results.length > 0) { + createMenuItems(results); + searchResults = results; + } else { + // Show no results or fallback message + menu.innerHTML = ''; + const noResultsDiv = document.createElement('div'); + if (searchResults.length === 0) { + // Try pressing Enter to search on full Innsida search page + const infoDiv = document.createElement('div'); + infoDiv.textContent = t('noResults'); + infoDiv.style.padding = '12px'; + infoDiv.style.color = '#999'; + infoDiv.style.textAlign = 'center'; + infoDiv.style.fontSize = '12px'; + infoDiv.style.marginBottom = '8px'; + menu.appendChild(infoDiv); + + const hintDiv = document.createElement('div'); + hintDiv.textContent = t('enterToSearchAll'); + hintDiv.style.padding = '8px'; + hintDiv.style.color = '#aaa'; + hintDiv.style.textAlign = 'center'; + hintDiv.style.fontSize = '11px'; + hintDiv.style.fontStyle = 'italic'; + menu.appendChild(hintDiv); + } + } + updateSelection(); + } + function updateSelection() { // Reset all styles inputBox.style.borderColor = '#007acc'; @@ -187,7 +354,11 @@ container.style.display = 'none'; inputBox.value = ''; currentSelection = 0; + searchMode = false; updateSelection(); + if (abortController) { + abortController.abort(); + } } function showContainer() { @@ -208,6 +379,21 @@ } }); + // Real-time search on input + inputBox.addEventListener('input', function(event) { + const query = event.target.value.trim(); + + // Clear existing debounce timeout + if (searchDebounceTimeout) { + clearTimeout(searchDebounceTimeout); + } + + // Debounce search requests (300ms delay) + searchDebounceTimeout = setTimeout(() => { + updateSearchResults(query); + }, 300); + }); + // Handle navigation and actions document.addEventListener('keydown', function(event) { // Only handle keys when container is visible