<template>
- <div>
- <div v-if="!hideLoader && !failed" class="loader"></div>
- <div v-else-if="failed" class="flex flex-col items-center">
+ <div :class="{ 'flex items-center justify-center': loadSuccessful !== true }">
+ <!-- Loading -->
+ <div
+ v-if="loadSuccessful === null && !props.hideLoader"
+ class="loader"
+ ></div>
+
+ <!-- Failed -->
+ <div v-else-if="loadSuccessful === false" class="flex flex-col items-center">
<SvgIcon
type="mdi"
:path="failedIconPath"
/>
<span class="text-lg text-theme-text-muted">{{ failedMessage }}</span>
</div>
+
+ <!-- Loaded -->
+ <slot v-else-if="loadSuccessful"></slot>
</div>
</template>
import { mdiTrafficCone } from "@mdi/js";
import { ref } from "vue";
-defineProps({ hideLoader: Boolean });
+const props = defineProps({ hideLoader: Boolean });
-const failed = ref(false);
+const loadSuccessful = ref(null);
const failedIconPath = ref("");
const failedMessage = ref("");
+function setLoading() {
+ loadSuccessful.value = null;
+}
+
function setFailed(message, iconPath) {
- failed.value = true;
failedMessage.value = message || "Loading Failed";
failedIconPath.value = iconPath || mdiTrafficCone;
+ loadSuccessful.value = false;
+}
+
+function setLoaded() {
+ loadSuccessful.value = true;
}
-defineExpose({ setFailed });
+defineExpose({ setLoading, setFailed, setLoaded });
</script>
<style scoped>
<template>
- <div class="h-full">
- <!-- Loading -->
- <LoadingIndicator
- v-if="showLoadingIndicator"
- ref="loadingIndicator"
- class="flex h-full items-center justify-center"
- />
-
- <!-- Loaded -->
- <div v-else class="flex h-full flex-col">
- <!-- Confirm Deletion Modal -->
- <ConfirmModal
- ref="deleteConfirmModal"
- title="Confirm Deletion"
- :message="`Are you sure you want to delete the note '${note.title}'?`"
- isDanger
- @confirm="deleteConfirmedHandler"
- />
-
- <!-- Header -->
- <div class="flex flex-wrap-reverse items-start">
- <!-- Title -->
- <div class="flex flex-1 text-3xl leading-[1.6em]">
- <span v-show="!editMode" class="flex-1 text-nowrap">{{
- note.title
- }}</span>
- <input
- v-show="editMode"
- v-model="newTitle"
- class="flex-1 bg-theme-background outline-none"
- placeholder="Title"
- />
- </div>
-
- <!-- Buttons -->
- <div v-show="!editMode">
- <CustomButton
- :iconPath="mdilDelete"
- label="Delete"
- @click="deleteHandler"
- />
- <CustomButton
- :iconPath="mdilPencil"
- label="Edit"
- @click="editHandler"
- />
- </div>
- <div v-show="editMode">
- <CustomButton
- :iconPath="mdilArrowLeft"
- label="Cancel"
- @click="cancelHandler"
- />
- <CustomButton
- :iconPath="mdilContentSave"
- label="Save"
- @click="saveHandler"
- />
- </div>
+ <!-- Confirm Deletion Modal -->
+ <ConfirmModal
+ ref="deleteConfirmModal"
+ title="Confirm Deletion"
+ :message="`Are you sure you want to delete the note '${note.title}'?`"
+ isDanger
+ @confirm="deleteConfirmedHandler"
+ />
+
+ <LoadingIndicator ref="loadingIndicator" class="flex h-full flex-col">
+ <!-- Header -->
+ <div class="flex flex-wrap-reverse items-start">
+ <!-- Title -->
+ <div class="flex flex-1 text-3xl leading-[1.6em]">
+ <span v-show="!editMode" class="flex-1 text-nowrap">{{
+ note.title
+ }}</span>
+ <input
+ v-show="editMode"
+ v-model="newTitle"
+ class="flex-1 bg-theme-background outline-none"
+ placeholder="Title"
+ />
</div>
- <hr v-if="!editMode" class="my-4 border-theme-border" />
-
- <!-- Content -->
- <div class="flex-1">
- <ToastViewer v-if="!editMode" :initialValue="note.content" />
- <ToastEditor
- v-if="editMode"
- ref="toastEditor"
- :initialValue="note.content"
+ <!-- Buttons -->
+ <div v-show="!editMode">
+ <CustomButton
+ :iconPath="mdilDelete"
+ label="Delete"
+ @click="deleteHandler"
+ />
+ <CustomButton
+ :iconPath="mdilPencil"
+ label="Edit"
+ @click="editHandler"
+ />
+ </div>
+ <div v-show="editMode">
+ <CustomButton
+ :iconPath="mdilArrowLeft"
+ label="Cancel"
+ @click="cancelHandler"
+ />
+ <CustomButton
+ :iconPath="mdilContentSave"
+ label="Save"
+ @click="saveHandler"
/>
</div>
</div>
- </div>
+
+ <hr v-if="!editMode" class="my-4 border-theme-border" />
+
+ <!-- Content -->
+ <div class="flex-1">
+ <ToastViewer v-if="!editMode" :initialValue="note.content" />
+ <ToastEditor
+ v-if="editMode"
+ ref="toastEditor"
+ :initialValue="note.content"
+ />
+ </div>
+ </LoadingIndicator>
</template>
<script setup>
import ToastEditor from "../components/toastui/ToastEditor.vue";
import ToastViewer from "../components/toastui/ToastViewer.vue";
import {
- getUnknownServerErrorToastOptions,
getToastOptions,
+ getUnknownServerErrorToastOptions,
} from "../helpers.js";
const props = defineProps({
const loadingIndicator = ref();
const note = ref({});
const router = useRouter();
-const showLoadingIndicator = ref(true);
const newTitle = ref();
const toast = useToast();
const toastEditor = ref();
return;
}
- showLoadingIndicator.value = true;
+ loadingIndicator.value?.setLoading(); // #CS
if (props.title) {
getNote(props.title)
.then((data) => {
note.value = data;
- showLoadingIndicator.value = false;
+ loadingIndicator.value.setLoaded();
})
.catch((error) => {
if (error.response.status === 404) {
newTitle.value = "";
note.value = new Note();
editMode.value = true;
- showLoadingIndicator.value = false;
+ loadingIndicator.value.setLoaded();
}
}
<template>
- <div class="max-w-[700px]">
+ <div class="flex h-full flex-col">
<!-- Search Input -->
- <SearchInput :initialSearchTerm="props.searchTerm" class="mb-8" />
+ <SearchInput
+ :initialSearchTerm="props.searchTerm"
+ class="mb-8 max-w-[700px]"
+ />
<!-- Search Results -->
- <div
- v-for="result in results"
- class="mb-4 cursor-pointer rounded px-2 py-1 hover:bg-theme-background-tint dark:hover:bg-theme-background-elevated"
- >
- <RouterLink :to="result.href">
- <!-- 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>
+ <LoadingIndicator ref="loadingIndicator" class="max-w-[700px] flex-1">
+ <div
+ v-for="result in results"
+ class="mb-4 cursor-pointer rounded px-2 py-1 hover:bg-theme-background-tint dark:hover:bg-theme-background-elevated"
+ >
+ <RouterLink :to="result.href">
+ <!-- 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>
+ </LoadingIndicator>
</div>
</template>
import { useToast } from "primevue/usetoast";
import { ref, watch } from "vue";
+import { mdiMagnify } from "@mdi/js";
import { getNotes } from "../api.js";
+import LoadingIndicator from "../components/LoadingIndicator.vue";
+import Tag from "../components/Tag.vue";
import { getUnknownServerErrorToastOptions } from "../helpers.js";
import SearchInput from "../partials/SearchInput.vue";
-import Tag from "../components/Tag.vue";
const props = defineProps({ searchTerm: String });
+const loadingIndicator = ref();
const results = ref([]);
const toast = useToast();
function init() {
+ loadingIndicator.value?.setLoading(); // #CS
getNotes(props.searchTerm)
.then((data) => {
results.value = data;
+ if (results.value.length > 0) {
+ loadingIndicator.value.setLoaded();
+ } else {
+ loadingIndicator.value.setFailed("No Results", mdiMagnify);
+ }
})
.catch(() => {
+ loadingIndicator.value.setFailed();
toast.add(getUnknownServerErrorToastOptions());
});
}