import NoteViewerEditor from "./NoteViewerEditor";
import RecentlyModified from "./RecentlyModified";
import SearchInput from "./SearchInput";
-import { SearchResult } from "../classes";
-import api from "../api";
+import SearchResults from "./SearchResults";
export default {
name: "App",
SearchInput,
Logo,
NoteViewerEditor,
+ SearchResults,
},
data: function() {
- return constants.dataDefaults();
+ return {
+ views: {
+ login: 0,
+ home: 1,
+ note: 2,
+ search: 3,
+ },
+ currentView: 1,
+
+ noteTitle: null,
+ searchTerm: null,
+ };
},
methods: {
else if (basePath == constants.basePaths.search) {
this.updateDocumentTitle("Search");
this.searchTerm = helpers.getSearchParam(constants.params.searchTerm);
- this.getSearchResults();
this.currentView = this.views.search;
}
window.open(href);
} else {
history.pushState(null, "", href);
- this.resetData();
+ this.noteTitle = null;
+ this.searchTerm = null;
this.route();
}
},
- resetData: function() {
- Object.assign(this.$data, constants.dataDefaults());
- },
-
updateDocumentTitle: function(suffix) {
window.document.title = (suffix ? `${suffix} - ` : "") + "flatnotes";
},
this.navigate(`/${constants.basePaths.login}`);
},
- getSearchResults: function() {
- let parent = this;
- this.searchFailed = false;
- api
- .get("/api/search", { params: { term: this.searchTerm } })
- .then(function(response) {
- parent.searchResults = [];
- if (response.data.length == 0) {
- parent.searchFailedIcon = "search";
- parent.searchFailedMessage = "No Results";
- parent.searchFailed = true;
- } else {
- response.data.forEach(function(result) {
- parent.searchResults.push(
- new SearchResult(
- result.title,
- result.lastModified,
- result.titleHighlights,
- result.contentHighlights
- )
- );
- });
- }
- })
- .catch(function(error) {
- if (!error.handled) {
- parent.searchFailed = true;
- parent.unhandledServerErrorToast();
- }
- });
- },
-
newNote: function() {
this.navigate(`/${constants.basePaths.new}`);
},
},
focusSearchInput: function() {
- document.getElementById("search-input").focus();
+ let input = document.getElementById("search-input");
+ input.focus();
+ input.select();
},
openSearch: function() {
- if (this.currentView == this.views.home) {
+ if ([this.views.home, this.views.search].includes(this.currentView)) {
this.focusSearchInput();
EventBus.$emit("highlight-search-input");
} else if (this.currentView != this.views.login) {
<!-- Nav Bar -->
<NavBar
v-if="currentView != views.login"
- class="mt-3 w-100"
+ class="w-100 mb-5"
:show-logo="currentView != views.home"
@navigate-home="navigate('/')"
@new-note="newNote()"
"
>
<Logo class="mb-3"></Logo>
- <SearchInput class="search-input mb-4"></SearchInput>
+ <SearchInput
+ :initial-value="searchTerm"
+ class="search-input mb-4"
+ ></SearchInput>
<RecentlyModified class="recently-modified"></RecentlyModified>
</div>
<!-- Search Results -->
- <div v-if="currentView == views.search" class="w-100 pt-5 flex-grow-1">
- <!-- Searching -->
- <div
- v-if="searchResults == null || searchResults.length == 0"
- class="h-100 d-flex flex-column justify-content-center"
- >
- <LoadingIndicator
- :failed="searchFailed"
- :failedBootstrapIcon="searchFailedIcon"
- :failedMessage="searchFailedMessage"
- />
- </div>
-
- <!-- Search Results Loaded -->
- <div v-else>
- <div v-for="result in searchResults" :key="result.title" class="mb-5">
- <p class="h5 text-center clickable-link">
- <a
- v-html="result.titleHighlightsOrTitle"
- :href="result.href"
- @click.prevent="navigate(result.href, $event)"
- ></a>
- </p>
- <p
- class="text-center text-muted"
- v-html="result.contentHighlights"
- ></p>
- </div>
- </div>
+ <div
+ v-if="currentView == views.search"
+ class="flex-grow-1 search-results-view"
+ >
+ <SearchInput
+ :initial-value="searchTerm"
+ class="search-input mb-4"
+ ></SearchInput>
+ <SearchResults :search-term="searchTerm" class="h-100"></SearchResults>
</div>
<!-- Note -->
<div>
<div v-if="showLoader && !failed" class="loader"></div>
<div v-else-if="failed" class="d-flex flex-column align-items-center">
- <b-icon class="failed-icon mb-3" :icon="failedBootstrapIcon"></b-icon>
+ <b-icon
+ class="failed-icon mb-3"
+ :icon="failedBootstrapIcon || 'cloud-slash'"
+ ></b-icon>
<p>{{ failedMessage }}</p>
</div>
</div>
<style lang="scss" scoped>
@import "../colours";
+p {
+ color: $muted-text;
+}
+
.failed-icon {
color: $logo-key-colour;
font-size: 60px;
props: {
showLoader: { type: Boolean, default: true },
failed: { type: Boolean },
- failedBootstrapIcon: { type: String, default: "cloud-slash" },
+ failedBootstrapIcon: { type: String },
failedMessage: { type: String, default: "Loading Failed" },
},
};
<template>
<!-- Note -->
- <div class="pt-5 pb-4">
+ <div class="pb-4">
<!-- Loading -->
<div
v-if="currentNote == null"
import EventBus from "../eventBus";
export default {
+ props: { initialValue: { type: String } },
+
data: function () {
return {
searchTermInput: null,
};
},
+ watch: {
+ initialValue: function () {
+ this.init();
+ },
+ },
+
methods: {
search: function () {
if (this.searchTermInput) {
parent.includeHighlightClass = false;
}, 1500);
},
+
+ init: function () {
+ this.searchTermInput = this.initialValue;
+ },
},
created: function () {
EventBus.$on("highlight-search-input", this.highlightSearchInput);
+ this.init();
},
};
</script>
--- /dev/null
+<template>
+ <div>
+ <!-- Searching -->
+ <div
+ v-if="searchResults == null || searchResults.length == 0"
+ class="h-100 d-flex flex-column justify-content-center"
+ >
+ <LoadingIndicator
+ :failed="searchFailed"
+ :failedBootstrapIcon="searchFailedIcon"
+ :failedMessage="searchFailedMessage"
+ />
+ </div>
+
+ <!-- Search Results Loaded -->
+ <div v-else>
+ <div v-for="result in searchResults" :key="result.title" class="mb-4">
+ <p class="font-weight-bold mb-0">
+ <a
+ v-html="result.titleHighlightsOrTitle"
+ :href="result.href"
+ @click.prevent="openNote(result.href)"
+ ></a>
+ </p>
+ <p class="result-contents" v-html="result.contentHighlights"></p>
+ </div>
+ </div>
+ </div>
+</template>
+
+<style lang="scss">
+@import "../colours";
+
+.result-contents {
+ color: $muted-text;
+}
+.match {
+ font-weight: bold;
+ color: $logo-key-colour;
+}
+</style>
+
+<script>
+import EventBus from "../eventBus";
+import LoadingIndicator from "./LoadingIndicator";
+import { SearchResult } from "../classes";
+import api from "../api";
+
+export default {
+ components: {
+ LoadingIndicator,
+ },
+
+ props: {
+ searchTerm: { type: String, required: true },
+ },
+
+ data: function () {
+ return {
+ searchFailed: false,
+ searchFailedMessage: "Failed to load Search Results",
+ searchFailedIcon: null,
+ searchResults: null,
+ };
+ },
+
+ watch: {
+ searchTerm: function () {
+ this.init();
+ },
+ },
+
+ methods: {
+ getSearchResults: function () {
+ let parent = this;
+ this.searchFailed = false;
+ api
+ .get("/api/search", { params: { term: this.searchTerm } })
+ .then(function (response) {
+ parent.searchResults = [];
+ if (response.data.length == 0) {
+ parent.searchFailedIcon = "search";
+ parent.searchFailedMessage = "No Results";
+ parent.searchFailed = true;
+ } else {
+ response.data.forEach(function (result) {
+ parent.searchResults.push(
+ new SearchResult(
+ result.title,
+ result.lastModified,
+ result.titleHighlights,
+ result.contentHighlights
+ )
+ );
+ });
+ }
+ })
+ .catch(function (error) {
+ if (!error.handled) {
+ parent.searchFailed = true;
+ EventBus.$emit("unhandledServerError");
+ }
+ });
+ },
+
+ openNote: function (href) {
+ EventBus.$emit("navigate", href);
+ },
+
+ init: function () {
+ this.getSearchResults();
+ },
+ },
+
+ created: function () {
+ this.init();
+ },
+};
+</script>
// Params
export const params = { searchTerm: "term", redirect: "redirect" };
-
-// Initial State
-export const dataDefaults = function() {
- return {
- // Views
- views: {
- login: 0,
- home: 1,
- note: 2,
- search: 3,
- },
-
- // State
- currentView: 1,
-
- // Note Data
- noteTitle: null,
-
- // Search Result Data
- searchFailed: false,
- searchFailedMessage: "Failed to load Search Results",
- searchFailedIcon: null,
- searchTerm: null,
- searchResults: null,
- };
-};
</head>
-<body class="h-100">
+<body class="h-100 pt-3">
<div id="app"></div>
<script src="index.js"></script>
</body>
font-family: "Inter", sans-serif;
}
-.clickable-link {
- cursor: pointer;
+a {
+ color: inherit;
&:hover {
- font-weight: bold;
- }
- a {
text-decoration: none;
- color: inherit;
+ filter: opacity(70%);
}
}
max-width: 500px;
}
+.search-results-view {
+ max-width: 700px;
+}
+
.search-input {
box-shadow: 0 0 20px $drop-shadow;
}
}
}
.bttn:hover {
- background-color: $button-background
+ background-color: $button-background;
}