diff --git a/src/plugins/built-in/globalSearch/src/components/SearchBar.svelte b/src/plugins/built-in/globalSearch/src/components/SearchBar.svelte
index 82c7811f..c0acdd89 100644
--- a/src/plugins/built-in/globalSearch/src/components/SearchBar.svelte
+++ b/src/plugins/built-in/globalSearch/src/components/SearchBar.svelte
@@ -6,13 +6,13 @@
import { type StaticCommandItem } from '../core/commands';
import type { CombinedResult } from '../core/types';
import { createSearchIndexes, performSearch as doSearch } from '../search/searchUtils';
- import { highlightMatch, highlightSnippet, stripHtmlButKeepHighlights } from '../utils/highlight';
import Fuse from 'fuse.js';
import Calculator from './Calculator.svelte';
import { actionMap } from '../indexing/actions';
import type { IndexItem } from '../indexing/types';
import debounce from 'lodash/debounce';
import { renderComponentMap } from '../indexing/renderComponents';
+ import HighlightedText from '../utils/HighlightedText.svelte';
const {
transparencyEffects,
@@ -279,7 +279,7 @@
>
@@ -310,7 +310,7 @@
{dynamicItem.metadata?.icon || '\ue924'}
- {@html stripHtmlButKeepHighlights(highlightMatch(dynamicItem.text, searchTerm, result.matches))}
+
{dynamicItem.category}
@@ -318,7 +318,7 @@
{#if dynamicItem.content}
- {@html stripHtmlButKeepHighlights(highlightSnippet(dynamicItem.content, searchTerm, result.matches))}
+
{/if}
diff --git a/src/plugins/built-in/globalSearch/src/components/items/AssessmentItem.svelte b/src/plugins/built-in/globalSearch/src/components/items/AssessmentItem.svelte
index 2c3bff15..55b88777 100644
--- a/src/plugins/built-in/globalSearch/src/components/items/AssessmentItem.svelte
+++ b/src/plugins/built-in/globalSearch/src/components/items/AssessmentItem.svelte
@@ -1,5 +1,5 @@
\ No newline at end of file
diff --git a/src/plugins/built-in/globalSearch/src/indexing/jobs/subjects.ts b/src/plugins/built-in/globalSearch/src/indexing/jobs/subjects.ts
index d5d8909d..e480db49 100755
--- a/src/plugins/built-in/globalSearch/src/indexing/jobs/subjects.ts
+++ b/src/plugins/built-in/globalSearch/src/indexing/jobs/subjects.ts
@@ -28,11 +28,10 @@ export const subjectsJob: Job = {
// Boost for active subjects
if (item.metadata?.isActive) {
score += 15; // Boost for active subjects
+ console.log("active subject:", item.metadata.subjectName);
+ } else {
+ console.log("inactive subject:", item.metadata.subjectName);
}
-
- // Boost for year level
- const yearLevel = item.metadata?.yearLevel || 0;
- score += yearLevel;
return score;
},
@@ -81,9 +80,7 @@ export const subjectsJob: Job = {
const id = `${semester.code}-${subject.code}-${subject.metaclass}`;
if (existingIds.has(id)) continue;
- // Extract year level from subject code (assuming format like "YEAR10" or "10ENG")
- const yearLevel = subject.code.match(/^YEAR(\d+)|^(\d+)/i)?.[1] || subject.code.match(/^(\d+)/)?.[1] || 0;
- const isActive = subject.active === 1;
+ const isActive = semester.active === 1;
// Create two items for each subject - one for assessments and one for course
items.push(
@@ -101,7 +98,6 @@ export const subjectsJob: Job = {
semesterCode: semester.code,
semesterDescription: semester.description,
type: "assessments",
- yearLevel: yearLevel ? Number(yearLevel) : 0,
isActive
},
actionId: "subjectassessment",
@@ -121,7 +117,6 @@ export const subjectsJob: Job = {
semesterCode: semester.code,
semesterDescription: semester.description,
type: "course",
- yearLevel: yearLevel ? Number(yearLevel) : 0,
isActive
},
actionId: "subjectcourse",
diff --git a/src/plugins/built-in/globalSearch/src/indexing/renderComponents.ts b/src/plugins/built-in/globalSearch/src/indexing/renderComponents.ts
index 7c958e7b..633fa49c 100644
--- a/src/plugins/built-in/globalSearch/src/indexing/renderComponents.ts
+++ b/src/plugins/built-in/globalSearch/src/indexing/renderComponents.ts
@@ -2,15 +2,10 @@ import type { SvelteComponent } from "svelte";
import AssessmentItem from "../components/items/AssessmentItem.svelte";
import ForumItem from "../components/items/ForumItem.svelte";
import SubjectItem from "../components/items/SubjectItem.svelte";
-import type { IndexItem } from "./types";
-import { highlightMatch } from "../utils/highlight";
-// import other components as needed
export const renderComponentMap: Record
= {
assessment: AssessmentItem as unknown as typeof SvelteComponent,
message: AssessmentItem as unknown as typeof SvelteComponent,
forum: ForumItem as unknown as typeof SvelteComponent,
subject: SubjectItem as unknown as typeof SvelteComponent,
- // subject: SubjectComponent,
- // etc...
};
\ No newline at end of file
diff --git a/src/plugins/built-in/globalSearch/src/search/searchUtils.ts b/src/plugins/built-in/globalSearch/src/search/searchUtils.ts
index ee9ec6ee..1b286875 100644
--- a/src/plugins/built-in/globalSearch/src/search/searchUtils.ts
+++ b/src/plugins/built-in/globalSearch/src/search/searchUtils.ts
@@ -25,10 +25,6 @@ export function createSearchIndexes() {
{ name: "text", weight: 2 },
{ name: "content", weight: 1 },
{ name: "category", weight: 1 },
- { name: "metadata.subjectName", weight: 3 },
- { name: "metadata.subjectCode", weight: 2.5 },
- { name: "metadata.semesterDescription", weight: 1 },
- { name: "metadata.yearLevel", weight: 1.5 }
],
includeScore: true,
includeMatches: true,
@@ -117,12 +113,6 @@ export function searchDynamicItems(
let score = fuseScore;
- // apply boost criteria if it exists
- const boost = jobs[item.category].boostCriteria?.(item, query);
- if (boost) {
- score += boost;
- }
-
const ageInDays = (now - item.dateAdded) / (1000 * 60 * 60 * 24);
const recencyBoost = sortByRecent ? 1 / (ageInDays + 1) : 0;
score += recencyBoost;
@@ -196,10 +186,18 @@ export async function performSearch(
if (!seenIds.has(id)) {
// This is a semantic match that Fuse missed - add it with the vector similarity as score
+ let score = v.similarity * 0.5; // High base score for semantic matches
+ const job = jobs[v.object.category];
+ if (job && typeof job.boostCriteria === 'function') {
+ const boost = job.boostCriteria(v.object, query);
+ if (boost) {
+ score += boost;
+ }
+ }
resultMap.set(id, {
id,
type: "dynamic" as const,
- score: v.similarity * 0.9, // High base score for semantic matches
+ score,
item: v.object,
});
}
diff --git a/src/plugins/built-in/globalSearch/src/utils/HighlightedText.svelte b/src/plugins/built-in/globalSearch/src/utils/HighlightedText.svelte
new file mode 100644
index 00000000..ce4410cd
--- /dev/null
+++ b/src/plugins/built-in/globalSearch/src/utils/HighlightedText.svelte
@@ -0,0 +1,80 @@
+
+
+
+ {#each segments as segment}
+ {#if segment.highlight}
+ {segment.text}
+ {:else}
+ {segment.text}
+ {/if}
+ {/each}
+
\ No newline at end of file
diff --git a/src/plugins/built-in/globalSearch/src/utils/highlight.ts b/src/plugins/built-in/globalSearch/src/utils/highlight.ts
deleted file mode 100644
index 9c2e3ca9..00000000
--- a/src/plugins/built-in/globalSearch/src/utils/highlight.ts
+++ /dev/null
@@ -1,274 +0,0 @@
-import type { FuseResultMatch, MatchIndices } from "../core/types";
-
-/**
- * Simple utility to remove HTML tags from a string.
- */
-export function stripHtmlTags(html: string): string {
- if (!html) return "";
- return html.replace(/<[^>]*>/g, "").replace("\n", " ");
-}
-
-/**
- * Removes HTML tags from a string, but preserves tags.
- */
-export function stripHtmlButKeepHighlights(html: string): string {
- if (!html) return "";
- // Use a placeholder for highlight tags, strip others, then restore placeholders.
- const highlightOpenPlaceholder = "__HIGHLIGHT_OPEN__";
- const highlightClosePlaceholder = "__HIGHLIGHT_CLOSE__";
-
- let processed = html.replace(
- //g,
- highlightOpenPlaceholder,
- );
- processed = processed.replace(/<\/span>/g, (match, offset, fullString) => {
- // Only replace if it likely corresponds to our highlight span
- // This is imperfect but helps avoid replacing unrelated spans.
- // Look backwards for the nearest opening placeholder.
- const lastPlaceholder = fullString.lastIndexOf(
- highlightOpenPlaceholder,
- offset,
- );
- if (lastPlaceholder !== -1) {
- // Check if there's another opening tag between the placeholder and the closing span
- const interveningContent = fullString.substring(
- lastPlaceholder + highlightOpenPlaceholder.length,
- offset,
- );
- if (!/ if unsure
- });
-
- // Strip all remaining HTML tags
- processed = processed.replace(/<[^>]*>/g, "");
-
- // Restore the highlight tags
- processed = processed.replace(
- new RegExp(highlightOpenPlaceholder, "g"),
- '',
- );
- processed = processed.replace(
- new RegExp(highlightClosePlaceholder, "g"),
- "",
- );
-
- return processed;
-}
-
-export function highlightMatch(
- text: string,
- term: string,
- matches?: readonly FuseResultMatch[],
-): string {
- if (!term.trim() || !matches || matches.length === 0) return text;
-
- try {
- // Find matches for the text field or allContent that contains the text
- const fieldMatches = matches.find(
- (match) =>
- match.key === "text" ||
- (match.key === "allContent" && match.value?.includes(text)),
- );
-
- if (
- !fieldMatches ||
- !fieldMatches.indices ||
- fieldMatches.indices.length === 0
- ) {
- return text;
- }
-
- // Create a map of character positions to mark which ones need highlighting
- const highlightMap = new Array(text.length).fill(false);
-
- fieldMatches.indices.forEach((indices: MatchIndices) => {
- const start = indices[0];
- const end = indices[1];
-
- if (fieldMatches.key === "allContent") {
- // Find where our text appears in the allContent
- const allContent = fieldMatches.value;
- const textPos = allContent?.indexOf(text) ?? -1;
-
- // Only highlight if the match overlaps with our text
- if (textPos >= 0) {
- // Adjust start and end to be relative to our text field
- const relStart = start - textPos;
- const relEnd = end - textPos;
-
- // Only highlight if the match actually overlaps with our text field
- if (relEnd >= 0 && relStart < text.length) {
- // Mark the overlapping characters
- for (
- let i = Math.max(0, relStart);
- i <= Math.min(text.length - 1, relEnd);
- i++
- ) {
- highlightMap[i] = true;
- }
- }
- }
- } else {
- // Regular text field match - ensure indices are within bounds
- if (start >= 0 && end < text.length) {
- for (let i = start; i <= end; i++) {
- highlightMap[i] = true;
- }
- }
- }
- });
-
- let result = "";
- let inHighlight = false;
-
- for (let i = 0; i < text.length; i++) {
- if (highlightMap[i] && !inHighlight) {
- result += '';
- inHighlight = true;
- } else if (!highlightMap[i] && inHighlight) {
- result += "";
- inHighlight = false;
- }
-
- result += text.charAt(i);
- }
-
- if (inHighlight) {
- result += "";
- }
-
- return result;
- } catch (e) {
- console.error("Error highlighting match:", e);
- return text;
- }
-}
-
-// Function to extract and highlight content snippet using Fuse matches
-export function highlightSnippet(
- content: string,
- term: string,
- matches?: readonly FuseResultMatch[],
-): string {
- if (!content || !term.trim() || !matches || matches.length === 0)
- return content;
-
- try {
- // Find matches for content field or allContent that contains the content
- const contentMatches = matches.find(
- (match) =>
- match.key === "content" ||
- (match.key === "allContent" && match.value?.includes(content)),
- );
-
- if (
- !contentMatches ||
- !contentMatches.indices ||
- contentMatches.indices.length === 0
- ) {
- // No content matches, return plain content
- return content.length > 100 ? content.substring(0, 100) + "..." : content;
- }
-
- // Find the match indices
- let allIndices: MatchIndices[] = contentMatches.indices as MatchIndices[];
-
- // If matching against allContent, adjust indices to be relative to content
- if (contentMatches.key === "allContent") {
- const allContent = contentMatches.value;
- const contentPos = allContent?.indexOf(content) ?? -1;
-
- if (contentPos >= 0) {
- // Adjust indices to be relative to the content field
- allIndices = allIndices
- .map(
- (indices) =>
- [
- indices[0] - contentPos,
- indices[1] - contentPos,
- ] as MatchIndices,
- )
- .filter((indices) => indices[1] >= 0 && indices[0] < content.length);
- }
- }
-
- if (allIndices.length === 0) {
- return content.length > 100 ? content.substring(0, 100) + "..." : content;
- }
-
- // Find a good center point for our snippet (average of first match)
- const firstMatch = allIndices[0];
- const matchCenter = Math.floor((firstMatch[0] + firstMatch[1]) / 2);
-
- // Extract a window around the match
- const windowSize = 100;
- const start = Math.max(0, matchCenter - windowSize / 2);
- const end = Math.min(content.length, matchCenter + windowSize / 2);
-
- // Create the basic snippet
- let snippet = content.substring(start, end);
- if (start > 0) snippet = "..." + snippet;
- if (end < content.length) snippet += "...";
-
- // Create a highlighting map for the snippet
- const snippetLength = snippet.length;
- const highlightMap = new Array(snippetLength).fill(false);
-
- // Calculate offset for the highlighting
- const startOffset = start > 0 ? start - 3 : start; // Account for '...' if present
-
- // Mark each matched character in the snippet
- allIndices.forEach((indices: MatchIndices) => {
- const matchStart = indices[0];
- const matchEnd = indices[1];
-
- // Skip matches outside our snippet window
- if (matchEnd < start || matchStart > end) return;
-
- // Adjust match indices to be relative to snippet
- const snippetMatchStart = Math.max(0, matchStart - startOffset);
- const snippetMatchEnd = Math.min(
- snippetLength - 1,
- matchEnd - startOffset,
- );
-
- // Mark characters for highlighting
- for (let i = snippetMatchStart; i <= snippetMatchEnd; i++) {
- if (i >= 0 && i < snippetLength) {
- highlightMap[i] = true;
- }
- }
- });
-
- // Build the highlighted snippet
- let result = "";
- let inHighlight = false;
-
- for (let i = 0; i < snippetLength; i++) {
- // If highlighting state changes, add appropriate tags
- if (highlightMap[i] && !inHighlight) {
- result += '';
- inHighlight = true;
- } else if (!highlightMap[i] && inHighlight) {
- result += "";
- inHighlight = false;
- }
-
- // Add the current character
- result += snippet.charAt(i);
- }
-
- // Close highlight tag if we're still in one at the end
- if (inHighlight) {
- result += "";
- }
-
- return result;
- } catch (e) {
- console.error("Error highlighting snippet:", e);
- return content.length > 100 ? content.substring(0, 100) + "..." : content;
- }
-}