simple search

This commit is contained in:
monoid 2022-04-28 00:01:41 +09:00
parent 7a3fdce4dc
commit 1eba3e43e7
7 changed files with 89 additions and 35 deletions

View File

@ -1,7 +1,7 @@
import React, { createContext, useEffect, useRef, useState } from 'react'; import React, { createContext, useEffect, useRef, useState } from 'react';
import ReactDom from 'react-dom'; import ReactDom from 'react-dom';
import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom'; import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom';
import { Gallery, DocumentAbout, LoginPage, NotFoundPage, ProfilePage, DifferencePage, SettingPage } from './page/mod'; import { Gallery, DocumentAbout, LoginPage, NotFoundPage, ProfilePage, DifferencePage, SettingPage, ReaderPage } from './page/mod';
import { getInitialValue, UserContext } from './state'; import { getInitialValue, UserContext } from './state';
import { ThemeProvider, createTheme } from '@mui/material'; import { ThemeProvider, createTheme } from '@mui/material';
@ -32,7 +32,8 @@ const App = () => {
<Routes> <Routes>
<Route path="/" element={<Navigate replace to='/search?' />} /> <Route path="/" element={<Navigate replace to='/search?' />} />
<Route path="/search" element={<Gallery />} /> <Route path="/search" element={<Gallery />} />
<Route path="/doc" element={<DocumentAbout />}></Route> <Route path="/doc/:id" element={<DocumentAbout />}></Route>
<Route path="/doc/:id/reader" element={<ReaderPage />}></Route>
<Route path="/login" element={<LoginPage></LoginPage>} /> <Route path="/login" element={<LoginPage></LoginPage>} />
<Route path="/profile" element={<ProfilePage />}></Route> <Route path="/profile" element={<ProfilePage />}></Route>
<Route path="/difference" element={<DifferencePage />}></Route> <Route path="/difference" element={<DifferencePage />}></Route>

View File

@ -83,7 +83,6 @@ export const ContentInfo = (props: {
//const classes = useStyles(); //const classes = useStyles();
const theme = useTheme(); const theme = useTheme();
const document = props.document; const document = props.document;
const propclasses = props.classes ?? {};
/*const rootName = props.short ? classes.short_root : classes.root; /*const rootName = props.short ? classes.short_root : classes.root;
const thumbnail_anchor = props.short ? classes.short_thumbnail_anchor : ""; const thumbnail_anchor = props.short ? classes.short_thumbnail_anchor : "";
const thumbnail_content = props.short ? classes.short_thumbnail_content : const thumbnail_content = props.short ? classes.short_thumbnail_content :

View File

@ -9,7 +9,7 @@ import {
ChevronLeft, ChevronRight, Menu as MenuIcon, Search as SearchIcon, AccountCircle ChevronLeft, ChevronRight, Menu as MenuIcon, Search as SearchIcon, AccountCircle
} from '@mui/icons-material'; } from '@mui/icons-material';
import { Link as RouterLink } from 'react-router-dom'; import { Link as RouterLink, useNavigate } from 'react-router-dom';
import { doLogout, UserContext } from '../state'; import { doLogout, UserContext } from '../state';
const drawerWidth = 270; const drawerWidth = 270;
@ -80,6 +80,8 @@ export const Headline = (prop: {
const menuId = 'primary-search-account-menu'; const menuId = 'primary-search-account-menu';
const user_ctx = useContext(UserContext); const user_ctx = useContext(UserContext);
const isLogin = user_ctx.username !== ""; const isLogin = user_ctx.username !== "";
const navigate = useNavigate();
const [search, setSearch] = useState("");
const renderProfileMenu = (<Menu const renderProfileMenu = (<Menu
anchorEl={anchorEl} anchorEl={anchorEl}
@ -139,9 +141,21 @@ export const Headline = (prop: {
alignItems: 'center', alignItems: 'center',
justifyContent: 'center' justifyContent: 'center'
}}> }}>
<SearchIcon /> <SearchIcon onClick={() => navigate(`/search?word=${encodeURIComponent(search)}`)} />
</div> </div>
<StyledInputBase placeholder="search"></StyledInputBase> <StyledInputBase placeholder="search"
onChange={(e) => setSearch(e.target.value)}
onKeyUp={(e) => {
if (e.key === "Enter") {
let words = search.includes("&") ? search.split("&") : [search];
words = words.map(w => w.trim())
.map(w => w.includes(":") ?
`allow_tag=${w}`
: `word=${encodeURIComponent(w)}`);
navigate(`/search?${words.join("&")}`);
}
}}
value={search}></StyledInputBase>
</StyledSearchBar> </StyledSearchBar>
{ {
isLogin ? isLogin ?
@ -159,7 +173,7 @@ export const Headline = (prop: {
</Toolbar> </Toolbar>
</AppBar> </AppBar>
{renderProfileMenu} {renderProfileMenu}
<nav> <nav style={{ width: theme.spacing(7) }}>
<Hidden smUp implementation="css"> <Hidden smUp implementation="css">
<StyledDrawer variant="temporary" anchor='left' open={v} onClose={toggleV} <StyledDrawer variant="temporary" anchor='left' open={v} onClose={toggleV}
sx={{ sx={{

View File

@ -1,11 +1,11 @@
import React, { useState, useEffect} from 'react'; import React, { useState, useEffect } from 'react';
import { Route, Routes, useLocation, useParams } from 'react-router-dom'; import { Route, Routes, useLocation, useParams } from 'react-router-dom';
import DocumentAccessor, { Document } from '../accessor/document'; import DocumentAccessor, { Document } from '../accessor/document';
import { LoadingCircle } from '../component/loading'; import { LoadingCircle } from '../component/loading';
import { Theme, Typography } from '@mui/material'; import { Theme, Typography } from '@mui/material';
import { getPresenter } from './reader/reader'; import { getPresenter } from './reader/reader';
import { CommonMenuList, ContentInfo, Headline } from '../component/mod'; import { CommonMenuList, ContentInfo, Headline } from '../component/mod';
import {NotFoundPage} from './404'; import { NotFoundPage } from './404';
export const makeContentInfoUrl = (id: number) => `/doc/${id}`; export const makeContentInfoUrl = (id: number) => `/doc/${id}`;
export const makeComicReaderUrl = (id: number) => `/doc/${id}/reader`; export const makeComicReaderUrl = (id: number) => `/doc/${id}/reader`;
@ -15,28 +15,28 @@ type DocumentState = {
notfound: boolean, notfound: boolean,
} }
const styles = ((theme:Theme)=>({ const styles = ((theme: Theme) => ({
noPaddingContent:{ noPaddingContent: {
display:'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
flexGrow: 1, flexGrow: 1,
}, },
noPaddingToolbar:{ noPaddingToolbar: {
flex: '0 1 auto', flex: '0 1 auto',
...theme.mixins.toolbar, ...theme.mixins.toolbar,
} }
})); }));
export const DocumentAbout = (prop?: {}) => { export function ReaderPage(props?: {}) {
const location = useLocation(); const location = useLocation();
const match = useParams<{id:string}>(); const match = useParams<{ id: string }>();
if (match == null) { if (match == null) {
throw new Error("unreachable"); throw new Error("unreachable");
} }
const id = Number.parseInt(match.id); const id = Number.parseInt(match.id);
const [info, setInfo] = useState<DocumentState>({ doc: undefined, notfound:false }); const [info, setInfo] = useState<DocumentState>({ doc: undefined, notfound: false });
const menu_list = (link?:string) => <CommonMenuList url={link}></CommonMenuList>; const menu_list = (link?: string) => <CommonMenuList url={link}></CommonMenuList>;
useEffect(() => { useEffect(() => {
(async () => { (async () => {
if (!isNaN(id)) { if (!isNaN(id)) {
@ -53,7 +53,7 @@ export const DocumentAbout = (prop?: {}) => {
</Headline> </Headline>
); );
} }
else if(info.notfound){ else if (info.notfound) {
return ( return (
<Headline menu={menu_list()}> <Headline menu={menu_list()}>
<Typography variant='h2'>Content has been removed.</Typography> <Typography variant='h2'>Content has been removed.</Typography>
@ -66,22 +66,57 @@ export const DocumentAbout = (prop?: {}) => {
</Headline> </Headline>
); );
} }
else{ else {
const ReaderPage = getPresenter(info.doc); const ReaderPage = getPresenter(info.doc);
return (<Routes> return <Headline menu={menu_list(location.pathname)}>
<Route path={`:id`}> <ReaderPage doc={info.doc}></ReaderPage>
</Headline>
}
}
export const DocumentAbout = (prop?: {}) => {
const match = useParams<{ id: string }>();
if (match == null) {
throw new Error("unreachable");
}
const id = Number.parseInt(match.id);
const [info, setInfo] = useState<DocumentState>({ doc: undefined, notfound: false });
const menu_list = (link?: string) => <CommonMenuList url={link}></CommonMenuList>;
useEffect(() => {
(async () => {
if (!isNaN(id)) {
const c = await DocumentAccessor.findById(id);
setInfo({ doc: c, notfound: c === undefined });
}
})();
}, []);
if (isNaN(id)) {
return (
<Headline menu={menu_list()}>
<Typography variant='h2'>Oops. Invalid ID</Typography>
</Headline>
);
}
else if (info.notfound) {
return (
<Headline menu={menu_list()}>
<Typography variant='h2'>Content has been removed.</Typography>
</Headline>
)
}
else if (info.doc === undefined) {
return (<Headline menu={menu_list()}>
<LoadingCircle />
</Headline>
);
}
else {
return (
<Headline menu={menu_list()}> <Headline menu={menu_list()}>
<ContentInfo document={info.doc}></ContentInfo> <ContentInfo document={info.doc}></ContentInfo>
</Headline> </Headline>
</Route> );
<Route path={`:id/reader`}>
<Headline menu={menu_list(location.pathname)}>
<ReaderPage doc={info.doc}></ReaderPage>
</Headline>
</Route>
<Route>
<NotFoundPage></NotFoundPage>
</Route>
</Routes>);
} }
} }

View File

@ -1,7 +1,7 @@
import React, { useContext, useEffect, useState } from 'react'; import React, { useContext, useEffect, useState } from 'react';
import { Headline, CommonMenuList,LoadingCircle, ContentInfo, NavList, NavItem, TagChip } from '../component/mod'; import { Headline, CommonMenuList,LoadingCircle, ContentInfo, NavList, NavItem, TagChip } from '../component/mod';
import { Box, Typography, Chip } from '@mui/material'; import { Box, Typography, Chip, Pagination } from '@mui/material';
import ContentAccessor,{QueryListOption, Document} from '../accessor/document'; import ContentAccessor,{QueryListOption, Document} from '../accessor/document';
import {toQueryString} from '../accessor/util'; import {toQueryString} from '../accessor/util';
@ -62,5 +62,6 @@ export const Gallery = ()=>{
option.limit = typeof query['limit'] === "string" ? parseInt(query['limit']) : undefined; option.limit = typeof query['limit'] === "string" ? parseInt(query['limit']) : undefined;
return (<Headline menu={menu_list}> return (<Headline menu={menu_list}>
<GalleryInfo diff={location.search} option={query}></GalleryInfo> <GalleryInfo diff={location.search} option={query}></GalleryInfo>
</Headline>) </Headline>)
} }

View File

@ -2,7 +2,7 @@ import React, {useState, useEffect} from 'react';
import { Typography, useTheme } from '@mui/material'; import { Typography, useTheme } from '@mui/material';
import { Document } from '../../accessor/document'; import { Document } from '../../accessor/document';
type ComicType = "comic"|"artist cg"|"donjinshi"|"western" type ComicType = "comic"|"artist cg"|"donjinshi"|"western";
export type PresentableTag = { export type PresentableTag = {
artist:string[], artist:string[],

View File

@ -16,7 +16,11 @@ class KnexDocumentAccessor implements DocumentAccessor{
this.tagController = createKnexTagController(knex); this.tagController = createKnexTagController(knex);
} }
async search(search_word: string): Promise<Document[]>{ async search(search_word: string): Promise<Document[]>{
throw new Error("Not implemented"); throw new Error("Method not implemented.");
const sw = `%${search_word}%`;
const docs = await this.knex.select("*").from("document")
.where("title","like",sw);
return docs;
} }
async addList(content_list: DocumentBody[]):Promise<number[]>{ async addList(content_list: DocumentBody[]):Promise<number[]>{
return await this.knex.transaction(async (trx)=>{ return await this.knex.transaction(async (trx)=>{