Skip to content

Commit

Permalink
Merge pull request #3271 from traPtitech/feat/search-history-normalize
Browse files Browse the repository at this point in the history
メッセージ検索の改善
  • Loading branch information
sapphi-red authored Apr 15, 2022
2 parents 46f6101 + 448f251 commit 80bf8c4
Show file tree
Hide file tree
Showing 6 changed files with 193 additions and 87 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ const usePaging = (
const useSearchMessages = () => {
const limit = 20
const { parseQuery, toSearchMessageParam } = useQueryParer()
const { query, searchState, setSearchResult, resetPaging } =
const { query, searchState, setSearchResult, resetPaging, addSearchHistory } =
useCommandPalette()
const { extendMessagesMap } = useMessagesStore()

Expand Down Expand Up @@ -108,7 +108,9 @@ const useSearchMessages = () => {
}

fetchingSearchResult.value = true
const queryObject = await parseQuery(query)
const { normalizedQuery, queryObject } = await parseQuery(query)
addSearchHistory(normalizedQuery)

const option = {
limit,
offset: currentOffset.value,
Expand Down
37 changes: 29 additions & 8 deletions src/composables/searchMessage/useQueryParser.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Ref } from 'vue'
import type { Ref, ComputedRef } from 'vue'
import { computed } from 'vue'
import type { ChannelTree } from '/@/lib/channelTree'
import { channelPathToId } from '/@/lib/channelTree'
import type { StoreForParser } from '/@/lib/searchMessage/parserBase'
Expand All @@ -10,15 +11,23 @@ import type { ViewInformation } from '/@/store/ui/mainView'
import { useMainViewStore } from '/@/store/ui/mainView'
import { useChannelTree } from '/@/store/domain/channelTree'
import { useUsersStore } from '/@/store/entities/users'
import type { User } from '@traptitech/traq'
import type { Channel, User } from '@traptitech/traq'
import { channelIdToPathString } from '/@/lib/channel'
import type { ChannelId } from '/@/types/entity-ids'
import { useChannelsStore } from '/@/store/entities/channels'
import { useMeStore } from '/@/store/domain/me'

const getStoreForParser = ({
primaryView,
channelsMap,
channelTree,
myUsername,
fetchUserByName
}: {
primaryView: Ref<ViewInformation>
channelsMap: Ref<ReadonlyMap<ChannelId, Channel>>
channelTree: Ref<ChannelTree>
myUsername: ComputedRef<string | undefined>
fetchUserByName: (param: { username: string }) => Promise<User | undefined>
}): StoreForParser => ({
channelPathToId: path => {
Expand All @@ -32,20 +41,32 @@ const getStoreForParser = ({
const user = await fetchUserByName({ username })
return user?.id
},
getCurrentChannelId: () => {
return primaryView.value.type === 'channel' ||
primaryView.value.type === 'dm'
? primaryView.value.channelId
getCurrentChannelPath: () => {
const channelId =
primaryView.value.type === 'channel' || primaryView.value.type === 'dm'
? primaryView.value.channelId
: undefined
return channelId
? channelIdToPathString(channelId, channelsMap.value)
: undefined
}
},
getMyUsername: () => myUsername.value
})

const useQueryParer = () => {
const { channelsMap } = useChannelsStore()
const { channelTree } = useChannelTree()
const { primaryView } = useMainViewStore()
const { fetchUserByName } = useUsersStore()
const { detail: me } = useMeStore()
const parseQuery = createQueryParser(
getStoreForParser({ primaryView, channelTree, fetchUserByName })
getStoreForParser({
primaryView,
channelsMap,
channelTree,
myUsername: computed(() => me.value?.name),
fetchUserByName
})
)

return { parseQuery, toSearchMessageParam }
Expand Down
12 changes: 9 additions & 3 deletions src/lib/searchMessage/parserBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ export const makePrefixedFilterExtractor =
export type StoreForParser = {
channelPathToId: ChannelPathToId
usernameToId: UsernameToId
getCurrentChannelId: () => ChannelId | undefined
getCurrentChannelPath: () => string | undefined
getMyUsername: () => string | undefined
}

type ChannelPathToId = (path: string) => ChannelId | undefined
Expand All @@ -95,7 +96,7 @@ type UsernameToId =
* @typeParam F フィルターの型
* @typeParam T フィルター種別の型
* @param parser `extracted`をフィルター種別`type`とみなして変換するパーサー
* @param filterExtractorMap フィルター種別とextractorのマップ
* @param extractor フィルター種別とextractorのマップ
* @param skipCondition チェックを飛ばす条件 ':'が含まれていない など
* @returns
*/
Expand Down Expand Up @@ -144,6 +145,7 @@ export const dateParser = <T extends string>(
}

export const InHereToken = Symbol('in:here')
export const FromToMeToken = Symbol('from:me / to:me')

export const channelParser = <T extends string>(
channelPathToId: ChannelPathToId,
Expand All @@ -162,11 +164,15 @@ export const channelParser = <T extends string>(
export const userParser = async <T extends string>(
usernameToId: UsernameToId,
extracted: ExtractedFilter<T>
): Promise<UserId | undefined> => {
): Promise<UserId | typeof FromToMeToken | undefined> => {
const username = extracted.body.startsWith('@')
? extracted.body.slice(1)
: extracted.body

if (username === 'me') {
return FromToMeToken
}

return usernameToId(username)
}

Expand Down
64 changes: 53 additions & 11 deletions src/lib/searchMessage/queryParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type {
FilterParser,
StoreForParser
} from './parserBase'
import { FromToMeToken } from './parserBase'
import {
channelParser,
InHereToken,
Expand Down Expand Up @@ -52,8 +53,8 @@ export type Filter =
| { type: 'after'; raw: string; value: Date }
| { type: 'before'; raw: string; value: Date }
| { type: 'in'; raw: string; value: typeof InHereToken | ChannelId }
| { type: 'to'; raw: string; value: UserId }
| { type: 'from'; raw: string; value: UserId }
| { type: 'to'; raw: string; value: typeof FromToMeToken | UserId }
| { type: 'from'; raw: string; value: typeof FromToMeToken | UserId }
| { type: 'citation'; raw: string; value: MessageId }
| { type: 'attrFlag'; raw: string; value: AttrFlagFilterKey; negate: boolean }
| {
Expand Down Expand Up @@ -186,6 +187,7 @@ const parseQueryFragmentToFilterWithoutStore = parseToFilterBase(
/** 実際のクエリに対応するオブジェクトへの変換 */
const filterOrStringToSearchMessageQuery = (
currentChannelId: string | undefined,
myUserId: string | undefined,
f: Filter | string
): SearchMessageQueryObject => {
if (typeof f === 'string') {
Expand All @@ -196,22 +198,23 @@ const filterOrStringToSearchMessageQuery = (
case 'after':
case 'before':
return {
...emptySearchMessageQueryObject,
[f.type]: f.value.toISOString()
}
case 'in': {
const channelId = f.value === InHereToken ? currentChannelId : f.value
return { ...emptySearchMessageQueryObject, in: channelId }
return { in: channelId }
}
case 'to':
case 'from':
case 'from': {
const user = f.value === FromToMeToken ? myUserId : f.value
return { [f.type]: user }
}
case 'citation':
return { ...emptySearchMessageQueryObject, [f.type]: f.value }
return { [f.type]: f.value }
case 'attrFlag':
return { ...emptySearchMessageQueryObject, [f.value]: !f.negate }
return { [f.value]: !f.negate }
case 'mediaFlag':
return {
...emptySearchMessageQueryObject,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
[`has${f.value[0]!.toUpperCase() + f.value.slice(1)}`]: !f.negate
}
Expand All @@ -225,19 +228,58 @@ export const createQueryParser = (store: StoreForParser) => {
const parseQueryFragmentToFilter =
parseQueryFragmentToFilterWithoutStore(store)

return async (query: string): Promise<SearchMessageQueryObject> => {
return async (
query: string
): Promise<{
normalizedQuery: string
queryObject: SearchMessageQueryObject
}> => {
const parseds = await Promise.all(
query
.split(' ')
.filter(q => q)
.map(parseQueryFragmentToFilter)
)
return parseds

const currentChannelPath = store.getCurrentChannelPath()
const currentChannelId = currentChannelPath
? store.channelPathToId(currentChannelPath)
: undefined
const myUsername = store.getMyUsername()
const myUserId = myUsername
? await store.usernameToId(myUsername)
: undefined

const normalizedQuery = parseds
.map(q =>
parsedFilterToNormalizedString(q, currentChannelPath, myUsername)
)
.join(' ')
const queryObject = parseds
.map(f =>
filterOrStringToSearchMessageQuery(store.getCurrentChannelId(), f)
filterOrStringToSearchMessageQuery(currentChannelId, myUserId, f)
)
.reduce(mergeSearchMessageQueryObject, emptySearchMessageQueryObject)

return { normalizedQuery, queryObject }
}
}

const parsedFilterToNormalizedString = (
f: string | Filter,
currentChannelPath: string | undefined,
myUsername: string | undefined
) => {
if (typeof f === 'string') {
return f
}
if (f.type === 'in' && f.value === InHereToken) {
return `in:${currentChannelPath}`
}
if ((f.type === 'from' || f.type === 'to') && f.value === FromToMeToken) {
return `${f.type}:${myUsername}`
}
return f.raw
}

// util
Expand Down
4 changes: 1 addition & 3 deletions src/store/app/commandPalette.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,6 @@ const useCommandPalettePinia = defineStore('app/commandPalette', () => {

const settleQuery = () => {
query.value = currentInput.value
if (currentInput.value !== '') {
addSearchHistory(currentInput.value)
}
}

const addSearchHistory = (newHistory: string) => {
Expand Down Expand Up @@ -148,6 +145,7 @@ const useCommandPalettePinia = defineStore('app/commandPalette', () => {
searchState,
isCommandPaletteShown,
settleQuery,
addSearchHistory,
removeSearchHistory,
openCommandPalette,
closeCommandPalette,
Expand Down
Loading

0 comments on commit 80bf8c4

Please sign in to comment.