feat: normalize search query

This commit is contained in:
João Peixoto
2021-04-21 03:35:54 +01:00
parent b7a6328889
commit 82ce152b64
15 changed files with 171 additions and 167 deletions
+31 -18
View File
@@ -32,18 +32,40 @@ module.exports = (options, context) => ({
extendPageData($page) {
const { frontmatter } = $page
// author config
const authorName = frontmatter.author
if (frontmatter.type) {
frontmatter.type = {
name: frontmatter.type,
slug: slug(frontmatter.type),
}
}
if (typeof authorName === 'string') {
const authorKey = slug(authorName, { lower: true })
if (typeof frontmatter.author === 'string') {
frontmatter.author = frontmatter.author
.split(/,|and|&/)
.map((author) => ({ name: author.trim(), slug: slug(author) }))
}
// setup author stub to keep templates happy
const author = { name: authorName }
if (frontmatter.tags) {
frontmatter.tags = frontmatter.tags.map((tag) => ({
name: tag,
slug: slug(tag),
}))
}
// setup the page author object
frontmatter.author = author
frontmatter.authorKey = authorKey
if (frontmatter.data) {
frontmatter.data.forEach((subPage) => {
if (subPage.tags) {
subPage.tags = subPage.tags.map((tag) => ({
name: tag,
slug: slug(tag),
}))
}
// set links has hidden (future publishes, etc)
if (shouldBeHidden(subPage)) {
subPage.hidden = true
}
})
}
// exclude hidden pages (sitemap config, future publishes, etc)
@@ -62,14 +84,5 @@ module.exports = (options, context) => ({
frontmatter.hidden = true
$page.hidden = true
}
// set links has hidden (future publishes, etc)
if (frontmatter.data) {
frontmatter.data.forEach((item) => {
if (shouldBeHidden(item)) {
item.hidden = true
}
})
}
},
})
@@ -22,7 +22,7 @@
(newest first)
</span>
of {{ resolvedActiveCategory }}
<span v-if="activeAuthor"> by {{ activeAuthor }} </span>
<span v-if="activeAuthor"> by {{ activeAuthor.name }} </span>
<span v-if="searchedText.length">
for {{ resolvedSearchedText }}
</span>
@@ -37,7 +37,7 @@
class="multiselect__tag active-tag text-sm"
@click="tagClick(tag)"
>
<span>#{{ tag }}</span
<span>#{{ tagsList.find((t) => t.slug === tag).name }}</span
><i class="multiselect__tag-icon"></i>
</button>
</div>
@@ -75,13 +75,16 @@ export default {
},
computed: {
...mapState('appState', [
'tagsList',
'activeCategory',
'activeTags',
'searchedText',
'activeAuthor',
]),
resolvedActiveCategory() {
return this.activeCategory ? `type "${this.activeCategory}"` : 'all types'
return this.activeCategory
? `type "${this.activeCategory.name}"`
: 'all types'
},
resolvedSearchedText() {
return `"${this.searchedText.join(' ')}"`
+1 -1
View File
@@ -33,7 +33,7 @@ export default {
return RegularCard
}
switch (this.card.type) {
switch (this.card.type.name) {
case 'Academic paper':
case 'Event':
case 'News coverage':
@@ -15,7 +15,7 @@
See here: https://github.com/ipfs/ipfs-blog/pull/94
-->
<div
v-if="frontmatter.type === 'Video'"
v-if="frontmatter.type.slug === 'video'"
class="cover embed-responsive embed-responsive-og"
@click="handleVideoClick"
>
@@ -47,10 +47,7 @@
:src="
frontmatter.card_image
? frontmatter.card_image
: `/${frontmatter.type
.toLowerCase()
.split(' ')
.join('-')}-placeholder.png`
: `/${frontmatter.type.slug}-placeholder.png`
"
/>
</div>
@@ -64,7 +61,9 @@
:title="title"
:description="frontmatter.description"
:post-path="path"
:onclick="frontmatter.type === 'Video' ? handleVideoClick : null"
:onclick="
frontmatter.type.slug === 'video' ? handleVideoClick : null
"
class="type-p4 text-primary"
/>
</div>
@@ -1,8 +1,8 @@
<template>
<div class="flex flex-row flex-wrap">
<div
v-for="(piece, index) in resolvedAuthorName"
:key="piece"
v-for="(piece, index) in author"
:key="piece.name"
itemprop="publisher author"
itemtype="http://schema.org/Person"
itemscope
@@ -10,21 +10,15 @@
>
<span itemprop="name" class="flex flex-row">
<router-link
:to="{ path: $localePath, query: { author: piece.trim() } }"
:to="{ path: $localePath, query: { author: piece.slug } }"
rel="nofollow"
>
<span
:class="computedClassName"
@click="handleAuthorClick(piece.trim())"
>
{{ piece }}
<span :class="computedClassName" @click="handleAuthorClick(piece)">
{{ piece.name }}
</span>
</router-link>
<span>{{
resolvedAuthorName.length !== 1 &&
index !== resolvedAuthorName.length - 1
? ',&nbsp;'
: ''
author.length !== 1 && index !== author.length - 1 ? ',&nbsp;' : ''
}}</span>
</span>
</div>
@@ -33,22 +27,20 @@
<script>
import { mapState } from 'vuex'
import Author from '@theme/components/mixins/Author'
import countly from '../../util/countly'
export default {
name: 'PostAuthor',
components: {},
mixins: [Author],
props: {
author: {
type: Array,
default: null,
},
light: {
type: Boolean,
default: null,
},
name: {
type: String,
default: '',
},
parent: {
type: String,
default: 'card',
@@ -56,16 +48,6 @@ export default {
},
computed: {
...mapState('appState', ['activeAuthor']),
resolvedAuthorName() {
const resolvedName = this.name.replace('and', ',')
const pieces = resolvedName.match(/[,&]/g)
if (!pieces) {
return [resolvedName]
}
return resolvedName.split(/[,&]/g)
},
computedClassName() {
return [
'hover:underline cursor-pointer',
@@ -74,15 +56,15 @@ export default {
},
},
methods: {
handleAuthorClick(authorName) {
handleAuthorClick(author) {
const authorTracking = {
author: authorName,
author: author.name,
method: `${this.parent}-select`,
}
countly.trackEvent(countly.events.FILTER, authorTracking)
this.$store.commit('appState/setActiveAuthor', authorName)
this.$store.commit('appState/setActiveAuthor', author)
},
},
}
@@ -6,7 +6,7 @@
<div class="grid grid-cols-1 md:grid-cols-2 pt-4">
<div class="flex flex-col md:pr-8">
<h1 class="type-h1">{{ title }}</h1>
<PostAuthor v-bind="author" light parent="blog-post" />
<PostAuthor :author="author" light parent="blog-post" />
<time
class="text-gray"
pubdate
@@ -37,7 +37,7 @@
>
<PostTag
v-for="tag in resolvedTags"
:key="tag"
:key="tag.slug"
:tag="tag"
link
dark
@@ -76,7 +76,7 @@ export default {
default: () => [],
},
author: {
type: Object,
type: Array,
default: null,
},
date: {
@@ -27,7 +27,7 @@
{{ title }}
</h1>
</a>
<PostAuthor v-if="author" v-bind="author" />
<PostAuthor v-if="author" :author="author" />
<p
v-if="description"
class="type-p1 text-sm text-primary clamp-3 mt-2"
@@ -41,9 +41,9 @@
class="p-1 mr-1 bg-aquaMuted leading-none bg-opacity-50 text-blueGreen font-semibold hover:bg-blueGreen hover:text-white transition duration-300 ease-in-out rounded cursor-pointer mt-1"
@click="handleCatClick"
>
{{ category }}
{{ category.name }}
</button>
<PostTag v-for="tag in resolvedTags" :key="tag" class="mt-1" :tag="tag" />
<PostTag v-for="tag in tags" :key="tag.name" class="mt-1" :tag="tag" />
</div>
</div>
</template>
@@ -69,7 +69,7 @@ export default {
default: () => [],
},
author: {
type: Object,
type: Array,
default: null,
},
date: {
@@ -77,7 +77,7 @@ export default {
default: null,
},
category: {
type: String,
type: Object,
default: null,
},
title: {
@@ -104,19 +104,11 @@ export default {
.utc(this.date)
.format(this.$themeLocaleConfig.dateFormat || 'YYYY-MM-DD')
},
resolvedTags() {
if (!this.tags || Array.isArray(this.tags)) return this.tags
return this.tags
.replace(/, /g, ',')
.split(',')
.filter((tag) => tag)
},
},
methods: {
handleCatClick() {
const categoryTracking = {
category: this.category,
category: this.category.name,
method: 'card-select',
}
@@ -1,14 +1,16 @@
<template>
<router-link
v-if="link"
:to="{ path: $localePath, query: { tags: tag } }"
:to="{ path: $localePath, query: { tags: tag.slug } }"
:class="computedClass"
rel="nofollow"
@click.native="handleTagClick"
>
#{{ tag }}
#{{ tag.name }}
</router-link>
<button v-else :class="computedClass" @click="addNewTag">#{{ tag }}</button>
<button v-else :class="computedClass" @click="addNewTag">
#{{ tag.name }}
</button>
</template>
<script>
@@ -22,7 +24,7 @@ export default {
default: () => false,
},
tag: {
type: String,
type: Object,
required: true,
},
dark: {
@@ -56,16 +58,16 @@ export default {
methods: {
handleTagClick() {
this.trackTag()
this.$store.commit('appState/setActiveTags', [this.tag])
this.$store.commit('appState/setActiveTags', [this.tag.slug])
},
addNewTag() {
this.trackTag()
this.$store.commit('appState/addNewTag', [this.tag])
this.$store.commit('appState/addNewTag', [this.tag.slug])
this.callback()
},
trackTag() {
const tagTracking = {
tag: this.tag,
tag: this.tag.name,
method: `${this.parent}-select`,
}
@@ -30,7 +30,7 @@
<div class="p-4 flex flex-grow flex-col">
<div class="flex flex-grow">
<PostMeta
category="Blog post"
:category="{ name: 'Blog post', slug: 'blog-post' }"
:author="frontmatter.author"
:date="frontmatter.date"
:tags="frontmatter.tags"
@@ -8,9 +8,13 @@
>
<multiselect
ref="select0"
:value="activeCategory !== '' ? activeCategory : 'All content'"
:value="
activeCategory !== '' ? activeCategory : { name: 'All content' }
"
class="mb-2 xl:mb-0 xl:mr-2 xl:max-w-xs"
:options="['All content', ...categoriesList]"
:options="[{ name: 'All content' }, ...categoriesList]"
label="name"
track-by="name"
:searchable="false"
:allow-empty="false"
select-label="Press 'enter' to select"
@@ -25,7 +29,12 @@
tag-placeholder="search for this text"
placeholder="Search for words or #tags"
track-by="name"
label="name"
:custom-label="
(option) =>
tagsList.map((tag) => tag.name).includes(option.name)
? `#${option.name}`
: option.name
"
:limit="['xxl'].includes($mq) ? tagsLimit : tagsList.length"
:options="resolvedTags"
:multiple="true"
@@ -83,8 +92,8 @@ export default {
]),
resolvedTags() {
return this.tags.map((tag) => ({
name: `#${tag}`,
value: tag,
name: tag.name,
value: tag.slug,
}))
},
queryProptertyWatchlist() {
@@ -115,7 +124,7 @@ export default {
},
calculateTagsLimit(newTags) {
// The max value a char chan occupy in px
const multiplier = 8
const multiplier = 12
// Width of the help text - "X or more"
const helpTextWidth = 75
// Padding and margin for the each tag
@@ -159,16 +168,17 @@ export default {
const queryTags = query.tags ? query.tags.split(',') : []
const querySearch = query.search ? query.search.split(',') : []
const tagsListSlugs = this.tagsList.map((tag) => tag.slug)
const tagArray = this.selectedTags.map((tag) => tag.value)
const tagsToAdd = [...queryTags, ...querySearch].filter(
(tag) => !tagArray.includes(tag)
)
const tagsToRemove = tagArray.filter(
(tag) => this.tagsList.includes(tag) && !queryTags.includes(tag)
(tag) => tagsListSlugs.includes(tag) && !queryTags.includes(tag)
)
const textsToRemove = tagArray.filter(
(tag) => !this.tagsList.includes(tag) && !querySearch.includes(tag)
(tag) => !tagsListSlugs.includes(tag) && !querySearch.includes(tag)
)
const newTags = this.selectedTags.filter(
@@ -178,11 +188,13 @@ export default {
)
tagsToAdd.forEach((tag) => {
const isTag = this.tagsList.includes(tag)
const filteredTag = this.tagsList.find(
(listTag) => listTag.slug === tag
)
newTags.push({
name: (isTag ? '#' : '') + tag,
value: tag,
name: filteredTag ? filteredTag.name : tag,
value: filteredTag ? filteredTag.slug : tag,
})
})
@@ -190,7 +202,7 @@ export default {
},
setActiveCategory(category) {
const categoryTracking = {
category: category,
category: category.name,
method: 'filter-select',
}
@@ -198,7 +210,11 @@ export default {
this.$store.commit(
'appState/setActiveCategory',
this.categoriesList.includes(category) ? category : ''
this.categoriesList
.map((category) => category.slug)
.includes(category.slug)
? category
: ''
)
},
removeTag(tagToRemove) {
@@ -211,8 +227,9 @@ export default {
},
handleSearch() {
const tagArray = this.selectedTags.map((tag) => tag.value)
const tags = tagArray.filter((tag) => this.tagsList.includes(tag))
const texts = tagArray.filter((tag) => !this.tagsList.includes(tag))
const tagsListSlugs = this.tagsList.map((tag) => tag.slug)
const tags = tagArray.filter((tag) => tagsListSlugs.includes(tag))
const texts = tagArray.filter((tag) => !tagsListSlugs.includes(tag))
this.$store.commit('appState/setActiveTags', tags)
this.$store.commit('appState/setSearchedText', texts)
@@ -236,7 +253,7 @@ export default {
},
focusOnSubmit(option) {
const tagTracking = {
tag: option.value,
tag: option.name,
method: 'filter-select',
}
@@ -35,11 +35,11 @@
class="p-1 bg-aquaMuted leading-none bg-opacity-50 rounded text-blueGreen font-semibold hover:bg-blueGreen hover:text-white text-sm mr-1"
@click="handleCatClick()"
>
{{ videoModalCard.frontmatter.type }}
{{ videoModalCard.frontmatter.type.name }}
</button>
<PostTag
v-for="tag in resolvedTags"
:key="tag"
:key="tag.slug"
:tag="tag"
:callback="closeModal"
class-name="text-sm"
@@ -141,7 +141,7 @@ export default {
methods: {
handleCatClick() {
const categoryTracking = {
category: this.videoModalCard.frontmatter.type,
category: this.videoModalCard.frontmatter.type.name,
method: 'video-modal-select',
}
@@ -1,18 +0,0 @@
<script>
export default {
props: {
svgIcon: {
type: String,
default: null,
},
twitter: {
type: String,
default: '',
},
name: {
type: String,
required: true,
},
},
}
</script>
+49 -29
View File
@@ -60,11 +60,13 @@ import LanguageSelector from '@theme/components/base/LanguageSelector'
import { getTags } from '@theme/util/tagUtils'
import { parseProtectedPost, checkItem } from '@theme/util/blogUtils'
import uniq from 'lodash/uniq'
import uniqBy from 'lodash/uniqBy'
import pick from 'lodash/pick'
import isEqual from 'lodash/isEqual'
import orderBy from 'lodash/orderBy'
import countly from '../util/countly'
const defaultCategory = 'Blog post'
const defaultCategory = { name: 'Blog post', slug: 'blog-post' }
export default {
name: 'BlogIndex',
@@ -95,12 +97,19 @@ export default {
'videoModalCard',
]),
tags() {
return getTags(this.activeTags, this.publicPages)
return getTags(
this.tagsList.filter((tag) => this.activeTags.includes(tag.slug)),
this.publicPages
)
},
publicPages: function () {
let result = []
this.$pagination.pages.forEach((page) => {
if (this.categoriesList.includes(page.frontmatter.type)) {
if (
this.categoriesList
.map((category) => category.slug)
.includes(page.frontmatter.type?.slug)
) {
result = [
...result,
...parseProtectedPost(
@@ -144,7 +153,9 @@ export default {
: this.publicPages.slice(0, this.numberOfPagesToShow)
},
queryProptertyWatchlist() {
return `${this.activeCategory}|${this.activeTags}|${this.searchedText}|${this.activeAuthor}`
return `${JSON.stringify(this.activeCategory)}|${this.activeTags}|${
this.searchedText
}|${this.activeAuthor}`
},
urlUpdate() {
return this.$route.query
@@ -177,25 +188,20 @@ export default {
}
if (data) {
tagsArray.push(
uniq(
data
.filter((subPage) => subPage.tags)
.map((subPage) => subPage.tags)
.flat(2)
)
)
data.forEach((subPage) => {
if (subPage.tags) {
tagsArray.push(...subPage.tags)
}
})
}
if (author) {
authorsArray.push(
author.name.split(/,|and|&/).map((author) => author.trim())
)
authorsArray.push(author)
}
})
categories = uniq(categories, true)
tagsArray = uniq(tagsArray.flat(2), true)
categories = orderBy(uniq(categories, true), 'name')
tagsArray = uniqBy(tagsArray.flat(2), 'name')
authorsArray = uniq(authorsArray.flat(2), true)
this.$store.commit('appState/setCategoriesList', [
@@ -225,7 +231,11 @@ export default {
let queryCategory = query.category || ''
if (queryCategory && !this.categoriesList.includes(queryCategory)) {
const filteredCategory = this.categoriesList.find(
(category) =>
category.slug === queryCategory || category.name === queryCategory
)
if (queryCategory && !filteredCategory) {
queryCategory = ''
delete newQuery.category
}
@@ -233,7 +243,9 @@ export default {
let queryTags = query.tags ? query.tags.split(',') : []
if (queryTags.length > 0) {
queryTags = queryTags.filter((tag) => this.tagsList.includes(tag))
queryTags = queryTags.filter((queryTag) =>
this.tagsList.find((tag) => tag.slug === queryTag)
)
if (queryTags.length === 0) {
delete newQuery.tags
@@ -242,7 +254,10 @@ export default {
let queryAuthor = query.author
if (queryAuthor && !this.authorsList.includes(queryAuthor)) {
if (
queryAuthor &&
!this.authorsList.map((author) => author.slug).includes(queryAuthor)
) {
queryAuthor = ''
delete newQuery.author
}
@@ -255,7 +270,7 @@ export default {
if (queryCategory !== '') {
const categoryTracking = {
category: queryCategory,
category: filteredCategory.name,
method: 'urlQuery',
}
@@ -263,9 +278,9 @@ export default {
}
if (queryTags.length > 0) {
queryTags.forEach((tag) => {
queryTags.forEach((queryTag) => {
const tagTracking = {
tag: tag,
tag: this.tagsList.find((tag) => tag.slug === queryTag).name,
method: 'urlQuery',
}
@@ -294,19 +309,25 @@ export default {
}
this.$store.commit('appState/setActiveTags', queryTags)
this.$store.commit('appState/setActiveCategory', queryCategory)
this.$store.commit(
'appState/setActiveCategory',
filteredCategory || queryCategory
)
this.$store.commit(
'appState/setSearchedText',
queryText ? queryText.split(',') : []
)
this.$store.commit('appState/setActiveAuthor', queryAuthor || '')
this.$store.commit(
'appState/setActiveAuthor',
this.authorsList.find((author) => author.slug === queryAuthor) || ''
)
const latestWeeklyPost = this.publicPages
.filter(
(item) =>
item.frontmatter &&
item.frontmatter.tags &&
item.frontmatter.tags.includes('weekly')
item.frontmatter.tags.find((tag) => tag.name === 'weekly')
)
.sort(
(a, b) => new Date(b.frontmatter.date) - new Date(a.frontmatter.date)
@@ -323,8 +344,8 @@ export default {
...this.$route.query,
tags: this.activeTags.join(','),
search: this.searchedText.join(','),
category: this.activeCategory,
author: this.activeAuthor,
category: this.activeCategory.slug || '',
author: this.activeAuthor.slug || '',
}
Object.keys(newQuery).forEach((entry) => {
@@ -333,7 +354,6 @@ export default {
delete newQuery[entry]
}
})
this.$router.replace({ query: newQuery }).catch(() => {})
},
blockLazyLoad() {
+7 -11
View File
@@ -2,29 +2,27 @@ export function checkItem({
postType,
tags,
title,
author = {},
author = [],
activeTags = [],
searchedText = [],
activeCategory = '',
activeAuthor = '',
activeAuthor,
}) {
if (activeCategory && decodeURI(activeCategory) !== postType) {
if (activeCategory && activeCategory.slug !== postType.slug) {
return false
}
if (
activeAuthor &&
((author.name &&
!author.name
.toLowerCase()
.includes(decodeURI(activeAuthor.toLowerCase()))) ||
!author.name)
((author.length > 0 &&
!author.map((entry) => entry.slug).includes(activeAuthor.slug)) ||
author.length === 0)
) {
return false
}
for (let i = 0; i < activeTags.length; i++) {
if (!tags || !tags.includes(activeTags[i])) {
if (!tags || !tags.map((tag) => tag.slug).includes(activeTags[i])) {
return false
}
}
@@ -76,13 +74,11 @@ export function parseProtectedPost(
type: post.frontmatter.type,
date: item.date,
title: item.title,
author: { name: item.author },
path: item.path,
frontmatter: {
...item,
date: item.date,
title: item.title,
author: { name: item.author },
path: item.path,
type: post.frontmatter.type,
},
+4 -6
View File
@@ -1,4 +1,4 @@
import isArray from 'lodash/isArray'
import orderBy from 'lodash/orderBy'
export const getTags = (activeTags, posts) => {
const tags = [...activeTags]
@@ -8,16 +8,14 @@ export const getTags = (activeTags, posts) => {
return
}
const postTags = isArray(post.frontmatter.tags)
? post.frontmatter.tags
: post.frontmatter.tags.replace(/, /g, ',').split(',')
const postTags = post.frontmatter.tags
for (let i = 0; i < postTags.length; i++) {
if (postTags[i] && !tags.includes(postTags[i])) {
if (postTags[i] && !tags.find((tag) => tag.slug === postTags[i].slug)) {
tags.push(postTags[i])
}
}
})
return tags.sort()
return orderBy(tags, 'name')
}