add tag link

This commit is contained in:
monoid 2021-01-17 01:33:45 +09:00
parent 64f5c82c8c
commit e63e82fde9
6 changed files with 53 additions and 30 deletions

View File

@ -11,8 +11,8 @@ export const toQueryString = (obj:ToQueryStringA)=> {
e[1] !== undefined) e[1] !== undefined)
.map(e => .map(e =>
e[1] instanceof Array e[1] instanceof Array
? e[1].map(f=>`${e[0]}[]=${encodeURIComponent(f)}`).join('&') ? e[1].map(f=>`${e[0]}=${(f)}`).join('&')
: `${e[0]}=${encodeURIComponent(e[1])}`) : `${e[0]}=${(e[1])}`)
.join('&'); .join('&');
} }
export const QueryStringToMap = (query:string) =>{ export const QueryStringToMap = (query:string) =>{
@ -20,16 +20,16 @@ export const QueryStringToMap = (query:string) =>{
const param:{[k:string]:string|string[]} = {}; const param:{[k:string]:string|string[]} = {};
keyValue.forEach((p)=>{ keyValue.forEach((p)=>{
const [k,v] = p.split("="); const [k,v] = p.split("=");
if(k.endsWith("[]")){ const pv = param[k];
let arr = param[k]; if(pv === undefined){
if(arr instanceof Array){ param[k] = v;
arr.push(v); }
} else if(typeof pv === "string"){
else{ param[k] = [pv,v];
param[k] = [v]; }
} else{
pv.push(v);
} }
else param[k] = v;
}); });
return param; return param;
} }

View File

@ -117,7 +117,7 @@ export const ContentInfo = (props: {
</Link> </Link>
<Box className={propclasses.subinfoContainer || subinfoContainer}> <Box className={propclasses.subinfoContainer || subinfoContainer}>
{props.short ? (<Box className={propclasses.tag_list || classes.tag_list}>{document.tags.map(x => {props.short ? (<Box className={propclasses.tag_list || classes.tag_list}>{document.tags.map(x =>
(<TagChip key={x} label={x} clickable={true} tagname={x} size="small"></TagChip>) (<TagChip key={x} label={x} clickable tagname={x} size="small"></TagChip>)
)}</Box>) : ( )}</Box>) : (
<> <>
<Typography variant='subtitle1'>Artist</Typography> <Typography variant='subtitle1'>Artist</Typography>
@ -125,7 +125,7 @@ export const ContentInfo = (props: {
<Typography variant='subtitle1'>Tags</Typography> <Typography variant='subtitle1'>Tags</Typography>
<Box className={propclasses.tag_list || classes.tag_list}> <Box className={propclasses.tag_list || classes.tag_list}>
{allTag.map(x => { {allTag.map(x => {
return (<TagChip key={x} label={x} clickable={true} tagname={x} size="small"></TagChip>); return (<TagChip key={x} label={x} clickable tagname={x} size="small"></TagChip>);
})} })}
</Box> </Box>
</>) </>)

View File

@ -1,7 +1,8 @@
import React from 'react'; import React from 'react';
import {ChipProps} from '@material-ui/core/Chip'; import {ChipTypeMap} from '@material-ui/core/Chip';
import { Chip, colors } from '@material-ui/core'; import { Chip, colors } from '@material-ui/core';
import {useTheme, makeStyles, Theme, emphasize, fade} from '@material-ui/core/styles'; import {useTheme, makeStyles, Theme, emphasize, fade} from '@material-ui/core/styles';
import {Link as RouterLink} from 'react-router-dom';
type TagChipStyleProp = { type TagChipStyleProp = {
color: string color: string
@ -43,14 +44,20 @@ const useTagStyles = makeStyles((theme:Theme)=>({
const {blue, pink} = colors; const {blue, pink} = colors;
const getTagColorName = (tagname :string):string=>{ const getTagColorName = (tagname :string):string=>{
if(tagname.startsWith("female")){ if(tagname.startsWith("female")){
return pink['700']; return pink[600];
} }
else if(tagname.startsWith("male")){ else if(tagname.startsWith("male")){
return blue[600]; return blue[600];
} }
else return "default"; else return "default";
} }
export const ColorChip = (props:Omit<ChipProps,"color"> & {color: string})=>{
type ColorChipProp = Omit<ChipTypeMap['props'],"color"> & TagChipStyleProp & {
component?: React.ElementType,
to?: string
}
export const ColorChip = (props:ColorChipProp)=>{
const {color,...rest} = props; const {color,...rest} = props;
const classes = useTagStyles({color : color !== "default" ? color : "#000"}); const classes = useTagStyles({color : color !== "default" ? color : "#000"});
return <Chip color="default" classes={{ return <Chip color="default" classes={{
@ -58,8 +65,12 @@ export const ColorChip = (props:Omit<ChipProps,"color"> & {color: string})=>{
}} {...rest}></Chip>; }} {...rest}></Chip>;
} }
export const TagChip = (props:Omit<ChipProps,"color"> & {tagname: string})=>{ type TagChipProp = Omit<ChipTypeMap['props'],"color"> & {
const {tagname,label,...rest} = props; tagname:string
}
export const TagChip = (props:TagChipProp)=>{
const {tagname,label,clickable,...rest} = props;
let newlabel:string|undefined = undefined; let newlabel:string|undefined = undefined;
if(typeof label === "string"){ if(typeof label === "string"){
if(label.startsWith("female:")){ if(label.startsWith("female:")){
@ -69,5 +80,9 @@ export const TagChip = (props:Omit<ChipProps,"color"> & {tagname: string})=>{
newlabel = "♂ "+label.slice(5); newlabel = "♂ "+label.slice(5);
} }
} }
return <ColorChip color={getTagColorName(tagname)} label={newlabel||label} {...rest}></ColorChip>; const inner = clickable ?
(<ColorChip color={getTagColorName(tagname)} clickable={clickable} label={newlabel||label} {...rest}
component={RouterLink} to={`/search?allow_tag=${tagname}`}></ColorChip>):
(<ColorChip color={getTagColorName(tagname)} clickable={clickable} label={newlabel||label} {...rest}></ColorChip>);
return inner;
} }

View File

@ -80,12 +80,16 @@ class KnexDocumentAccessor implements DocumentAccessor{
const cursor = option.cursor; const cursor = option.cursor;
const buildquery = ()=>{ const buildquery = ()=>{
let query = this.knex.select("*"); let query = this.knex.select("document.*");
if(allow_tag.length > 0){ if(allow_tag.length > 0){
query = query.from("doc_tag_relation").innerJoin("document","doc_tag_relation.doc_id","document.id"); query = query.from("doc_tag_relation as tags_0");
for(const tag of allow_tag){ query = query.where("tags_0.tag_name","=",allow_tag[0]);
query = query.where({tag_name:tag}); for (let index = 1; index < allow_tag.length; index++) {
const element = allow_tag[index];
query = query.innerJoin(`doc_tag_relation as tags_${index}`,`tags_${index}.doc_id`,"tags_0.doc_id");
query = query.where(`tags_${index}.tag_name`,'=',element);
} }
query = query.innerJoin("document","tags_0.doc_id","document.id");
} }
else{ else{
query = query.from("document"); query = query.from("document");

View File

@ -2,7 +2,7 @@ import { Context, Next } from 'koa';
import Router from 'koa-router'; import Router from 'koa-router';
import {Document, DocumentAccessor, isDocBody} from '../model/doc'; import {Document, DocumentAccessor, isDocBody} from '../model/doc';
import {QueryListOption} from '../model/doc'; import {QueryListOption} from '../model/doc';
import {ParseQueryNumber, ParseQueryArray, ParseQueryBoolean} from './util' import {ParseQueryNumber, ParseQueryArray, ParseQueryBoolean, ParseQueryArgString} from './util'
import {sendError} from './error_handler'; import {sendError} from './error_handler';
import { join } from 'path'; import { join } from 'path';
import {AllContentRouter} from './all'; import {AllContentRouter} from './all';
@ -31,14 +31,14 @@ const ContentTagIDHandler = (controller: DocumentAccessor) => async (ctx: Contex
const ContentQueryHandler = (controller : DocumentAccessor) => async (ctx: Context,next: Next)=>{ const ContentQueryHandler = (controller : DocumentAccessor) => async (ctx: Context,next: Next)=>{
const limit = ParseQueryNumber(ctx.query['limit']); const limit = ParseQueryNumber(ctx.query['limit']);
const cursor = ParseQueryNumber(ctx.query['cursor']); const cursor = ParseQueryNumber(ctx.query['cursor']);
const word: string|undefined = ctx.query['word']; const word = ParseQueryArgString(ctx.query['word']);
const content_type:string|undefined = ctx.query['content_type']; const content_type = ParseQueryArgString(ctx.query['content_type']);
const offset = ParseQueryNumber(ctx.query['offset']); const offset = ParseQueryNumber(ctx.query['offset']);
if(limit === NaN || cursor === NaN || offset === NaN){ if(limit === NaN || cursor === NaN || offset === NaN){
return sendError(400,"parameter limit, cursor or offset is not a number"); return sendError(400,"parameter limit, cursor or offset is not a number");
} }
const allow_tag = ParseQueryArray(ctx.query['allow_tag[]']); const allow_tag = ParseQueryArray(ctx.query['allow_tag']);
let [ok,use_offset] = ParseQueryBoolean(ctx.query['use_offset']); const [ok,use_offset] = ParseQueryBoolean(ctx.query['use_offset']);
if(!ok){ if(!ok){
return sendError(400,"use_offset must be true or false."); return sendError(400,"use_offset must be true or false.");
} }
@ -50,7 +50,7 @@ const ContentQueryHandler = (controller : DocumentAccessor) => async (ctx: Conte
eager_loading: true, eager_loading: true,
offset: offset, offset: offset,
use_offset: use_offset, use_offset: use_offset,
content_type:content_type, content_type: content_type,
}; };
let document = await controller.findList(option); let document = await controller.findList(option);
ctx.body = document; ctx.body = document;

View File

@ -8,7 +8,11 @@ export function ParseQueryNumber(s: string|undefined): number| undefined{
} }
export function ParseQueryArray(s: string[]|string|undefined){ export function ParseQueryArray(s: string[]|string|undefined){
s = s || []; s = s || [];
return s instanceof Array ? s : [s]; const r = s instanceof Array ? s : [s];
return r.map(x=>decodeURIComponent(x));
}
export function ParseQueryArgString(s: string|undefined){
return s === undefined ? s : decodeURIComponent(s);
} }
export function ParseQueryBoolean(s: string|undefined): [boolean,boolean|undefined]{ export function ParseQueryBoolean(s: string|undefined): [boolean,boolean|undefined]{
let value:boolean|undefined; let value:boolean|undefined;