feat: added search bar and results page
All checks were successful
Build and Push App Image / build-and-push (push) Successful in 1m34s
All checks were successful
Build and Push App Image / build-and-push (push) Successful in 1m34s
This commit is contained in:
@@ -117,7 +117,7 @@
|
||||
</h5>
|
||||
</div>
|
||||
<div class="col-auto d-flex align-items-center">
|
||||
<div v-if="!selectedNote" class="btn-group me-2 d-none d-md-flex" role="group" aria-label="View mode">
|
||||
<div v-if="!selectedNote || isSearchRoute" class="btn-group me-2 d-none d-md-flex" role="group" aria-label="View mode">
|
||||
<button
|
||||
type="button"
|
||||
class="btn action-button"
|
||||
@@ -140,7 +140,7 @@
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
v-if="canEditNotes && selectedNote && !isEditingNote"
|
||||
v-if="canEditNotes && selectedNote && !isEditingNote && !isSearchRoute"
|
||||
class="btn btn-outline-secondary me-2 action-button"
|
||||
aria-label="Edit note"
|
||||
title="Edit note"
|
||||
@@ -150,7 +150,7 @@
|
||||
<span class="action-label">Edit Note</span>
|
||||
</button>
|
||||
<button
|
||||
v-if="canShareSelectedNote && !isEditingNote"
|
||||
v-if="canShareSelectedNote && !isEditingNote && !isSearchRoute"
|
||||
class="btn btn-outline-primary me-2 action-button"
|
||||
:aria-label="shareCopied ? 'Link copied' : 'Share note'"
|
||||
:title="shareCopied ? 'Link copied' : 'Share note'"
|
||||
@@ -169,8 +169,18 @@
|
||||
|
||||
<!-- Note Editor or Note List -->
|
||||
<div class="content p-4">
|
||||
<SearchResultsPage
|
||||
v-if="isSearchRoute"
|
||||
:notes="searchResults"
|
||||
:query="searchQuery"
|
||||
:current-page="searchPage"
|
||||
:page-size="searchPageSize"
|
||||
:view-mode="noteViewMode"
|
||||
@select-note="selectSearchResultNote"
|
||||
@page-change="setSearchPage"
|
||||
/>
|
||||
<NoteEditor
|
||||
v-if="selectedNote && isEditingNote"
|
||||
v-else-if="selectedNote && isEditingNote"
|
||||
:note="selectedNote"
|
||||
:category-options="categoryOptions"
|
||||
:can-delete="canDeleteNotes"
|
||||
@@ -278,6 +288,7 @@ import CategoryTree from "./components/CategoryTree.vue";
|
||||
import NoteEditor from "./components/NoteEditor.vue";
|
||||
import NoteViewer from "./components/NoteViewer.vue";
|
||||
import NoteList from "./components/NoteList.vue";
|
||||
import SearchResultsPage from "./components/SearchResultsPage.vue";
|
||||
import CreateSpaceModal from "./components/CreateSpaceModal.vue";
|
||||
import CreateCategoryModal from "./components/CreateCategoryModal.vue";
|
||||
import CreateNoteModal from "./components/CreateNoteModal.vue";
|
||||
@@ -319,10 +330,20 @@ const unlockingNote = ref(false);
|
||||
|
||||
const currentUser = computed(() => authStore.user);
|
||||
const isAdminRoute = computed(() => route.path === "/admin");
|
||||
const isSearchRoute = computed(() => route.path === "/search");
|
||||
const isPublicRoute = computed(() => route.path.startsWith("/s/"));
|
||||
const isAuthRoute = computed(() => route.path === "/login" || route.path === "/register");
|
||||
const spaces = computed(() => spaceStore.spaces);
|
||||
const currentSpace = computed(() => spaceStore.currentSpace);
|
||||
const searchResults = computed(() => sortNotesByPriority(spaceStore.searchResults));
|
||||
const searchPageSize = 12;
|
||||
const searchPage = computed(() => {
|
||||
const pageValue = Number.parseInt(route.query.page || "1", 10);
|
||||
if (Number.isNaN(pageValue) || pageValue < 1) {
|
||||
return 1;
|
||||
}
|
||||
return pageValue;
|
||||
});
|
||||
const categoryTree = computed(() => spaceStore.categoryTree);
|
||||
const canCreateSpaces = computed(() => authStore.hasPermission("space.create"));
|
||||
const canCreateCategories = computed(() => authStore.hasSpacePermission(currentSpace.value, "category.create"));
|
||||
@@ -382,7 +403,7 @@ const canLoadMoreMainNotes = computed(() => {
|
||||
if (selectedCategory.value || selectedNote.value) {
|
||||
return false;
|
||||
}
|
||||
if (searchQuery.value.trim()) {
|
||||
if (isSearchRoute.value) {
|
||||
return false;
|
||||
}
|
||||
return spaceStore.notesHasMore;
|
||||
@@ -411,6 +432,10 @@ const openSpaceHome = () => {
|
||||
unlockPassword.value = "";
|
||||
unlockError.value = "";
|
||||
searchQuery.value = "";
|
||||
spaceStore.clearSearchResults();
|
||||
if (route.path !== "/") {
|
||||
router.push("/");
|
||||
}
|
||||
if (currentSpace.value?.id) {
|
||||
spaceStore.fetchNotes(currentSpace.value.id, { reset: true });
|
||||
}
|
||||
@@ -425,6 +450,21 @@ const breadcrumbItems = computed(() => {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (isSearchRoute.value) {
|
||||
return [
|
||||
{
|
||||
label: currentSpace.value.name,
|
||||
clickable: true,
|
||||
onClick: openSpaceHome,
|
||||
},
|
||||
{
|
||||
label: searchQuery.value.trim() ? `Search: ${searchQuery.value.trim()}` : "Search",
|
||||
clickable: false,
|
||||
onClick: null,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
const items = [
|
||||
{
|
||||
label: currentSpace.value.name,
|
||||
@@ -527,6 +567,30 @@ watch(
|
||||
},
|
||||
);
|
||||
|
||||
watch(
|
||||
[() => route.path, () => route.query.q, () => currentSpace.value?.id],
|
||||
async ([path, routeQuery, spaceId]) => {
|
||||
if (path !== "/search") {
|
||||
return;
|
||||
}
|
||||
|
||||
selectedNote.value = null;
|
||||
selectedCategory.value = null;
|
||||
isEditingNote.value = false;
|
||||
|
||||
const q = typeof routeQuery === "string" ? routeQuery.trim() : "";
|
||||
searchQuery.value = q;
|
||||
|
||||
if (!spaceId || !q) {
|
||||
spaceStore.clearSearchResults();
|
||||
return;
|
||||
}
|
||||
|
||||
await spaceStore.searchNotes(q);
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
watch(
|
||||
() => selectedNote.value?.id,
|
||||
() => {
|
||||
@@ -683,10 +747,52 @@ const selectCategory = (category) => {
|
||||
};
|
||||
|
||||
const performSearch = async () => {
|
||||
if (searchQuery.value.trim()) {
|
||||
await spaceStore.searchNotes(searchQuery.value);
|
||||
} else if (currentSpace.value?.id) {
|
||||
await spaceStore.fetchNotes(currentSpace.value.id, { reset: true });
|
||||
const q = searchQuery.value.trim();
|
||||
if (!q) {
|
||||
spaceStore.clearSearchResults();
|
||||
if (route.path !== "/") {
|
||||
await router.push("/");
|
||||
}
|
||||
if (currentSpace.value?.id) {
|
||||
await spaceStore.fetchNotes(currentSpace.value.id, { reset: true });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (route.path !== "/search" || route.query.q !== q || route.query.page !== "1") {
|
||||
await router.push({
|
||||
path: "/search",
|
||||
query: {
|
||||
q,
|
||||
page: "1",
|
||||
},
|
||||
});
|
||||
} else {
|
||||
await spaceStore.searchNotes(q);
|
||||
}
|
||||
};
|
||||
|
||||
const setSearchPage = async (page) => {
|
||||
const q = typeof route.query.q === "string" ? route.query.q : "";
|
||||
if (!q) {
|
||||
return;
|
||||
}
|
||||
await router.push({
|
||||
path: "/search",
|
||||
query: {
|
||||
q,
|
||||
page: String(page),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const selectSearchResultNote = async (note) => {
|
||||
if (!note) {
|
||||
return;
|
||||
}
|
||||
await selectNote(note);
|
||||
if (route.path === "/search") {
|
||||
router.push("/");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user