Skip to content
Draft
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
8 changes: 8 additions & 0 deletions client/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,11 @@ export function loadTheme() {
setDarkThemeOn(false);
}
}

export function getViewMode() {
return localStorage.getItem("viewMode") || "list";
}

export function setViewMode(mode) {
localStorage.setItem("viewMode", mode);
}
127 changes: 103 additions & 24 deletions client/views/SearchResults.vue
Original file line number Diff line number Diff line change
@@ -1,42 +1,113 @@
<template>
<div class="flex h-full max-w-[700px] flex-col">
<div
class="flex h-full flex-col"
:class="isGridView ? 'max-w-[1100px]' : 'max-w-[700px]'"
>
<!-- Search Input -->
<SearchInput :initialSearchTerm="props.searchTerm" class="mb-2" />

<LoadingIndicator ref="loadingIndicator" class="flex-1">
<!-- Sort By -->
<div class="flex justify-end">
<!-- Toolbar -->
<div class="mb-1 flex items-center justify-end gap-2">
<!-- View Toggle -->
<div class="flex">
<CustomButton
:iconPath="mdiViewList"
:style="!isGridView ? 'cta' : 'subtle'"
@click="setView('list')"
/>
<CustomButton
:iconPath="mdiViewGrid"
:style="isGridView ? 'cta' : 'subtle'"
@click="setView('grid')"
/>
</div>

<!-- Sort By -->
<CustomButton
:label="`Sort By: ${sortByName}`"
:iconPath="mdiSort"
class="mb-1"
@click="toggleSortMenu"
/>
<PrimeMenu ref="sortMenu" :model="menuItems" :popup="true" />
</div>

<!-- Search Results -->
<!-- List View -->
<div v-if="!isGridView">
<div
v-for="result in results"
class="mb-4 cursor-pointer rounded px-2 py-1 hover:bg-theme-background-elevated"
>
<RouterLink
:to="{ name: 'note', params: { title: result.title } }"
>
<!-- Title and Tags -->
<div>
<span
v-html="result.titleHighlightsOrTitle"
class="mr-2"
></span>
<Tag
v-for="tag in result.tagMatches"
:tag="tag"
class="mr-1"
/>
</div>
<!-- Last Modified and Content Highlights -->
<div>
<span class="text-theme-text-muted">{{
result.lastModifiedAsString
}}</span>
<span v-if="result.contentHighlights"> - </span>
<span
v-html="result.contentHighlights"
class="text-theme-text-muted"
></span>
</div>
</RouterLink>
</div>
</div>

<!-- Grid View -->
<div
v-for="result in results"
class="mb-4 cursor-pointer rounded px-2 py-1 hover:bg-theme-background-elevated"
v-else
class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3"
>
<RouterLink :to="{ name: 'note', params: { title: result.title } }">
<!-- Title and Tags -->
<div>
<span v-html="result.titleHighlightsOrTitle" class="mr-2"></span>
<Tag v-for="tag in result.tagMatches" :tag="tag" class="mr-1" />
</div>
<!-- Last Modified and Content Highlights -->
<div>
<span class="text-theme-text-muted">{{
result.lastModifiedAsString
}}</span>
<span v-if="result.contentHighlights"> - </span>
<span
v-html="result.contentHighlights"
class="text-theme-text-muted"
></span>
<RouterLink
v-for="result in results"
:to="{ name: 'note', params: { title: result.title } }"
class="relative flex h-48 cursor-pointer flex-col overflow-hidden rounded-md border border-theme-border p-4 hover:bg-theme-background-elevated"
>
<!-- Title -->
<h3
v-html="result.titleHighlightsOrTitle"
class="mb-1 truncate font-semibold"
></h3>

<!-- Tags -->
<div
v-if="result.tagMatches?.length"
class="mb-2 flex flex-wrap gap-1"
>
<Tag v-for="tag in result.tagMatches" :tag="tag" />
</div>

<!-- Last Modified -->
<span class="mb-2 text-xs text-theme-text-muted">
{{ result.lastModifiedAsString }}
</span>

<!-- Content Snippet -->
<p
v-if="result.contentHighlights"
v-html="result.contentHighlights"
class="flex-1 overflow-hidden text-sm text-theme-text-muted"
></p>

<!-- Fade overlay -->
<div
class="pointer-events-none absolute inset-x-0 bottom-0 h-10 bg-gradient-to-t from-theme-background to-transparent"
></div>
</RouterLink>
</div>
</LoadingIndicator>
Expand All @@ -48,13 +119,14 @@ import { useToast } from "primevue/usetoast";
import { computed, onMounted, ref, watch } from "vue";
import { useRouter } from "vue-router";

import { mdiMagnify, mdiSort } from "@mdi/js";
import { mdiMagnify, mdiSort, mdiViewGrid, mdiViewList } from "@mdi/js";
import { apiErrorHandler, getNotes } from "../api.js";
import CustomButton from "../components/CustomButton.vue";
import LoadingIndicator from "../components/LoadingIndicator.vue";
import PrimeMenu from "../components/PrimeMenu.vue";
import Tag from "../components/Tag.vue";
import { params, searchSortOptions } from "../constants.js";
import { getViewMode, setViewMode } from "../helpers.js";
import SearchInput from "../partials/SearchInput.vue";

const props = defineProps({
Expand All @@ -70,6 +142,13 @@ const results = ref([]);
const router = useRouter();
const sortMenu = ref();
const toast = useToast();
const viewMode = ref(getViewMode());
const isGridView = computed(() => viewMode.value === "grid");

function setView(mode) {
viewMode.value = mode;
setViewMode(mode);
}

const sortByName = computed(() => {
const sortOptionNames = {
Expand Down