Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
W
wiki-js
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
1
Issues
1
List
Board
Labels
Milestones
Merge Requests
1
Merge Requests
1
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Jacklull
wiki-js
Commits
dbfe7d4f
Unverified
Commit
dbfe7d4f
authored
Jul 06, 2023
by
NGPixel
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: search filters + ux improvements
parent
b75275d7
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
121 additions
and
48 deletions
+121
-48
page.mjs
server/graph/resolvers/page.mjs
+28
-14
en.json
server/locales/en.json
+5
-1
HeaderNav.vue
ux/src/components/HeaderNav.vue
+7
-0
AdminSearch.vue
ux/src/pages/AdminSearch.vue
+1
-1
Search.vue
ux/src/pages/Search.vue
+79
-32
site.js
ux/src/stores/site.js
+1
-0
No files found.
server/graph/resolvers/page.mjs
View file @
dbfe7d4f
...
...
@@ -56,25 +56,30 @@ export default {
throw
new
Error
(
'Limit must be between 1 and 100.'
)
}
try
{
const
dictName
=
'english'
const
dictName
=
'english'
// TODO: Use provided locale or fallback on site locale
const
searchCols
=
[
'id'
,
'path'
,
'localeCode AS locale'
,
'title'
,
'description'
,
'icon'
,
'updatedAt'
,
WIKI
.
db
.
knex
.
raw
(
'ts_rank_cd(ts, query) AS relevancy'
),
WIKI
.
db
.
knex
.
raw
(
'count(*) OVER() AS total'
)
]
if
(
WIKI
.
config
.
search
.
termHighlighting
)
{
searchCols
.
push
(
WIKI
.
db
.
knex
.
raw
(
`ts_headline(?, "searchContent", query, 'MaxWords=5, MinWords=3, MaxFragments=5') AS highlight`
,
[
dictName
]))
}
const
results
=
await
WIKI
.
db
.
knex
.
select
(
'id'
,
'path'
,
'localeCode AS locale'
,
'title'
,
'description'
,
'icon'
,
'updatedAt'
,
WIKI
.
db
.
knex
.
raw
(
'ts_rank_cd(ts, query) AS relevancy'
),
WIKI
.
db
.
knex
.
raw
(
`ts_headline(?, "searchContent", query, 'MaxWords=5, MinWords=3, MaxFragments=5') AS highlight`
,
[
dictName
]),
WIKI
.
db
.
knex
.
raw
(
'count(*) OVER() AS total'
)
)
.
select
(
searchCols
)
.
fromRaw
(
'pages, websearch_to_tsquery(?, ?) query'
,
[
dictName
,
args
.
query
])
.
where
(
'siteId'
,
args
.
siteId
)
.
where
(
builder
=>
{
if
(
args
.
path
)
{
builder
.
where
(
'path'
,
'ILIKE'
,
`
${
path
}
%`
)
builder
.
where
(
'path'
,
'ILIKE'
,
`
${
args
.
path
}
%`
)
}
if
(
args
.
locale
?.
length
>
0
)
{
builder
.
whereIn
(
'localeCode'
,
args
.
locale
)
...
...
@@ -91,6 +96,15 @@ export default {
.
offset
(
args
.
offset
||
0
)
.
limit
(
args
.
limit
||
25
)
// -> Remove highlights without matches
if
(
WIKI
.
config
.
search
.
termHighlighting
)
{
for
(
const
r
of
results
)
{
if
(
r
.
highlight
?.
indexOf
(
'<b>'
)
<
0
)
{
r
.
highlight
=
null
}
}
}
return
{
results
,
totalHits
:
results
?.
length
>
0
?
results
[
0
].
total
:
0
...
...
server/locales/en.json
View file @
dbfe7d4f
...
...
@@ -525,7 +525,7 @@
"admin.scheduler.waitUntil"
:
"Start"
,
"admin.search.configSaveSuccess"
:
"Search engine configuration saved successfully."
,
"admin.search.dictOverrides"
:
"PostgreSQL Dictionary Mapping Overrides"
,
"admin.search.dictOverridesHint"
:
"
One override per line, in the format: en=english
"
,
"admin.search.dictOverridesHint"
:
"
JSON object of 2 letters locale codes and their PostgreSQL dictionary association. e.g. {
\"
en
\"
:
\"
english
\"
}
"
,
"admin.search.engineConfig"
:
"Engine Configuration"
,
"admin.search.engineNoConfig"
:
"This engine has no configuration options you can modify."
,
"admin.search.highlighting"
:
"Enable Term Highlighting"
,
...
...
@@ -1757,9 +1757,13 @@
"profile.title"
:
"Profile"
,
"profile.uploadNewAvatar"
:
"Upload New Image"
,
"profile.viewPublicProfile"
:
"View Public Profile"
,
"search.filterLocaleDisplay"
:
"Any locale | {n} locale only | {count} locales selected"
,
"search.filters"
:
"Filters"
,
"search.results"
:
"Search Results"
,
"search.sortBy"
:
"Sort By"
,
"search.sortByLastUpdated"
:
"Last Updated"
,
"search.sortByRelevance"
:
"Relevance"
,
"search.sortByTitle"
:
"Title"
,
"tags.clearSelection"
:
"Clear Selection"
,
"tags.currentSelection"
:
"Current Selection"
,
"tags.locale"
:
"Locale"
,
...
...
ux/src/components/HeaderNav.vue
View file @
dbfe7d4f
...
...
@@ -59,6 +59,13 @@ q-header.bg-header.text-white.site-header(
outline
@click='searchField.focus()'
)
q-badge.q-mr-sm(
v-else-if='siteStore.search && siteStore.search !== siteStore.searchLastQuery'
label='Press Enter'
color='grey-7'
outline
@click='searchField.focus()'
)
q-icon.cursor-pointer(
name='las la-times'
size='20px'
...
...
ux/src/pages/AdminSearch.vue
View file @
dbfe7d4f
...
...
@@ -62,7 +62,7 @@ q-page.admin-flags
language='json'
:min-height='250'
)
q-item-label(caption)
JSON object of 2 letters locale codes and their PostgreSQL dictionary association. e.g. { "en": "english"
}
q-item-label(caption)
{{
t
(
'admin.search.dictOverridesHint'
)
}
}
.col-12.col-lg-5.gt-md
.q-pa-md.text-center
...
...
ux/src/pages/Search.vue
View file @
dbfe7d4f
...
...
@@ -6,21 +6,25 @@ q-layout(view='hHh Lpr lff')
.layout-search-sd
.text-header
{{
t
(
'search.sortBy'
)
}}
q-list(dense, padding)
q-item(clickable, active)
q-item(
v-for='item of orderByOptions'
clickable
:active='item.value === state.params.orderBy'
@click='setOrderBy(item.value)'
)
q-item-section(side)
q-icon(
name='las la-stream', color='primary
')
q-icon(
:name='item.icon', :color='item.value === state.params.orderBy ? `primary` : ``
')
q-item-section
q-item-label Relevance
q-item-section(side)
q-icon(name='mdi-chevron-double-down', size='sm', color='primary')
q-item(clickable)
q-item-section(side)
q-icon(name='las la-heading')
q-item-section Title
q-item(clickable)
q-item-section(side)
q-icon(name='las la-calendar')
q-item-section Last Updated
q-item-label
{{
item
.
label
}}
q-item-section(
v-if='item.value === state.params.orderBy'
side
)
q-icon(
:name='state.params.orderByDirection === `desc` ? `mdi-chevron-double-down` : `mdi-chevron-double-up`'
size='sm'
color='primary'
)
.text-header
{{
t
(
'search.filters'
)
}}
.q-pa-sm
q-input(
...
...
@@ -28,7 +32,7 @@ q-layout(view='hHh Lpr lff')
dense
placeholder='Path starting with...'
prefix='/'
v-model='state.filterPath'
v-model='state.
params.
filterPath'
)
template(v-slot:prepend)
q-icon(name='las la-caret-square-right', size='xs')
...
...
@@ -55,7 +59,7 @@ q-layout(view='hHh Lpr lff')
q-icon(name='las la-user-edit', size='xs')
q-select.q-mt-sm(
outlined
v-model='state.filterLocale'
v-model='state.
params.
filterLocale'
emit-value
map-options
dense
...
...
@@ -63,14 +67,21 @@ q-layout(view='hHh Lpr lff')
:options='siteStore.locales.active'
option-value='code'
option-label='name'
options-dense
multiple
:display-value='t(`
admin.groups.selectedLocales`, { n: state.filterLocale.length > 0 ? state.filterLocale[0].toUpperCase() : state.filterLocale.length }, state
.filterLocale.length)'
:display-value='t(`
search.filterLocaleDisplay`, { n: state.params.filterLocale.length > 0 ? state.params.filterLocale[0].toUpperCase() : state.params.filterLocale.length }, state.params
.filterLocale.length)'
)
template(v-slot:prepend)
q-icon(name='las la-language', size='xs')
template(v-slot:option='scope')
q-item(v-bind='scope.itemProps')
q-item-section(side)
q-checkbox(:model-value='scope.selected', @update:model-value='scope.toggleOption(scope.opt)')
q-item-section
q-item-label(v-html='scope.opt.name')
q-select.q-mt-sm(
outlined
v-model='state.filterEditor'
v-model='state.
params.
filterEditor'
emit-value
map-options
dense
...
...
@@ -81,7 +92,7 @@ q-layout(view='hHh Lpr lff')
q-icon(name='las la-pen-nib', size='xs')
q-select.q-mt-sm(
outlined
v-model='state.filterPublishState'
v-model='state.
params.
filterPublishState'
emit-value
map-options
dense
...
...
@@ -95,6 +106,9 @@ q-layout(view='hHh Lpr lff')
span
{{
t
(
'search.results'
)
}}
q-space
span.text-caption #[strong
{{
state
.
total
}}
] results
.q-pa-lg(v-if='state.results.length < 1')
span(v-if='siteStore.search && siteStore.searchLastQuery') No results found for #[strong "
{{
siteStore
.
searchLastQuery
}}
"] with the current filters.
span(v-else): em Enter a query in the search field above and press Enter.
q-list(separator)
q-item(
v-for='item of state.results'
...
...
@@ -105,8 +119,8 @@ q-layout(view='hHh Lpr lff')
q-avatar(color='primary' text-color='white' rounded :icon='item.icon')
q-item-section
q-item-label
{{
item
.
title
}}
q-item-label(caption)
{{
item
.
description
}}
q-item-label.text-highlight(caption, v-html='item.highlight')
q-item-label(
v-if='item.description',
caption)
{{
item
.
description
}}
q-item-label.text-highlight(
v-if='item.highlight',
caption, v-html='item.highlight')
q-item-section(side)
.flex
q-chip(
...
...
@@ -129,10 +143,10 @@ q-layout(view='hHh Lpr lff')
<
script
setup
>
import
{
useI18n
}
from
'vue-i18n'
import
{
useMeta
,
useQuasar
}
from
'quasar'
import
{
computed
,
onMounted
,
reactive
,
watch
}
from
'vue'
import
{
computed
,
onMounted
,
onUnmounted
,
reactive
,
watch
}
from
'vue'
import
{
useRouter
,
useRoute
}
from
'vue-router'
import
gql
from
'graphql-tag'
import
{
cloneDeep
}
from
'lodash-es'
import
{
cloneDeep
,
debounce
}
from
'lodash-es'
import
{
DateTime
}
from
'luxon'
import
{
useFlagsStore
}
from
'src/stores/flags'
...
...
@@ -172,15 +186,27 @@ useMeta({
const
state
=
reactive
({
loading
:
0
,
filterPath
:
''
,
filterTags
:
[],
filterLocale
:
[
'en'
],
filterEditor
:
''
,
filterPublishState
:
''
,
params
:
{
filterPath
:
''
,
filterTags
:
[],
filterLocale
:
[],
filterEditor
:
''
,
filterPublishState
:
''
,
orderBy
:
'relevancy'
,
orderByDirection
:
'desc'
},
results
:
[],
total
:
0
})
const
orderByOptions
=
computed
(()
=>
{
return
[
{
label
:
t
(
'search.sortByRelevance'
),
value
:
'relevancy'
,
icon
:
'las la-stream'
},
{
label
:
t
(
'search.sortByTitle'
),
value
:
'title'
,
icon
:
'las la-heading'
},
{
label
:
t
(
'search.sortByLastUpdated'
),
value
:
'updatedAt'
,
icon
:
'las la-calendar'
}
]
})
const
editors
=
computed
(()
=>
{
return
[
{
label
:
'Any editor'
,
value
:
''
},
...
...
@@ -208,6 +234,8 @@ watch(() => route.query, async (newQueryObj) => {
}
},
{
immediate
:
true
})
watch
(()
=>
state
.
params
,
debounce
(
performSearch
,
500
),
{
deep
:
true
})
// METHODS
function
pageStyle
(
offset
,
height
)
{
...
...
@@ -220,6 +248,15 @@ function humanizeDate (val) {
return
DateTime
.
fromISO
(
val
).
toFormat
(
userStore
.
preferredDateFormat
)
}
function
setOrderBy
(
val
)
{
if
(
val
===
state
.
params
.
orderBy
)
{
state
.
params
.
orderByDirection
=
state
.
params
.
orderByDirection
===
'desc'
?
'asc'
:
'desc'
}
else
{
state
.
params
.
orderBy
=
val
state
.
params
.
orderByDirection
=
val
===
'title'
?
'asc'
:
'desc'
}
}
async
function
performSearch
()
{
siteStore
.
searchIsLoading
=
true
try
{
...
...
@@ -268,7 +305,13 @@ async function performSearch () {
`
,
variables
:
{
siteId
:
siteStore
.
id
,
query
:
siteStore
.
search
query
:
siteStore
.
search
,
path
:
state
.
params
.
filterPath
,
locale
:
state
.
params
.
filterLocale
,
editor
:
state
.
params
.
filterEditor
,
publishState
:
state
.
params
.
filterPublishState
||
null
,
orderBy
:
state
.
params
.
orderBy
,
orderByDirection
:
state
.
params
.
orderByDirection
},
fetchPolicy
:
'network-only'
})
...
...
@@ -277,6 +320,7 @@ async function performSearch () {
}
state
.
results
=
cloneDeep
(
resp
.
data
.
searchPages
.
results
)
state
.
total
=
resp
.
data
.
searchPages
.
totalHits
siteStore
.
searchLastQuery
=
siteStore
.
search
}
catch
(
err
)
{
$q
.
notify
({
type
:
'negative'
,
...
...
@@ -290,13 +334,17 @@ async function performSearch () {
// MOUNTED
onMounted
(()
=>
{
if
(
siteStore
.
search
)
{
// performSearch()
}
else
{
if
(
!
siteStore
.
search
)
{
siteStore
.
searchIsLoading
=
false
}
})
onUnmounted
(()
=>
{
siteStore
.
search
=
''
siteStore
.
searchLastQuery
=
''
siteStore
.
searchIsLoading
=
false
})
</
script
>
<
style
lang=
"scss"
>
...
...
@@ -386,7 +434,6 @@ onMounted(() => {
>
b
{
background-color
:
rgba
(
$yellow-7
,
.5
);
padding
:
0
3px
;
border-radius
:
3px
;
}
}
...
...
ux/src/stores/site.js
View file @
dbfe7d4f
...
...
@@ -16,6 +16,7 @@ export const useSiteStore = defineStore('site', {
description
:
''
,
logoText
:
true
,
search
:
''
,
searchLastQuery
:
''
,
searchIsLoading
:
false
,
printView
:
false
,
pageDataTemplates
:
[],
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment