mirror of
https://github.com/documize/community.git
synced 2025-08-09 15:35:27 +02:00
search results UX
This commit is contained in:
parent
e173bccf52
commit
d17351c6e4
10 changed files with 157 additions and 90 deletions
|
@ -16,18 +16,28 @@ export default Ember.Component.extend({
|
||||||
resultPhrase: "",
|
resultPhrase: "",
|
||||||
|
|
||||||
didReceiveAttrs() {
|
didReceiveAttrs() {
|
||||||
let count = this.get('results').length;
|
let results = this.get('results');
|
||||||
let self = this;
|
let temp = _.groupBy(results, 'documentId');
|
||||||
|
let documents = [];
|
||||||
|
|
||||||
switch (count) {
|
_.each(temp, function(document) {
|
||||||
case 0:
|
documents.pushObject( {
|
||||||
self.set("resultPhrase", "No results.");
|
doc: document[0],
|
||||||
break;
|
ref: document
|
||||||
case 1:
|
});
|
||||||
self.set("resultPhrase", "1 reference found");
|
});
|
||||||
break;
|
|
||||||
default:
|
let phrase = 'Nothing found';
|
||||||
self.set("resultPhrase", `${count} references found`);
|
|
||||||
|
if (results.length > 0) {
|
||||||
|
let places = documents.length === 1 ? "place" : "places";
|
||||||
|
let references = results.length === 1 ? "secton" : "sections";
|
||||||
|
let i = results.length;
|
||||||
|
let j = documents.length;
|
||||||
|
phrase = `${i} ${references} in ${j} ${places}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.set('resultPhrase', phrase);
|
||||||
|
this.set('documents', documents);
|
||||||
}
|
}
|
||||||
});
|
});
|
|
@ -7,18 +7,16 @@ export default Ember.Controller.extend({
|
||||||
filter: "",
|
filter: "",
|
||||||
results: [],
|
results: [],
|
||||||
|
|
||||||
filterResults(filter) {
|
onKeywordChange: function() {
|
||||||
|
Ember.run.debounce(this, this.fetch, 750);
|
||||||
|
}.observes('filter'),
|
||||||
|
|
||||||
|
fetch() {
|
||||||
this.audit.record('searched');
|
this.audit.record('searched');
|
||||||
let self = this;
|
let self = this;
|
||||||
|
|
||||||
this.get('searchService').find(filter).then(function(response) {
|
this.get('searchService').find(this.get('filter')).then(function(response) {
|
||||||
self.set('results', response);
|
self.set('results', response);
|
||||||
});
|
});
|
||||||
},
|
|
||||||
|
|
||||||
actions: {
|
|
||||||
onFilter(filter) {
|
|
||||||
this.filterResults(filter);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,7 +1,17 @@
|
||||||
{{#header/page-navigation searchMode=true}}
|
{{#layout/zone-container}}
|
||||||
{{header/search-box filter=filter onFilter=(action 'onFilter')}}
|
|
||||||
{{/header/page-navigation}}
|
|
||||||
|
|
||||||
{{#layout/page-container}}
|
{{layout/zone-navigation}}
|
||||||
{{search/search-results results=results}}
|
|
||||||
{{/layout/page-container}}
|
{{#layout/zone-header title=session.appMeta.title message=session.appMeta.message}}
|
||||||
|
{{/layout/zone-header}}
|
||||||
|
|
||||||
|
<div class="page-search">
|
||||||
|
<div class="input-control">
|
||||||
|
<label>Search</label>
|
||||||
|
<div class="tip">#tags, keywords, "some phrase", keyword AND keyword, keyword OR keyword</div>
|
||||||
|
{{focus-input class="input" type="text" value=filter placeholder='start typing...'}}
|
||||||
|
</div>
|
||||||
|
{{search/search-results results=results}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{/layout/zone-container}}
|
||||||
|
|
|
@ -12,7 +12,6 @@
|
||||||
padding: 15px 40px 15px 40px;
|
padding: 15px 40px 15px 40px;
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
|
|
||||||
> .title {
|
> .title {
|
||||||
padding-left: 15px;
|
padding-left: 15px;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
|
|
|
@ -1,44 +1,85 @@
|
||||||
|
.page-search {
|
||||||
|
margin: 30px 40px;
|
||||||
|
|
||||||
.search-results {
|
.search-results {
|
||||||
.heading {
|
margin-top: 50px;
|
||||||
font-size: 1.5rem;
|
|
||||||
color: $color-primary;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .search-list {
|
.heading {
|
||||||
@extend .cards-list;
|
font-size: 1.2rem;
|
||||||
margin-top: 20px;
|
color: $color-blue;
|
||||||
|
}
|
||||||
|
|
||||||
> .search-card {
|
> .list {
|
||||||
@extend .content-card;
|
margin-top: 20px;
|
||||||
width: 220px;
|
list-style: none;
|
||||||
height: 200px;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
.folder {
|
> .item {
|
||||||
font-size: 0.9rem;
|
//width: 400px;
|
||||||
margin: 1.7rem 0 0 0;
|
//float: left;
|
||||||
height: 1.2rem;
|
cursor: pointer;
|
||||||
overflow: hidden;
|
margin: 0 30px 30px 0;
|
||||||
color: $color-gray;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tags {
|
> .link {
|
||||||
font-size: 0.9rem;
|
text-decoration: none;
|
||||||
height: 1.2rem;
|
color: $color-off-black;
|
||||||
overflow: hidden;
|
|
||||||
color: $color-gray;
|
|
||||||
|
|
||||||
.tag {
|
&:hover {
|
||||||
display: inline-block;
|
color: $color-link;
|
||||||
margin-right: 5px;
|
}
|
||||||
|
|
||||||
|
> .title {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .folder {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: $color-gray;
|
||||||
|
margin-left: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .excerpt {
|
||||||
|
margin-top: 1rem;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .chips {
|
||||||
|
margin-top: 1rem;
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
a, a:hover {
|
> .references {
|
||||||
text-decoration: none;
|
margin-top: 1rem;
|
||||||
color: $color-off-black;
|
margin-left: 20px;
|
||||||
}
|
|
||||||
}
|
> .label {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: $color-gray;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .link {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: $color-off-black;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: $color-link;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
content: '·'
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-of-type:after {
|
||||||
|
content: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,21 @@
|
||||||
<div class="search-results">
|
<div class="search-results">
|
||||||
<h2 class="heading">{{resultPhrase}}</h2>
|
<h2 class="heading">{{resultPhrase}}</h2>
|
||||||
<ul class="search-list">
|
<ul class="list">
|
||||||
{{#each results key="id" as |result index|}}
|
{{#each documents key="doc.id" as |result index|}}
|
||||||
<li class="search-card">
|
<li class="item">
|
||||||
<a href="s/{{result.folderId}}/{{result.folderSlug}}/d/{{ result.documentId }}/{{result.documentSlug}}?page={{ result.id }}">
|
<a class="link" href="s/{{result.doc.folderId}}/{{result.doc.folderSlug}}/d/{{ result.doc.documentId }}/{{result.doc.documentSlug}}?page={{ result.doc.id }}">
|
||||||
<div class="title">{{ result.documentTitle }}</div>
|
<div class="title">{{ result.doc.documentTitle }}</div><div class="folder">{{ result.doc.folderName }}</div>
|
||||||
<div class="snippet">{{ result.pageTitle }}</div>
|
<div class="excerpt">{{ result.doc.documentExcerpt }}</div>
|
||||||
<div class="folder">{{ result.folderName }}</div>
|
<div class="chips">{{search/tag-list documentTags=result.doc.documentTags}}</div>
|
||||||
<div class="tags">{{search/tag-list documentTags=result.documentTags}}</div>
|
|
||||||
</a>
|
</a>
|
||||||
|
<div class="references">
|
||||||
|
<span class="label">referenced »</span>
|
||||||
|
{{#each result.ref as |ref index|}}
|
||||||
|
<a class="link" href="s/{{result.doc.folderId}}/{{result.doc.folderSlug}}/d/{{ result.doc.documentId }}/{{result.doc.documentSlug}}?page={{ ref.id }}">
|
||||||
|
{{ref.pageTitle}}
|
||||||
|
</a>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
</li>
|
</li>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
<div class="tags">
|
<div class="chips">
|
||||||
{{#each tagz as |tg|}}
|
{{#each tagz as |tg|}}
|
||||||
<div class="tag">#{{tg}}</div>
|
<div class="chip">
|
||||||
|
<span class="chip-text">#{{tg}}</span>
|
||||||
|
</div>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -18,7 +18,6 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/documize/community/documize/api/entity"
|
"github.com/documize/community/documize/api/entity"
|
||||||
"github.com/documize/community/documize/api/plugins"
|
"github.com/documize/community/documize/api/plugins"
|
||||||
|
@ -54,6 +53,10 @@ func SearchDocuments(w http.ResponseWriter, r *http.Request) {
|
||||||
results[key] = result
|
results[key] = result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(results) == 0 {
|
||||||
|
results = []entity.DocumentSearch{}
|
||||||
|
}
|
||||||
|
|
||||||
data, err := json.Marshal(results)
|
data, err := json.Marshal(results)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -61,10 +64,6 @@ func SearchDocuments(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.ToLower(string(data)) == "null" {
|
|
||||||
data = []byte("[ ]")
|
|
||||||
}
|
|
||||||
|
|
||||||
writeSuccessBytes(w, data)
|
writeSuccessBytes(w, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -275,15 +275,16 @@ type Search struct {
|
||||||
|
|
||||||
// DocumentSearch represents 'presentable' search results.
|
// DocumentSearch represents 'presentable' search results.
|
||||||
type DocumentSearch struct {
|
type DocumentSearch struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
DocumentID string `json:"documentId"`
|
DocumentID string `json:"documentId"`
|
||||||
DocumentTitle string `json:"documentTitle"`
|
DocumentTitle string `json:"documentTitle"`
|
||||||
DocumentSlug string `json:"documentSlug"`
|
DocumentSlug string `json:"documentSlug"`
|
||||||
Tags string `json:"documentTags"`
|
DocumentExcerpt string `json:"documentExcerpt"`
|
||||||
PageTitle string `json:"pageTitle"`
|
Tags string `json:"documentTags"`
|
||||||
LabelID string `json:"folderId"`
|
PageTitle string `json:"pageTitle"`
|
||||||
LabelName string `json:"folderName"`
|
LabelID string `json:"folderId"`
|
||||||
FolderSlug string `json:"folderSlug"`
|
LabelName string `json:"folderName"`
|
||||||
|
FolderSlug string `json:"folderSlug"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// SiteMeta holds information associated with an Organization.
|
// SiteMeta holds information associated with an Organization.
|
||||||
|
|
|
@ -244,7 +244,7 @@ func (p *Persister) SearchDocument(keywords string) (results []entity.DocumentSe
|
||||||
}
|
}
|
||||||
|
|
||||||
sql := `SELECT search.id, documentid, pagetitle, document.labelid, document.title as documenttitle, document.tags,
|
sql := `SELECT search.id, documentid, pagetitle, document.labelid, document.title as documenttitle, document.tags,
|
||||||
COALESCE(label.label,'Unknown') AS labelname
|
COALESCE(label.label,'Unknown') AS labelname, document.excerpt as documentexcerpt
|
||||||
FROM search, document LEFT JOIN label ON label.orgid=document.orgid AND label.refid = document.labelid
|
FROM search, document LEFT JOIN label ON label.orgid=document.orgid AND label.refid = document.labelid
|
||||||
WHERE search.documentid = document.refid AND search.orgid=? AND document.template=0 ` + tagQuery +
|
WHERE search.documentid = document.refid AND search.orgid=? AND document.template=0 ` + tagQuery +
|
||||||
`AND document.labelid IN
|
`AND document.labelid IN
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue