From 8bd53d86625df5b1df872de3aeb70462fc96fbd6 Mon Sep 17 00:00:00 2001
From: querwurzel <>
Date: Sat, 11 Nov 2023 15:05:28 +0100
Subject: [PATCH] add robots.txt, introduce first search draft
---
.../binpastes/paste/api/PasteController.java | 4 +-
frontend/index.html | 1 +
frontend/public/robots.txt | 2 +
frontend/src/App.tsx | 2 +-
frontend/src/api/client.ts | 7 +-
.../components/CreatePaste/CreatePaste.tsx | 4 +-
.../CreatePaste/createPaste.module.css | 2 +-
.../components/RecentPastes/RecentPastes.tsx | 2 +
.../RecentPastes/recentPastes.module.css | 10 ++-
.../components/SearchPastes/SearchPastes.tsx | 67 ++++++++++++++++---
.../SearchPastes/searchPastes.module.css | 61 +++++++++++++++++
frontend/src/components/Spinner/Spinner.tsx | 10 +++
frontend/src/components/Spinner/spinner.css | 56 ++++++++++++++++
frontend/src/pages/Search.tsx | 58 ++++++++++------
14 files changed, 247 insertions(+), 39 deletions(-)
create mode 100644 frontend/public/robots.txt
create mode 100644 frontend/src/components/SearchPastes/searchPastes.module.css
create mode 100644 frontend/src/components/Spinner/Spinner.tsx
create mode 100644 frontend/src/components/Spinner/spinner.css
diff --git a/backend/src/main/java/com/github/binpastes/paste/api/PasteController.java b/backend/src/main/java/com/github/binpastes/paste/api/PasteController.java
index 62c30ef..b70aa56 100644
--- a/backend/src/main/java/com/github/binpastes/paste/api/PasteController.java
+++ b/backend/src/main/java/com/github/binpastes/paste/api/PasteController.java
@@ -29,8 +29,8 @@
import static com.github.binpastes.paste.api.model.ListView.ListItemView;
-@RestController
@Validated
+@RestController
@RequestMapping("/api/v1/paste")
class PasteController {
@@ -83,7 +83,7 @@ public Mono searchPastes(
final String term,
final ServerHttpResponse response
) {
- response.getHeaders().add(HttpHeaders.CACHE_CONTROL, "max-age=300");
+ response.getHeaders().add(HttpHeaders.CACHE_CONTROL, "max-age=60");
return pasteService
.findByFullText(term)
.map(paste -> SearchItemView.of(paste, term))
diff --git a/frontend/index.html b/frontend/index.html
index 713ddcc..b69f8c0 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -8,6 +8,7 @@
+
diff --git a/frontend/public/robots.txt b/frontend/public/robots.txt
new file mode 100644
index 0000000..d346249
--- /dev/null
+++ b/frontend/public/robots.txt
@@ -0,0 +1,2 @@
+User-agent: *
+Disallow: /api/
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 9d3fc62..624d242 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -23,8 +23,8 @@ const App: () => JSX.Element = () => {
-
+
diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts
index 94ba078..e0839d8 100644
--- a/frontend/src/api/client.ts
+++ b/frontend/src/api/client.ts
@@ -56,14 +56,15 @@ const findAll = (): Promise> => {
const searchAll = (term: string): Promise> => {
const params = new URLSearchParams([['term', term]]);
- const url = new URL('/api/v1/paste/search?' + params.toString(), apiBaseUrl());
+ const url = new URL('/api/v1/paste/search?' + encodeURI(params.toString()), apiBaseUrl());
return fetch(url)
.then(value => value.json())
- .then(value => value.pastes);
+ .then(value => value.pastes)
+ .catch(_ => [])
}
-const deletePaste = (id: string) => {
+const deletePaste = (id: string): Promise => {
const url = new URL('/api/v1/paste/' + id, apiBaseUrl());
return fetch(url, {
diff --git a/frontend/src/components/CreatePaste/CreatePaste.tsx b/frontend/src/components/CreatePaste/CreatePaste.tsx
index a238e76..82af189 100644
--- a/frontend/src/components/CreatePaste/CreatePaste.tsx
+++ b/frontend/src/components/CreatePaste/CreatePaste.tsx
@@ -36,6 +36,7 @@ const CreatePaste: Component = ({onCreatePaste, initialPaste})
const [lastPasteUrl, setLastPasteUrl] = createSignal();
let creationForm: HTMLFormElement
+ let submitInput: HTMLInputElement
const updateFormField = (fieldName: keyof FormModel) => (event: Event) => {
const inputElement = event.currentTarget as HTMLInputElement;
@@ -81,6 +82,7 @@ const CreatePaste: Component = ({onCreatePaste, initialPaste})
resetStore();
setLastPasteUrl(url);
})
+ .catch(e => submitInput.style.backgroundColor = 'red');
}
return (
@@ -153,7 +155,7 @@ const CreatePaste: Component = ({onCreatePaste, initialPaste})
{lastPasteUrl()}
-
+
diff --git a/frontend/src/components/CreatePaste/createPaste.module.css b/frontend/src/components/CreatePaste/createPaste.module.css
index a7019ed..5dfe5d6 100644
--- a/frontend/src/components/CreatePaste/createPaste.module.css
+++ b/frontend/src/components/CreatePaste/createPaste.module.css
@@ -65,7 +65,7 @@
.createForm input[type=submit],
.createForm input[type=reset] {
display: inline-block;
- width: initial;
+ color: var(--color-text);
margin: .5rem;
}
diff --git a/frontend/src/components/RecentPastes/RecentPastes.tsx b/frontend/src/components/RecentPastes/RecentPastes.tsx
index b191b74..039227b 100644
--- a/frontend/src/components/RecentPastes/RecentPastes.tsx
+++ b/frontend/src/components/RecentPastes/RecentPastes.tsx
@@ -76,6 +76,8 @@ const RecentPastes: () => JSX.Element = () => {
↻
+ Search all pastes
+
{item =>
-
diff --git a/frontend/src/components/RecentPastes/recentPastes.module.css b/frontend/src/components/RecentPastes/recentPastes.module.css
index 5a955e7..f085eb1 100644
--- a/frontend/src/components/RecentPastes/recentPastes.module.css
+++ b/frontend/src/components/RecentPastes/recentPastes.module.css
@@ -14,7 +14,6 @@
list-style: none;
margin: 0;
padding: 0;
-
}
.recentPastes .item {
@@ -50,3 +49,12 @@
border-bottom: 1px dotted #a5a5a5;
}
}
+
+.searchLink {
+ margin-left: 1rem;
+ margin-bottom: 1rem;
+}
+
+.searchLinkActive {
+ display: none;
+}
\ No newline at end of file
diff --git a/frontend/src/components/SearchPastes/SearchPastes.tsx b/frontend/src/components/SearchPastes/SearchPastes.tsx
index 7bd6f0c..e4f0cc0 100644
--- a/frontend/src/components/SearchPastes/SearchPastes.tsx
+++ b/frontend/src/components/SearchPastes/SearchPastes.tsx
@@ -1,13 +1,25 @@
-import {createResource, createSignal, JSX} from 'solid-js';
+import {Component, createResource, createSignal, JSX, Show} from 'solid-js';
+import {A} from '@solidjs/router';
import ApiClient from '../../api/client';
-import {PasteListView} from '../../api/model/PasteListView';
+import {PasteSearchView} from '../../api/model/PasteSearchView';
+import {toDateTimeString} from '../../datetime/DateTimeUtil';
+import styles from "./searchPastes.module.css";
-const SearchPastes: () => JSX.Element = () => {
+type SearchPastesProps = {
+ term: String
+ pastes: PasteSearchView
+ onSearchEnter: (term: String) => void
+}
+
+const SearchPastes: Component = ({term, pastes, onSearchEnter}): JSX.Element => {
const [search, setSearch] = createSignal();
const [results, { refetch }] = createResource(() => search(), () => searchTerm());
+ let searchInput: HTMLInputElement;
+
+
const searchTerm = (): Promise> => {
if (search() && search().length >= 3) {
return ApiClient.searchAll(search());
@@ -21,17 +33,56 @@ const SearchPastes: () => JSX.Element = () => {
refetch();
}
- const submitSearchForm = (e: Event) => {
+
+ const submitOnClick = (e: Event) => {
e.preventDefault();
- refetch();
+ if (searchInput.value?.length >= 3) {
+ onSearchEnter(searchInput.value);
+ }
+ }
+
+ const submitOnEnter = (e: Event) => {
+ e.preventDefault();
+
+ if (searchInput.value?.length >= 3) {
+ if (e instanceof KeyboardEvent && e.key === "Enter") {
+ onSearchEnter(searchInput.value);
+ }
+ }
}
return (
-
+ <>
+
+
+
+ Nothing found}>
+
+
+ {item =>
+ -
+
{item.title || 'Untitled' }
+
+ Created: |
+ Expires: |
+ Size: {item.sizeInBytes} bytes
+
+ “{item.highlight} [..]”
+
+ }
+
+
- BinPastes
+
+
-
+ >
)
}
diff --git a/frontend/src/components/SearchPastes/searchPastes.module.css b/frontend/src/components/SearchPastes/searchPastes.module.css
new file mode 100644
index 0000000..89d63c1
--- /dev/null
+++ b/frontend/src/components/SearchPastes/searchPastes.module.css
@@ -0,0 +1,61 @@
+
+.searchForm {
+ border: none;
+ padding: 0;
+}
+
+.searchForm fieldset {
+ border: 1px dotted #a5a5a5;
+ border-radius: 7px;
+ margin-bottom: 10px;
+ padding: 5px 10px;
+}
+
+.searchForm input[type=search] {
+ display: inline-block;
+ width: 20rem;
+ margin: 0;
+}
+
+.searchForm input[type=submit],
+.searchForm input[type=reset] {
+ display: inline-block;
+ padding: 0.4rem 0.8rem;
+ color: var(--color-text);
+ margin: .5rem;
+ cursor: pointer;
+}
+
+
+
+
+.searchResults {
+ margin-top: 0;
+ padding-top: 0;
+ border-left: 1px dotted #a5a5a5;
+}
+
+.searchResults ol {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+
+}
+
+.searchResults .item {
+ list-style: none;
+ padding-left: 1rem;
+ border-top: 1px dotted #a5a5a5;
+}
+
+.searchResults .item p {
+ display: inline-flex;
+ align-items: start;
+ gap: 3px;
+}
+
+.searchResults .item p {
+ display: inline-flex;
+ align-items: start;
+ gap: 3px;
+}
\ No newline at end of file
diff --git a/frontend/src/components/Spinner/Spinner.tsx b/frontend/src/components/Spinner/Spinner.tsx
new file mode 100644
index 0000000..1b5c147
--- /dev/null
+++ b/frontend/src/components/Spinner/Spinner.tsx
@@ -0,0 +1,10 @@
+import {JSX} from 'solid-js';
+import './spinner.css';
+
+const Spinner: () => JSX.Element = () => {
+ return (
+
+ )
+}
+
+export default Spinner;
diff --git a/frontend/src/components/Spinner/spinner.css b/frontend/src/components/Spinner/spinner.css
new file mode 100644
index 0000000..77f5799
--- /dev/null
+++ b/frontend/src/components/Spinner/spinner.css
@@ -0,0 +1,56 @@
+.lds-ellipsis {
+ display: inline-block;
+ position: relative;
+ width: 80px;
+ height: 80px;
+}
+.lds-ellipsis div {
+ background: #000;
+ position: absolute;
+ top: 33px;
+ width: 13px;
+ height: 13px;
+ border-radius: 50%;
+ animation-timing-function: cubic-bezier(0, 1, 1, 0);
+}
+.lds-ellipsis div:nth-child(1) {
+ left: 8px;color:black;
+ animation: lds-ellipsis1 0.6s infinite;
+}
+.lds-ellipsis div:nth-child(2) {
+ left: 8px;
+ animation: lds-ellipsis2 0.6s infinite;
+}
+.lds-ellipsis div:nth-child(3) {
+ left: 32px;
+ animation: lds-ellipsis2 0.6s infinite;
+}
+.lds-ellipsis div:nth-child(4) {
+ left: 56px;
+ animation: lds-ellipsis3 0.6s infinite;
+}
+
+@keyframes lds-ellipsis1 {
+ 0% {
+ transform: scale(0);
+ }
+ 100% {
+ transform: scale(1);
+ }
+}
+@keyframes lds-ellipsis3 {
+ 0% {
+ transform: scale(1);
+ }
+ 100% {
+ transform: scale(0);
+ }
+}
+@keyframes lds-ellipsis2 {
+ 0% {
+ transform: translate(0, 0);
+ }
+ 100% {
+ transform: translate(24px, 0);
+ }
+}
diff --git a/frontend/src/pages/Search.tsx b/frontend/src/pages/Search.tsx
index 74fcbd6..ebb08c1 100644
--- a/frontend/src/pages/Search.tsx
+++ b/frontend/src/pages/Search.tsx
@@ -1,35 +1,49 @@
-import {JSX, createResource} from 'solid-js';
-import {A, useNavigate, useLocation} from '@solidjs/router';
+import {JSX, createResource, Switch, Match} from 'solid-js';
+import {A, useLocation, useSearchParams} from '@solidjs/router';
import ApiClient from '../api/client';
import {toDateTimeString} from '../datetime/DateTimeUtil';
+import SearchPastes from '../components/SearchPastes/SearchPastes';
+import Spinner from '../components/Spinner/Spinner';
const Search: () => JSX.Element = () => {
- const navigate = useNavigate();
-
const location = useLocation();
- const [pastes, { mutate, refetch }] = createResource(() => location.query.q, (term) => ApiClient.searchAll(term));
+ const [searchTerm, setSearchTerm] = useSearchParams();
+
+ const delay = (delayInMs) => {
+ return new Promise(resolve => setTimeout(resolve, delayInMs));
+ };
+
+ const effectiveTerm = () => {
+ return (searchTerm.q && searchTerm.q.length >= 3) ? searchTerm.q : null;
+ }
+
+ const [pastes] = createResource(
+ effectiveTerm,
+ (term) => delay(600).then(_ => ApiClient.searchAll(term)),
+ //(term) => ApiClient.searchAll(term)
+ {initialValue: []}
+ );
+
+ function onSearchEnter(term: String) {
+ setSearchTerm({q: term})
+ }
return (
<>
- Search
- Nothing found
}>
-
- {item =>
- -
-
{item.title || 'Untitled' }
-
- Created: |
- Expires: |
- Size: {item.sizeInBytes} bytes
-
- “{item.highlight} [..]”
-
- }
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
>
)
}